Elixir Axon tutorial
Mix.install([
{:nx, "~> 0.2.0"},
{:exla, "~> 0.2.0"},
{:axon, "~> 0.1.0-dev", github: "elixir-nx/axon", branch: "main"}
])
Aim
We will create and train a neural network to detect oddity on one byte numbers
Create a neural network
Let’s start creating a neural network for our aim
require Axon
model =
Axon.input({nil, 8})
|> Axon.dense(20, activation: :relu)
|> Axon.dense(20, activation: :relu)
# |> Axon.dropout(rate: 0.5)
|> Axon.dense(2, activation: :softmax)
Easy-peasy
As you can see, its a network with 8 neurons as input
Axon.input({nil, 8})
Later, we put two small layers of neurons (just 20 neurons per layer, it looks an easy problem and probably is not necessary more)
|> Axon.dense(20, activation: :relu)
|> Axon.dense(20, activation: :relu)
And for finish, just two neurons as output
|> Axon.dense(2, activation: :softmax)
And… that’s all we already have our Neuron network
With a little initialization, we already can ask to our network
Let’s do it!
state_init = model |> Axon.init(compiler: EXLA)
Now we have a neural network initialized and we can ask to it
Let’s start asking for number 0
Axon.predict(
model,
state_init,
%{
"input_0" => Nx.tensor([[0.0, 0.0, 0, 0, 0, 0, 0.0, 0.0]])
},
compiler: EXLA
)
The values of the tensor should say as the probability of be odd or even
> [0.5, 0.5]
As you can see, the system has no clue about the answer
And with number 1, similar result
It’s quite logical, at the moment we only have a neural network with a specific topology but we didn’t add to the network any information about odd and even numbers
How to?
Trainning
Prepare data to train
First we need data to train
It consist on input values, with the right response
Let’s start with something simple
train_data = [
{Nx.tensor([[0.0, 0, 0, 0, 0, 0, 0, 1.0]]), Nx.tensor([0.0, 1.0])},
{Nx.tensor([[0.0, 0, 0, 0, 0, 0, 0.0, 0.0]]), Nx.tensor([1.0, 0.0])}
]
And update the neural network with these training data
trained0 =
model
|> Axon.Loop.trainer(:categorical_cross_entropy, Axon.Optimizers.adamw(0.005))
|> Axon.Loop.run(train_data, epochs: 100, compiler: EXLA)
Now we have a neural network trainned with a little information (but we repeated a lot).
Lets ask to our NN again
Axon.predict(
model,
trained0,
%{
"input_0" => Nx.tensor([[0.0, 0.0, 0, 0, 0, 0, 0.0, 0.0]])
},
compiler: EXLA
)
Axon.predict(
model,
trained0,
%{
"input_0" => Nx.tensor([[0.0, 0.0, 0, 0, 0, 0, 0.0, 1.0]])
},
compiler: EXLA
)
That looks quite different!!!
But, what if we ask a not trainned number?
Axon.predict(
model,
trained0,
%{
"input_0" => Nx.tensor([[0, 0, 0, 1, 0, 0, 0, 0]])
},
compiler: EXLA
)
Not very good answer
We have to train with several numbers, let’s do it!
To do that, we want a big (infinity will be enough ;-) list of random numbers with the right response.
random = Enum.random(0..255)
even = if rem(random, 2) == 0, do: true, else: false
{random, even}
Let’s put this in an infinite list
trainning_data =
Stream.unfold(0, fn _ ->
random = Enum.random(0..255)
even = if rem(random, 2) == 0, do: true, else: false
{{random, even}, 0}
end)
Show some data to verify
trainning_data |> Enum.take(10)
Looks great, but… remember how we introduced our data in the network
train_data = [
{Nx.tensor([[0.0, 0, 0, 0, 0, 0, 0, 1.0]]), Nx.tensor([0.0, 1.0])},
{Nx.tensor([[0.0, 0, 0, 0, 0, 0, 0.0, 0.0]]), Nx.tensor([1.0, 0.0])}
]
Then we have to map to this format
First, a function to convert a number, to a tensor with the neurons to be activated
number2input_tensor = fn num ->
:io_lib.format("~8.2.0B", [num])
|> Enum.map(fn d -> if d == ?0, do: 0.0, else: 1.0 end)
|> Nx.tensor()
|> Nx.reshape({1, 8})
end
number2input_tensor.(123)
And now, a function to convert true or false to the tensor result
even2output_tensor = fn is_even ->
if(is_even, do: Nx.tensor([1.0, 0.0]), else: Nx.tensor([0.0, 1.0]))
end
even2output_tensor.(false)
Remember we started…
trainning_data |> Enum.take(10)
trainning_data =
trainning_data
|> Stream.map(fn {n, is_even} ->
{number2input_tensor.(n), even2output_tensor.(is_even)}
end)
trainning_data |> Enum.take(10)
Trainning day!
Now we have a infinite list of data to train!
Just train it again with some datas
trained1 =
model
|> Axon.Loop.trainer(:categorical_cross_entropy, Axon.Optimizers.adamw(0.005))
|> Axon.Loop.run(trainning_data |> Stream.take(1000), epochs: 1, compiler: EXLA)
Section
Section
Section
Section
require Axon
model =
Axon.input({nil, 8})
|> Axon.dense(20, activation: :relu)
|> Axon.dense(20, activation: :relu)
# |> Axon.dropout(rate: 0.5)
|> Axon.dense(2, activation: :softmax)
# |> Axon.dense(2, activation: :sigmoid)
# state_init = model |> Axon.init(compiler: EXLA)
train_data = [
{Nx.tensor([[0.0, 0, 0, 0, 0, 0, 0, 1.0]]), Nx.tensor([0.0, 1.0])},
{Nx.tensor([[0.0, 0, 0, 0, 0, 0, 0.0, 0.0]]), Nx.tensor([1.0, 0.0])}
]
trained =
model
|> Axon.Loop.trainer(:categorical_cross_entropy, Axon.Optimizers.adamw(0.005))
|> Axon.Loop.run(train_data, epochs: 100, compiler: EXLA)
Axon.predict(model, trained, %{
"input_0" => Nx.tensor([[0.0, 0.0, 0, 0, 0, 0, 0.0, 1.0]])
})
Testing
require Axon
model =
Axon.input({nil, 8})
|> Axon.dense(128)
|> Axon.dense(2, activation: :softmax)
state_init = model |> Axon.init(compiler: EXLA)
train_data = [
{Nx.tensor([[0.0, 0, 0, 0, 0, 0, 0, 1.0]]), Nx.tensor([0.0, 1.0])},
{Nx.tensor([[0.0, 0, 0, 0, 0, 0, 1.0, 0.0]]), Nx.tensor([1.0, 0.0])}
]
model_state =
model
|> Axon.Loop.trainer(:categorical_cross_entropy, Axon.Optimizers.adamw(0.005))
|> Axon.Loop.run(train_data, epochs: 10, compiler: EXLA)
Axon.predict(model, model_state, %{
"input_0" => Nx.tensor([[0.0, 0.0, 0, 0, 0, 0, 1.0, 0.0]])
})
:io_lib.format("~8.2.0B", [1])
|> Enum.map(fn d -> if d == ?0, do: 0.0, else: 1.0 end)
|> Nx.tensor()
random_num_oddity = fn ->
random = Enum.random(0..255)
odd = if rem(random, 2) == 0, do: true, else: false
bin_tensor =
:io_lib.format("~8.2.0B", [random])
|> Enum.map(fn d -> if d == ?0, do: 0.0, else: 1.0 end)
|> Nx.tensor()
|> Nx.reshape({1, 8})
{bin_tensor, odd}
end
{test_1, test_2} = random_num_oddity.()
random_num_oddity_list = fn ->
Stream.unfold(0, fn _ ->
{random_num_oddity.(), 0}
end)
end
random_num_oddity_list.() |> Enum.take(10)
data_trainning =
Stream.unfold(0, fn _ ->
{random_num_oddity.(), 0}
end)
|> Stream.map(fn {tn, oddity} ->
{tn, if(oddity, do: Nx.tensor([0.0, 1.0]), else: Nx.tensor([1.0, 0.0]))}
end)
# |> Enum.take(10)
require Axon
model =
Axon.input({nil, 8})
|> Axon.dense(20, activation: :relu)
|> Axon.dense(20, activation: :relu)
# |> Axon.dropout(rate: 0.5)
|> Axon.dense(2, activation: :softmax)
# |> Axon.dense(2, activation: :sigmoid)
trained =
model
|> Axon.Loop.trainer(:categorical_cross_entropy, Axon.Optimizers.adamw(0.005))
|> Axon.Loop.run(data_trainning |> Enum.take(1_000), epochs: 1, compiler: EXLA)
Axon.predict(
model,
trained,
%{
"input_0" => Nx.tensor([[0.0, 0.0, 0, 0, 0, 0, 0.0, 1.0]])
},
compiler: EXLA
)
Nx.tensor([[0.0, 0, 0, 0, 0, 0, 0, 1.0]])