Code

The loss function — fill in two holes

The first piece is the loss. In the last step you saw its formula for one picture; now you write it for all of them at once.

Pseudocode

cross_entropy(probs, y):                  # probs [n, 10], y [n]
    for every picture i:
        correct[i] ← probs[i, y[i]]       # the probability the model gave the CORRECT class
    return  mean( −ln(correct) )

The name is cross-entropy. The word does not concern us: it is the same "minus the logarithm of the probability the model gave the right answer", averaged.

Two things that look simple here and are not.

"Take the correct class's probability." probs is [n, 10] and y is [n] numbers. From every row you must take one column — and a different one for each row. PyTorch has "paired" indexing for that: probs[row_numbers, column_numbers] picks one element per pair. The row numbers are torch.arange(n).

"Mean" and "logarithm" — which first. You must average the losses, not the probabilities: mean(−log p), not −log(mean p). They are different things.

Your job

neuron.py (right panel) has a new function, cross_entropy, with two holes in it:

  • HOLE 1 — for each picture, take its correct class's probability. The placeholder "takes class zero for every picture", so the code runs but is wrong almost always.
  • HOLE 2 — turn them into a loss: minus the logarithm, then the mean. The placeholder simply averages the probabilities.

Everything else in the file is last lesson's neuron, softmax, forward, accuracy, unchanged.

test_neuron.py grew: a TestCrossEntropy with five tests appeared. Run it first without editing anything:

$ python -m unittest
...FFFFF...........
FAIL: test_a_shrug_costs_ln_of_the_number_of_classes (...)
AssertionError: 0.10000000149011612 != 2.302585 within 4 places (...)
FAIL: test_confidently_wrong_is_punished_hard (...)
AssertionError: 0.009999999776482582 not greater than 1.600000023841858
FAIL: test_it_averages_the_losses_not_the_probabilities (...)
AssertionError: 0.30000001192092896 != 1.4978660000000001 within 4 places (...)
FAIL: test_it_looks_at_the_probability_of_the_true_class (...)
AssertionError: 0.375 != 0.693147 within 4 places (...)
FAIL: test_perfect_predictions_cost_nothing (...)
AssertionError: 0.5 != 0.0 within 6 places (0.5 difference)
----------------------------------------------------------------------
Ran 19 tests in 0.056s

FAILED (failures=5)

Fill both holes — you should see OK.

Three of the five tests are worth a look. a_shrug_costs_ln_of_the_number_of_classes: a model that gives 0.1 to every one of ten classes must get ln 10 = 2.3026 — the same "no clue" number you saw in the last step. confidently_wrong_is_punished_hard: 0.01 for the correct class must cost more than four times as much as 0.4. perfect_predictions_cost_nothing: everything right — zero loss.

When it does not pass

All of this was tried:

  • -0.1 != 2.302585 — you returned -correct.mean() and forgot the logarithm. The loss must be positive; the probability is below 1, and a minus without a logarithm only flips the sign.
  • 1.2039 != 1.4978 (only the averages_the_losses test fails) — you wrote -torch.log(correct.mean()). The logarithm of the mean is not the mean of the logarithms; this test was made precisely to catch that version.
  • -2.3025 != 2.302585 — you wrote torch.log(correct).mean() without the minus. A probability below 1 has a negative log — you got a negative "loss". Then training would increase the error.
  • IndexError: index 7 is out of bounds for dimension 0 with size 4 — probs[y] instead of probs[torch.arange(len(y)), y]. One index picks rows by class numbers, not columns.