Matrices as maps
A matrix W with m rows and n columns turns a vector of size n into one of size m: each output is the dot product of one row with the input. A dense layer is exactly this, plus a bias vector b of size m: W @ x + b.
import numpy as np
W = np.array([[1.0, 0.0, 2.0],
[0.0, 1.0, -1.0]])
b = np.array([0.5, 0.0])
x = np.array([1.0, 2.0, 3.0])
print(W.shape, x.shape) # (2, 3) (3,)
print(W @ x + b) # [ 7.5 -1. ]Shapes are the first thing to check. The columns of W must match the input, and its rows decide the output. Composing two layers multiplies their matrices: a (4, 2) matrix after a (2, 3) one maps 3 numbers to 4.
A layer from n inputs to m outputs has m times n weights and m biases. Counting parameters this way tells you how big a model is before you build it.
Your task
Write layer(W, b, x) returning W @ x + b, raising ValueError when W's columns do not match the size of x or b's size does not match W's rows. Write param_count(sizes) returning the number of weights and biases in a stack of dense layers with those sizes, so [3, 4, 2] means 3 to 4 to 2.
The explanation opens here once every check passes.