GoogLeNet的心路历程(二)
本文介绍关于GoogLeNet的续作,习惯称为inception v2,如下:
[v2] Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift,top5 error 4.8%
这篇文章做出的贡献不是一般的大,它提出了Batch Normalization(BN),以至于网上关于它的介绍铺天盖地,但中文优秀原创没几个,都是转载来转载去,挑几个好的比如:这个(http://blog.csdn.net/hjimce/article/details/50866313)、这个(http://blog.csdn.net/u012816943/article/details/51691868)、这个(http://blog.csdn.net/happynear/article/details/44238541)。我之前也写过一个谈谈Tensorflow的Batch Normalization(https://www.jianshu.com/p/0312e04e4e83),讲了讲BN在Tensorflow中的实现。
前人关于BN介绍的已经太详细了,我就不再重复的了。本文就是想讲一讲BN的反向传播,BN需要调节的参数有两个,γ 和 β,反向传播的计算方式就是下面这张图:
又是令人作呕的公式。
几乎所有介绍BN的文章都把这部分略过了,估计是怕讲不清楚,或者作者根本就不明白也不想深究。BN的理念很好理解,它的优良效果也很好理解,可BN的训练到底是怎么回事?怎么反向传播?Szegedy在论文原文里也只是一句话带过了:
During training we need to backpropagate the gradient of loss ℓ through this transformation, as well as compute the gradients with respect to the parameters of the BN transform. We use chain rule...
上面那一坨公式对于深度学习的老鸟们应该不会构成理解障碍,但对于接触不久的人群,简直就是天书!鉴于此,参考xiaia的cs231n_2016_winter(https://github.com/xiaia/cs231n_2016_winter)作业,捋一捋BN的反向传播到底是怎么实现的,好有个直观理解。
下面的介绍基于cs231n_2016_winter/assignment2的全连接网络,隐藏层5个,每个100个神经元(hidden_dims = [100, 100, 100, 100, 100]),激活函数ReLU,每个隐藏层激活函数前都加了BN层,输出层是softmax-10,optimizer是adam。
Batch Normalization反向传播实现
根据上面那一坨公式,写出来的代码是这样子的:
def batchnorm_backward(dout, cache):
"""
Backward pass for batch normalization.
For this implementation, you should write out a computation graph for
batch normalization on paper and propagate gradients backward through intermediate nodes.
Inputs:
- dout: Upstream derivatives, of shape (N, D)
- cache: Variable of intermediates from batchnorm_forward.
Returns a tuple of:
- dx: Gradient with respect to inputs x, of shape (N, D)
- dgamma: Gradient with respect to scale parameter gamma, of shape (D,)
- dbeta: Gradient with respect to shift parameter beta, of shape (D,)
"""
dx, dgamma, dbeta = None, None, None
x, gamma, beta, var, miu, x_hat, eps = cache
m = len(x)
dx_hat = dout * gamma
dvar = np.sum(dx_hat * (x-miu), axis=0) * -0.5 * (var + eps) ** (-1.5)
dmiu = np.sum(dx_hat * (-1) / np.sqrt(var+eps), axis=0) + dvar * np.mean(-2 * (x - miu), axis=0)
dx = dx_hat / np.sqrt(var + eps) + dvar * 2 * (x - miu) / m + dmiu / m
dgamma = np.sum(dout * x_hat, axis=0)
dbeta = np.sum(dout, axis=0)
return dx, dgamma, dbeta
Tensorflow的源码里应该也会有相应的实现,以后我再找找看。
上面的batchnorm_backward函数就是BN反向传播的python实现版本,仅仅是把公式改写成了python语言而已,这篇博文对代码做了一些解释,可以参考,这里不再赘述。
问题就来了,dout是个什么东西?作为函数的输入,它怎么来的?我再翻一翻源码,找到了这个函数:
def softmax_loss(x, y): """ Computes the loss and gradient for softmax classification. Inputs: - x: Input data, of shape (N, C) where x[i, j] is the score for the jth class for the ith input. - y: Vector of labels, of shape (N,) where y[i] is the label for x[i] and 0 <= y[i] < C Returns a tuple of: - loss: Scalar giving the loss - dx: Gradient of the loss with respect to x """ probs = np.exp(x - np.max(x, axis=1, keepdims=True)) probs /= np.sum(probs, axis=1, keepdims=True) N = x.shape[0] loss = -np.sum(np.log(probs[np.arange(N), y])) / N dx = probs.copy() dx[np.arange(N), y] -= 1 dx /= N return loss, dx
softmax_loss用来计算最后softmax层的loss和gradient,函数返回两个值,一个是loss,一个是dx(gradient),这个dx就是dout的源头!也是反向传播的最最最开始的地方!它是这么得来的:
dx = probs.copy() dx[np.arange(N), y] -= 1
注:其中probs是softmax的输出结果。
上面的程序代码是如此的简洁!让人完全蒙圈!逼得我重温了一下反向传播算法,输出层的残差是这么算的:
代码里的f'(z)去哪儿了???或者这种计算方式是softmax独有?深深的感觉到了自己基础知识的薄弱。我又查阅了Neural Networks and Deep Learning(http://neuralnetworksanddeeplearning.com/chap3.html#problems_68177),终于找到了,其中的公式 (84) 是 softmax 层的残差计算方法,如下:
可是作者让读者自己推倒公式!又蒙圈了,有兴趣的可以自己推倒试一试。
简而言之,dx就是最后一层的gradient,这个dx要一层一层的反向传播回去,不同层的反向传播计算方式也不同,比如ReLU的反向传播计算是这样的:
def relu_backward(dout, cache):
"""
Computes the backward pass for a layer of rectified linear units (ReLUs).
Input:
- dout: Upstream derivatives, of any shape
- cache: Input x, of same shape as dout Returns:
- dx: Gradient with respect to x
"""
dx, x = None, cache dx = dout dx[x <= 0] = 0
return dx
当然还有 dropout_backward、affine_backward(全连层) 还有上面的 batchnorm_backward 计算函数,不再一一列举。反向传播其实就是把gradient作为输入,按照前向传播相反的方向再计算一遍而已。
总的来讲,加入BN层的反向传播没有发生根本的改变,只是多了一个反向计算过程(batchnorm_backward函数)而已,上述网络的最后几层的前向和反向传播示意图如下:
图也画了,代码也给了,公式还是没明白,不深究了。
总之,加入BN层的网络,反向传播的时候也相应的多了BN-back,其中的dgamma、dbeta会根据反向传播的gradient(或者叫残差)计算出来,再利用 optimizer 更新 γ 和 β。
- 单分子数据储存取得一大突破,一枚“硬币”存量相当于100部iPhone 7
- Windows 7 旗舰版 VHD安装体验
- Nodejs学习笔记(二)——Eclipse中运行调试Nodejs
- Nodejs学习笔记(三)——一张图看懂Nodejs建站
- 不规则图形的碰撞检测
- 自学WP7第一个例子:时钟
- 教您最简单粗暴的MATLAB入门级爬虫2
- 前台JS(Jquery)调用后台方法 无刷新级联菜单示例
- 项目中对图片的缩放和水印效果
- 照虎画猫写自己的Spring——自定义注解
- 数据分析进阶课程笔记(六)
- 微信发布重磅更新!上线小游戏,小程序间可快速切换
- 鼠标点击层以外的地方层隐藏
- WCF后续之旅(11): 关于并发、回调的线程关联性(Thread Affinity)
- 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 数组属性和方法
- LeetCode125|二叉树的前序遍历
- LeetCode124|二叉树的中序遍历
- LeetCode123|二叉搜索树中第K小的元素
- LeetCode122|删除排序链表中的重复元素II
- LeetCode121|单值二叉树
- LeetCode120|二维数组中的查找
- LeetCode129|不用加减乘除做加法
- LeetCode128|二叉树的最大深度
- LeetCode127|检查平衡性
- 详解Android端与JavaWeb传输加密(DES+RSA)
- Android编程实现下载时主界面与详细界面一致更新的方法
- 详解Android Studio 3.0的新特性与适配
- Android开发实现Launcher3应用列表修改透明背景的方法
- Android开发中方向传感器定义与用法详解【附指南针实现方法】
- Android利用ViewDragHelper轻松实现拼图游戏的示例