Pizzeria
Section
Mix.install([
{:scholar, "~> 0.1"},
{:explorer, "~> 0.5.6"},
{:exla, "~> 0.5.2"},
{:req, "~> 0.3.6"},
{:kino_vega_lite, "~> 0.1.8"},
{:kino, "~> 0.9.0"},
{:kino_explorer, "~> 0.1.6"}
])
:ok
And then alias our modules
alias Scholar.Linear.LinearRegression, as: LR
alias VegaLite, as: Vl
require Explorer.DataFrame, as: DF
require Explorer.Series, as: S
Nx.global_default_backend(EXLA.Backend)
{Nx.BinaryBackend, []}
Generating Data
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
{x, y} = {
Nx.stack([12, 20, 18, 30, 24]) |> Nx.stack(axis: -1),
Nx.tensor([36, 72, 68, 82, 75])
}
model = LR.fit(x, y)
12:06:40.232 [info] TfrtCpuClient created.
%Scholar.Linear.LinearRegression{
coefficients: #Nx.Tensor<
f32[1]
EXLA.Backend
[2.3761065006256104]
>,
intercept: #Nx.Tensor<
f32
EXLA.Backend
17.176986694335938
>
}
And our prediction for Monday
LR.predict(model, Nx.tensor([[15]]))
#Nx.Tensor<
f32[1]
EXLA.Backend
[52.81858444213867]
>
Save The Model
Great, so now we want to save this model for re-use. For that, we reach into Axom (I hope).
model
|> Nx.serialize()
|> then(&File.write!("pizzeria.nx", &1))
:ok
Load The Model
Let’s reload the model and run our predictions.
model = File.read!("pizzeria.nx") |> Nx.deserialize()
%Scholar.Linear.LinearRegression{
coefficients: #Nx.Tensor<
f32[1]
EXLA.Backend
[2.3761065006256104]
>,
intercept: #Nx.Tensor<
f32
EXLA.Backend
17.176986694335938
>
}
Run the model
Now that the model is loaded back into Elixir, we can run the model and predict the outcome of the number of pizza doughs to create.
LR.predict(model, Nx.tensor([15]))
|> Nx.to_number()
|> Float.round(0)
53.0
More Generic Model Runner
As we are using scholar directly, instead of a neural network (aka Axon), we could assume the model, but it is better to assume the method predict.
for num_resi <- [5, 10, 15, 20, 25, 30, 35, 40] do
num_doughs =
apply(model.__struct__, :predict, [model, Nx.tensor([num_resi])])
|> Nx.to_number()
|> Float.round(0)
IO.puts("With #{num_resi} reseravations we should make #{num_doughs} pizza doughs")
num_doughs
end
With 5 reseravations we should make 29.0 pizza doughs
With 10 reseravations we should make 41.0 pizza doughs
With 15 reseravations we should make 53.0 pizza doughs
With 20 reseravations we should make 65.0 pizza doughs
With 25 reseravations we should make 77.0 pizza doughs
With 30 reseravations we should make 88.0 pizza doughs
With 35 reseravations we should make 100.0 pizza doughs
With 40 reseravations we should make 112.0 pizza doughs
[29.0, 41.0, 53.0, 65.0, 77.0, 88.0, 100.0, 112.0]
Visualize Data
Let’s visualize the data to confirm our reply
df = DF.new(x: x, y: y)
Vl.new(
title: [text: "Pizza Dough Approximation"],
width: 630,
height: 630
)
|> Vl.data_from_values(df)
|> Vl.mark(:circle, size: 500)
|> Vl.encode_field(:x, "x", type: :quantitative)
|> Vl.encode_field(:y, "y", type: :quantitative)
{"$schema":"https://vega.github.io/schema/vega-lite/v5.json","data":{"values":[{"x":12,"y":36},{"x":20,"y":72},{"x":18,"y":68},{"x":30,"y":82},{"x":24,"y":75}]},"encoding":{"x":{"field":"x","type":"quantitative"},"y":{"field":"y","type":"quantitative"}},"height":630,"mark":{"size":500,"type":"circle"},"title":{"text":"Pizza Dough Approximation"},"width":630}
As we can see, the coefficient is almost 2.0, and the intercept is nearly 1.0. Let’s plot the result of linear regression.
[intercept] = Nx.to_flat_list(model.intercept)
[coefficients] = Nx.to_flat_list(model.coefficients)
x_1 = 0
x_2 = 40
line = %{
x: [x_1, x_2],
y: [x_1 * coefficients + intercept, x_2 * coefficients + intercept]
}
Vl.new(
title: [text: "Pizza Dough Approximation"],
width: 630,
height: 630
)
|> Vl.layers([
Vl.new()
|> Vl.data_from_values(df)
|> Vl.mark(:circle, size: 500)
|> 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: :green)
|> Vl.encode_field(:x, "x", type: :quantitative)
|> Vl.encode_field(:y, "y", type: :quantitative)
])
{"$schema":"https://vega.github.io/schema/vega-lite/v5.json","height":630,"layer":[{"data":{"values":[{"x":12,"y":36},{"x":20,"y":72},{"x":18,"y":68},{"x":30,"y":82},{"x":24,"y":75}]},"encoding":{"x":{"axis":{"grid":false},"field":"x","type":"quantitative"},"y":{"axis":{"grid":false},"field":"y","type":"quantitative"}},"mark":{"size":500,"type":"circle"}},{"data":{"values":[{"x":0,"y":17.176986694335938},{"x":40,"y":112.22124671936035}]},"encoding":{"x":{"field":"x","type":"quantitative"},"y":{"field":"y","type":"quantitative"}},"mark":{"color":"green","type":"line"}}],"title":{"text":"Pizza Dough Approximation"},"width":630}
Overfitting the data
Here we demonstrate an example of “overfitting” where we our model too closely follows the test data.
df = DF.new(x: x, y: y)
linex = Nx.concatenate([x, Nx.tensor([[0]])])
liney = Nx.concatenate([y, Nx.tensor([10])])
df_line = DF.new(x: linex, y: liney)
Vl.new(
title: [text: "Pizza Dough Approximation"],
width: 630,
height: 630
)
|> Vl.layers([
Vl.new()
|> Vl.data_from_values(df)
|> Vl.mark(:circle, size: 500)
|> 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(df_line)
|> Vl.mark(:line)
|> Vl.encode_field(:x, "x", type: :quantitative, axis: [grid: false])
|> Vl.encode_field(:y, "y", type: :quantitative, axis: [grid: false])
])
{"$schema":"https://vega.github.io/schema/vega-lite/v5.json","height":630,"layer":[{"data":{"values":[{"x":12,"y":36},{"x":20,"y":72},{"x":18,"y":68},{"x":30,"y":82},{"x":24,"y":75}]},"encoding":{"x":{"axis":{"grid":false},"field":"x","type":"quantitative"},"y":{"axis":{"grid":false},"field":"y","type":"quantitative"}},"mark":{"size":500,"type":"circle"}},{"data":{"values":[{"x":12,"y":36},{"x":20,"y":72},{"x":18,"y":68},{"x":30,"y":82},{"x":24,"y":75},{"x":0,"y":10}]},"encoding":{"x":{"axis":{"grid":false},"field":"x","type":"quantitative"},"y":{"axis":{"grid":false},"field":"y","type":"quantitative"}},"mark":"line"}],"title":{"text":"Pizza Dough Approximation"},"width":630}