反向传播
1 反向传播的直观理解
反向传播是利用链式法则递归计算表达式的梯度的方法。
理解反向传播过程及其精妙之处,对于理解、实现、设计和调试神经网络非常关键。
反向传播是一个优美的局部过程。在整个计算线路图中,每个门单元都会得到一些输入并立即计算两个东西:
- 这个门的输出值
- 其输出值关于输入值的局部梯度
门单元完成这两件事是完全独立的,它不需要知道计算线路中的其他细节。
然而,一旦前向传播完毕,在反向传播的过程中,门单元门将最终获得整个网络的最终输出值在自己的输出值上的梯度。
链式法则指出,门单元应该将回传的梯度乘以它对其的输入的局部梯度,从而得到整个网络的输出对该门单元的每个输入值的梯度。
下面通过例子来对这一过程进行理解。
加法门收到了输入[-2, 5],计算输出是3。既然这个门是加法操作,那么对于两个输入的局部梯度都是+1。网络的其余部分计算出最终值为-12。
在反向传播时将递归地使用链式法则,算到加法门(是乘法门的输入)的时候,知道加法门的输出的梯度是-4。
如果网络想要输出值更高,那么可以认为它会想要加法门的输出更小一点(因为负号),而且还有一个4的倍数。
继续递归并对梯度使用链式法则,加法门拿到梯度,然后把这个梯度分别乘到每个输入值的局部梯度(就是让-4乘以x和y的局部梯度,x和y的局部梯度都是1,所以最终都是-4)。
可以看到得到了想要的效果:如果x,y减小(它们的梯度为负),那么加法门的输出值减小,这会让乘法门的输出值增大。
因此,反向传播可以看做是门单元之间在通过梯度信号相互通信,只要让它们的输入沿着梯度方向变化,无论它们自己的输出值在何种程度上升或降低,都是为了让整个网络的输出值更高。
2 Sigmoid例子
上面介绍的门是相对随意的。任何可微分的函数都可以看做门。可以将多个门组合成一个门,也可以根据需要将一个函数分拆成多个门。
现在看看一个表达式:
这个表达式描述了一个含输入\(x\)和权重\(w\)的2维的神经元,该神经元使用了sigmoid激活函数。
但是现在只是看做是一个简单的输入为\(x\)和\(w\),输出为一个数字的函数。
这个函数是由多个门组成的。除了上文介绍的加法门,乘法门,取最大值门,还有下面这4种:
其中,函数\(f_{c}\)使用对输入值进行了常量\(c\)的平移,\(f_{a}\)将输入值扩大了常量\(a\)倍。它们是加法和乘法的特例,但是这里将其看做一元门单元,因为确实需要计算常量\(c, a\)的梯度。整个计算线路如下:
上图使用sigmoid激活函数的2维神经元的例子。输入是\([\mathrm{x} 0, \mathrm{x} 1]\),可学习的权重是\([\mathrm{w} 0, \mathrm{w} 1, \mathrm{w} 2]\)。
一会儿可以看见,这个神经元对输入数据做点积运算,然后其激活数据被sigmoid函数挤压到0到1之间。
在上面的例子中可以看见一个函数操作的长链条,链条上的门都对\(\mathbf{W}\)和\(x\)的点积结果进行操作。
sigmoid函数关于其输入的求导是可以简化的:
\(\sigma(x)=\frac{1}{1+e^{-x}}\)
可以看到梯度计算简单了很多。
举个例子,sigmoid表达式输入为1.0,则在前向传播中计算出输出为0.73。根据上面的公式,局部梯度为(1-0.73)*0.73~=0.2。
该sigmoid神经元反向传播的代码实现如下:
w = [2,-3,-3] # 假设一些随机数据和权重
x = [-1, -2]
# 前向传播
dot = w[0]*x[0] + w[1]*x[1] + w[2]
f = 1.0 / (1 + math.exp(-dot)) # sigmoid函数
# 对神经元反向传播
ddot = (1 - f) * f # 点积变量的梯度, 使用sigmoid函数求导
dx = [w[0] * ddot, w[1] * ddot] # 回传到x
dw = [x[0] * ddot, x[1] * ddot, 1.0 * ddot] # 回传到w
# 完成!得到输入的梯度
上面的代码展示了在实际操作中,为了使反向传播过程更加简洁,把向前传播分成不同的阶段将是很有帮助的。
比如我们创建了一个中间变量dot,它装着w和x的点乘结果。在反向传播的时,就可以(反向地)计算出装着w和x等的梯度的对应的变量(比如ddot,dx和dw)。
3 向量化操作计算梯度
上述内容考虑的都是单个变量情况,但是所有概念都适用于矩阵和向量操作。然而,在操作的时候要注意关注维度和转置操作。
可能最有技巧的操作是矩阵相乘(也适用于矩阵和向量,向量和向量相乘)的乘法操作,举例体会如下:
# 前向传播
W = np.random.randn(5, 10)
X = np.random.randn(10, 3)
D = W.dot(X)
# 假设我们得到了D的梯度
dD = np.random.randn(*D.shape) # 和D一样的尺寸
dW = dD.dot(X.T) #.T就是对矩阵进行转置
dX = W.T.dot(dD)
注意:要分析维度!!!
4 补充
加法门单元把输出的梯度相等地分发给它所有的输入,这一行为与输入值在前向传播时的值无关。这是因为加法操作的局部梯度都是简单的+1,所以所有输入的梯度实际上就等于输出的梯度,因为乘以1.0保持不变。上例中,加法门把梯度2.00不变且相等地路由给了两个输入。
取最大值门单元对梯度做路由。和加法门不同,取最大值门将梯度转给其中一个输入,这个输入是在前向传播中值最大的那个输入。这是因为在取最大值门中,最高值的局部梯度是1.0,其余的是0。
乘法门单元相对不容易解释。它的局部梯度就是输入值,但是是相互交换之后的,然后根据链式法则乘以输出值的梯度。
注意一种比较特殊的情况,如果乘法门单元的其中一个输入非常小,而另一个输入非常大,那么乘法门的操作将会不是那么直观:它将会把大的梯度分配给小的输入,把小的梯度分配给大的输入。
在线性分类器中,权重和输入是进行点积\(w^{T} x_{i}\),这说明输入数据的大小对于权重梯度的大小有影响。
这就是为什么数据预处理关系重大,它即使只是有微小变化,也会产生巨大影响。
原文地址:https://www.cnblogs.com/Terrypython/p/11009613.html
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Java的访问控制符详解(结合代码演示)
- Python贪吃蛇小游戏_完整源码免费分享
- GitHub修改昵称和用户名(图解详细教程)
- Python飞机大战小游戏_完整源码免费分享
- Linux求助命令
- Linux关机命令及步骤
- Java实现二叉树层次遍历:从上往下打印出二叉树的每个节点,同层节点从左至右打印。
- Django 用户认证系统使用总结
- 前端综合面试题(9道)
- 用SQL查询Oracle数据库名和实例名
- Hadoop历史服务器配置详细步骤
- MySQL常见关键字优先级
- Linux进程管理命令及状态详解
- sqoop把hive数据导入mysql出现中文乱码
- Flink实现WordCount(实操详细步骤)