Playing with Chain.jl

By: Blog by Bogumił Kamiński

Re-posted from: https://bkamins.github.io/julialang/2021/01/15/chain.html

Introduction

DataFrames.jl was designed to support chaining of operations well.
For a long time my favorite package that helped with this was Pipe.jl.
It is very easy to understand how it works and is clear visually.

There are many alternative packages that support chaining, but they all required
much higher mental effort from the developer to master them. However, in
November 2020 Chain.jl was created and it really is as simple as
Pipe.jl, but at the same time more powerful. In this post I briefly investigate
what it has to offer.

This post was written with Julia 1.5.3, Chain.jl 0.4.2, and Combinatorics 1.0.2.

Experimenting with Chain.jl

The Chain.jl README.md does a really great job of explaining why and
how of the package so I refer you to the website to read the details. In short
it introduces:

  • a macro @chain and an extra annotation,
  • an @aside annotation that can be used inside a @chain block to produce
    side effects,
  • _ is used to signal where the value of the previous expression should be
    inserted (unless it is a first argument in which case _ can be omitted).

Let me give one exemplary usage of the @chain macro. Assume we have a 6
element set and want to get all permutations of its 4 element subsets (if you
ever try to implement a Mastermind solver you might need it).
Here is how you can generate it using Chain.jl:

julia> using Chain

julia> using Combinatorics

julia> @chain 1:6 begin
                 combinations(4)
                 @aside println("# of combinations: ", length(_))
                 collect # we could skip this step
                 @. permutations
                 mapreduce(collect, vcat, _)
             end
# of combinations: 15
360-element Array{Array{Int64,1},1}:
 [1, 2, 3, 4]
 [1, 2, 4, 3]
 [1, 3, 2, 4]
 ⋮
 [6, 4, 5, 3]
 [6, 5, 3, 4]
 [6, 5, 4, 3]

Note that in the first call combinations(4) Chain.jl has put _ implicitly
as the first argument of the combinations function, so the actual call is
combinations(1:6, 4).

In line collect result of combinations(1:6, 4) is passed as a single
argument to collect (you do not need to write parentheses). Similarly in line
@. permutations we use the same pattern but this time we broadcast the
permutations function over a collection passed from the previous step of the
chain. If we want to pass other than the first argument then _ is used as
shown in the mapreduce(collect, vcat, _) line.

In the second line @aside is executed but is ignored in the pipeline. Note
that it would be tempting to write

@aside println("# of combinations: ", length)

instead of

@aside println("# of combinations: ", length(_))

The reason is that length takes only one argument. However, in this case a
call to length is nested so you have to pass _ explicitly.

You can see that Chain.jl has two key features:

  • everything is wrapped in beginend block,
  • there is no visual separator (like standard |> in e.g. Pipe.jl)
    signaling an end of the expression.

Many people will find that it exactly fits their needs, but here is an
alternative syntax that I have found to be potentially usable with
Chain.jl:

@chain 1:6 (
    combinations(4);
    @aside println("# of combinations: ", length(_));
    collect; # we could skip it
    @. permutations;
    mapreduce(collect, vcat, _);
)

which produces the same result.

The difference here is that I replace beginend block with ( and ), so
it is a bit less typing. In this case one has to separate the expressions with
;. I also added ; at the end of the last expression, though it is not
strictly necessary, as in this way you can safely add/remove lines in @chain
without changing the remaining lines.

If having to add ; in this style is good or bad is a matter of taste. On one
hand it adds typing, but on the other hand it clearly shows the end of one
expression (which in beginend style is not explicit, sometimes it might
be confusing if someone needed to add a line break in an expression, and e.g.
indentation should be used to signal line continuation then).

Also () style has an additional benefit that in some editors it is easy to
select the code block enclosed in the parentheses if you would need to
copy-paste the contents of @chain.

Conclusions

I think Chain.jl is excellent. If you like chaining function calls in
your code I really recommend you to check it out.

Parameter Inference in dynamical systems

By: julia | Victor Boussange

Re-posted from: https://vboussange.github.io/post/param-inference/

One of the challenges modellers face in biological sciences is to calibrate models in order to match as closely as possible observations and gain predictive power. This can be done via direct measurements through experimental design, but this process is often costly, time consuming and even sometimes not possible.
Scientific machine learning addresses this problem by applying optimisation techniques originally developed within the field of machine learning to mechanistic models, allowing to infer parameters directly from observation data.
In this blog post, I shall explain the basics of this approach, and how the Julia ecosystem has efficiently embedded such techniques into ready to use packages. This promises exciting perspectives for modellers in all areas of environmental sciences.

? This is Work in progress ?

Dynamical systems are models that allow to reproduce, understand and forecast systems. They connect the time variation of the state of the system to the fundamental processes we believe driving it, that is

$$
\begin{equation*}
\text{ time variation of } ?_t = \sum \text{processes acting on } ?_t
\end{equation*}
$$

where $?_t$ denotes the state of the system at time $t$. This translates mathematically into

$$
\begin{equation}
\partial_t(?_t) = f_\theta( ?_t )
\end{equation}\tag{1}
$$

where the function $f_\theta$ captures the ensembles of the processes considered, and depend on the parameters $\theta$.

Eq. (1) is a Differential Equation, that can be integrated with respect to time to obtain the state of the system at time $t$ given an initial state $?_{t_0}$.

$$
\begin{equation}
?_t = ?_{t_0} + \int_0^t f_\theta( ?_s ) ds
\end{equation}\tag{2}
$$

Dynamical systems have been used for hundreds of years and have successfully captured e.g. the motion of planets (second law of Kepler), the voltage in an electrical circuit, population dynamics (Lotka Volterra equations) and morphogenesis (Turing patterns)…

Such models can be used to forecast the state of the system in the future, or can be used in the sense of virtual laboratories. In both cases, one of the requirement is that they reproduce patterns – at least at a qualitative level. To do so, the modeler needs to find the true parameter combination $\theta$ that correspond to the system under consideration. And this is tricky! In this post we adress this challenge.

Model calibration

How to determine $\theta$ so that $\text{simulations} \approx \text{empirical data}$?

The best way to do that is to design an experiment!

When possible, measuring directly the parameters in a controlled experiment with e.g. physical devices is a great approach. This is a very powerful scientific method, used e.g. in global circulation models where scientists can measure the water viscosity, the change in water density with respect to temperature, etc… Unfortunately, such direct methods are often not possible considering other systems.

An opposite approach, known as inverse modelling, is to infer the parameters undirectly with the empirical data available.

Parameter exploration

One way to find right parameters is to perform parameter exploration, that is, slicing the parameter space and running the model for all parameter combinations chosen. Comparing the simulation results to the empirical data available, one can elect the combination with the higher explanatory power.

But as the parameter space becomes larger (higher number of parameters) this becomes tricky. Such problem is often refered to as the curse of dimensionality. Feels very much like being lost in a giant maze. We need more clever technique to get out!

A Machine Learning problem

In machine learning, people try to predict a variable $y$ from predictors $x$ by finding suitable parameters $\theta$ of a parametric function $F_\theta$ so that

$$
\begin{equation}
y = F_\theta(x)
\end{equation}\tag{3}
$$

For example, in computer vision, this function might be designed for the specific task of labelling images, such as for instance

$F_\theta ($

$) \to \{\text{cat}, \text{dog}\}$

Usually people use neural networks so that $F_\theta \equiv NN_\theta$, as they are good approximators for high dimensional function (see the Universal approximation theorem). One should really see neural networks as functions ! For example, feed forward neural networks are mathematically described by a series of matrix multiplications and nonlinear operations, i.e. $NN_\theta (x) = \sigma_1 \circ f_1 \circ \dots \circ \sigma_n \circ f_n(x)$
where $\sigma_i$ is an activation function and $f_i$ is linear function
$$
\begin{equation*}
f_i (x) = A_i x + b_i .
\end{equation*}
$$
Notice that Eq. (2) is similar to Eq. (3)! Indeed one can think of $?_0$ as the analogous to $x$ – i.e. the predictor – and $?_t$ as the variable $y$ to predict:

$$
\begin{equation*}
?_t = F_\theta(?_{t_0})
\end{equation*}
$$

where $$F_\theta (?_{t_0}) \equiv ?_{t_0} + \int_0^t f_\theta( ?_s ) ds .$$

With this perspective in mind, techniques developed within the field of Machine Learning – to find suitable parameters $\theta$ that best predict $y$ – become readily available to reach our specific needs: model calibration!

Parameter inference

The general strategy to find a suitable neural network that can perform the tasks required is to “train” it, that is, to find the parameters $\theta$ so that its predictions are accurate.

In order to train it, one “scores” how good a combination of parameter $\theta$ performs. A way to do so is to introduce a “Loss function

$$
\begin{equation*}
L(\theta) = (F_\theta(x) – y_{\text{empirical}})^2
\end{equation*}
$$

One can then use an optimisation method to find a local minima (and in the best scenario, the global minima) for $L$.

Gradient descent

You ready?

Gradient descent and stochastic gradient descent are “iterative optimisation methods that seek to find a local minimum of a differentiable function” (Wikipedia). Such methods have become widely used with the development of artifical intelligence.

Those methods are used to compute iteratively $\theta$ using the sensitivity of the loss function to changes in $\theta$, denoted by $\partial_\theta L(\theta)$

$$
\begin{equation*}
\theta^{i+1} = \theta^{(i)} – \lambda \partial_\theta L(\theta)
\end{equation*}
$$

where $\lambda$ is called the learning rate.

In practice

The sensitivity with respect to the parameters $\partial_\theta L(\theta)$ is in practice obtained by differentiating the code (Automatic Differentiation).

For some programming languages this can be done automatically, with low computational cost. In particular, Flux.jl allows to efficiently obtain the gradient of any function written in the wonderful language Julia.

The library DiffEqFlux.jl based on Flux.jl implements differentiation rules (custom adjoints) to obtain even more efficiently the sensitivity of a loss function that depends on the numerical solution of a differential equation. That is, DiffEqFlux.jl precisely allows to do parameter inference in dynamical systems. Go and check it out!