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

Pizzeria

notebooks/pizzeria.livemd

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(&amp;File.write!("pizzeria.nx", &amp;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}