Educode--全连接层和激活函数的反向传播的实现

第1关:实现全连接层的反向传播

任务描述

本关任务:实现全连接层的反向传播。

相关知识

为了完成本关任务,你需要掌握:

  1. 神经网络的反向传播;
  2. 全连接层的反向传播。

本实训内容可参考《深度学习入门——基于 Python 的理论与实现》一书中第5章的内容。

神经网络的反向传播

在之前的实训中,我们学习了神经网络通过反向传播来计算每个参数的梯度,同时反向传播的核心思想是求导的链式法则,即:

∂x∂l​=∂f(x)∂l​⋅∂x∂f(x)​

那么,给定一个神经网络,反向传播是如何进行的呢?这里我们以一个三层神经网络为例,来讲解神经网络的反向传播。下图展示了这个简单神经网络的结构。


图1 简单三层神经网络

首先,我们引入一个记号f(x;W),来表示输入为x、参数为W的一个网络层。假设这个神经网络的三层分别为f1​(x;W1​), f2​(x;W2​), f3​(x;W3​),每层之后的激活函数为g1​(x), g2​(x), g3​(x)。网络训练使用的损失函数为L(x,t),其中x表示网络的输出,t表示目标。那么这个网络的计算过程可以表示为:

y1​z1​y2​z2​y3​z3​l​=f1​(x;W1​)=g1​(y1​)=f2​(z1​;W2​)=g2​(y2​)=f3​(z2​;W3​)=g3​(y3​)=L(z3​,t)​

在进行反向传播时,首先我们对损失函数进行反向传播:

∂z3​∂l​=∂z3​∂L(z3​,t)​

之后,对第三层进行反向传播,按照相同的方法,可以对之前的网络层进行推导:

∂y3​∂l​∂W3​∂l​∂z2​∂l​​=∂z3​∂l​⋅∂y3​∂z3​​=∂z3​∂l​⋅∂y3​∂g3​(y3​)​=∂y3​∂l​⋅∂W3​∂y3​​=∂y3​∂l​⋅∂W3​∂f3​(z2​;W3​)​=∂y3​∂l​⋅∂z2​∂y3​​=∂y3​∂l​⋅∂z2​∂f3​(z2​;W3​)​​

通过上面的推导可以看到,对于一个特定的网络层,只要其输出的梯度已知,那么其参数和输入的梯度只与该层的计算有关,而与之前和之后的网络层是什么没有关系。因此,只要对每个网络层都定义好其前向传播和反向传播的计算,那么神经网络就可以计算每个参数的梯度。在这个过程中,神经网络的计算形成了计算图,这里不做详细展开,感兴趣的同学可以参考教材第5.1−5.2章节的内容。

全连接层的反向传播

下面,我们来学习全连接层的反向传播。首先,回顾一下全连接层的前向传播。一个包含N个输入神经元,M个输出神经元的全连接层包含两组参数:权重W∈RN×M和偏置b∈RM,其输入可以看作是一个N维的(列)向量x∈RN,此时全连接层的前向传播的计算可以表示为:

y=xTW+b

先假设根据链式法则,已知∂l/∂y,按照全连接层的定义,其应该是一个M维的(列)向量。根据矩阵计算求导法则,可以得到:

∂b∂l​∂W∂l​∂x∂l​​=∂y∂l​⋅∂b∂y​=∂y∂l​=∂y∂l​⋅∂W∂y​=x×(∂y∂l​)T=∂y∂l​⋅∂x∂y​=W×∂y∂l​​

全连接层反向传播的实现

实训拓展了在之前的实训定义的FullyConnected类,实训已经给出了forward(x)的实现,并针对反向传播的需要对其进行了一定的修改。你需要实现该类的反向传播函数backward(dout)dout是损失函数相对于全连接层输出的梯度,即之前公式中的∂y∂l​,是一个形状为(B,M)的numpy.ndarray,M是全连接层的输出通道数。在前向传播时,因为全连接层的输入的形状可以有所变化,并记录在了self.original_x_shape中,因此在计算完成后,请把输入的梯度还原为原来的形状。全连接层的输入记录在了self.x中。在反向传播的过程中,请将self.Wself.b的梯度分别存储在self.dWself.db中,并将x的梯度返回。

编程要求

根据提示,在右侧编辑器 Begin 和 End 之间补充代码,实现全连接层的反向传播。

测试说明

平台会对你编写的代码进行测试,测试方法为:平台会随机产生输入x、权重W、偏置b和输出梯度dout,然后根据你的实现代码,创建一个FullyConnected类的实例,然后利用该实例先进行前向传播计算,再进行反向传播计算。你的答案将并与标准答案进行比较。因为浮点数的计算可能会有误差,因此只要你的答案与标准答案之间的误差不超过10−5即可。

样例输入:

 
  
  1. W:
  2. [[0.1, 0.2, 0.3],
  3. [0.4, 0.5, 0.6]]
  4. b:
  5. [0.1, 0.2, 0.3]
  6. x:
  7. [[1, 2],
  8. [3, 4]]
  9. dout:
  10. [[0.1, 0.2, 0.3],
  11. [0.4, 0.5, 0.6]]

则对应的梯度为:

 
  
  1. dx:
  2. [[0.14 0.32]
  3. [0.32 0.77]]
  4. dW:
  5. [[1.3 1.7 2.1]
  6. [1.8 2.4 3. ]]
  7. db:
  8. [0.5 0.7 0.9]

上述结果有四舍五入的误差,你可以忽略。


开始你的任务吧,祝你成功!

实现代码: 

import numpy as np

class FullyConnected:

    def __init__(self, W, b):

        r'''

        全连接层的初始化。

        Parameter:

        - W: numpy.array, (D_in, D_out)

        - b: numpy.array, (D_out)

        '''

        self.W = W

        self.b = b

        self.x = None

        self.original_x_shape = None

        self.dW = None

        self.db = None

    def forward(self, x):

        r'''

        全连接层的前向传播。

        Parameter:

        - x: numpy.array, (B, d1, d2, ..., dk)

        Return:

        - y: numpy.array, (B, M)

        '''

        self.original_x_shape = x.shape

        x = x.reshape(x.shape[0], -1)

        self.x = x

        out = np.dot(self.x, self.W) + self.b

        return out

    def backward(self, dout):

        r'''

        全连接层的反向传播

        Parameter:

        - dout: numpy.array, (B, M)

        Return:

        - dx: numpy.array, (B, d1, d2, ..., dk) 与self.original_x_shape形状相同

        另外,还需计算以下结果:

        - self.dW: numpy.array, (N, M) 与self.W形状相同

        - self.db: numpy.array, (M,)

        '''

        ########## Begin ##########

        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.original_x_shape)

        return dx

        ########## End ##########

代码截图:

Educode--全连接层和激活函数的反向传播的实现_第1张图片

第2关:实现常用激活函数的反向传播

任务描述

本关任务:实现常用激活函数的反向传播。

相关知识

为了完成本关任务,你需要掌握:常用激活函数的反向传播。

本实训内容可参考《深度学习入门——基于 Python 的理论与实现》一书中第5.5章节的内容。

常用激活函数的反向传播

在上一关中,我们学习了全连接层的反向传播。在这一关中,我们更进一步,学习常用激活函数的反向传播。跟之前的实训一样,我们主要关注 sigmoid 和 ReLU 这两个激活函数。

1. sigmoid激活函数

在之前的实训中,我们学习了 sigmoid 激活函数的前向传播:

y=sigmoid(x)=1/(1+e−x)

因为激活函数都是逐元素进行计算的,因此激活函数的反向传播只需要根据函数求导法则求解即可:

∂x∂y​=(1−y)y

对于 sigmoid 激活函数,可以得到:

∂x∂l​=∂y∂l​⋅∂x∂y​=∂y∂l​⋅(1−y)y

2. ReLU激活函数

在之前的实训中,我们学习了 ReLU 激活函数的前向传播:

y=ReLU(x)=max(0,x)

可以看到,ReLU 激活函数存在不可导点x=0,不能直接求导。此时,我们在该点处使用 ReLU 的次梯度:

∂x∂y​(0)=0

关于次梯度的概念,这里不做深入的介绍,感兴趣的同学可以阅读相关书籍。在引入次梯度之后,ReLU 激活函数的梯度可以完整的表示为:

∂x∂y​={0,x≤01,x>0​

再结合之前的反向传播公式,就可以得到 ReLU 激活函数的反向传播计算方法。

常用激活函数反向传播的实现

对于 sigmoid 激活函数,实训拓展了在之前的实训定义的Sigmoid类,实训已经给出了forward(x)的实现,并针对反向传播的需要对其进行了一定的修改。你需要实现该类的反向传播函数backward(dout)dout是损失函数相对于 Sigmoid 输出的梯度,即之前公式中的∂y∂l​,是一个形状与输入x相同的numpy.ndarray。前向传播的输出记录在了self.out中,以方便你进行反向传播的计算。backward(dout)函数需要返回输入x的梯度。

对于 ReLU 激活函数,实训拓展了在之前的实训定义的ReLU类,实训已经给出了forward(x)的实现,并针对反向传播的需要对其进行了一定的修改。你需要实现该类的反向传播函数backward(dout)dout是损失函数相对于 Sigmoid 输出的梯度,即之前公式中的∂y∂l​,是一个形状与输入x相同的numpy.ndarrayself.mask记录了前向传播时每个输入是否小于等于 0,以方便你进行方向传播的计算。backward(dout)函数需要返回输入x的梯度。

编程要求

根据提示,在右侧编辑器 Begin 和 End 之间补充代码,实现上述激活函数的反向传播。

测试说明

平台会对你编写的代码进行测试,测试方法为:平台会随机产生输入x和输出梯度dout,然后根据你的实现创建一个Sigmoid/ReLU类的实例,然后利用该实例先进行前向传播计算,再进行反向传播计算。你的答案将并与标准答案进行比较。因为浮点数的计算可能会有误差,因此只要你的答案与标准答案之间的误差不超过10−5即可。

样例输入:

 
  
  1. # 对于sigmoid激活函数:
  2. x:
  3. [[-1, 0, 1]]
  4. dout:
  5. [[1, 2, 3]]
  6. #对于ReLU激活函数:
  7. x:
  8. [[-1, 0, 1]]
  9. dout:
  10. [[1, 2, 3]]

则对应的梯度为:

 
  
  1. # 对于sigmoid激活函数:
  2. dx:
  3. [[0.20, 0.50, 0.59]]
  4. #对于ReLU激活函数:
  5. dx:
  6. [0, 2, 3]

上述结果有四舍五入的误差,你可以忽略。


开始你的任务吧,祝你成功!

实现代码:

import numpy as np


 

class Sigmoid:

    def __init__(self):

        self.out = None

    def forward(self, x):

        r'''

        Sigmoid激活函数的前向传播。

        Parameter:

        - x: numpy.array, (B, d1, d2, ..., dk)

        Return:

        - y: numpy.array, (B, d1, d2, ..., dk)

        '''

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

        self.out = out

        return out

    def backward(self, dout):

        r'''

        sigmoid的反向传播

        Parameter:

        - dout: numpy.array, (B, d1, d2, ..., dk)

        Return:

        - dx: numpy.array, (B, d1, d2, ..., dk)

        '''

        ########## Begin ##########

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

        return dx

        ########## End ##########


 

class Relu:

    def __init__(self):

        self.mask = None

    def forward(self, x):

        r'''

        ReLU激活函数的前向传播。

        Parameter:

        - x: numpy.array, (B, d1, d2, ..., dk)

        Return:

        - y: numpy.array, (B, d1, d2, ..., dk)

        '''

        self.mask = (x <= 0)

        out = x.copy()

        out[self.mask] = 0

        return out

    def backward(self, dout):

        r'''

        relu的反向传播

        Parameter:

        - dout: numpy.array, (B, d1, d2, ..., dk)

        Return:

        - dx: numpy.array, (B, d1, d2, ..., dk)

        '''

        ########## Begin ##########

        dout[self.mask] = 0

        dx = dout

        return dx

        ########## End ##########

 Educode--全连接层和激活函数的反向传播的实现_第2张图片

 

你可能感兴趣的:(神经网络,深度学习,网络)