Lab#FU01-1: Taxi fare, pure Python

Lab

📘 Lab#FU01-1: Taxi fare, pure Python

In this lab you will:

  1. Load three taxi receipts.
  2. Guess a price per kilometre, predict, measure the error, and nudge the guess.
  3. Repeat the loop until the guess settles on €1.50 / km.
  4. Plot how the loss falls.

Open In Colab   Code on GitHub

The story starts in The learning loop. Here you run it yourself, in the page. The editor below is live Python in the browser — no install, no libraries for the loop, matplotlib only draws the loss curve. The first load takes a few seconds.

Price = w · kilometres. The true rate on these receipts is €1.50 per kilometre. You do not tell the program that number. It has to find it.

1 Creating the algorithm

The question. A taxi charges by distance. We have three receipts: 2 km cost €3, 5 km cost €7.50 and 10 km cost €15. What does 7 km cost? We don’t know the rate per kilometre, so we let the receipts teach it to us.

# TODO: find the price of a 7 km trip from three receipts

What we know and what we don’t. We know the kilometres (the input) and the prices (the answers). We don’t know the rate, which we call w. Our whole model is one line, price = w · km, and the machine’s only job is to find a good w.

km    = [2, 5, 10]       # what we know: inputs
price = [3, 7.5, 15]     # what we know: answers
# TODO: w is unknown, the thing to learn

A first guess. We have no idea, so we start badly on purpose and watch it improve.

w = 0.0                  # a bad guess on purpose

Predict. With our guess, what would each trip cost? With w = 0, every trip comes out free.

pred = [w * x for x in km]          # [0, 0, 0]

How wrong are we? We compare each prediction with the real receipt. We square the gaps, so a miss above or below counts the same and big misses count more, then average them. That number is the loss, and we want it small.

loss = sum((p - y)**2 for p, y in zip(pred, price)) / len(km)

Which way to move? Every prediction was too low, so w must go up. The gradient tells us which way and how strongly, like feeling the slope of the hill under your feet.

grad = sum(2 * (p - y) * x for p, y, x in zip(pred, price, km)) / len(km)

A small step. We nudge w a little that way. The learning rate is the step size: too small and we crawl, too big and we bounce past the bottom.

lr = 0.01
w = w - lr * grad

Again and again. We predict, measure and nudge, over and over. The loss drops and w creeps towards 1.50.

for step in range(20):
    # predict, measure, find the direction, step
    ...

When to stop. We stop when the loss barely changes, or after a fixed number of steps.

# TODO: stop when loss < 0.0001, or after N steps

Check the answer. w ≈ 1.50, so 7 km costs about €10.50. Nobody told the program the rate; it found the rate from examples. That’s learning.

print(w * 7)             # ≈ 10.5

2 Guess, predict, error, nudge

Three rides: 2 km for €3, 5 km for €7.50, 10 km for €15. Start at \(w = 0\) — every ride is free — and take steps of size \(0.01\).

The gradient of the mean squared error is the average of \(2 \cdot \text{error} \cdot \text{km}\). A nudge is a small step against that gradient:

\[ w \leftarrow w - 0.01 \cdot \nabla L \]

Try it

Change learning_rate and press Run. Try a smaller rate, or a larger one.

After the first nudge, \(w\) jumps from \(0\) to \(1.29\). After four steps it is already \(1.4994\). By step 6 it has settled on \(1.50\). Nobody programmed the rate.

Loss starts at \(96.75\) — every ride predicted as free — and drops to about zero once \(w\) reaches \(1.50\).

Reading the code: mean_squared_error

This little function answers one question: how wrong is this weight? Let’s run it by hand with weight = 1.

Ride Real price Prediction (1 · km) Error Error²
2 km €3.00 €2.00 −1.00 1.00
5 km €7.50 €5.00 −2.50 6.25
10 km €15.00 €10.00 −5.00 25.00
  • predictions: what we would charge with this weight: 2, 5, 10.
  • errors: prediction minus real price: −1, −2.5, −5. Negative means we charged too little.
  • e * e: square each error: 1, 6.25, 25.
  • sum(...) / len(km): add them (32.25) and divide by the number of rides (3). The loss is 10.75.

Why square? A miss of −5 and a miss of +5 are equally bad, so the sign must go. And squaring makes big misses hurt much more than small ones: missing by 5 costs 25, missing by 1 costs only 1.

In maths it is written:

\[ \text{MSE} = \frac{1}{n}\sum_{i=1}^{n} (w \cdot x_i - y_i)^2 \]

Same thing as the code: for every ride, predict, subtract the real price, square, then take the average. With weight = 0 you get the 96.75 above; with weight = 1.5 you get 0. See the MSE card, \(\Sigma\) summation and The loss is a landscape.

3 What you should notice

The learning rate \(0.01\) is a gentle pinch. The same loop, with a bad learning rate, can be slow or can blow up — that is in The learning loop and in Optimization essentials.

Next: a pickup fee, two parameters, and a check against scikit-learn, in Lab#FU01-2: Taxi fare, NumPy and scikit-learn.

4 See also

Back to top