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

Programming Machine Learning - Chapter 3

livebook/ch03.livemd

Programming Machine Learning - Chapter 3

Mix.install(
  [
    {:nx, "~> 0.4"},
    {:explorer, "~> 0.4"},
    {:exla, "~> 0.4"}
  ],
  config: [
    nx: [
      default_backend: EXLA.Backend,
      default_defn_options: [compiler: EXLA]
    ]
  ]
)
:ok

Notes

Load data

The data provided with the book is not using a particular format, it’s just plain text. The following code converts it to CSV.

file = File.stream!("#{__DIR__}/../book/02_first/pizza.txt")

{:ok, data} =
  file
  |> Enum.reduce([], fn line, acc ->
    line =
      line
      |> String.trim()
      |> String.split()
      |> Enum.join(",")

    [acc | [line, "\n"]]
  end)
  |> :binary.list_to_bin()
  |> Explorer.DataFrame.load_csv(dtypes: [{"Pizzas", :float}, {"Reservations", :float}])
{:ok,
 #Explorer.DataFrame<
   Polars[30 x 2]
   Reservations float [13.0, 2.0, 14.0, 23.0, 13.0, ...]
   Pizzas float [33.0, 16.0, 32.0, 51.0, 27.0, ...]
 >}

Without bias

defmodule GradientDescent do
  import Nx.Defn

  def train(x, y, iterations, lr) do
    seed = DateTime.utc_now() |> DateTime.to_unix()
    {w, _new_key} = Nx.Random.normal(Nx.Random.key(seed))

    for _ <- 1..iterations, reduce: w do
      w_acc -> update(x, y, lr, w_acc)
    end
  end

  # -- Private

  defnp predict(x, w) do
    x * w
  end

  defnp loss(x, y, w) do
    Nx.mean((predict(x, w) - y) ** 2)
  end

  defnp gradient(x, y, w) do
    # Equivalent to
    #   w_gradient = 2 * Nx.mean(x * (predict(x, w) - y))
    grad(w, &amp;loss(x, y, &amp;1))
  end

  defnp update(x, y, lr, w) do
    w - gradient(x, y, w) * lr
  end
end
{:module, GradientDescent, <<70, 79, 82, 49, 0, 0, 17, ...>>, {:update, 4}}
GradientDescent.train(data["Reservations"], data["Pizzas"], 100, 0.001)
#Nx.Tensor<
  f64
  EXLA.Backend
  1.8436928702010968
>

With bias

defmodule GradientDescentWithBias do
  import Nx.Defn

  def train(x, y, iterations, lr) do
    seed = DateTime.utc_now() |> DateTime.to_unix()
    {w, _new_key} = Nx.Random.normal(Nx.Random.key(seed))
    {b, _new_key} = Nx.Random.normal(Nx.Random.key(seed))

    for _i <- 1..iterations, reduce: {w, b} do
      {w_acc, b_acc} ->
        # IO.puts("#{i}: #{inspect(Nx.to_number(w_acc))} #{inspect(Nx.to_number(b_acc))}")
        update(x, y, lr, {w_acc, b_acc})
    end
  end

  # -- Private

  defnp predict(x, {w, b}) do
    x * w + b
  end

  defnp loss(x, y, {w, b}) do
    Nx.mean((predict(x, {w, b}) - y) ** 2)
  end

  defnp gradient(x, y, {w, b}) do
    # Equivalent to
    #   w_gradient = 2 * Nx.mean(x * (predict(x, w, b) - y))
    #   b_gradient = 2 * Nx.Mean(predict(x, w, b) - y)
    grad({w, b}, &amp;loss(x, y, &amp;1))
  end

  defnp update(x, y, lr, {w, b}) do
    {new_w, new_b} = gradient(x, y, {w, b})

    {w - new_w * lr, b - new_b * lr}
  end
end
{:module, GradientDescentWithBias, <<70, 79, 82, 49, 0, 0, 18, ...>>, {:update, 4}}
{time, {w, b}} =
  :timer.tc(
    GradientDescentWithBias,
    :train,
    [data["Reservations"], data["Pizzas"], 200_000, 0.0001]
  )

IO.puts("w=#{Nx.to_number(w)} b=#{Nx.to_number(b)} in #{time / 1_000} ms")

20:50:21.081 [info] TfrtCpuClient created.
w=1.0811303090591726 b=13.172265257054141 in 12849.925 ms
:ok