Hands On: Time Travel Testing
Mix.install(
[
{:nx, "~> 0.6.3"},
{:exla, "~> 0.6.4"},
{:jason, "~> 1.4"}
],
config: [nx: [default_backend: EXLA.Backend]]
)
Loading JSON
path = __DIR__ |> Path.join("files/weights.json")
[w1, w2] = path |> File.read!() |> Jason.decode!()
w1 = Nx.tensor(w1)
w2 = Nx.tensor(w2)
MNIST
defmodule C7.MNIST do
@moduledoc "MNIST functions from Chapter 7"
def load_images(path) do
# Open and unzip the file of images then store header information into variables
<<_magic_number::32, n_images::32, n_rows::32, n_cols::32, images::binary>> =
path
|> File.read!()
|> :zlib.gunzip()
images
# Create 1D tensor of type unsigned 8-bit integer from binary
|> Nx.from_binary({:u, 8})
# Reshape the pixels into a matrix into a matrix where each row is an image
|> Nx.reshape({n_images, n_cols * n_rows})
end
@doc """
Inserts a column of 1's into position 0 of tensor X along the the x-axis
"""
def prepend_bias(x) do
{row, _col} = Nx.shape(x)
bias = Nx.broadcast(Nx.tensor(1), {row, 1})
Nx.concatenate([bias, x], axis: 1)
end
def load_labels(filename) do
# Open and unzip the file of images then read all the labels into a list
<<_magic_number::32, n_items::32, labels::binary>> =
filename
|> File.read!()
|> :zlib.gunzip()
labels
# Create 1D tensor of type unsigned 8-bit integer from binary
|> Nx.from_binary({:u, 8})
# Reshape the labels into a 1-column matrix
|> Nx.reshape({n_items, 1})
end
@doc "Flip hot values to 1"
def one_hot_encode(y) do
{rows, _} = Nx.shape(y)
# Create y_rows x 10 matrix where each row has elements 0..9
template = Nx.broadcast(0..9 |> Range.to_list() |> Nx.tensor(), {rows, 10})
# Run element-wise equal on template and y to flip `equal` value to 1's and the rest 0's
Nx.equal(template, y)
end
end
files_path = __DIR__ |> Path.join("/files")
training_images_path = Path.join(files_path, "train-images-idx3-ubyte.gz")
training_labels_path = Path.join(files_path, "train-labels-idx1-ubyte.gz")
test_images_path = Path.join(files_path, "t10k-images-idx3-ubyte.gz")
test_labels_path = Path.join(files_path, "t10k-labels-idx1-ubyte.gz")
import C7.MNIST
y_train_unencoded = load_labels(training_labels_path)
y_train = one_hot_encode(y_train_unencoded)
y_test = load_labels(test_labels_path)
x_train = load_images(training_images_path)
x_test = load_images(test_images_path)
Neural Network
defmodule C10.NeuralNetwork do
import C7.MNIST, only: [prepend_bias: 1]
import Nx.Defn
defn sigmoid(z) do
1 / (1 + Nx.exp(-z))
end
def softmax(logits) do
exponentials = Nx.exp(logits)
sum_of_exponentials_by_row =
exponentials
|> Nx.sum(axes: [1])
|> Nx.reshape({:auto, 1})
Nx.divide(exponentials, sum_of_exponentials_by_row)
end
@doc """
Implements the operation called "forward propagation"
Steps:
1. We add a bias to the inputs
2. Compute the weighted sum using the 1st matrix of weights, w1
3. Pass the result to the activation function (sigmoid or softmax)
4. Repeat for all layers
"""
def forward(x, w1, w2) do
# Hidden layer
h =
x
|> prepend_bias()
|> then(&Nx.dot(&1, w1))
|> sigmoid()
# Output layer
y_hat =
h
|> prepend_bias()
|> then(&Nx.dot(&1, w2))
|> softmax()
y_hat
end
@doc """
Same classify/2 function from Ch 7 but modified for neutral network
"""
def classify(x, w1, w2) do
x
|> forward(w1, w2)
|> Nx.argmax(axis: 1)
|> Nx.reshape({:auto, 1})
end
def report(iteration, x_train, y_train, x_test, y_test, w1, w2) do
y_hat = forward(x_train, w1, w2)
training_loss = loss(y_train, y_hat)
classifications = classify(x_test, w1, w2)
# y_test is not one-hot encoded
# Measure how many classifications were gotten correctly by comparing
# with y_test. The mean/1 function essentially will get the the sum of 1's (matches)
# divided by the total number of classifications
accuracy =
classifications
|> Nx.equal(y_test)
|> Nx.mean()
|> Nx.multiply(100.0)
IO.puts(
"Iteration: #{iteration}, Loss: #{Nx.to_number(training_loss)}, Accuracy: #{Nx.to_number(accuracy)}"
)
end
@doc """
Cross-entropy loss
Loss formula specific for multiclass classifiers.
Measures the distance between the classifier's predictions and the labels.
Lower loss means better classifier
"""
def loss(y_train, y_hat) do
{rows, _} = Nx.shape(y_train)
y_train
|> Nx.multiply(Nx.log(y_hat))
|> Nx.sum()
|> Nx.multiply(-1)
|> Nx.divide(rows)
end
end
Running Classification using MNIST Test Set
C10.NeuralNetwork.report(0, x_train, y_train, x_test, y_test, w1, w2)
Result: 43.19% correct matches