误差反向传播法(二)【神经网络以层的方式实现】

        我们来看激活函数层的实现,对于激活函数,大家初学神经网络的时候就经常听到,准确来说是在接触感知机的时候熟悉的,它是进入神经网络大门的钥匙,是现代神经网络快速发展的源头。

ReLU层(Rectified Linear Unit)修正线性单元

import numpy as np
class Relu:
   def __init__(self):
       self.mask=None
   def forward(self,x):
       self.mask=(x<=0)#[[False  True][ True False]]
       out=x.copy()
       out[self.mask]=0#将True设为0
       return out
   def backward(self,dout):
       dout=(~self.mask).astype(int)
       dx=dout
       return dx
x=np.array([[1.0,-0.5],[-2.0,3.0]])
relu=Relu()
relu.forward(x)
[[ 1.  0.]
 [ 0.  3.]]
relu.backward(x)
[[ 1.  0.]
 [ 0.  1.]]
上面的正向和反向等价于下面的代码:
def relu(x):
    return np.maximum(0, x)#[[ 1.  0.][ 0.  3.]]
def relu_grad(x):
    grad=np.zeros_like(x)#zeros的参数是形状,这个需要注意
    grad[x>=0]=1
    return grad
#[[ 1.  0.][ 0.  1.]]

Sigmoid层

数学式:y=\frac{1}{1+exp(-x))}

 代码如下:

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*self.out*(1.0-self.out)
        return dx
或写成下面这样的代码:
def sigmoid(x):
    return 1/(1+np.exp(-x))
def sigmoid_grad(x):
    return sigmoid(x)*(1.0-sigmoid(x))

Affine层

        神经网络的正向传播中进行的矩阵的乘积运算在几何学领域叫做“放射变换”,所以将这里的处理实现为“Affine层”
乘积反向传播求导需要特别注意两点
1、除了翻转值之外还需要将翻转来的数组进行转置之后再乘积
2、乘积的顺序不要弄错了

因为乘积对数组的形状有要求,需要对应维度的元素个数保持一致,关于乘积更详细说明 :矩阵的乘法、乘积(点积)和softmax函数的溢出https://blog.csdn.net/weixin_41896770/article/details/119141707

αL/αX=αL/Y  *  W.T(W权重数组的转置)
αL/αW=X.T  *   αL/Y

下面的代码也考虑到了四维数组(张量)的处理:

import numpy as np
class Affine:
    def __init__(self,W,B):
        self.W=W
        self.B=B
        self.X=None
        #保存X的形状,针对四维数组(张量)做变形之后的还原
        self.Xshape=None
        #权重和偏置的导数
        self.dW=None
        self.dB=None
    def forward(self,X):
        self.Xshape=X.shape
        X=X.reshape(X.shape[0],-1)#转换成矩阵,比如(5,2,3,4)转成(5,24)
        self.X=X
        out=np.dot(self.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)
        dX=dX.reshape(self.Xshape)#还原输入数据的形状,针对张量
        return dX
W=np.array([[0,0,0],[10,10,10]])
B=np.array([1,2,3])
affine=Affine(W,B)
X=np.array([[1,1],[2,5]])
dY=affine.forward(X)
array([[11, 12, 13],
       [51, 52, 53]])
dX=affine.backward(dY)
array([[   0,  360],
       [   0, 1560]])
affine.dW
array([[113, 116, 119],
       [266, 272, 278]])
affine.dB
array([62, 64, 66])

Softmax层(最后一层,还有一个交叉熵误差函数层)

        正向传播时若有分支流出,则反向传播时它们的反向传播的值会相加,所以大家在上面的除法运算这个节点可以看到有分支流出,那么需要求和再做除法的导数,-t1S-t2S-t3S=-(S(t1+t2+t3))=-S,因为t1,t2,t3这些是独热编码的标签,所以只有一个1,其余为0,故他们的和就是1,求-S的除法的导数,我们知道就是乘以-(1/S)²,就是1/S。从上面的这个反向传播推导图得到了一个非常漂亮的简单结果,是(y1-t1,y2-t2,y3-t3)这样的差分结果(Softmax层的输出y-监督标签t)

        有了前面的基础,我们现在来实现N层的神经网络,还是以手写数字识别为例,通过若干层的Affine层 | ReLU层的传播之后,最后经过Affine层将得到10个分类的分数(10个数字分别的打分),再通过Softmax层就可以知道每个数字识别的概率,最大的那个就是我们识别的标签
        神经网络学习的目的就是通过调整权重参数,使神经网络的输出接近监督标签。那么反向求导的代码就显得非常简单了,其中需要用到的两个函数softmax和cross_entropy_error,需要查阅的可以点击 Python随机梯度下降法(四)【完结篇】

import sys,os
import numpy as np
os.chdir('D:\Anaconda3\TONYTEST')
sys.path.append('D:\Anaconda3\TONYTEST')
from common.functions import *
from common.gradient import numerical_gradient
from collections import OrderedDict

class SoftmaxLoss:
    '''
    Softmax分类函数和交叉熵误差的损失函数
    误差反向传播求导的最后一层

    '''
    def __init__(self):
        self.loss=None
        self.y=None
        self.t=None
    def forward(self,x,t):
        self.y=softmax(x)
        self.t=t
        self.loss=cross_entropy_error(self.y,self.t)
        return self.loss
    def backward(self,dout=1):
        batchSize=self.t.shape[0]
        dx=(self.y-self.t)/batchSize
        return dx

class TwoLayerNet:
    def __init__(self,inputSize,hiddenSize,outputSize,weightStd=0.01):
        self.params={}
        self.params['W1']=weightStd*np.random.randn(inputSize,hiddenSize)
        self.params['b1']=np.zeros(hiddenSize)
        self.params['W2']=weightStd*np.random.randn(hiddenSize,outputSize)
        self.params['b2']=np.zeros(outputSize)
        #通过有序字典来保存层,反向传播的时候只需要把层的顺序反向调用即可
        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=SoftmaxLoss()
    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)
        accu=np.sum(y==t)/float(x.shape[0])
        return accu

    def gradient(self,x,t):
        '''
        比数值微分高效的误差反向传播求导
        获取权重和偏置参数的梯度
        '''
        #forward
        self.loss(x,t)
        #backward
        dout=1
        dout=self.lastLayer.backward(dout)

        layers=list(self.layers.values())#将有序字典保存的层的值,先转换成list再进行反转
        layers.reverse()#反转
        for layer in layers:
            dout=layer.backward(dout)

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

    def numerical_gradient(self,x,t):
        loss_W=lambda W:self.loss(x,t)
        grads={}
        grads['W1']=numerical_gradient(loss_W,self.params['W1'])
        grads['b1']=numerical_gradient(loss_W,self.params['b1'])
        grads['W2']=numerical_gradient(loss_W,self.params['W2'])
        grads['b2']=numerical_gradient(loss_W,self.params['b2'])
        return grads

        做完之后需要做一个梯度确认的操作,前面说过数值微分简单不容易出错,但效率低,所以一般都是拿来检测误差反向传播法是否正确的。确保数值微分求出来的梯度和误差反向传播法求出来的结果相差很小就对了,一般为0是很少见的,因为计算机存在一个精度有限的问题(如32位浮点数),受到数值精度的限制,所以一般都不会为0,只能是一个很接近于0的值

from dataset.mnist import load_mnist
(x_train,t_train),(x_test,t_test)=load_mnist(normalize=True,one_hot_label=True)
network=TwoLayerNet(inputSize=784,hiddenSize=50,outputSize=10)
x_batch=x_train[:3]#(3,784)
t_batch=t_train[:3]
grad_numerical=network.numerical_gradient(x_batch,t_batch)#数值微分求导
grad_backprop=network.gradient(x_batch,t_batch)#误差反向传播法求导
for k in grad_numerical.keys():
    diff=np.average(np.abs(grad_backprop[k]-grad_numerical[k]))#差的绝对值的平均值
    #print(grad_backprop[k].shape)#(784,50)(50,)(50,10),(10,)
    print(k+":"+str(diff))

W1:0.17803059095
b1:1.33964910075
W2:4.77057756268e-09
b2:1.40236227449e-07

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寅恪光潜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值