Pizzera Web App
# Don't forget to start pizzeria
# iex --sname pizzeria --cookie pizzeriacookie -S mix phx.server
alias VegaLite, as: Vl
require Explorer.DataFrame, as: DF
require Explorer.Series, as: S
:ok
Load The Data
Let’s load our logarithmic dataset (you can also load a linear()
version).
alias Pizzeria.Migrations.Seeds
# Seeds.linear()
Seeds.logarithmic()
Training The Data
We have, for example 31 days of data (Jan 1 - 31). We will split our data into a training set into two, the most often used splits are 80/20, 90/10, and 67/33. We will pick 80/20.
alias Pizzeria.Ml.Engine
traindata = Engine.dataset({~D[2023-01-01], ~D[2023-09-30]})
testdata = Engine.dataset({~D[2023-10-01], ~D[2023-12-31]})
Visualize the Data
{x, y} = traindata
df = DF.new(x: x, y: y)
{testx, testy} = testdata
traindf = DF.new(x: testx, y: testy)
Vl.new(
title: [text: "Pizza Dough Approximation"],
width: 630,
height: 630
)
|> Vl.data_from_values(df)
|> Vl.mark(:circle, color: :blue, size: 100)
|> Vl.encode_field(:x, "x", type: :quantitative, axis: [grid: false])
|> Vl.encode_field(:y, "y", type: :quantitative, axis: [grid: false])
Train The Data
# Let's try linear regression
alias Scholar.Linear.LinearRegression, as: LR
{features, labels} = traindata
model = LR.fit(features, labels)
Visualize The Model
{x, y} = traindata
df = DF.new(x: x, y: y)
{testx, testy} = testdata
traindf = DF.new(x: testx, y: testy)
[intercept] = Nx.to_flat_list(model.intercept)
[coefficients] = Nx.to_flat_list(model.coefficients)
x_1 = 0
x_2 = 60
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, 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: :green, weight: 2)
|> Vl.encode_field(:x, "x", type: :quantitative, axis: [grid: false])
|> Vl.encode_field(:y, "y", type: :quantitative, axis: [grid: false])
])
Evaluate The Model
Using Mean Average Error, we can see how well the model fits.
alias Scholar.Linear.LinearRegression, as: LR
{features, labels} = testdata
model
|> LR.predict(features)
|> Nx.subtract(labels)
|> Nx.abs()
|> Nx.mean()
Tweak the model
alias Scholar.Linear.PolynomialRegression, as: PR
{features, labels} = traindata
model = PR.fit(features, labels)
Let’s see if a polynominal regression fits nicer.
{x, y} = traindata
df = DF.new(x: x, y: y)
{testx, testy} = testdata
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])
])
Re-evaluate The Model
{features, labels} = testdata
model
|> PR.predict(features)
|> Nx.subtract(labels)
|> Nx.abs()
|> Nx.mean()