Batch normalization理解

       在学习源码的过程中,发现在搭建网络架构的时候,经常会用到bn算法(即batch_normalization,批标准化),所以有必要深入去研究其中的奥妙。bn算法的提出在2015年的论文《Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift》

       正如论文开始所说:由于训练过程中各层输入的分布随着前几层参数的变化而变化,使得训练深度神经网络变得复杂。这通过要求较低的学习速率仔细的参数初始化来减慢训练,并且使得训练具有饱和非线性的模型变得非常困难。我们将这种现象称为 internal covariate shift。显而易见,为了解决这个问题,作者提出了bn,所以前提,我们得先理解何为internal covariate shift。

将在训练过程中深度网络内部节点分布的变化称为internal covariate shift。这个internal可以看作隐层
作者以sigmoid为例,z = g(wu+b),其中u为输入层,w和b为权重矩阵和偏移向量(即网络层里需要学习的参数)


随着|x|的增加,g'(x)会趋向于0,这意味着,在所有维数上,除了那些绝对值较小的 x=wu+b ,下降到 u 的梯度将消失,模型将缓慢训练。

x 受 W,b 和前面所有层的参数的影响,在训练过程中这些参数的变化可能会将 x 的许多维度移入非线性的饱和状态并减慢收敛速度。这种影响随着网络深度的增加而放大。

我们都知道在train网络之前,会对数据进行归一化处理,为的是保持训练和测试数据的分布相同,而在神经网络内部,每一层我们都需要有输出和输出,除了对原始数据的标准化处理,在经过网络每一层计算后的数据,它们的分布是不同的。网络的训练,需要去学习适应不同的数据分布,明显造成的后果就是收敛慢,效果不佳。
另一方面,网络前面的参数变更,会随着网络的深度,其影响不断累积增大,所以说只要有某一层的数据分布发生变化,后面层的数据输入分布也会不同,结合前面说的,为了解决中间层数据分布改变的情况。

总的来说,bn的操作很简单,也很容易理解。就是在网络的每一层输入之前,做了一个归一化处理,就是作用于(wu+b),即bn(wu+b),然后再接激活函数(非线性映射)。而且,很多论文的代码里,bn算作了独立的一层。

公式如下:


E[x]为均值,sqrt(var)为标准差,然后加上scale和shift两个可训练的变量
而Batch Normalization可使各隐藏层输入的均值和方差为任意值。实际上,从激活函数的角度来说,如果各隐藏层的输入均值在靠近0的区域即处于激活函数的线性区域,这样不利于训练好的非线性神经网络,得到的模型效果也不会太好。这也解释了为什么需要用 γ 和 β 来做进一步处理



y(k)就是经过bn处理后的输出了,论文里提到,当

可以恢复原始的激活,也就是这一层所学到的原始特征
对于批处理的推理如下,整个思路也很清晰:

到了这里,其实脑子还是很懵,一知半解,还是属于抽象的理解,对于其中还是很多疑惑
想要更好的理解,还是得回到数学的层面去看

一般来说,如果模型的输入特征不相关且满足标准正态分布时,模型的表现一般较好。
在训练神经网络模型时,我们可以事先将特征去相关并使得它们满足一个比较好的分布,
这样,模型的第一层网络一般都会有一个比较好的输入特征,
但是随着模型的层数加深,网络的非线性变换使得每一层的结果变得相关了,且不再满足分布。
甚至,这些隐藏层的特征分布或许已经发生了偏移。

在经过激活层之前,就是x = wu+b,也就是激活函数的输入,随着网络加深,x的会逐渐向两端靠拢(红色箭头的地方),那么会造成什么后果,我们可以看下这两个函数的导数:
Sigmoid' = sigmoid*(1-sigmoid)
Tanh' = 1-tanh^2


不难看出,在函数的两侧梯度变化趋向于0,且变化很慢,这会导致在Back propagation的时候梯度消失,也就是说收敛越来越慢。 在这里感觉也可以理解为:



至于为什么要这么做最后的公式(scale,offset),论文里有提到


简单地对图层的每个输入进行规范化可能会更改图层可以表示的内容。
例如,正则化一个 sigmoid 的输入将限制他们非线性的线性状态
作者也没具体说明,在我也是不太明白其原理,只能理解为上面的变换会改变原来学习到的特征分布,因此加入了可学习的γ和β,为什么是可学习的,感觉应该是让网络自己找到一个在正态变换后不破坏原特征分布的平衡状态。

好吧,很玄,希望有小伙伴能给我解答一下。

测试

       到这里,大致的原理就这样了,至于后面如何反向传播,以及推理过程中 均值mean 和 方差var 的设置,就不再写下去,网上也很多解读的资源。
理解了理论之后,结合实战操作才有意思,先附一张图.


在cnn中,batch_normalization就是取同一个channel上所有批次做处理,粗略画了这个示意图
代表batch = 3,channel = 2 , W和H = 2

下面用了numpy,pytorch以及tensorflow的函数计算batch_normalization
先看一下pytorch的函数以及描述

nn.batchnorm2d / tf.layers.batch_normalization


最需要注意的是pytorch和tensorflow 的公式不太一样,所以结果会有稍微差异,在测试的时候,我们只需要把其他因素统一就好:


# 导库
import numpy as np
import tensorflow as tf
import torch.nn as nn
# 创建一个随机矩阵
test1 = np.random.rand(4,3,2,2)
test1 = test1.astype(np.float32)
test =test1

先算numpy均值,方差与pytorch(注意设置momentum = 1,affine=False)

a1 = 0
v = 0
d = []
std = []
for i in range(3):
    a1 = test1[:,i,:,:]
    d.append(a1.sum())
d = np.array(d)
mean = d/16
m = nn.BatchNorm2d(3,affine=False,momentum=1)
input = torch.from_numpy(test1)
output = m(input)
 
# 均值mean
print('torch 尺寸:',input.shape)
print('pytorch 均值:',m.running_mean.data[0],m.running_mean.data[1],m.running_mean.data[2])
print('手算 均值:',mean)
#numpy 计算: np.mean(np.mean(np.mean(test1,axis=0),axis=1),axis=1)
 
# 方差var = (x-mean) / n
for i in range(3):
    v = test1[:,i,:,:]-mean[i]
    v = ((v**2).sum()/16)**0.5
    std.append(v)
std = np.array(std)
# numpy计算 :np.std(test1[1,2,3])
print('标准差:',std)
 
#bathnorm
for i in range(3):
    test[:,i,:,:] = (test1[:,i,:,:]-mean[i])/(std[i]+1e-5)
 
print(test[1])
print(output[1])

输出:

torch 尺寸: torch.Size([4, 3, 2, 2])
pytorch 均值: tensor(0.4218) tensor(0.5399) tensor(0.3418)
手算 均值: [0.42182693 0.5398935  0.34179664]
标准差: [0.26444062 0.2462885  0.22490181]
numpy结果: 
 [[[-1.0535254  -0.4905532 ]
  [-1.3194345  -0.22114275]]

 [[ 0.5717635   1.1570975 ]
  [-1.1665905  -1.5158345 ]]

 [[ 1.6828372   0.8369611 ]
  [ 0.32095668 -0.89949685]]]
pytorch结果:
 tensor([[[-1.0535, -0.4905],
         [-1.3194, -0.2211]],

        [[ 0.5717,  1.1570],
         [-1.1665, -1.5158]],

        [[ 1.6827,  0.8369],
         [ 0.3209, -0.8994]]])

可以看出bn的计算是一样的,接下来看一下tf版本的,在这里我加上了tf.nn.batch_normalitization
需要将test维度转成(N, H, W, C)

x = test1
b = test
x = np.transpose(x,(0,2,3,1))
b = np.transpose(b,(0,2,3,1))
axis = list(range(len(x)-1))
x = tf.convert_to_tensor(x)
wb_mean, wb_var = tf.nn.moments(x,axis)
scale = tf.Variable(tf.ones([3]))
offset = tf.Variable(tf.zeros([3]))
variance_epsilon = 1e-5
Wx_plus_b = tf.nn.batch_normalization(x, wb_mean, wb_var, offset, scale, variance_epsilon)

Wx_plus_b1 = (x - wb_mean) / tf.sqrt(wb_var + variance_epsilon)
Wx_plus_b1 = Wx_plus_b1 * scale + offset

Wx_plus_b2 = tf.layers.batch_normalization(x,momentum=1,scale=False,epsilon= 1e-5)

• 最后对比各种计算结果

with tf.Session() as sess:
    tf.global_variables_initializer().run()
    print('nn.bn: \n',sess.run(Wx_plus_b[1]))
    print('手算bn:\n',sess.run(Wx_plus_b1[1]))
    print('layers.bn: \n',sess.run(Wx_plus_b2[1]))
    print('numpy bn: \n',b[1])
    print('output: \n',output[1])

输出:

nn.bn: 
 [[[-1.0535603   0.57178396  1.6829035 ]
  [-0.49056947  1.1571388   0.83699405]]

 [[-1.319478   -1.1666319   0.32096925]
  [-0.22115016 -1.5158883  -0.8995324 ]]]
手算bn:
 [[[-1.0535601   0.57178396  1.6829034 ]
  [-0.49056944  1.1571388   0.836994  ]]

 [[-1.319478   -1.1666319   0.32096922]
  [-0.22115014 -1.5158883  -0.8995324 ]]]
layers.bn: 
 [[[-1.0535202   0.57176065  1.6828288 ]
  [-0.49055076  1.1570916   0.8369569 ]]

 [[-1.319428   -1.1665846   0.32095507]
  [-0.22114165 -1.5158268  -0.8994923 ]]]
numpy bn: 
 [[[-1.0535254   0.5717635   1.6828372 ]
  [-0.4905532   1.1570975   0.8369611 ]]

 [[-1.3194345  -1.1665905   0.32095668]
  [-0.22114275 -1.5158345  -0.89949685]]]
output: 
 tensor([[[-1.0535, -0.4905],
         [-1.3194, -0.2211]],

        [[ 0.5717,  1.1570],
         [-1.1665, -1.5158]],

        [[ 1.6827,  0.8369],
         [ 0.3209, -0.8994]]])

结语

对于batchnorm,还是有很多地方不懂或者不理解的。
文章写得比较乱,也并不严谨,有需要改正的也请指出

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容

  • 所用工具 笔:hb铅笔,樱花针管笔 pn头,温莎水彩笔2.3号,高光笔 颜料:歌文12色 纸:获多福32K
    马小七0813阅读 1,057评论 12 38
  • 前段时间在看这本书, 看了一大半了, 很多东西讲的还是很有道理的, 这一系列文章作为笔记, 供自己今后翻阅. 1....
    Dev_hell03W阅读 395评论 0 0
  • (一)通过URL传递函数名称和参数 这个方案是历史最悠久,至少目前也是使用最普遍的方案。这个方案的优点是技术最简单...
    勇往直前888阅读 1,597评论 0 2
  • 《八月情思》 流年此月桂花开,叶绿花黄百里香。 楚楚娇娥情切切,嫣语若兰醉牛郎。 今朝月桂花复至,...
    潜龙随笔阅读 174评论 0 0