第五章误差反向传播算法——基于numpy的代码详解

本专栏是书《深度学习入门》的阅读笔记一共八章:

第一章深度学习中的Python基础。主要讲解了深度学习将要用到的python的基础知识以及简单介绍了numpy库和matpoltlib库,本书编写深度学习神经网络代码仅使用Python和numpy库,不使用目前流行的各种深度学习框架,适合入门新手学习理论知识。

第二章感知机。主要介绍了神经网络和深度学习的基本单元感知机。感知机接收多个输入,产生一个输出,单层感知器可以实现与门,或门以及与非门,但是不能实现异或门,异或门的实现需要借助多层感知机,这也就是说,单层感知机只能表示线性空间,而非线性空间的表示需要借助多层感知机。

第三章神经网络——基于numpy的代码详解。主要讲解了神经网络的构成,神经网络中的激活函数,神经网络中层与层的矩阵乘法,3层神经网络的代码,输出层的设计和批处理。

第四章神经网络的学习算法——随机梯度下降numpy代码详解。主要讲解了神经网络中的学习算法,介绍了损失函数,通过微分求导的方法求梯度,随机梯度下降算法的原理以及基于numpy的代码详解。

第五章 误差反向传播算法

    误差反向传播算法是一种高效计算参数梯度的方法,在参数众多的神经网络中应用十分广泛。

5.1 计算图

    计算图将计算过程用图形表示出来,由边和节点构成。计算图中的节点表示运算进行的操作,例如加法操作,乘法操作等,边表示计算的中间结果。下面通过几个简单的问题来说明计算图的使用方法。

    问题一:小然然去超市买了2个苹果,每个苹果100元,消费税是10%,求小然然总共需要支付的金额。

第五章误差反向传播算法——基于numpy的代码详解_第1张图片

 

    问题二:小然然又去了超市,这次买了2个苹果和3个橘子,每个苹果100元,每个橘子150元,消费税10%,求总共需要支付的金额。

第五章误差反向传播算法——基于numpy的代码详解_第2张图片

    使用计算图的优点有:

    1.可以进行局部计算,每个节点计算时,只关心连接到次节点的边的内容,并不关心其他内容,因此我们可以将复杂的问题进行分解,每一部分都是最基本的运算,最终结合起来就能得到复杂的结果。

第五章误差反向传播算法——基于numpy的代码详解_第3张图片

    2.计算图可以将中间的结果全部保存起来。

    3.这一点是最重要的,计算图可以简化求梯度的过程,通过反向传播进行求参数的梯度。例如,求最终支付金额关于苹果价格的导数,可以按照下图进行:

5.2链式法则和计算图

    链式法则:如果某个函数由复合函数表示,则该复合函数的导数可以用构成复合函数的各个函数的导数的乘积表示。

    我们来看一个简单的例子:z=(x+y)^2。该复合函数可以分解为:z=t^2,t=x+y,则根据链式法则求z对x的偏导数为:\frac{\partial z}{\partial x}=\frac{\partial z}{\partial t}\frac{\partial t}{\partial x},用计算图表示为:

第五章误差反向传播算法——基于numpy的代码详解_第4张图片

    以上两个例子的带入数值的反向传播为:

第五章误差反向传播算法——基于numpy的代码详解_第5张图片

 

第五章误差反向传播算法——基于numpy的代码详解_第6张图片

5.3简单层的实现

5.3.1乘法层

class MulLayer:
    def __init__(self):
        self.x=None
        self.y=None

    def forward(self,x,y):
        self.x=x
        self.y=y
        out=x*y

        return out

    def backward(self,dout):

        dx=dout*self.y
        dy=dout*self.x

        return dx,dy

5.3.2加法层

class AddLayer:

    def __init__(self):

        pass

    def forward(self,x,y):

        return x+y

    def backward(self,x,y):

        dx=dout
        dy=dout

        return dx,dy

下面用加法层和乘法层来实现买苹果和橘子的例子的前向传播和后向传播:

import numpy as np
apple=100
apple_num=2
orange=150
orange_num=3
tax=1.1


apple_mul=MulLayer()
orange_mul=MulLayer()
apple_add_orange=AddLayer()
all_mul_tax=MulLayer()


apple_price=apple_mul.forward(apple,apple_num)
orange_price=orange_mul.forward(orange,orange_num)
apple_add_orange_price=apple_add_orange.forward(apple_price,orange_price)
all_mul_tax_price=all_mul_tax.forward(apple_add_orange_price,tax)


dprice=1
dall_price,dtax=all_mul_tax.backward(dprice)
dapple_price,dorange_price=apple_add_orange.backward(dall_price)
dorange,dorange_num=orange_mul.backward(dorange_price)
dapple,dapple_num=apple_mul.backward(dapple_price)


5.3.3ReLU层

class ReLU:
    
    def __init__(self):

        self.mask=None

    def forward(self,x):

        self.mask=(x<=0)#x中小于等于0的元素标记为True,其余元素标记为False
        out=x.copy()
        out[self.mask]=0#标记为Ture的元素的位置的值用0代替

        return out

    def backward(self,dout):

        dout[self.mask]=0
        
        return dout

5.3.4 sigmoid层

    首先,我们先来回顾sigmoid函数的公式:y=\frac{1}{1+e^{-x}},用计算图表示sigmoid的前向传播过程为:

第五章误差反向传播算法——基于numpy的代码详解_第7张图片

    对sigmoid函数进行求导,计算得导数为:\frac{\partial y}{\partial x} =y^2 e^{-x},用计算图表示为:

第五章误差反向传播算法——基于numpy的代码详解_第8张图片

    用一个节点表示以上计算图,集约成一个sigmoid的计算图节点为:

第五章误差反向传播算法——基于numpy的代码详解_第9张图片

    我们进一步对求出的导数进行化简,得到\frac{\partial y}{\partial x}=y(1-y),则sigmoid的计算图可以简化为:

第五章误差反向传播算法——基于numpy的代码详解_第10张图片

    sigmoid层的代码实现为:

class sigmoid:

    def __init__(self):

        self.out=None

    def forward(self,x):

        out=1/(1+exp(-x))
        self.out=out

        return out

    def backward(self,dout):

        dx=dout*self.out*(1-self.out)

        return dx

5.3.5 Affine层

    在神经网络的前向传播过程中,我们定义了矩阵乘法运算,矩阵乘法在几何学领域被称为“仿射变换”,因此我们将矩阵的乘法层叫做“Affine”层。下面我们首先看一下Affine层的前向传播计算图:

第五章误差反向传播算法——基于numpy的代码详解_第11张图片

    这里与之前不同的是,参与传播的是数组数据,之前的计算图中,在各个节点中参与流动的都是标量,但是在此计算图中,参与流动的都是矩阵。

    对矩阵乘法进行求导可得:\frac{\partial Y}{\partial X}=\frac{\partial L}{\partial Y}W^T\frac{\partial Y}{\partial W}=X^T\frac{\partial L}{\partial Y},则其对应的反向传播计算图为:

第五章误差反向传播算法——基于numpy的代码详解_第12张图片

    前面介绍的计算图X都是一维数组,表示一个数据,当有N个数据时,Affine层的反向传播计算图为:

第五章误差反向传播算法——基于numpy的代码详解_第13张图片

    代码为:

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
        y=np.dot(self.x,self.W)+self.b

        return y

    def backward(self,dy):

        self.dW=np.dot(self.x.T,dy)
        dx=np.dot(dy,self.W.T)
        db=np.sum(dy,axis=0)

        return dx

5.3.6 softmax+cross entropy error层

    我们先回顾softmax的函数表达式:y_k=\frac{e^{a_k}}{\sum_{i=1}^{n} e^{a_i}},用计算图表示softmax函数为:

第五章误差反向传播算法——基于numpy的代码详解_第14张图片

    cross entropy error的数学表达式为:L=-\sum t_k logy_k,用计算图表示cross entropy error函数为:

第五章误差反向传播算法——基于numpy的代码详解_第15张图片

    下面我们来看反向传播,首先看cross entropy error的反向传播:

第五章误差反向传播算法——基于numpy的代码详解_第16张图片

    接下来我们来求softmax层的反向传播,softmax层的反向传播比较复杂,我们将分6个步骤进行:

    步骤一:cross entropy error层的反向传播的值传递过来:

    步骤二:“×”节点将正向传播的值翻转后相乘,需要用到的公式是:-\frac{t_1}{y_1}exp(a_1)=-t_1\frac{S}{exp(a_1)}exp(a_1)=-t_1S

第五章误差反向传播算法——基于numpy的代码详解_第17张图片

    步骤三:正向传播时若有分支流出,则反向传播时它们的反向传播的值会相加。这里需要强调的是对“/”节点进行求导,y=\frac{1}{S}\frac{\partial y }{\partial x}=-\frac{1}{S^2}

第五章误差反向传播算法——基于numpy的代码详解_第18张图片

    步骤四:“+”节点原封不动地传递上游的值。

第五章误差反向传播算法——基于numpy的代码详解_第19张图片

    步骤五:“×”节点将值反转后相乘。其中y_1=\frac{exp(a_1)}{S}

第五章误差反向传播算法——基于numpy的代码详解_第20张图片

    步骤六:正向传播存在分支,反向传播将各分支的数相加求和。exp(x)的导数是其自身。

第五章误差反向传播算法——基于numpy的代码详解_第21张图片

    总结:最后将softmax和cross entropy error层的正向和反向传播的计算图为:

第五章误差反向传播算法——基于numpy的代码详解_第22张图片

    softmax+cross entropy error层的实现代码为:

class softmax_cross:

    def __init__(self):
        
        self.loss=None
        self.t=None
        self.y=None


    def forward(self,x,t):

        self.t=t
        self.y=softmax(x)
        self.loss=corss_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

5.4 反向传播算法的实现

    首先我们来回顾神经网络学习的步骤。

    步骤一:选取batch数据作为训练数据。

    步骤二:计算损失函数关于各个权重的梯度。

    步骤三:将权重参数沿着梯度的方向更新。

    步骤四:重复以上步骤。

    下面我们进行神经网络学习的实现,我们这里要实现一个两层的类TwoLayerNet,首先将这个类是实例变量和方法整理如下:

第五章误差反向传播算法——基于numpy的代码详解_第23张图片

    基于误差反向传播算法的神经网络代码实现为:

import sys,os
sys.path.append(os.pardir)
import numpy as np
from collections import OrderedDict#神经网络的层按顺序保留在此有序字典中


class TwoLayerNet:

    def __init__(self,input_size,hidden_size,output_size,weight_init_std=0.01):

        self.params={}
        self.params['W1']=weight_init_std*np.random.randn(input_size,hidden_size)
        self.params['b1']=np.zeros(hidden_size)
        self.params['W2']=weight_init_std*np.random.randn(hidden_size,output_size)
        self.params['b2']=np.zeros(output_size)

        
        self.layers=OrderedDict()
        self.layers['Affine1']=Affine(self.params['W1'],self.params['b1'])
        self.layers['ReLU1']=ReLU()
        self.layers['Affine2']=Affine(self.params['W2'],self.params['b2'])

        self.lastlayer=softmax_and_cross()


    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 accuracy(self,x,t):

        y=self.predict(x)
        y=np.argmax(y,axis=1)
        if t.ndim !=1  :  t=np.argmax(t,axis=1)

        accuracy=np.sum(y==t)/float(x.shape[0])
        return accuracy

    def gradient(self,x,t):

        self.loss(x,t)

        dout=1
        dout=self.lastlayer.backward(dout)

        layers=list(self.layers.values())
        layers.reverse()
        for layer in layers:
            dout=layer.backward(dout)


        grads={}
        grad['W1']=self.layers['Affine1'].dW
        grad['b1']=self.layers['Affine1'].db
        grad['W2']=self.layers['Affine2'].dW
        grad['b2']=self.layers['Affine2'].db
        
        return grads

    








 

你可能感兴趣的:(第五章误差反向传播算法——基于numpy的代码详解)