利用计算图的反向传播特性,将神经网络变成层结构。本文是关于《深度学习入门:基于Python的理论与实现》中误差反向传播法一章的笔记。
误差反向传播法的目标和理论基础
目标
直接计算梯度的方法,在计算上时间复杂度较高。误差反向传播法能够有效的降低时间复杂度。
理论基础
如果一个函数h(x)和函数g(x)、f(x)满足: 且再求导数的目标范围内函数连续可导,假设y=f(x)则h(x)的导数满足: 导数的这一性质,使得导数在计算过程中具有传递的特征,这种特征也被称为链式法则。利用这一性质,可将一个复杂的函数表达成简单的复合函数,逐级求解。
计算图
概念
计算图就是一个函数传递的过程,将函数通过一个一个的操作符逐级生成。通过单一操作符形成的函数,都可以利用计算图的方式来求解导数或者梯度。
这一构建计算图的过程称为“正向传播”。而通过结果,求解每一级的导数的过程,称为“反向传播”。
优势
利用计算图的概念,每次计算导数的时候,可以只计算局部的导数。利用链式法则(上文中复合函数的导数性质),最终可以构成完整的函数导数。
几个常见操作符的导数性质
加法节点
以z=x+y为例,。因此,加法节点需要传播的都是1。
乘法节点
以z=xy为例,。
加法节点和乘法节点的计算图表示如下:
层的基本结构
根据上一节中所列出的反向误差传播法,一个层应该包含一个正向传播方法和一个反向传播方法;通常还包含一个初始化的设置方法。因此,一个层的类可表示为:
class NetLayer:
# 初始化
def __init__(self):
# 正向传播
def forward(self, x, y):
# 反向传播
def backward(self, dout):
正向传播的方法参数不一定是两个也可能是1个或多个。
各种层的实现
激活层:ReLU层
ReLU层的导数可表示为: 对应的Python实现
class Relu:
def __init__(self):
self.mask = None
def forward(self, x):
self.mask = (x <= 0)
out = x.copy()
out[self.mask] = 0
return out
def backward(self, dout):
dout[self.mask] = 0
dx = dout
return dx
激活层:sigmoid层
sigmoid函数可表示为: 则有 通过计算图逐步分解,也可以得到相同的结果。因此,可实现为:
class Sigmoid:
def __init__(self):
self.out = None
def forward(self, x):
out = 1 / (1 + np.exp(-x))
self.out = out
return out
def backward(self, dout):
dx = dout * (1.0 - self.out) * self.out
return dx
隐藏层:Affine层
隐藏节点中的矩阵运算可抽象为Affine层,数学上可表示为: 因此,假设L(Y)为损失函数,则有: ==为什么右乘变成了左乘?矩阵运算没有实际的除法,都是对乘法的另一种表达。==
Python函数实现:
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-with-Loss
==为什么按层处理时,Softmax一定要with Loss?== 这里选择的损失函数为交叉熵函数。Softmax搭配交叉熵函数为损失函数,正好可以得到一个比较简单的反向传播函数。这使得层的计算变得简单。 如果损失函数不是交叉熵函数,而是均方误差函数,则输出层选择恒等函数,可以得到较为简单的层的传播。 计算过程不做过多阐述,最终层可以得到这样得反向传输结果。 ==而且,对于恒等函数搭配均方误差函数的输出层聚合,可以得到相同的反向传输结果==
关键的代码调整
class TwoLayerNet:
......
def predict(self, x):
for layer in self.layers.values():
x = layer.forward(x)
return x
def loss(self, x, t):
y = self.predict(x)
return self.lastLayer.forward(y, t)
def gradient(self, x, t):
# forward
self.loss(x, t)
# backward
dout = 1
dout = self.lastLayer.backward(dout)
layers = list(self.layers.values())
layers.reverse()
for layer in layers:
dout = layer.backward(dout)
可以看到,虽然类名称没改变,但predict、loss和gradient的实现中,和具体的层数并没有对应关系,这就使得以层为单位的神经网络非常容易拓展为多层神经网络。只需要在配置网络和更新参数时,做出相应的调整即可。对于数据的预测、损失函数、梯度计算等方法都无需改变。