Two operations every Dense layer hides under the hood
Matrix multiplication is the core operation of neural networks. A Dense layer is, mathematically, a matmul + bias add + activation. tf.matmul(a, b) requires a.shape[-1] == b.shape[-2] — the inner dimensions must contract. Leading dimensions are batch dims and must match (or broadcast).
The Python @ operator is shorthand for tf.matmul. Use whichever is more readable in context.
Broadcasting lets ops work on tensors of different shapes by implicitly expanding size-1 dimensions. The rules: right-align shapes, treat any size-1 dim as expandable, error if neither side is 1 and the sizes don't match. Adding a bias vector (n,) to a batch of activations (batch, n) works because the bias is treated as (1, n) and broadcast across the batch.
(n, 1) when you meant a row vector (1, n) produces a different result without raising. Print the output shape after operations involving different sizes.