2.1 计算梯度的数值方法

第一次实验我留的一个课后作业里问你是否能够想出一个求解梯度的办法,其实不难想到一种简单的办法就是使用“数值法”计算梯度。

办法很简单,就是对于损失函数中的一个初始取值为a0的参数a,先计算当前的损失函数值J0,再保持其他参数不变,而使a改变一个很小的量,比如变成a0+0.000001,再求改变之后的损失函数值J1。然后(J1-J0)/0.000001就是J对于a的偏导的近似值。我们对每一个参数采用类似的方法求偏导,最后将偏导的值组成一个向量,即为梯度向量。

这个办法看上去很简单,但却无法应用在实际的神经网络当中。一方面的原因是,我们很难知道对参数的改变,有多小才算足够小,即我们很难保证最后求出的梯度是准确的。

另一方面的原因是,这种方法计算量太大,现在的神经网络中经常会有上亿个参数,而这里每求一个分量的偏导都要把所有参数值代入损失函数求两次损失函数值,而且每个分量都要执行这样的计算。相当于每计算一次梯度需要2x1亿x1亿次计算,而梯度下降算法又要求我们多次(可能是上万次)计算梯度。这样巨大的计算量即使是超级计算机也很难承受(世界第一的“神威·太湖之光”超级计算机峰值性能为12.5亿亿次/秒,每秒也只能计算大概6次梯度)。

所以,我们需要更加高效准确的算法来计算梯度,而反向传播算法正好能满足我们的需求。

2.2 “计算图(compute graph)”与链式法则

其实如果你已经理解了链式法则,那么可以说,你几乎已经学会反向传播算法了。让人感到很愉快对不对,好像什么都还没做,我们就已经掌握了一个名字看起来有些吓人的算法。

为了帮助我们真正理解反向传播算法,我们先来看一下什么是“计算图”,我们以第一次实验提到的sigmoid函数为例:

它的计算图,是这样的:

此处输入图片的描述

我们将sigmoid函数视为一个复合函数,并将其中的每一个子函数都视为一个节点,每个节点按照复合函数实际的运算顺序链接起来,最终得到的F其实就是sigmoid函数本身。

根据求导法则,我们可以求得每一个节点对它直接子节点的导函数:

此处输入图片的描述

最重要的地方来了,再根据求导链式法则,我们现在可以轻易写出图中任意一个高层节点对其任意后代节点的导函数:只需要把连接它们的路径上的所有部分导函数都乘起来就可以了。

比如:

dF/dC=(dF/dE)(dE/dC)=(-1/E^2)1=-1/E^2

dF/dA=(dF/dE)(dE/dC)(dC/dB)(dB/dA)=(-1/E^2)(1)(e^B)(-1)=e^B/E^2

2.3 反向传播算法

到这里反向传播算法已经呼之欲出了,对于一个具体的参数值,我们只需要把每个节点的值代入求得的导函数公式就可以求得导数(偏导数),进而得到梯度。

这很简单,我们先从计算图的底部开始向上,逐个节点计算函数值并保存下来。这个步骤,叫做前向计算(forward)。

然后,我们从计算图的顶部开始向下,逐步计算损失函数对每个子节点的导函数,代入前向计算过程中得到的节点值,得到导数值。这个步骤,叫做反向传播(backward)或者更明确一点叫做反向梯度传播。

我们来具体实践一下,对于上图中的sigmoid函数,计算x=0时的导数:

前向计算:

A=0, B=0, C=1, D=1, E=2, F=-1/4

反向传播:

dF/dE=-1/E^2=-1/2^2=-1/4

dF/dC=dF/dEdE/dC=-1/4

dF/dB=dF/dCdC/dB=-1/4e^B=-1/41=-1/4

dF/dA=dF/dBdB/dA=-1/4(-1)=1/4

以上就是反向传播算法的全部内容。对于有1亿个参数的损失函数,我们只需要2*1亿次计算就可以求出梯度。复杂度大大降低,速度将大大加快。

2.4 将sigmoid视为一个整体

sigmoid函数中没有参数,在实际的神经网络中,我们都是将sigmoid函数视为一个整体来对待,没必要求它的内部节点的导函数。

sigmoid函数的导函数是什么呢?你可以自己求导试试,实际上sigmoid(x)'=sigmoid(x)*(1-sigmoid(x))。

2.5 反向传播算法--动手实现

激动人心的时刻到了,我们终于要开始用python代码实现深度神经网络的过程,这里我们打算对第一次实验中的神经网络示例图中的“复合函数”编写反向传播算法。不过为了循序渐进,我们考虑第一层(输入层)只有两个节点,第二层只有一个节点的情况,即如下图:

此处输入图片的描述

注意我们将sigmoid函数图像放在了b1节点后面,代表我们这里对b1运用sigmoid函数得到了最终的输出h1。

如果你对自己比较有信心,可以不看接下来实现的代码,自己动手试一试。

我们可以先把图中包含的函数表达式写出来,方便我们之后写代码参考:

b1=w11a1+w12a2+bias1

h1=sigmoid(b1)

h1=sigmoid(w11a1+w12a2+bias1)

现在我们创建bp.py文件,开始编写代码。先来编写从第一层到第二层之间的代码:

import numpy as np

class FullyConnect:

def init(self, l_x, l_y): # 两个参数分别为输入层的长度和输出层的长度

self.weights = np.random.randn(l_y, l_x) # 使用随机数初始化参数

self.bias = np.random.randn(1) # 使用随机数初始化参数

def forward(self, x):

self.x = x # 把中间结果保存下来,以备反向传播时使用

self.y = np.dot(self.weights, x) + self.bias # 计算w11*a1+w12*a2+bias1

return self.y # 将这一层计算的结果向前传递

def backward(self, d):

self.dw = d * self.x # 根据链式法则,将反向传递回来的导数值乘以x,得到对参数的梯度

self.db = d

self.dx = d * self.weights

return self.dw, self.db # 返回求得的参数梯度,注意这里如果要继续反向传递梯度,应该返回self.dx

注意在神经网络中,我们将层与层之间的每个点都有连接的层叫做全连接(fully connect)层,所以我们将这里的类命名为FullyConnect。

上面的代码非常清楚简洁,我们的全连接层完成了三个工作:

随机初始化网络参数

根据x计算这层的输出y,并前向传递给下一层

运用求导链式法则,将前面的网络层向后传递的导数值与本层的相关数值相乘,得到最后一层对本层参数的梯度。注意这里如果要继续反向传递梯度(如果后面还有别的层的话),backward()应该返回self.dx

然后是第二层的输入到最后的输出之间的代码,也就是我们的sigmoid层:

class Sigmoid:

def init(self): # 无参数,不需初始化

pass

def sigmoid(self, x):

return 1 / (1 + np.exp(x))

def forward(self, x):

self.x = x

self.y = self.sigmoid(x)

return self.y

def backward(self): # 这里sigmoid是最后一层,所以从这里开始反向计算梯度

sig = self.sigmoid(self.x)

self.dx = sig * (1 - sig)

return self.dx # 反向传递梯度

由于我们要多次使用sigmoid函数,所以我们单独的把sigmoid写成了类的一个成员函数。

我们这里同样完成了三个工作。只不过由于Sigmoid层没有参数,所以不需要进行参数初始化。同时由于这里需要反向传播梯度,所以backward()函数必须返回self.dx

把上面的两层拼起来,就完成了我们的总体的网络结构:

def main():

fc = FullyConnect(2, 1)

sigmoid = Sigmoid()

x = np.array([[1], [2]])

print 'weights:', fc.weights, ' bias:', fc.bias, ' input: ', x

# 执行前向计算

y1 = fc.forward(x)

y2 = sigmoid.forward(y1)

print 'forward result: ', y2

# 执行反向传播

d1 = sigmoid.backward()

dx = fc.backward(d1)

print 'backward result: ', dx

if name == 'main':

main()

请你自行运行上面的代码,并修改输入的x值。观察输出的中间值和最终结果,并手动验证我们计算的梯度是否正确。

此处输入图片的描述

如果你发现你不知道如何手动计算验证结果,那说明你还没有理解反向传播算法的原理,请回过头去再仔细看一下之前的讲解。

这里给出完整代码的下载链接,但我还是希望你能尽量自己尝试写出代码,至少自己动手将上面的代码重新敲一遍。这样学习效果会好得多。

完整代码文件下载:

上面的代码将每个网络层写在不同的类里,并且类里面的接口都是一致的(forward 和 backward),这样做有很多好处,一是最大程度地降低了不同模块之间的耦合程度,如果某一个层里面的代码需要修改,则只需要修改该层的代码就够了,不需要关心其他层是怎么实现的。另一方面可以完全自由地组合不同的网络层(我们最后会介绍神经网络里其他种类的网络层)。

实际上,目前很多用于科研和工业生产的深度学习框架很多都是采用这种结构,你可以找一个深度学习框架(比如caffe)看看它的源码,你会发现里面就是这样一个个写好的网络层。

三、实验总结

本次实验,我们完全地掌握了梯度下降算法中的关键--反向传播算法。至此,神经网络中最基本的东西你已经全部掌握了。你现在完全可以自己尝试构建神经网络并使用反向传播算法优化网络中的参数。

如果你把到此为止讲的东西差不多都弄懂了,那非常恭喜你,你应该为自己感到骄傲。如果你暂时还有些东西没有理解,不要气馁,回过头去仔细看看,到网上查查资料,如果实在无法理解,问问我们实验楼的助教,我相信你最终也能理解。

本次实验,我们学习了:

使用计算图理解反向传播算法

层次化的神经网络结构

四、课后作业

[选做]请你自己尝试将我们上面实现的第二层网络的节点改为2个(或多个),注意这里涉及到对矩阵求导,如果你没学过相关知识可能无法下手。

php梯度区间计算,快速计算梯度的魔法--反向传播算法相关推荐

  1. 快速计算梯度的魔法--反向传播算法

    一.实验介绍 1.1 实验内容 第一次实验最后我们说了,我们已经学习了深度学习中的模型model(神经网络).衡量模型性能的损失函数和使损失函数减小的学习算法learn(梯度下降算法),还了解了训练数 ...

  2. 10分钟快速理解反向传播算法

    一. 反向传播算法 1. BP算法自然语言描述   概念:梯度下降是利用损失函数的梯度,来决定最终的下降方向.反向传播算法是计算复杂梯度的方式.数学原理就是链式法则.   梯度向量中每一项,不光告诉我 ...

  3. 反向传播算法推导、激活函数、梯度消失与爆炸

    目录 反向传播算法 定义 推导过程 激活函数 定义 性质 常用的激活函数 Sigmoid Tanh ReLU softmax 梯度消失与梯度爆炸 起因 出现的原因 表现 解决方案 参考文档 反向传播算 ...

  4. 梯度下降与反向传播算法的原理与推导

    梯度下降算法是机器学习中最常用的优化算法之一,它可以求得目标函数的最小值,即算法的最优解.而对于复杂的多层神经网络来说,运用梯度下降算法十分复杂,因为其包含求导过程,为此学者将多层神经网络的优化问题简 ...

  5. 独家 | 数据科学家指南:梯度下降与反向传播算法

    作者:Richmond Alake 翻译:陈之炎 校对:zrx本文约3300字,建议阅读5分钟 本文旨在为数据科学家提供一些基础知识,以理解在训练神经网络时所需调用的底层函数和方法. 标签:神经网络, ...

  6. 使用反向传播算法计算参数的梯度并用python实现加法和乘法节点的反向传播

    使用反向传播算法计算参数的梯度并用python实现加法和乘法节点的反向传播 一.what is 反向传播 二.乘法节点的反向传播 三.加法节点的反向传播 四.加法层和乘法层混合应用 一.what is ...

  7. 梯度的直观理解_BP反向传播算法的思考和直观理解 -卷积小白的随机世界

    本篇文章,本来计划再进一步完善对CNN卷积神经网络的理解,但在对卷积层反向传播算法的理解中,越发觉得之前对于BP反向传播算法的理解是不到位的.小白近日觉得,对于深度神经网络,"反向传播&qu ...

  8. 机器学习(深度学习)中的反向传播算法与梯度下降

    这是自己在CSDN的第一篇博客,目的是为了给自己学习过的知识做一个总结,方便后续温习,避免每次都重复搜索相关文章. 一.反向传播算法 定义:反向传播(Backpropagation,缩写为BP)是&q ...

  9. 使用随机梯度下降SGD的BP反向传播算法的PyTorch代码实现

    Index 目录索引 写在前面 PyTorch的 .data() PyTorch的 .item() BP with SGD的PyTorch代码实现 参考文章 写在前面 本文将用一个完整的例子,借助Py ...

最新文章

  1. 总帐科目字段选择存储在表 T004F 中;科目分配存储在表 T162K 中。
  2. Canal Adapter二次开发,实现MySQL实时同步到Redis
  3. linux apache添加多站点配置(Ubuntn和Centos)
  4. 演练 青鸟游戏迷你平台
  5. 额度降为0剩下欠款怎么办?
  6. 7-14 到底是不是太胖了 (10 分)
  7. 曹国伟:新浪将推新社交产品与微博互补
  8. 3.1 测试能否对标准输入设置偏移量
  9. java工程师的关键绩效指标_绩效考核表(JAVA高级工程师)
  10. php怎么设置div圆角,不用图片的DIV圆角(兼容各浏览器)
  11. WOMic 使用wifi 将手机作为电脑麦克风音频输入
  12. Excel如何批量查询手机号码归属地?
  13. html5分辨率异常自动检测
  14. ERROR in ./src/vue/login.vue?vuetype=templateid=28b333d2 2:0 Module parse failed: Unexpected toke
  15. SUSE12系统安装
  16. 语音识别软件_语音识别 软件_日语语音识别软件 - 云+社区 - 腾讯云
  17. 超级详细的CentOS8.2搭建个人网站(WordPress)
  18. 99%的人投资区块链项目亏钱, 都犯了这6大致命错误!
  19. 排中律与实无穷问题的性质分析
  20. php 前端超出字数,标题字数超出范围截断处理方案比较

热门文章

  1. 前端学习(3195):虚拟dom的创建方式1的js
  2. 前端学习(3146):react-hello-react之getBeforeUpdate
  3. 前端学习(3110):react-hello-函数式组件
  4. [css] 写出固定子容器在固定的父容器下水平垂直居中的布局
  5. 前端学习(2432):上午总结
  6. “约见”面试官系列之常见面试题之第七十六篇之vue-router中的路由钩子函数基本用法 (建议收藏)
  7. oracle之基本的过滤和排序数据
  8. java面试题2 牛客:定义类中成员变量时不可能用到的修饰是
  9. Python isnumeric()方法
  10. linux gst-launch 播放视频旋转,【视频开发】Gstreamer中一些gst-launch常用命令