Chapter 1
Mix.install([
{:axon, "~> 0.5"},
{:nx, "~> 0.5"},
{:explorer, "~> 0.5"},
{:kino, "~> 0.8"},
{:kino_explorer, "~> 0.1"}
])
Dataset Normalization and Slicing
require Explorer.DataFrame, as: DF
iris = Explorer.Datasets.iris()
# Normalize
columns = ~w(petal_length petal_width sepal_length sepal_width)
iris =
iris
# Ensure input features operate on a common scale, i.e. standardize
# `across`, `mean`, and `variance` are not imported here. They are from the `mutate` macro
|> DF.mutate(Enum.map(across(^columns), &{&1.name, (&1 - mean(&1)) / variance(&1)}))
# `species` is from the `mutate` macro
# `species` is a categorical feature with no notion of scale, nothing to standardize
|> DF.mutate(species: Explorer.Series.cast(species, :category))
# Shuffle so we don't train or test on the same data every time
|> DF.shuffle()
# Split dataframe into training and testing sets
# Can we use `sample` here?
# https://hexdocs.pm/explorer/Explorer.DataFrame.html#sample/3
train_df = DF.slice(iris, 0..119)
test_df = DF.slice(iris, 120..149)
Formatting
# `stack` converts it to a tensor
# Why axis -1?
# Grab the last column and turn it into a row
x_train = Nx.stack(train_df[columns], axis: -1)
# Why `iota` with these values?
# 1x3 shaped tensor
y_train =
train_df["species"]
|> Nx.stack(axis: -1)
# One-hot encode the field
|> Nx.equal(Nx.iota({1, 3}, axis: -1))
x_test = Nx.stack(test_df[columns], axis: -1)
y_test =
test_df["species"]
|> Nx.stack(axis: -1)
|> Nx.equal(Nx.iota({1, 3}, axis: -1))
Model Training
# Shape represents the values of each dimension
# Create an input layer named "iris_features"
model =
"iris_features"
# Why this particular shape?
# Accept a tensor of 4 values in each row
# `nil` because it's 1 dimensional
|> Axon.input(shape: {nil, 4})
# Why these values?
# 3 neurons to encode this data
# Dense layer because it's fully connected. Also called a linear layer.
# Each neuron is connected to all other neurons in the next layer
# The output is probabilities
# Softmax is used more for classification and less for regression
|> Axon.dense(3, activation: :softmax)
Axon.Display.as_graph(model, Nx.template({1, 4}, :f32))
-
Grab inputs from the input pipeline
-
Make predictions from inputs
-
Determine how good the predictions were
-
Update the model based on prediction goodness
-
Repeat
# Categorical cross entropy is a loss function
# SGD stands for stochastic-gradient descent
trained_model =
model
|> Axon.Loop.trainer(:categorical_cross_entropy, :sgd)
|> Axon.Loop.metric(:accuracy)
# Can pass in the trained model as the initial state to progressively train it.
|> Axon.Loop.run(Stream.repeatedly(fn -> {x_train, y_train} end), %{},
iterations: 500,
epochs: 10
)
Evaluation
model
|> Axon.Loop.evaluator()
|> Axon.Loop.metric(:accuracy)
|> Axon.Loop.run([{x_test, y_test}], trained_model)