Scratch Pad
Mix.install([
{:nx, github: "elixirnx/nx", sparse: "nx", override: true},
{:scholar, github: "elixirnx/scholar", branch: "main"},
{:optimus, "~> 0.1"},
{:explorer, "~> 0.5.6"},
{:exla, "~> 0.5.2"},
{:req, "~> 0.3.6"},
{:kino_vega_lite, "~> 0.1.8"},
{:kino, "~> 0.9.3"},
{:kino_explorer, "~> 0.1.6"},
{:scidata, "~> 0.1"}
])
Tensors
The tensor arg can be one of many things
 A number (aka scalar)
 A boolean (synatic sugar on a special 0/1 tensor)
 A list of numbers, booleans, or list of numbers and booleans

A tensor (why? well the
opts
we haven’t talke about yet would then be used to tweak an existing tensor)
Pizzeria
Based on the last week of data we see the following
 Date  # Resies  # Pizzas  —  —  —   Monday  12  36  Tuesday  20  72  Wednesday  18  68  Thursday  30  82  Friday  0  98  Saturday  0  168  Sunday  24  75 
Now, the resaurant doesn’t take reservations on Friday and Saturday. So how should we proceed? We can drop the data and then not predict on Friday’s and Saturdays. Or, we could estimate the “resrvations” based on how many “flips” we normally get. For today, we will just drop that data. Here’s our model
Load The Data
# x =
# y =
Visualize The Data
require Explorer.DataFrame, as: DF
alias VegaLite, as: Vl
# df =
# vl =
Fit The Data
alias Scholar.Linear.LinearRegression, as: LR
# model =
Extract The Linear Equation
The equation is y = mx + b
where m
is the coefficients
(just one) and the b
is intercept
(a scalar).
# m =
# b =
# "y = #{m}x + #{b}"
Visualize the The Equation
Lines are simple to graph, you need a start x=0
and the end x=30
(why 30, as that’s the biggest x value in our data above).
# line =
# vl =
Estimate Pizza Doughs
# example_x = 15
# prediction_y =
# inferences =
Deploy Model
Our model might not be perfect, but it’s trained and built, so how do we get our system to then use that model in our code? We don’t want to retrain every time we want to predict something.
# save the model
What can be serialized can then be deserialized to predict future reservation needs. Using the file based approach, we can actually compile in the model and build time and its just another data structure for elixir to use.
# mdl =
As the model is just data representing the algorihtm, we can use to make prediction.
# LR.predict
Evaluate Model
Our data is quite sparse on many fronts… only a weeks worth of reservation numbers AND only 1 dimension of data, the # of reservations. And, it might not actually be a good approximation to assume the relationship is linear. We call this “underfitting” where we don’t have enough of the right stuff to make a good prediction.
We started with a weeks worth, but now we have a years worth of data and looks like our linear regression does not seem to be giving our Pizzeria friend great results. Looks like the data seems to follow more of a curve than a line, so with a oneline change to use PolynomialRegression we almost get a 50% reduction in error.
defmodule Pizzeria.Migrations.Seeds do
@dates [
{1, 31},
{2, 28},
{3, 31},
{4, 30},
{5, 31},
{6, 30},
{7, 31},
{8, 31},
{9, 30},
{10, 31},
{11, 30},
{12, 31}
]
def run() do
for {_, num} < @dates,
_ < 1..num do
num_resi = :rand.uniform(60)  1
if num_resi > 0 do
weight = 10 + (:rand.uniform() * 0.1  0.5)
bias = 15 + (:rand.uniform() * 2.0  1.0)
num_pizzas = round(weight * :math.log2(num_resi) + bias)
[num_resi, num_pizzas]
else
nil
end
end
> Enum.reject(&is_nil/1)
end
end
data = Pizzeria.Migrations.Seeds.run() > Nx.tensor()
Let’s split our data into test and train.
# n =
# train_size =
# test_size =
# Probably a better way
# traindata =
# testdata =
# x =
# y =
# testx =
# testy =
# model = LR.fit(x, y)
Visualize Updated Data
Based on way more data, let’s visualize our curve.
df = DF.new(x: x, y: y)
testdf = DF.new(x: testx, y: testy)
[m] = Nx.to_flat_list(model.coefficients)
[b] = Nx.to_flat_list(model.intercept)
line = %{
x: [0, 60],
y: [m * 0 + b, m * 60 + b]
}
Vl.new(title: ["Pizzeria Line"], width: 630, height: 630)
> Vl.layers([
Vl.new()
> Vl.data_from_values(df)
> Vl.mark(:circle, size: 100, color: :green)
> Vl.encode_field(:x, "x", type: :quantitative)
> Vl.encode_field(:y, "y", type: :quantitative),
Vl.new()
> Vl.data_from_values(testdf)
> Vl.mark(:circle, size: 100, color: :blue)
> Vl.encode_field(:x, "x", type: :quantitative)
> Vl.encode_field(:y, "y", type: :quantitative),
Vl.new()
> Vl.data_from_values(line)
> Vl.mark(:line, size: 4, color: :red)
> Vl.encode_field(:x, "x", type: :quantitative)
> Vl.encode_field(:y, "y", type: :quantitative)
])
We can then evaluate our model by measuring the error of each prediction in our test dataset.
# calculate MAE
From our visual inspection, maybe a curve is better. Let’s retrain and reevaluate our model.
# But we need bleeding edge Scholar / Nx and it wasn't working in livebook....
# {:nx, github: "elixirnx/nx", sparse: "nx", override: true},
# {:scholar, github: "elixirnx/scholar", branch: "main"},
alias Scholar.Linear.PolynomialRegression, as: PR
# model =
Let’s visualize this line
df = DF.new(x: x, y: y)
traindf = DF.new(x: testx, y: testy)
polyx = Enum.to_list(1..60)
polyy =
polyx
> Enum.map(&Nx.tensor([[&1]]))
> Enum.map(&(PR.predict(model, &1) > Nx.to_flat_list() > List.first()))
Vl.new(
title: [text: "Pizza Dough Approximation"],
width: 630,
height: 630
)
> Vl.layers([
Vl.new()
> Vl.data_from_values(df)
> Vl.mark(:circle, color: :blue)
> Vl.encode_field(:x, "x", type: :quantitative, axis: [grid: false])
> Vl.encode_field(:y, "y", type: :quantitative, axis: [grid: false]),
Vl.new()
> Vl.data_from_values(traindf)
> Vl.mark(:square, color: :red)
> Vl.encode_field(:x, "x", type: :quantitative, axis: [grid: false])
> Vl.encode_field(:y, "y", type: :quantitative, axis: [grid: false]),
Vl.new()
> Vl.data_from_values(line)
> Vl.mark(:line, color: :lightgrey, weight: 1)
> Vl.encode_field(:x, "x", type: :quantitative, axis: [grid: false])
> Vl.encode_field(:y, "y", type: :quantitative, axis: [grid: false]),
Vl.new()
> Vl.data_from_values(%{x: polyx, y: polyy})
> Vl.mark(:line, color: :green, weight: 2)
> Vl.encode_field(:x, "x", type: :quantitative, axis: [grid: false])
> Vl.encode_field(:y, "y", type: :quantitative, axis: [grid: false])
])
And then we can reevaluate the model
# calculate MAE