神经网络的正向传播中,为了计算加权信号的总和,使用了矩阵的乘积运算(NumPy中是np.dot())。举个之前的例子,X、W、B 分别是形状为(2,)、(2, 3)、(3,)的多维数组,神经元的加权和可以用Y = np.dot(X, W) + B计算出来。然后,Y 经过激活函数转换后,传递给下一层。这就是神经网络正向传播的流程。此外,矩阵的乘积运算的要点是使对应维度的元素个数一致。如下图所示,X和W 的乘积必须使对应维度的元素个数一致。
另外,这里矩阵的形状用(2, 3)这样的括号表示(为了和NumPy的shape属性的输出一致)。神经网络的正向传播中进行的矩阵的乘积运算在几何学领域被称为“仿射变换”。因此,这里将进行仿射变换的处理实现为“Affine层”。现在将这里进行的求矩阵的乘积与偏置的和的运算用计算图表示出来。将乘积运算用“dot”节点表示的话,则np.dot(X, W) + B的运算可用下图所示的计算图表示出来:
另外,在各个变量的上方标记了它们的形状(比如,计算图上显示了X的形状为(2,),X·W的形状为(3,)等)。
上图是比较简单的计算图,不过要注意X、W、B是矩阵(多维数组)。之前我们见到的计算图中各个节点间流动的是标量,而这个例子中各个节点间传播的是矩阵。现在我们来考虑上图的计算图的反向传播。以矩阵为对象的反向传播,按矩阵的各个元素进行计算时,步骤和以标量为对象的计算图相同。实际写一下的话,可以得到下式:
WT的T表示转置。转置操作会把W的元素(i, j)换成元素(j, i)。我们根据上式,尝试写出计算图的反向传播,如下图所示:
我们看一下上图中各个变量的形状。尤其要注意,X和∂ L/∂ X形状相同,W和 ∂ L/∂ W形状相同。从下面的数学式可以很明确地看出X和∂ L/∂ X形状相同:
为什么要注意矩阵的形状呢?因为矩阵的乘积运算要求对应维度的元素个数保持一致,通过确认一致性,就可以导出反向传播的结果。如下图所示,∂ L/∂ Y的形状是(3,),W的形状是(2, 3)时,思考∂ L/∂ Y和WT的乘积,使得∂ L/∂ X的形状为(2,)。这样一来,就会自然而然地推导出反向传播的结果。
前面介绍的Affine层的输入X是以单个数据为对象的。现在我们考虑N个数据一起进行正向传播的情况,也就是批版本的Affine层。先给出批版本的Affine层的计算图:
与刚刚不同的是,现在输入X的形状是(N, 2)。之后就和前面一样,在计算图上进行单纯的矩阵计算。反向传播时,如果注意矩阵的形状,就可以和前面一样推导出∂ L/∂ x和∂ L/∂ W。加上偏置时,需要特别注意。正向传播时,偏置被加到X·W的各个数据上。因此,反向传播时,各个数据的反向传播的值需要汇总为偏置的元素。综上所述,Affine的实现如下所示:
class Affine:
def __init__(self, W, b):
self.W = W
self.b = b
self.x = None
self.dW = None
self.db = None
def forward(self, x):
self.x = x
out = np.dot(x, self.W) + self.b
return out
def backward(self, dout):
dx = np.dot(dout, self.W.T)
self.dW = np.dot(self.x.T, dout)
self.db = np.sum(dout, axis=0)
return dx
最后介绍一下输出层的softmax函数。前面我们提到过,softmax函数会将输入值正规化之后再输出。比如手写数字识别时,Softmax层的输出如下图所示:
下面来实现Softmax层。考虑到这里也包含作为损失函数的交叉熵误差(cross entropy error),所以称为“Softmax-with-Loss层”。Softmax-with-Loss层(Softmax函数和交叉熵误差)的计算图如下图所示:
计算图中,softmax函数记为Softmax层,交叉熵误差记为Cross Entropy Error层。这里假设要进行3类分类,从前面的层接收3个输入(得分)。如图下所示,Softmax层将输入(a1, a2, a3)正规化,输出(y1, y2, y3)。Cross Entropy Error层接收Softmax的输出(y1, y2, y3)和教师标签(t1, t2, t3),从这些数据中输出损失L。
图中要注意的是反向传播的结果。Softmax层的反向传播得到了(y1 − t1, y2 − t2, y3 − t3)这样“漂亮”的结果。由于(y1, y2, y3)是Softmax层的输出,(t1, t2, t3)是监督数据,所以(y1 − t1, y2 − t2, y3 − t3)是Softmax层的输出和教师标签的差分。神经网络的反向传播会把这个差分表示的误差传递给前面的层,这是神经网络学习中的重要性质。
神经网络学习的目的就是通过调整权重参数,使神经网络的输出(Softmax的输出)接近教师标签。因此,必须将神经网络的输出与教师标签的误差高效地传递给前面的层。刚刚的(y1 − t1, y2 − t2, y3 − t3)正是Softmax层的输出与教师标签的差,直截了当地表示了当前神经网络的输出与教师标签的误差。
这里考虑一个具体的例子,比如思考教师标签是(0, 1, 0),Softmax层的输出是(0.3, 0.2, 0.5)的情形。因为正确解标签处的概率是0.2(20%),这个时候的神经网络未能进行正确的识别。此时,Softmax层的反向传播传递的是(0.3, −0.8, 0.5)这样一个大的误差。因为这个大的误差会向前面的层传播,所以Softmax层前面的层会从这个大的误差中学习到“大”的内容。
再举一个例子,比如思考教师标签是(0, 1, 0),Softmax层的输出是(0.01, 0.99, 0)的情形(这个神经网络识别得相当准确)。此时Softmax层的反向传播传递的是(0.01, −0.01, 0)这样一个小的误差。这个小的误差也会向前面的层传播,因为误差很小,所以Softmax层前面的层学到的内容也“小”。
现在来进行Softmax-with-Loss层的实现,实现过程如下所示:
class SoftmaxWithLoss:
def __init__(self):
self.loss = None # 损失
self.y = None # softmax的输出
self.t = None # 监督数据(one-hot vector)
def forward(self, x, t):
self.t = t
self.y = softmax(x)
self.loss = cross_entropy_error(self.y, self.t)
return self.loss
def backward(self, dout=1):
batch_size = self.t.shape[0]
dx = (self.y - self.t) / batch_size
return dx
请注意反向传播时,将要传播的值除以批的大小(batch_size)后,传递给前面的层的是单个数据的误差。