Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Chapter 7

chapter7.livemd

Chapter 7

Mix.install(
  [
    {:nx, "~> 0.6.4"},
    {:exla, "~> 0.6.4"}
  ],
  config: [nx: [default_backend: EXLA.Backend]]
)

Multi classification

defmodule OHE do
  import Nx.Defn

  defn one_hot_encode(y, opts \\ [classes: 10]) do
    Nx.new_axis(y, -1)
    |> Nx.equal(Nx.iota({opts[:classes]}))
  end
end
defmodule Classifier do
  import Nx.Defn

  defn forward(x, w) do
    Nx.dot(x, w)
    |> Nx.sigmoid()
  end

  defn classify(x, w) do
    forward(x, w)
    |> Nx.argmax(axis: 1)
  end

  defn loss(x, y, w) do
    y_hat = forward(x, w)
    first_term = y * Nx.log(y_hat)
    second_term = (1 - y) * Nx.log(1 - y_hat)
    -Nx.sum(first_term + second_term) / Nx.axis_size(x, 0)
  end

  defn gradient(x, y, w) do
    Nx.transpose(x)
    |> Nx.dot(forward(x, w) - y)
    |> Nx.divide(Nx.axis_size(x, 0))
  end

  def train(x_train, y_train, x_test, y_test, iterations, lr) do
    Enum.reduce(
      1..iterations,
      Nx.broadcast(0.0, {Nx.axis_size(x_train, 1), Nx.axis_size(y_train, 1)}),
      fn i, w ->
        report(i, x_train, y_train, x_test, y_test, w)
        Nx.subtract(w, Nx.multiply(gradient(x_train, y_train, w), lr))
      end
    )
  end

  def report(i, x_train, y_train, x_test, y_test, w) do
    training_loss = loss(x_train, y_train, w)
    accuracy = Nx.equal(classify(x_test, w), y_test) |> Nx.mean()

    IO.puts("#{i} - Loss #{Nx.to_number(training_loss)}, Accuracy #{Nx.to_number(accuracy)}")
  end
end

MNIST

defmodule MNIST do
  def load_images(:train), do: load_images(__DIR__ <> "/data/mnist/train-images-idx3-ubyte.gz")
  def load_images(:test), do: load_images(__DIR__ <> "/data/mnist/t10k-images-idx3-ubyte.gz")

  def load_images(filename) when is_binary(filename) do
    <<2051::32, count::32, rows::32, cols::32, pixels::binary>> =
      File.read!(filename)
      |> :zlib.gunzip()

    Nx.from_binary(pixels, :u8)
    |> Nx.reshape({count, rows * cols})
  end

  def load_labels(:train), do: load_labels(__DIR__ <> "/data/mnist/train-labels-idx1-ubyte.gz")
  def load_labels(:test), do: load_labels(__DIR__ <> "/data/mnist/t10k-labels-idx1-ubyte.gz")

  def load_labels(filename) when is_binary(filename) do
    <<2049::32, count::32, labels::binary>> =
      File.read!(filename)
      |> :zlib.gunzip()

    Nx.from_binary(labels, :u8)
    |> Nx.reshape({count})
  end
end
x_train = MNIST.load_images(:train) |> Nx.pad(1, [{0, 0, 0}, {1, 0, 0}])
x_test = MNIST.load_images(:test) |> Nx.pad(1, [{0, 0, 0}, {1, 0, 0}])

y_train = MNIST.load_labels(:train) |> OHE.one_hot_encode()
y_test = MNIST.load_labels(:test)
w = Classifier.train(x_train, y_train, x_test, y_test, 200, 1.0e-5)

Mines vs. Rocks

{x, y} =
  File.stream!(__DIR__ <> "/data/sonar.all-data")
  |> Stream.map(&amp;Enum.split(String.split(&amp;1, ","), -1))
  |> Stream.map(fn {numbers, [label]} ->
    {Enum.map(numbers, &amp;String.to_float/1), (label == "R\n" &amp;&amp; 1) || 0}
  end)
  |> Enum.shuffle()
  |> Enum.unzip()
  |> then(fn {inputs, labels} -> {Nx.tensor(inputs), Nx.tensor(labels)} end)

x_train = x[[0..-47//1]] |> Nx.pad(1, [{0, 0, 0}, {1, 0, 0}])
x_test = x[[-48..-1]] |> Nx.pad(1, [{0, 0, 0}, {1, 0, 0}])

y_train = y[[0..-47//1]] |> OHE.one_hot_encode(classes: 2)
y_test = y[[-48..-1]]
w = Classifier.train(x_train, y_train, x_test, y_test, 10000, 0.01)