Numpy实现神经网络全连接层
详细源码戳:https://github.com/AKGWSB/Convolution-Neural-Network-Frame-only-based-on-Numpy-
(位于 Layer.py 文件中 的 Dense 类 的 FP 函数中)
Part 1 前向传播
首先假设一个这样的全连接层(不带偏置项)
输入是一个(3, 1)的矩阵,输出是一个(2, 1)的矩阵,那么权重矩阵的大小就应该是 (3, 2)
- 即(输入神经元个数, 输出神经元个数)
那么根据全连接层的定义,输出项的计算公式如下:
O1 = W11 * I1 + W21 * I2 + W31 * I3
O2 = W12 * I1 + W22 * I2 + W32 * I3
结论:输出矩阵 = 权重矩阵的转置 左乘 输入矩阵 (矩阵乘法)
如果算上偏置项:
self.output = self.weights.T.dot(self.input) + self.bias
Part 2 更新当前层权重
已知上一层的梯度,即损失函数对输出矩阵O的偏导 g,并且g的形状和输出的形状一致
根据求导的链式法则我们有:
损失函数对权重矩阵的偏导 = 损失函数对输出矩阵的偏导 * 输出矩阵对输入矩阵的偏导
结论:
输出矩阵对权重矩阵的偏导 = 输入矩阵的转置 复制到 [ 输出神经元个数 ] 个数
(图中复制了两次)
在 Numpy 中使用 tile 函数进行复制,得到输出对权重矩阵的导数:
grad_for_w = np.tile(self.input.T, self.output_shape) # gradient for weights
那么,根据链式法则
损失函数对权重矩阵的偏导 = 损失函数对输出矩阵的偏导 * 输出矩阵对输入矩阵的偏导
我们可以求得权重矩阵对损失函数的偏导数,从而更新权重矩阵 ( lr是学习率 , gradient 是损失函数对当前层之后一层的输入矩阵的偏导,即损失函数对当前层输出矩阵的偏导 )
self.weights -= (grad_for_w * self.gradient).T * lr
如果算上偏置项,其实输出对偏置项的导数就是 1:
self.bias -= self.gradient * lr # gradient for bias mat is 1
Part 3 反向传播
我们还需要计算损失函数对输入矩阵的偏导,即损失函数对当前层之前一层的输出的偏导
同样,根据链式法则
损失函数对当前层输入矩阵的偏导 = 损失函数对当前层输出矩阵的偏导 * 当前层输出矩阵对输入矩阵的偏导
如图,可以推导出:
结论:
损失函数对当前层输入矩阵的偏导 = 权重矩阵 左乘 损失函数对当前层输出矩阵的偏导
即 权重矩阵 左乘 后一层回传的梯度矩阵(后一层的输入就是当前层的输出)
即 权重矩阵 左乘 损失函数对后一层输入矩阵的偏导
即:
last_layer_gradient = self.weights.dot(self.gradient)
(gradient 是损失函数对当前层之后一层的输入矩阵的偏导,即损失函数对当前层输出矩阵的偏导 )
Layer 类完整实现的代码:
详细戳:https://github.com/AKGWSB/Convolution-Neural-Network-Frame-only-based-on-Numpy-/blob/master/Layers.py
# Forward propagation
# param x : last layer's output
# 前向传播
# x 是当前层的输入
def FP(self, x):
self.input = x.copy()
self.output = self.weights.T.dot(self.input) + self.bias
self.next_layer.FP(x=self.output)
# Back propagation
# param gradient : last layer's gradient
# param lr : learning rate
# 反向传播,gradient是当前层输出对损失函数的梯度, lr是学习率
def BP(self, gradient, lr):
self.gradient = gradient.copy()
last_layer_gradient = self.weights.dot(self.gradient)
self.last_layer.BP(gradient=last_layer_gradient, lr=lr)
grad_for_w = np.tile(self.input.T, self.output_shape) # gradient for weights
self.weights -= (grad_for_w * self.gradient).T * lr
self.bias -= self.gradient * lr # gradient for bias mat is 1