cs224n学习笔记L4: Backpropagation and computation graphs

课堂安排

  • 简单网络的梯度计算以及一些小提示
  • 计算图和反向传播
  • 需要掌握的知识
    a. 防止过拟合的规则 b. 向量化 c. 非线性 d. 初始化 e. 优化器 f.学习率

一、反向传播·续

1.1 s W \frac{\partial s}{\partial W} 的计算推导

上一节课我们得到:
s W = δ z W \frac { \partial s } { \partial \boldsymbol { W } } = \boldsymbol { \delta }\frac{\partial z}{\partial W}
假设 z z 长度为n, x x 长度为m,那么我们现在来看看 z W \frac{\partial z}{\partial W} , 由于W是一个n × m的矩阵, 我们单独看一个元素 W i j W_{ij} 。由于 W i j W_ij 只对 z i z_i 有影响,因此:
z i W i j = W i j k = 1 m W i k x k = x j ( j = k ) \frac{\partial z_i}{\partial W_{ij}} = \frac{\partial }{\partial W_{ij}} \sum_{k = 1}^{m}W_{ik}x_k = x_j (即j=k时的系数)
所以有:
z i W = x T \frac{\partial z_i}{\partial W} = x^T
那么 z W \frac{\partial z}{\partial W} 就应该是一个n × m的矩阵, 而 s W i j = δ i x j \frac{\partial s}{\partial W_{ij}} = \delta_ix_j , 所以有:
s W = δ x T [ n × m ] [ n × 1 ] [ 1 × m ] \begin{aligned} & \frac { \partial s } { \partial \boldsymbol { W } } = &\boldsymbol { \delta }&\boldsymbol { x } ^ { T } \\ &[ n \times m ] &[ n \times 1 ]& [ 1 \times m ] \end{aligned}

1.2 梯度推导温馨提示

  1. 严肃认真的定义变量,并且始终记得维护他们的维度一致性
  2. 运用链式法则
  3. 对于顶层的softmax推导,要先考虑 c = y c=y ,即正确类别,然后考虑 c y c \ne y , 即错误类别。
  4. 如果对矩阵推导感觉迷惑了,就进行元素级别的计算
  5. 运用维度传播规律,任意一层的误差信号 δ \delta 的长度与该层的大小相同。

1.3 输入x的偏导

对于x的偏导,输入阶段我们将窗口内的单词拼接成了一个向量,对于反向传播的误差,也可以直接拆分到各个单词向量进行更新。
x J = W T δ \nabla_xJ = W^T\delta
如果一个单词在同一个窗口中出现了两次,那么我们就对这个单词进行两次更新。

1.4 下游任务更新词向量存在的风险

假如预训练词向量中有电视、电影、节目三个词义相近,词向量相似的词语,但训练集只有电视、电影两个词出现,而测试集中出现了节目这个词。那么如果我们在训练阶段更新了词向量,就会导致三个词向量之间相似度降低。
解决方案: 如果下游任务数据集较小,就不要更新词向量。如果下游任务数据集很大,那么可以在训练阶段更新词向量(相当于fine-tune)

二、计算图及其反向传播

这之前我们要了解图计算(graph computing) 和 **计算图(computation graph)**是两个不同的概念。本节课的计算图是用图的方式来表示计算流程的神经网络计算框架,而图计算是利用图论方法研究任意数据的内在联系的一类算法统称。

2.1 计算图的概念

前向传播(黑色箭头方向):
起始节点x为输入
每个中间节点为运算操作
箭头为运算操作的输出信号
反向传播(蓝色箭头方向)
每条边代表Loss函数对该层输入信号的偏导(通过链式法则)。
在这里插入图片描述
对于单个节点的视图如下:
在这里插入图片描述

2.2 一个简单地计算实例

注意这里的max函数,需要跟情况讨论输入参数的梯度,并根据运行反向传播的时刻的输入值来决定使用哪一个梯度(相当于激活的神经元或抑制的神经元)。下图中蓝色部分为反向传播的梯度值,在更新时需要与学习率 α \alpha 相乘。
在这里插入图片描述

2.3 分支网络的处理

如果一个节点输出被下一层的多个节点使用,那么反向更新时这个节点就会接受到两个误差信号(如图),我们只需要将这两个输入信号相加(事实上就是多元复合函数求偏导的公式呀)。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200223122953606.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2dlZWtfaGNo,size_16,color_FFFFFF,t_70) ## 2.4 反向传播运算规律 观察2.2节中的计算过程,可以得出以下规律: + +运算将上游梯度分发复制到下游每个分支 + max运算将上游梯度路由到下游中最大值的那一个分支 + *运算将下游的值交换后作为梯度发送给下游分支。但这只是针对上游分支只有两个的情况。如果有多个呢?(当然就是一个分支的梯度等于其他分支的前向传播值的乘积)

2.5 高效计算:每个局部误差信号仅计算一遍

对于如下计算图,如果我们直接计算 s b \frac{\partial s}{\partial \boldsymbol{b}} ,这是极其低效的,因为这样的话我们计算 s W \frac{\partial s}{\partial \boldsymbol{W}} 时又会重复计算上游的梯度。
高效的计算方式是Top-Down,从loss反向一层层的传播,每一次计算都通过链式法则,利用上一层的梯度信息
在这里插入图片描述

2.6 更复杂的计算图

对于以下计算图,有了前面的知识,我们可以轻易地进行Fprop和Bprop,如果方法正确,Fprop和Bprop的计算复杂度应该相同。根据2.3节:
z x = i = 1 n z y i y i x \frac{\partial z}{\partial \boldsymbol x}=\sum_{i=1}^{n} \frac{\partial z}{\partial y_{i}} \frac{\partial y_{i}}{\partial x}
在这里插入图片描述
对于一些规范的图(大多数神经网络),我们都可以利用矩阵和雅克比来简化计算

三、神经网络代码实现及相关框架

3.1 自动求导

对于每个节点,如果满足下面的条件,那么由这些节点构成的网络就能够自动求导,反向传播。
+ 定义节点的输入与输出
+ 定义该节点输出对每个输入的偏导
现代神经网络框架正式利用这个原理,通过程序员定义每一层/节点的手写梯度计算,来实现整个深度神经网络的自动求导。求偏导公式是一个符号计算过程,tf, torch等框架只提供数值计算,所以需要程序员给出所有可能的计算公式(前向、反向)

扫描二维码关注公众号,回复: 10256194 查看本文章

3.2 计算图的代码(伪代码)

这是pytorch框架计算图的定义,可以很清晰地看到前向传播、反向传播的逻辑:
在这里插入图片描述
对于单个单元,这里以简单的乘法运算为例,实现代码如下,这里要注意forward和backword的接口参数在这里插入图片描述

3.3 梯度检查:数值梯度

(注意区分梯度的数值计算符号计算)
根据偏导的定义,我们可以通过以下公式计算梯度的近似值,但由于计算中h(1e-4)不是无穷小的数,所以并不是真实的梯度值。
f ( x ) f ( x + h ) f ( x h ) 2 h f ^ { \prime } ( x ) \approx \frac { f ( x + h ) - f ( x - h ) } { 2 h }
理论上我们可以用这个方法实现完全自动的求导,但我们可以看到这个方法要求每次计算梯度时都要重新计算f(x+h)、f(x-h),并且每次更新对每个参数都要计算一次,性能较低。通常使用这个方法来检查我们的代码实现。

3.4 了解梯度的计算过程的意义

  • 有助于我们思考如何提升模型性能
  • 可能存在的bug: 梯度消失和爆炸

四、其他细节

4.1 参数正则化

当网络参数量很大的时候,为了防止过拟合,我们需要在一般的loss后面添加一个L2正则项来限制参数。(通过惩罚偏离0较远的参数值,限制模型的拟合能力)
J ( θ ) = 1 N i = 1 N log ( e f y i c = 1 C e f c ) + λ k θ k 2 J ( \theta ) = \frac { 1 } { N } \sum _ { i = 1 } ^ { N } - \log \left( \frac { e ^ { f _ { y _ { i } } } } { \sum _ { c = 1 } ^ { C } e ^ { f _ { c } } } \right) + \lambda \sum _ { k } \theta _ { k } ^ { 2 }

4.2 向量化运算

向量化是指,尽可能的将循环计算转换为更高维的向量计算,因为计算框架对向量运算有优化,比一般的循环快很多。比如下面两行,计算输入和输出一样,但时间却相差了40多倍,如果使用GPU, 差距会远大得多。
在这里插入图片描述

4.3 非线性激活函数

早起使用的非线性激活函数如下,其中tanh(z) = 2*sigmod(z) - 1, sigmod 和 tanh现在仍然会在某些特殊情况下被用到,但不再是默认的激活函数:

  • sigmod
  • tanh
  • hard-tanh(出现的原因是为了加速运算,虽然在每一段上都是线性的,但其分段打破了线性,因此是有效的激活函数)
    在这里插入图片描述
    这之后,激活函数不断被改进,出现了更加简化且高效的版本ReLU及其改进版本,ReLU的功能就是简单地赋予神经元(抑制、有效)两种状态,求导非常容易,是在构建网络时推荐的默认激活函数。leaky ReLU只在部分论文中被称有效
    在这里插入图片描述

4.4 参数初始化

通常需要将权重初始化为较小的值

  • biases初始化为0,
  • 其他权重初始化为uniform(-r, r)(r为一个较小的数)

4.5 优化器

一般来说,简单地SGD就会很有效。但一些复杂的网络可能需要适应性更强的优化器,这些优化器可能通过给每个变量设置不同的学习率并记录,来提高其优化效率(Adagrad、RMSprp、Adam(推荐作为默认使用)、sparseAdam)

4.6 学习率

一般默认起始学习率0.001, 可以以10倍减小,太大会导致不收敛,太小则优化过程很慢。
最好允许学习率在学习过程中逐渐减小,比如每k个epoch减半或使用如下公式:
l r = l r 0 e k t ( t e p o c h ) lr = lr_0e^{-kt}(t为epoch)

发布了24 篇原创文章 · 获赞 8 · 访问量 7205

猜你喜欢

转载自blog.csdn.net/geek_hch/article/details/104443297
今日推荐