《深度学习入门基于Python的理论与实现》中实现了2层全连接神经网络的代码对MNIST数据集的28x28像素0-9手写数字灰度图像进行分类,本文将重点对代码中的two_layer_net类的gradient函数中的误差反向传播的代码进行公式推导验证。验证小批量数据的交叉熵损失函数对第2层权重参数的梯度表达式
- 1 本文目的
- 2 TwoLayerNet神经网络示意图与参数介绍
- 3 第 i i i个样本交叉熵损失函数 L i L_i Li对输出层(第2层)的第 j j j个输出 y i j y_{ij} yij的偏导数
- 4 第 i i i个样本的输出层(第2层)的第 j j j个输出 y i j y_{ij} yij对输出层(第2层)的第 m m m个输入 a i m ( 2 ) a^{(2)}_{im} aim(2)的偏导数
- 5 第 i i i个样本交叉熵损失函数 L i L_i Li对输出层(第2层)的第 m m m个输入 a i m ( 2 ) a^{(2)}_{im} aim(2)的偏导数
- 6 平均交叉熵损失函数 L L L对输出层(第2层)的第 m m m个输入 a i m ( 2 ) a^{(2)}_{im} aim(2)的偏导数
- 7 平均交叉熵损失函数 L L L对输出层(第2层)的输入 A 100 x 10 ( 2 ) \mathbf{A^{(2)}_{100x10}} A100x10(2)的梯度 Δ 100 x 10 ( 2 ) \mathbf{\Delta^{(2)}_{100x10}} Δ100x10(2)
- 8 由神经单元误差 Δ 100 x 10 ( 2 ) \mathbf{\Delta^{(2)}_{100x10}} Δ100x10(2)反向推导出第二权重矩阵 W ( 2 ) \mathbf{W^{(2)}} W(2)的梯度
-
- 8.1 平均交叉熵损失函数 L L L对第2层权重 w i j ( 2 ) w_{ij}^{(2)} wij(2)的偏导数
- 8.2 第 n n n个样本在输出层(第2层)的第i个神经元的输入值 a n i ( 2 ) a_{ni}^{(2)} ani(2)的表达式
- 8.3 ∂ a n i ( 2 ) ∂ w i j ( 2 ) \frac{\partial a_{ni}^{(2)}}{\partial w_{ij}^{(2)}} ∂wij(2)∂ani(2) 的推导过程
- 8.3 ∂ L ∂ w i j ( 2 ) \frac{\partial L}{\partial w_{ij}^{(2)}} ∂wij(2)∂L 的推导过程
- 8.4 平均交叉熵损失函数 L L L对第二权重矩阵 W ( 2 ) \mathbf{W^{(2)}} W(2)的梯度
- 9 TwoLayerNet.py文件的gradient函数代码验证
1 本文目的
在书籍《深度学习入门:基于Python的理论与实现》中的第4章“神经网络的学习”的第4.5节“学习算法的实现”的4.5.1节介绍了实现了对MNIST手写数字数据集的28像素x28像素灰度图像进行数字识别的神经网络。
我将着重分析本书籍配套代码https://github.com/qiaohaoforever/DeepLearningFromScratch中ch04文件夹下的two_layer_net.py文件中实现的TwoLayerNet类中的gradient函数。
gradient函数的代码如下所示。
def gradient(self, x, t):
"""
计算神经网络中参数的梯度。
参数:
x : ndarray
输入数据,形状为 (batch_size, input_size)。实际为100x784
t : ndarray
目标标签,形状为 (batch_size, output_size)。实际为100x10
返回:
grads : dict
包含所有参数(权重和偏置)梯度的字典:
- 'W1' : 第一层权重的梯度
- 'b1' : 第一层偏置的梯度
- 'W2' : 第二层权重的梯度
- 'b2' : 第二层偏置的梯度
"""
W1, W2 = self.params['W1'], self.params['W2'] # 获取权重 W1是784x50矩阵,W1是50x10矩阵
b1, b2 = self.params['b1'], self.params['b2'] # 获取偏置 b1是1x50行向量,b2是1x10行向量
grads = {
} # 用于存储梯度的字典
batch_num = x.shape[0] # 获取批量样本数
# 前向传播
a1 = np.dot(x, W1) + b1 # 输入层到隐藏层的加权和,注意一行是一个样本,所以是用x左乘W1,而不是W1左乘x。a1是100x50矩阵
z1 = sigmoid(a1) # 隐藏层的激活值。z1是100x50矩阵
a2 = np.dot(z1, W2) + b2 # 隐藏层到输出层的加权和。a1是100x10矩阵
y = softmax(a2) # 输出层的激活值,经过 softmax 函数。y是100x10矩阵
# 反向传播
dy = (y - t) / batch_num # 计算平均交叉熵损失函数对输出层的a2的梯度。笔者认为dy误导读者,dy改成da2才合适
grads['W2'] = np.dot(z1.T, dy) # 计算第2层权重的梯度
grads['b2'] = np.sum(dy, axis=0) # 计算第2层偏置的梯度
da1 = np.dot(dy, W2.T) # 反向传播到隐藏层
dz1 = sigmoid_grad(a1) * da1 # 根据 sigmoid 函数的导数调整梯度
grads['W1'] = np.dot(x.T, dz1) # 计算第1层权重的梯度
grads['b1'] = np.sum(dz1, axis=0) # 计第1层偏置的梯度
return grads # 返回所有计算出的梯度
上一篇文章
【深度学习】从公式推导来深入理解误差反向传播算法1:《深度学习入门基于Python的理论与实现》中实现的2层全连接神经网络的two_layer_net类的gradient函数分析
已经深入分析并验证了下面两句代码的第一句,本文将分析第二句。
dy = (y - t) / batch_num # 计算平均交叉熵损失函数对输出层的a2的梯度。笔者认为dy误导读者,dy改成da2才合适
grads['W2'] = np.dot(z1.T, dy) # 计算第2层权重的梯度
2 TwoLayerNet神经网络示意图与参数介绍
TwoLayerNet设计的神经网络如图所示。
神经网络的输入层(第0层)有784个特征。隐藏层(第1层)50个神经元。输出层(第2层)有10个神经元。
第0层与第1层的所有权重参数为第一权重矩阵,记为
W ( 1 ) = ( w 1 , 1 ( 1 ) w 2 , 1 ( 1 ) ⋯ w 50 , 1 ( 1 ) w 1 , 2 ( 1 ) w 2 , 2 ( 1 ) ⋯ w 50 , 2 ( 1 ) ⋮ ⋮ ⋮ ⋮ w 1 , 784 ( 1 ) w 2 , 784 ( 1 ) ⋯ w 50 , 784 ( 1 ) ) \mathbf{W^{(1)}} = \begin{pmatrix} w_{1,1}^{(1)} & w_{2,1}^{(1)} & \cdots & w_{50,1}^{(1)} \\ w_{1,2}^{(1)} & w_{2,2}^{(1)} & \cdots & w_{50,2}^{(1)} \\ \vdots & \vdots & \vdots & \vdots \\ w_{1,784}^{(1)} & w_{2,784}^{(1)} & \cdots & w_{50,784}^{(1)} \end{pmatrix} W(1)=
w1,1(1)w1,2(1)⋮w1,784(1)w2,1(1)w2,2(1)⋮w2,784(1)⋯⋯⋮⋯w50,1(1)w50,2(1)⋮w50,784(1)
第1层与第2层的所有权重参数为第二权重矩阵,记为
W ( 2 ) = ( w 1 , 1 ( 2 ) w 2 , 1 ( 2 ) ⋯ w 10 , 1 ( 2 ) w 1 , 2 ( 2 ) w 2 , 2 ( 2 ) ⋯ w 10 , 2 ( 2 ) ⋮ ⋮ ⋮ ⋮ w 1 , 50 ( 2 ) w 2 , 50 ( 2 ) ⋯ w 10 , 50 ( 2 ) ) \mathbf{W^{(2)}} = \begin{pmatrix} w_{1,1}^{(2)} & w_{2,1}^{(2)} & \cdots & w_{10,1}^{(2)} \\ w_{1,2}^{(2)} & w_{2,2}^{(2)} & \cdots & w_{10,2}^{(2)} \\ \vdots & \vdots & \vdots & \vdots \\ w_{1,50}^{(2)} & w_{2,50}^{(2)} & \cdots & w_{10,50}^{(2)} \end{pmatrix} W(2)=
w1,1(2)w1,2(2)⋮w1,50(2)w2,1(2)w2,2(2)⋮w2,50(2)⋯⋯⋮⋯w10,1(2)w10,2(2)⋮w10,50(2)
书中110页的4.5节介绍采用了小批量随机梯度下降,一批次有100个样本。所以损失函数更改为平均交叉熵损失函数如下,其中N=100,K=10。
L = − 1 N ∑ n = 1 N ∑ k = 1 K t n k log ( y n k ) L = -\frac{1}{N} \sum_{n=1}^{N} \sum_{k=1}^{K} t_{nk} \log(y_{nk}) L=−N1n=1∑Nk=1∑Ktnklog(ynk)
第i个样本的交叉熵损失函数如下,其中K=10
L i = − ∑ k = 1 K t i k log ( y i k ) L_i = - \sum_{k=1}^{K} t_{ik} \log(y_{ik}) Li=−k=1∑Ktiklog(yik)
3 第 i i i个样本交叉熵损失函数 L i L_i Li对输出层(第2层)的第 j j j个输出 y i j y_{ij} yij的偏导数
计算 ∂ L i ∂ y i j \frac{\partial L_i}{\partial y_{ij}} ∂yij∂Li,即损失函数 L i L_i Li对输出 y i j y_{ij} yij的偏导数。注意这里我使用了 j j j作为特指,因为 k k k是迭代变量。
上一篇文章中已经计算出结果。
∂ L i ∂ y i j = − t i j y i j \frac{\partial L_{i}}{\partial y_{ij}} = -\frac{t_{ij}}{y_{ij}} ∂yij∂Li=−yijtij
4 第 i i i个样本的输出层(第2层)的第 j j j个输出 y i j y_{ij} yij对输出层(第2层)的第 m m m个输入 a i m ( 2 ) a^{(2)}_{im} aim(2)的偏导数
我们需要计算 ∂ y i j ∂ a i m ( 2 ) \frac{\partial y_{ij}}{\partial a^{(2)}_{im}} ∂aim(2)∂yij,即Softmax函数输出第 j j j个神经元的输出 y i j y_{ij} yij对第 m个神经元的输入 a i m ( 2 ) a_{im}^{(2)} aim(2) 的偏导数。
上一篇文章中已经计算出结果。
∂ y i j ∂ a i m ( 2 ) = y i j ( δ j m − y i m ) \frac{\partial y_{ij}}{\partial a^{(2)}_{im}} = y_{ij}(\delta_{jm}- y_{im} ) ∂aim(2)∂yij=yij(δjm−yim)
其中 δ j m \delta_{jm} δjm 是Kronecker delta,当 j = m j=m j=m时为1,否则为0。
5 第 i i i个样本交叉熵损失函数 L i L_i Li对输出层(第2层)的第 m m m个输入 a i m ( 2 ) a^{(2)}_{im} aim(2)的偏导数
上一篇文章中已经计算出结果。
∂ L i ∂ a i m ( 2 ) = y i m − t i m \frac{\partial L_i}{\partial a^{(2)}_{im}} = y_{im}-t_{im} ∂aim(2)∂Li=yim−tim
6 平均交叉熵损失函数 L L L对输出层(第2层)的第 m m m个输入 a i m ( 2 ) a^{(2)}_{im} aim(2)的偏导数
上一篇文章中已经计算出结果。
∂ L ∂ a i m ( 2 ) = 1 N ∂ L i ∂ a i m ( 2 ) = 1 N ( y i m − t i m ) \frac{\partial L}{\partial a_{im}^{(2)}} = \frac{1}{N} \frac{\partial L_i}{\partial a_{im}^{(2)}} = \frac{1}{N} (y_{im} - t_{im}) ∂aim(2)∂L=N1∂aim(2)∂Li=N1(yim−tim)
7 平均交叉熵损失函数 L L L对输出层(第2层)的输入 A 100 x 10 ( 2 ) \mathbf{A^{(2)}_{100x10}} A100x10(2)的梯度 Δ 100 x 10 ( 2 ) \mathbf{\Delta^{(2)}_{100x10}} Δ100x10(2)
因为采用小批量随机梯度下降,一批次有100个样本。将这100个样本的one-hot标签 t i , m t_{i,m} ti,m 组成100行10列的矩阵 T 100 x 10 \mathbf{T_{100x10}} T100x10
同样将100个样本的输出层(第2层)的输出 y i , m y_{i,m} yi,m 组成100行10列的矩阵 Y 100 x 10 \mathbf{Y_{100x10}} Y100x10
将100个样本的输出层(第2层)的神经单元误差 δ i m ( 2 ) \delta^{(2)}_{im} δim(2)组成100行10列的矩阵 Δ 100 x 10 ( 2 ) \mathbf{\Delta^{(2)}_{100x10}} Δ100x10(2)
上一篇文章中已经计算出结果。
Δ 100 x 10 ( 2 ) = ( δ 1 , 1 ( 2 ) δ 1 , 2 ( 2 ) ⋯ δ 1 , 10 ( 2 ) δ 2 , 1 ( 2 ) δ 2 , 2 ( 2 ) ⋯ δ 2 , 10 ( 2 ) ⋮ ⋮ δ i , m ( 2 ) ⋮ δ 100 , 1 ( 2 ) δ 100 , 2 ( 2 ) ⋯ δ 100 , 10 ( 2 ) ) \mathbf{\Delta^{(2)}_{100x10}} = \begin{pmatrix} \delta^{(2)}_{1,1} & \delta^{(2)}_{1,2} & \cdots & \delta^{(2)}_{1,10} \\ \delta^{(2)}_{2,1} & \delta^{(2)}_{2,2} & \cdots & \delta^{(2)}_{2,10} \\ \vdots & \vdots & \delta^{(2)}_{i,m} & \vdots \\ \delta^{(2)}_{100,1} & \delta^{(2)}_{100,2} & \cdots & \delta^{(2)}_{100,10} \end{pmatrix} Δ