Skip to the content.

利用计算图的反向传播特性,将神经网络变成层结构。本文是关于《深度学习入门:基于Python的理论与实现》中误差反向传播法一章的笔记。

误差反向传播法的目标和理论基础

目标

直接计算梯度的方法,在计算上时间复杂度较高。误差反向传播法能够有效的降低时间复杂度。

理论基础

如果一个函数h(x)和函数g(x)、f(x)满足: 且再求导数的目标范围内函数连续可导,假设y=f(x)则h(x)的导数满足: 导数的这一性质,使得导数在计算过程中具有传递的特征,这种特征也被称为链式法则。利用这一性质,可将一个复杂的函数表达成简单的复合函数,逐级求解。

计算图

概念

计算图就是一个函数传递的过程,将函数通过一个一个的操作符逐级生成。通过单一操作符形成的函数,都可以利用计算图的方式来求解导数或者梯度。

image

这一构建计算图的过程称为“正向传播”。而通过结果,求解每一级的导数的过程,称为“反向传播”。

优势

利用计算图的概念,每次计算导数的时候,可以只计算局部的导数。利用链式法则(上文中复合函数的导数性质),最终可以构成完整的函数导数。

image

几个常见操作符的导数性质

加法节点

以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搭配交叉熵函数为损失函数,正好可以得到一个比较简单的反向传播函数。这使得层的计算变得简单。 如果损失函数不是交叉熵函数,而是均方误差函数,则输出层选择恒等函数,可以得到较为简单的层的传播。 计算过程不做过多阐述,最终层可以得到这样得反向传输结果。 ==而且,对于恒等函数搭配均方误差函数的输出层聚合,可以得到相同的反向传输结果==

image

关键的代码调整

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的实现中,和具体的层数并没有对应关系,这就使得以层为单位的神经网络非常容易拓展为多层神经网络。只需要在配置网络和更新参数时,做出相应的调整即可。对于数据的预测、损失函数、梯度计算等方法都无需改变。