# Flux Overview

Flux is a pure Julia ML stack that allows you to build predictive models. Here are the steps for a typical Flux program:

- Provide training and test data
- Build a model with configurable
*parameters*to make predictions - Iteratively train the model by tweaking the parameters to improve predictions
- Verify your model

Under the hood, Flux uses a technique called automatic differentiation to take gradients that help improve predictions. Flux is also fully written in Julia so you can easily replace any layer of Flux with your own code to improve your understanding or satisfy special requirements.

Here's how you'd use Flux to build and train the most basic of models, step by step.

## Make a Trivial Prediction

This example will predict the output of the function `4x + 2`

. First, import `Flux`

and define the function we want to simulate:

```
julia> using Flux
julia> actual(x) = 4x + 2
actual (generic function with 1 method)
```

This example will build a model to approximate the `actual`

function.

## Provide Training and Test Data

Use the `actual`

function to build sets of data for training and verification:

```
julia> x_train, x_test = hcat(0:5...), hcat(6:10...)
([0 1 … 4 5], [6 7 … 9 10])
julia> y_train, y_test = actual.(x_train), actual.(x_test)
([2 6 … 18 22], [26 30 … 38 42])
```

Normally, your training and test data come from real world observations, but this function will simulate real-world observations.

## Build a Model to Make Predictions

Now, build a model to make predictions with `1`

input and `1`

output:

```
julia> model = Dense(1 => 1)
Dense(1 => 1) # 2 parameters
julia> model.weight
1×1 Matrix{Float32}:
0.95041317
julia> model.bias
1-element Vector{Float32}:
0.0
```

Under the hood, a dense layer is a struct with fields `weight`

and `bias`

. `weight`

represents a weights' matrix and `bias`

represents a bias vector. There's another way to think about a model. In Flux, *models are conceptually predictive functions*:

```
julia> predict = Dense(1 => 1)
Dense(1 => 1) # 2 parameters
```

`Dense(1 => 1)`

also implements the function `σ(Wx+b)`

where `W`

and `b`

are the weights and biases. `σ`

is an activation function (more on activations later). Our model has one weight and one bias, but typical models will have many more. Think of weights and biases as knobs and levers Flux can use to tune predictions. Activation functions are transformations that tailor models to your needs.

This model will already make predictions, though not accurate ones yet:

```
julia> predict(x_train)
1×6 Matrix{Float32}:
0.0 0.906654 1.81331 2.71996 3.62662 4.53327
```

In order to make better predictions, you'll need to provide a *loss function* to tell Flux how to objectively *evaluate* the quality of a prediction. Loss functions compute the cumulative distance between actual values and predictions.

```
julia> loss(x, y) = Flux.Losses.mse(predict(x), y);
julia> loss(x_train, y_train)
122.64734f0
```

More accurate predictions will yield a lower loss. You can write your own loss functions or rely on those already provided by Flux. This loss function is called mean squared error. Flux works by iteratively reducing the loss through *training*.

## Improve the Prediction

Under the hood, the Flux `Flux.train!`

function uses *a loss function* and *training data* to improve the *parameters* of your model based on a pluggable `optimiser`

:

```
julia> using Flux: train!
julia> opt = Descent()
Descent(0.1)
julia> data = [(x_train, y_train)]
1-element Vector{Tuple{Matrix{Int64}, Matrix{Int64}}}:
([0 1 … 4 5], [2 6 … 18 22])
```

Now, we have the optimiser and data we'll pass to `train!`

. All that remains are the parameters of the model. Remember, each model is a Julia struct with a function and configurable parameters. Remember, the dense layer has weights and biases that depend on the dimensions of the inputs and outputs:

```
julia> predict.weight
1×1 Matrix{Float32}:
0.9066542
julia> predict.bias
1-element Vector{Float32}:
0.0
```

The dimensions of these model parameters depend on the number of inputs and outputs. Since models can have hundreds of inputs and several layers, it helps to have a function to collect the parameters into the data structure Flux expects:

```
julia> parameters = Flux.params(predict)
Params([Float32[0.9066542], Float32[0.0]])
```

These are the parameters Flux will change, one step at a time, to improve predictions. At each step, the contents of this `Params`

object changes too, since it is just a collection of references to the mutable arrays inside the model:

```
julia> predict.weight in parameters, predict.bias in parameters
(true, true)
```

The first parameter is the weight and the second is the bias. Flux will adjust predictions by iteratively changing these parameters according to the optimizer.

This optimiser implements the classic gradient descent strategy. Now improve the parameters of the model with a call to `Flux.train!`

like this:

`julia> train!(loss, parameters, data, opt)`

And check the loss:

```
julia> loss(x_train, y_train)
116.38745f0
```

It went down. Why?

```
julia> parameters
Params([Float32[7.5777884], Float32[1.9466728]])
```

The parameters have changed. This single step is the essence of machine learning.

## Iteratively Train the Model

In the previous section, we made a single call to `train!`

which iterates over the data we passed in just once. An *epoch* refers to one pass over the dataset. Typically, we will run the training for multiple epochs to drive the loss down even further. Let's run it a few more times:

```
julia> for epoch in 1:200
train!(loss, parameters, data, opt)
end
julia> loss(x_train, y_train)
0.00339581f0
julia> parameters
Params([Float32[4.0178537], Float32[2.0050256]])
```

After 200 training steps, the loss went down, and the parameters are getting close to those in the function the model is built to predict.

## Verify the Results

Now, let's verify the predictions:

```
julia> predict(x_test)
1×5 Matrix{Float32}:
26.1121 30.13 34.1479 38.1657 42.1836
julia> y_test
1×5 Matrix{Int64}:
26 30 34 38 42
```

The predictions are good. Here's how we got there.

First, we gathered real-world data into the variables `x_train`

, `y_train`

, `x_test`

, and `y_test`

. The `x_*`

data defines inputs, and the `y_*`

data defines outputs. The `*_train`

data is for training the model, and the `*_test`

data is for verifying the model. Our data was based on the function `4x + 2`

.

Then, we built a single input, single output predictive model, `predict = Dense(1 => 1)`

. The initial predictions weren't accurate, because we had not trained the model yet.

After building the model, we trained it with `train!(loss, parameters, data, opt)`

. The loss function is first, followed by the `parameters`

holding the weights and biases of the model, the training data, and the `Descent`

optimizer provided by Flux. We ran the training step once, and observed that the parameters changed and the loss went down. Then, we ran the `train!`

many times to finish the training process.

After we trained the model, we verified it with the test data to verify the results.

This overall flow represents how Flux works. Let's drill down a bit to understand what's going on inside the individual layers of Flux.