Code

Neuron and softmax — fill in two holes

In the last step you saw the neuron and softmax at work. Now you write them as Python — and a piece is missing from each.

Pseudocode

neuron(x, w, b):
    return  x @ w + b

softmax(z):                        # z is [n, 10], one row = one picture's scores
    for every row:
        e ← exp(score − the largest score in that row)
        return  e / (the sum of e in that row)

Nothing complicated, but there is one line that looks unnecessary — and is not.

"Subtract the largest." Mathematically softmax does not change: subtracting the same number from all scores leaves the ratios of the probabilities as they were. But on a computer exp(1000) is infinity, and infinity / infinity is nan. After subtracting the largest, the biggest score becomes 0, and exp(0) = 1 — it never overflows.

Your job

Create neuron.py and test_neuron.py (see the right-hand panel) in the same folder as tensors_demo.py.

neuron.py is complete and runs, but is wrong: two places are marked HOLE 1 and HOLE 2.

  • HOLE 1 — neuron. In place of the weighted sum there are "zeros of the right shape": the code runs, but every score is 0. Write the real formula.
  • HOLE 2 — softmax. The division by the sum is already there. What is missing is one line of stability: subtract each row's largest value before torch.exp.

Three hints for HOLE 2:

  • z.max(dim=1) returns a pair (values and indices); the values are .values;
  • "for every row" means dim=1;
  • the result must be [n, 1], not [n] — otherwise it subtracts along the wrong axis. Which max parameter keeps that, you remember from the lesson 2 drills (task 4).

sigmoid is already written — it is only there so you can see what one activation looks like.

Run the test before filling anything

$ python -m unittest
FFF.FF..
FAIL: test_bias_is_added (...)
AssertionError: 0.0 != 0.7 within 5 places (0.7 difference)
FAIL: test_many_neurons_many_images (...)
AssertionError: Lists differ: [[0.0, 0.0], [0.0, 0.0]] != [[1.5, 9.5], [5.5, 49.5]]
FAIL: test_one_neuron_one_image (...)
AssertionError: 0.0 != 4.75 within 5 places (4.75 difference)
FAIL: test_big_scores_do_not_overflow (...)
AssertionError: True is not false : got [[nan, 0.0, 0.0]] - overflow?
FAIL: test_every_row_is_stabilised_on_its_own (...)
AssertionError: True is not false : got [[nan, 0.0, 0.0], [nan, nan, nan]]
----------------------------------------------------------------------
Ran 8 tests in 0.003s

FAILED (failures=5)

(Only the important lines are kept.)

Notice what passes: three dots — sigmoid, "rows add up to 1" and "known softmax values". The unstable softmax works correctly on ordinary numbers and breaks only on big ones. A person would not notice this on their own — the test does: got [[nan, 0.0, 0.0]].

Fill both holes and run again — you should see OK.

When it does not pass

Each of these was tried here:

  • 0.0 != 0.7 (the bias test), while the other two neuron tests are off by exactly the bias — you wrote x @ w and forgot + b. The formula is almost right: 4.5 != 4.75 and [[1.0, 10.0], [5.0, 50.0]] instead of [[1.5, 9.5], [5.5, 49.5]].
  • a Tensor with 3 elements cannot be converted to Scalar — you wrote x * w + b. * is element by element: you got three numbers, not one sum.
  • got [[1.0, 0.0, 0.0], [nan, nan, nan]] with z - z.max() — you subtracted one largest value for the whole table, not one per row. The first row survived; in the second exp(−2000) is 0, and 0 / 0 is nan.
  • The size of tensor a (3) must match the size of tensor b (2) at non-singleton dimension 1 — z.max(dim=1).values without keepdim=True: you got [n] where [n, 1] is needed, and PyTorch tries to match the wrong axis.