Welcome to the first lesson of "Training Neural Networks: the Backpropagation Algorithm"! This is the third course in our neural networks from scratch path. In the previous courses, we started by defining single neurons, worked our way through layers and the multi-layer perceptron (MLP) architecture, implemented various activation functions like ReLU and sigmoid, and explored proper weight initialization techniques.
So far, we've created neural networks that can make predictions, but there's a critical question we haven't addressed: how do we know if those predictions are any good? More importantly, how can we systematically improve them? This is where loss functions come in.
In this course, we'll finally tackle the most exciting part of neural networks: training them to learn from data. This is admittedly the most mathematically intensive part of our journey, as we'll be working with concepts like gradients, derivatives, and the backpropagation algorithm. But don't worry! We'll build up these concepts gradually and provide intuitive explanations alongside the mathematics.
Our first step on this journey is to understand how to measure the error of our network's predictions, which is the focus of today's lesson on loss functions and specifically mean squared error (MSE).
Before we dive into specific loss functions, let's understand what they are and why they're crucial.
A loss function (sometimes called a cost function or objective function) measures how far our model's predictions deviate from the true values. It quantifies the "wrongness" of our predictions into a single number that we aim to minimize through training. The lower the loss, the better our model is performing.
Think of a loss function as a kind of "fitness score" or "report card" for our neural network: a high loss value means the predictions are far from the truth and performance is poor, while a low loss value means the predictions are close to the truth and performance is good; in fact, a perfect model would have a loss of zero, indicating its predictions exactly match the true values.
Loss functions are essential because they:
- Provide direction: They tell us whether changes to our model are helping or hurting.
- Enable optimization: Their mathematical properties allow us to use algorithms to minimize them.
- Quantify performance: They give us a consistent way to measure and compare model quality.
Different tasks require different loss functions. For example, binary classification problems often use binary cross entropy (BCE), while regression problems (predicting continuous values) typically use mean squared error (MSE), which is what we'll focus on today and for the remainder of this course path.
Now that we understand MSE conceptually, let's implement it in code. While C++ doesn't have the vectorized operations that some other languages provide, we can still create an efficient and clear implementation using standard library functions and loops:
Let's break down how this works:
- We first check that both vectors have the same size to ensure valid computation.
- We initialize a variable to accumulate the sum of squared errors.
- We loop through each pair of true and predicted values.
- For each pair, we calculate the difference (
error = y_true[i] - y_pred[i]). - We square the error and add it to our running sum.
- Finally, we return the average by dividing by the number of samples.
This implementation is explicit and clear about what's happening at each step. While it requires more code than vectorized operations, it gives us complete control over the computation and helps us understand exactly how MSE is calculated.
To demonstrate how we can use our MSE loss function with a neural network, let's first set up a simple MLP using the architecture we developed in the previous course. We'll create a network with two layers: a hidden layer with ReLU activation and an output layer with linear activation (appropriate for regression tasks).
We're building a neural network with:
- An input layer that accepts
3features. - A hidden layer with
5neurons andReLUactivation. - An output layer with
1neuron andlinearactivation (because we're doing regression).
Using linear activation for the output layer is a common choice for regression problems since we want to predict unbounded continuous values. We're reusing our DenseLayer and MLP classes from the previous course, which include the weight initialization strategies we learned about.
Now that we have our network set up, let's make a prediction and calculate the MSE loss function for a single sample:
This code:
- Passes our input sample through a simplified forward pass (in practice, you'd use your
MLPclass). - Prints the input, true value, and predicted value using C++ I/O streams.
- Calculates the
MSEloss function between the true and predicted values. - Prints the loss value formatted to
4decimal places usingstd::setprecision.
The output would look something like:
Notice how our untrained network's prediction (approximately 0.00002) is very different from the true value (0.8), resulting in a relatively high MSE of 0.64. This is expected since we haven't trained the network yet — its weights are still initialized randomly. The purpose of training will be to adjust those weights to minimize this loss.
In practice, neural networks are rarely trained or evaluated on a single example at a time. Instead, we process multiple examples simultaneously in batches, which improves computational efficiency and training stability. Let's see how we can handle a batch of data in C++:
This code:
- Creates a batch of
3samples, each with3features using nested vectors. - Creates a corresponding batch of
3true values. - Processes the entire batch through our network using a loop.
- Calculates the
MSEloss function across the entire batch.
The output would be:
Note that the batch MSE (0.31) is different from our single sample MSE (0.64). This is because the batch MSE is the average error across all samples in the batch. Some predictions might be closer to their true values than others, leading to a different overall average.
While C++ requires us to implement batch processing manually using loops and explicit memory management, this gives us fine-grained control over the computation and helps us understand exactly what's happening during batch processing.
Congratulations! We've taken our first step into the world of neural network training by understanding and implementing the mean squared error loss function. This gives us a way to quantify how good (or bad) our network's predictions are, which is the foundation for learning from data. We've seen how MSE can be efficiently calculated for both individual samples and batches using C++ standard library functions, and how an untrained network produces high loss values that we'll aim to reduce through training.
In the upcoming practice exercises, you'll have the opportunity to experiment with the MSE loss function and see how it behaves with different predictions. After that, we'll continue our journey by exploring how to use this loss measure to actually improve our network through training. We'll learn about gradients, which tell us how changes in each weight affect the loss, and the backpropagation algorithm, which efficiently computes these gradients across all layers of the network. Keep learning!
