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

Programming Machine Learning - Chapter 2

livebook/ch02.livemd

Programming Machine Learning - Chapter 2

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

Load data

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}])
Kino.DataTable.new(data)
VegaLite.new(width: 400)
|> VegaLite.data_from_values(data, only: ["Reservations", "Pizzas"])
|> VegaLite.mark(:point)
|> VegaLite.encode_field(:x, "Reservations", type: :quantitative)
|> VegaLite.encode_field(:y, "Pizzas", type: :quantitative)

Linear regression

defmodule Linear do
  import Nx.Defn

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

  def train(kino_frame, x, y, iterations, lr) do
    do_train(kino_frame, x, y, iterations, lr, _w = 0)
  end

  # -- Private

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

  defp loss(x, y, w) do
    Nx.to_number(loss_tensor(x, y, w))
  end

  defp do_train(_kino_frame, _x, _y, 0, _lr, _w) do
    :error
  end

  defp do_train(kino_frame, x, y, iterations, lr, w) do
    current_loss = loss(x, y, w)
    # IO.puts("[#{iterations}] Current loss: #{current_loss} | w = #{w}")
    Kino.Frame.render(kino_frame, "[#{iterations}] Current loss: #{current_loss} | w = #{w}")

    cond do
      loss(x, y, w + lr) < current_loss ->
        do_train(kino_frame, x, y, iterations - 1, lr, w + lr)

      loss(x, y, w - lr) < current_loss ->
        do_train(kino_frame, x, y, iterations - 1, lr, w - lr)

      true ->
        {:ok, w, current_loss}
    end
  end
end
frame = Kino.Frame.new()
iterations = Kino.Input.number("Iterations", default: 10_000) |> Kino.render()
learning_rate = Kino.Input.range("Learning rate", default: 0.01, min: 0.01, max: 1.0, step: 0.01)
iterations = Kino.Input.read(iterations)
learning_rate = Kino.Input.read(learning_rate) |> IO.inspect(label: "Learning rate")

pred =
  with {:ok, w, loss} <-
         Linear.train(frame, data["Reservations"], data["Pizzas"], iterations, learning_rate) do
    IO.inspect({w, loss})
    Linear.predict(data["Reservations"], w)
  end

data = Explorer.DataFrame.put(data, "Prediction", pred)
VegaLite.new(width: 400)
|> VegaLite.data_from_values(data, only: ["Reservations", "Pizzas", "Prediction"])
|> VegaLite.layers([
  VegaLite.new()
  |> VegaLite.mark(:point)
  |> VegaLite.encode_field(:x, "Reservations", type: :quantitative)
  |> VegaLite.encode_field(:y, "Pizzas", type: :quantitative),
  VegaLite.new()
  |> VegaLite.mark(:line)
  |> VegaLite.encode_field(:x, "Reservations", type: :quantitative)
  |> VegaLite.encode_field(:y, "Prediction", type: :quantitative)
])

Linear regression - with bias

defmodule LinearBias do
  import Nx.Defn

  defn predict(x, w, b) do
    x * w + b
  end

  def train(x, y, iterations, lr) do
    current_loss = loss(x, y, _w = 0, _b = 0)

    do_train(x, y, iterations, lr, _w = 0, _b = 0, current_loss)
  end

  # -- Private

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

  defp loss(x, y, w, b) do
    Nx.to_number(loss_tensor(x, y, w, b))
  end

  defp do_train(_x, _y, 0, _lr, _w, _b, _current_loss) do
    :error
  end

  defp do_train(x, y, iterations, lr, w, b, current_loss) do
    cond do
      (loss = loss(x, y, w + lr, b)) < current_loss ->
        do_train(x, y, iterations - 1, lr, w + lr, b, loss)

      (loss = loss(x, y, w - lr, b)) < current_loss ->
        do_train(x, y, iterations - 1, lr, w - lr, b, loss)

      (loss = loss(x, y, w, b + lr)) < current_loss ->
        do_train(x, y, iterations - 1, lr, w, b + lr, loss)

      (loss = loss(x, y, w, b - lr)) < current_loss ->
        do_train(x, y, iterations - 1, lr, w, b - lr, loss)

      true ->
        {:ok, w, b, current_loss}
    end
  end
end
iterations = 200_000
learning_rate = 0.0001

{time, {:ok, w, b, _loss}} =
  :timer.tc(
    LinearBias,
    :train,
    [data["Reservations"], data["Pizzas"], iterations, learning_rate]
  )

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

prediction = LinearBias.predict(data["Reservations"], w, b)

data = Explorer.DataFrame.put(data, "Prediction", prediction)
VegaLite.new(width: 400)
|> VegaLite.data_from_values(data, only: ["Reservations", "Pizzas", "Prediction"])
|> VegaLite.layers([
  VegaLite.new()
  |> VegaLite.mark(:point)
  |> VegaLite.encode_field(:x, "Reservations", type: :quantitative)
  |> VegaLite.encode_field(:y, "Pizzas", type: :quantitative),
  VegaLite.new()
  |> VegaLite.mark(:line)
  |> VegaLite.encode_field(:x, "Reservations", type: :quantitative)
  |> VegaLite.encode_field(:y, "Prediction", type: :quantitative)
])