编写前向传播代码
了解前向传播的有趣事实及其代码实现。
回顾
在上一章中,我们制定了构建用于分类 MNIST 图像的神经网络的计划。我们从串联两个感知机开始,并记录了所有相关矩阵的行数和列数,最终得到了如下结构图:
请保留这张示意图,因为我们即将将其转化为代码。编码过程将分为两章完成:在本章中,我们将编写神经网络的分类函数——也就是说,除了训练代码之外的所有网络代码;在下一章中,我们将完成整个网络并将其应用于 MNIST 数据集。
现在,让我们开始编写分类函数。作为参考,我们先回顾一下在课程第一部分中为实现感知机所写的函数。
编写前向传播代码
以下是 forward() 函数的重新展示,这是我们感知机的核心函数之一:
1def forward(X, w): 2 weighted_sum = np.matmul(X, w) 3 return sigmoid(weighted_sum)
forward() 函数实现了称为前向传播的操作,即根据输入计算系统的输出。对于感知机而言,该操作包括加权求和后接一个 sigmoid 激活函数。而对于神经网络来说,这一过程会稍微复杂一些。
事实上,“前向传播”这一名称正是在此体现其意义:将一张 MNIST 图像通过神经网络的过程,本质上是将数据逐层向前传播。
前向传播的第一步与普通感知机相同:
1h = sigmoid(np.matmul(prepend_bias(X), w1))
首先,我们在输入数据中添加偏置项(bias)列;其次,使用第一组权重矩阵 计算加权和;最后,将结果传入 sigmoid() 函数。根据代码结构可以看出,这一步完成了隐藏层的计算。
接下来,我们重复类似过程来计算输出层:
1y_hat = softmax(np.matmul(prepend_bias(h), w2))
这里我们使用了第二组权重矩阵,并将加权和结果传入 softmax() 函数而非 sigmoid()。(目前我们尚未实现 softmax() 函数,但很快就会编写。)除此之外,输出层的计算逻辑与隐藏层基本一致:添加偏置项、计算加权和、再通过激活函数处理。
现在,我们将这两行代码封装成一个函数:
1def forward(X, w1, w2): 2 h = sigmoid(np.matmul(prepend_bias(X), w1)) 3 y_hat = softmax(np.matmul(prepend_bias(h), w2)) 4 return y_hat
这段代码还有一个需要注意的地方:此前在实现感知机时,prepend_bias() 函数位于 mnist.py 库中,作为数据加载和预处理的一部分。但在神经网络中,我们需要两次添加偏置项——分别在输入层和隐藏层。因此,我们应该将 prepend_bias() 函数从 mnist.py 移至主网络代码中,以便多次调用。该函数定义如下:
1def prepend_bias(X): 2 return np.insert(X, 0, 1, axis=1)