Welcome back, intrepid explorer! I'm thrilled to see you for the second lesson in our JAX in Action: Neural Networks from Scratch course. In our previous transmission, we laid the critical groundwork: we prepared our XOR dataset and skillfully crafted the initialize_mlp_params function to set up our network's weights and biases using the Xavier/Glorot method. Our parameters are now neatly organized in a PyTree, ready for action!
Today, we're shifting gears to something truly dynamic: implementing forward propagation. This is the very heart of how a neural network makes predictions. We'll build a function that takes our carefully prepared inputs and parameters and channels the data through the network's layers and activation functions to produce an output. By the end of this lesson, you'll have a working JAX function that performs the complete forward pass for our Multi-Layer Perceptron (MLP), and you'll see it generate its very first (untrained) predictions for the XOR problem!
Now, let's translate this understanding into a JAX function. We'll create mlp_forward_pass, which takes the list of parameters (our PyTree of weights and biases for each layer) and an input x_input. It will then guide the input data through the network.
Let's break this down:
- We initialize
activationswith thex_input. This variable will hold the output of the current layer, which becomes the input to the next. - We loop through
params_listup to the second-to-last element. These are our hidden layers.- Inside the loop, we extract the
weightsandbiasesfor the current layer. - We perform the affine transformation:
z = jnp.dot(activations, weights) + biases. - We apply the
jax.nn.sigmoidactivation function tozand updateactivations.
- Inside the loop, we extract the
- After the loop, we handle the output layer separately using
params_list[-1]. This is good practice, as output layers sometimes have different activation functions (though here, we use sigmoid again, suitable for binary classification like XOR). - Finally, the function returns
output_predictions. - The
@jax.jitdecorator just-in-time compiles this function for optimized performance, making it run super fast, especially when we call it repeatedly during training!
Before we can see our mlp_forward_pass function in action, we need two things: our input data (the XOR examples) and the initialized parameters for our network. We prepared the XOR data in the previous lesson, and we also developed a function, initialize_mlp_params, to create the network's weights and biases.
Let's bring in the initialize_mlp_params function. You'll recall its detailed construction from our last lesson; we include it here as it's essential for our current task of running the forward pass.
Here, we've defined our xor_X data. Then, we specified layer_sizes for an MLP with an input layer of 2 neurons, a hidden layer of 3 neurons, and an output layer of 1 neuron. Finally, we used jax.random.key(0) to create a PRNG key and called initialize_mlp_params to get our mlp_params PyTree. With our inputs and parameters ready, we can now perform the forward pass!
It's time for the exciting part: let's use our mlp_forward_pass function with the xor_X data and the mlp_params we just prepared. This will give us the initial predictions of our untrained network.
When you run this code, you'll see the following output:
Let's analyze this output:
- The "Initial Predictions" are the network's outputs for each of the four XOR input pairs. Since the network's weights were initialized randomly and it hasn't been trained, these predictions are essentially random values (between 0 and 1 due to the sigmoid activation in the output layer). They don't match the true XOR outputs yet (which should be
[[0.], [1.], [1.], [0.]]). - The
Shape of predictions: (4, 1)confirms that our network is producing one output for each of the four input samples, which is exactly what we expect. - The test with a
single_sample([[0. 0.]]) shows that ourmlp_forward_passfunction correctly handles inputs with a batch size of 1, producing a single prediction[[0.24461032]]. This demonstrates the flexibility of our implementation.
Seeing these outputs, even if random, is a significant milestone. It means our data is flowing correctly through the network architecture we've defined!
Fantastic work, cosmic coder! You've successfully implemented the forward propagation mechanism for our MLP, allowing it to take inputs and generate predictions. This mlp_forward_pass function is a cornerstone of any neural network, representing how it processes information.
While our network currently makes random guesses, we've built the essential pathway for information flow. In our next lesson, we'll tackle the other half of the learning puzzle: calculating how wrong our predictions are (the loss) and figuring out how to adjust our network's parameters to improve them using backpropagation and JAX's powerful automatic differentiation.
