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)
])