头歌——机器、深度学习——全连接层和激活函数的反向传播的实现

17 篇文章 0 订阅
11 篇文章 0 订阅

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

任务描述

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

相关知识

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

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

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

神经网络的反向传播

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

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

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

图1


图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关任务代码

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 ##########

第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即可。


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

第2关任务代码

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 ##########

  • 18
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

absths

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

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

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

打赏作者

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

抵扣说明:

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

余额充值