Network architecture
The model is a fully connected feedforward neural network, also called a multilayer perceptron (MLP). Information travels from the inputs through a single hidden layer to the output; there are no recurrent connections.
Two inputs, three hidden units, one output
The layer sizes are fixed in the constructor. The weights are initialized using samples from a standard normal distribution.
self.input_layer_size = 2
self.hidden_layer_size = 3
self.output_layer_size = 1
self.W1 = np.random.randn(self.input_layer_size, self.hidden_layer_size)
self.W2 = np.random.randn(self.hidden_layer_size, self.output_layer_size)
There are weights in the first matrix and in the second. No bias vectors are initialized or used. The network therefore has nine trainable scalars.
The original code does not set a random seed. Fresh runs begin at different points in parameter space and need not converge to the same result.
Keep track of the shapes
For a batch of examples, the tensors have the following dimensions. In the implemented training split, .
| Quantity | Meaning | Shape |
|---|---|---|
| X | Normalized inputs | m × 2 |
| W¹ | Input-to-hidden weights | 2 × 3 |
| Z², A² | Hidden pre-activations and activations | m × 3 |
| W² | Hidden-to-output weights | 3 × 1 |
| Z³, ŷ | Output pre-activations and predictions | m × 1 |
Parenthesized superscripts label layers or weight matrices; they are not powers. The input is layer 1, the hidden layer is 2, and the output layer is 3. The two weight matrices are numbered separately: connects layers 1 and 2, and connects layers 2 and 3. Throughout the network equations, is the normalized input matrix and is the normalized target column.
The first matrix multiplication
Each hidden unit receives both input features. Its pre-activation is their weighted sum.
For example and hidden unit :
Applying the sigmoid to every entry produces . Processing the entire matrix together is the vectorized equivalent of evaluating each example and each hidden unit individually.
Retrospective correction: one equation in the original architecture text reverses the matrix product to . The source correctly uses np.dot(X, self.W1). The original prose also uses three-row dimensions from the introductory example; the executable training batch has four rows.
The sigmoid and its derivative
The activation and its derivative are:
Equivalently, . At , the activation is and the derivative is . Far from zero, the activation approaches an endpoint and the derivative approaches zero.
def sigmoid(self, z):
return 1 / (1 + np.exp(-z))
def sigmoid_prime(self, z):
return np.exp(-z) / ((1 + np.exp(-z))**2)
The code evaluates the exponential directly. Very large negative pre-activations can overflow numerically; the original implementation has no stability guard. This is one reason to distinguish an educational implementation from a production model.
From the hidden layer to a score
The output unit combines the three hidden activations using a second weight matrix, then applies the sigmoid again.
The output can approach 0 and 100 points but, for finite pre-activations in exact arithmetic, never reaches those endpoints. This bound is part of the model design.
The complete forward pass
def forward(self, X):
self.z2 = np.dot(X, self.W1)
self.a2 = self.sigmoid(self.z2)
self.z3 = np.dot(self.a2, self.W2)
prediction = self.sigmoid(self.z3)
return prediction
The intermediate arrays are stored on the object because backpropagation needs them. Each call replaces those values, so the gradients must correspond to the inputs used in the latest forward pass. The gradient method calls forward itself to ensure that is true.
Before training
The architecture notebook preserves predictions from an untrained initialization:
[[0.36756757],
[0.35411977],
[0.34999250],
[0.35427414]]
These correspond to about 35–37 points, while the targets are 75, 82, 93, and 70. Random weights do not encode the relationship the model is meant to learn.
The later training notebook initializes another network. These initial outputs are evidence of an untrained forward pass, not a recorded starting checkpoint for the final training run.
Source: original architecture notebook and Python implementation.