大家好,又见面了,我是你们的朋友全栈君。
Batchnorm原理详解
前言:Batchnorm是深度网络中经常用到的加速神经网络训练,加速收敛速度及稳定性的算法,可以说是目前深度网络必不可少的一部分。
本文旨在用通俗易懂的语言,对深度学习的常用算法–batchnorm的原理及其代码实现做一个详细的解读。本文主要包括以下几个部分。
- Batchnorm主要解决的问题
- Batchnorm原理解读
- Batchnorm的优点
- Batchnorm的源码解读
第一节:Batchnorm主要解决的问题
首先,此部分也即是讲为什么深度网络会需要
,我们都知道,深度学习的话尤其是在CV上都需要对数据做归一化,因为深度神经网络主要就是为了学习训练数据的分布,并在测试集上达到很好的泛化效果,但是,如果我们每一个batch输入的数据都具有不同的分布,显然会给网络的训练带来困难。另一方面,数据经过一层层网络计算后,其数据分布也在发生着变化,此现象称为 ,接下来会详细解释,会给下一层的网络学习带来困难。
直译过来就是批规范化,就是为了解决这个分布变化问题。
1.1 Internal Covariate Shift
:此术语是google小组在论文
中提出来的,其主要描述的是:训练深度网络的时候经常发生训练困难的问题,因为,每一次参数迭代更新后,上一层网络的输出数据经过这一层网络计算后,数据的分布会发生变化,为下一层网络的学习带来困难(神经网络本来就是要学习数据的分布,要是分布一直在变,学习就很难了),此现象称之为
。
之前的解决方案就是使用较小的学习率,和小心的初始化参数,对数据做白化处理,但是显然治标不治本。
1.2 covariate shift
和 具有相似性,但并不是一个东西,前者发生在神经网络的内部,所以是,后者发生在输入数据上。
主要描述的是由于训练数据和测试数据存在分布的差异性,给网络的泛化性和训练速度带来了影响,我们经常使用的方法是做归一化或者白化。想要直观感受的话,看下图:
举个简单线性分类栗子,假设我们的数据分布如a所示,参数初始化一般是0均值,和较小的方差,此时拟合的
如b图中的橘色线,经过多次迭代后,达到紫色线,此时具有很好的分类效果,但是如果我们将其归一化到0点附近,显然会加快训练速度,如此我们更进一步的通过变换拉大数据之间的相对差异性,那么就更容易区分了。
就是描述的输入数据分布不一致的现象,对数据做归一化当然可以加快训练速度,能对数据做去相关性,突出它们之间的分布相对差异就更好了。做到了,前文已说过,是归一化的一种手段,极限来说,这种方式会减小图像之间的绝对差异,突出相对差异,加快训练速度。所以说,并不是在深度学习的所有领域都可以使用
,下文会写到其不适用的情况。
第二节:Batchnorm 原理解读
本部分主要结合原论文部分,排除一些复杂的数学公式,对
的原理做尽可能详细的解释。
之前就说过,为了减小
,对神经网络的每一层做归一化不就可以了,假设将每一层输出后的数据都归一化到0均值,1方差,满足正太分布,但是,此时有一个问题,每一层的数据分布都是标准正太分布,导致其完全学习不到输入数据的特征,因为,费劲心思学习到的特征分布被归一化了,因此,直接对每一层做归一化显然是不合理的。
但是如果稍作修改,加入可训练的参数做归一化,那就是
实现的了,接下来结合下图的伪代码做详细的分析:
之所以称之为batchnorm是因为所norm的数据是一个batch的,假设输入数据是
共m个数据,输出是,
的步骤如下:
1.先求出此次批量数据
的均值,
2.求出此次batch的方差,
3.接下来就是对做归一化,得到
4.最重要的一步,引入缩放和平移变量和 ,计算归一化后的值,
接下来详细介绍一下这额外的两个参数,之前也说过如果直接做归一化不做其他处理,神经网络是学不到任何东西的,但是加入这两个参数后,事情就不一样了,先考虑特殊情况下,如果
和分别等于此batch的方差和均值,那么不就还原到归一化前的了吗,也即是缩放平移到了归一化前的分布,相当于没有起作用, 和
分别称之为 平移参数和缩放参数 。这样就保证了每一次数据经过归一化后还保留的有学习来的特征,同时又能完成归一化这个操作,加速训练。
先用一个简单的代码举个小栗子:
def Batchnorm_simple_for_train(x, gamma, beta, bn_param):
""" param:x : 输入数据,设shape(B,L) param:gama : 缩放因子 γ param:beta : 平移因子 β param:bn_param : batchnorm所需要的一些参数 eps : 接近0的数,防止分母出现0 momentum : 动量参数,一般为0.9, 0.99, 0.999 running_mean :滑动平均的方式计算新的均值,训练时计算,为测试数据做准备 running_var : 滑动平均的方式计算新的方差,训练时计算,为测试数据做准备 """
running_mean = bn_param['running_mean'] #shape = [B]
running_var = bn_param['running_var'] #shape = [B]
results = 0. # 建立一个新的变量
x_mean=x.mean(axis=<span class="hljs-number">0</span>) <span class="hljs-comment"># 计算x的均值</span>
x_var=x.var(axis=<span class="hljs-number">0</span>) <span class="hljs-comment"># 计算方差</span>
x_normalized=(x-x_mean)/np.sqrt(x_var+eps) <span class="hljs-comment"># 归一化</span>
results = gamma * x_normalized + beta <span class="hljs-comment"># 缩放平移</span>
running_mean = momentum * running_mean + (<span class="hljs-number">1</span> - momentum) * x_mean
running_var = momentum * running_var + (<span class="hljs-number">1</span> - momentum) * x_var
<span class="hljs-comment">#记录新的值</span>
bn_param[<span class="hljs-string">'running_mean'</span>] = running_mean
bn_param[<span class="hljs-string">'running_var'</span>] = running_var
<span class="hljs-keyword">return</span> results , bn_param</code></pre><p>看完这个代码是不是对batchnorm有了一个清晰的理解,首先计算均值和方差,然后归一化,然后缩放和平移,完事!但是这是在训练中完成的任务,每次训练给一个批量,然后计算批量的均值方差,但是在测试的时候可不是这样,测试的时候每次只输入一张图片,这怎么计算批量的均值和方差,于是,就有了代码中下面两行,在训练的时候实现计算好</p> <p>测试的时候直接拿来用就可以了,不用计算均值和方差。</p><pre class="prettyprint" name="code"><code class="hljs ini has-numbering"><span class="hljs-setting">running_mean = <span class="hljs-value">momentum * running_mean + (<span class="hljs-number">1</span> - momentum) * x_mean</span></span>
running_var = momentum * running_var + (1 - momentum) * x_var
所以,测试的时候是这样的:
def Batchnorm_simple_for_test(x, gamma, beta, bn_param):
""" param:x : 输入数据,设shape(B,L) param:gama : 缩放因子 γ param:beta : 平移因子 β param:bn_param : batchnorm所需要的一些参数 eps : 接近0的数,防止分母出现0 momentum : 动量参数,一般为0.9, 0.99, 0.999 running_mean :滑动平均的方式计算新的均值,训练时计算,为测试数据做准备 running_var : 滑动平均的方式计算新的方差,训练时计算,为测试数据做准备 """
running_mean = bn_param['running_mean'] #shape = [B]
running_var = bn_param['running_var'] #shape = [B]
results = 0. # 建立一个新的变量
x_normalized=(x-running_mean )/np.sqrt(running_var +eps) <span class="hljs-comment"># 归一化</span>
results = gamma * x_normalized + beta <span class="hljs-comment"># 缩放平移</span>
<span class="hljs-keyword">return</span> results , bn_param</code></pre><p>你是否理解了呢?如果还没有理解的话,欢迎再多看几遍。</p><h3><a name="t5"></a><a target="_blank"></a>第三节:Batchnorm源码解读</h3><hr><p>本节主要讲解一段tensorflow中</p>的可以使用的代码<p>,如下: <br>代码来自知乎,这里加入注释帮助阅读。</p><pre class="prettyprint" name="code"><code class="hljs python has-numbering"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">batch_norm_layer</span><span class="hljs-params">(x, train_phase, scope_bn)</span>:</span>
<span class="hljs-keyword">with</span> tf.variable_scope(scope_bn):
<span class="hljs-comment"># 新建两个变量,平移、缩放因子</span>
beta = tf.Variable(tf.constant(<span class="hljs-number">0.0</span>, shape=[x.shape[-<span class="hljs-number">1</span>]]), name=<span class="hljs-string">'beta'</span>, trainable=<span class="hljs-keyword">True</span>)
gamma = tf.Variable(tf.constant(<span class="hljs-number">1.0</span>, shape=[x.shape[-<span class="hljs-number">1</span>]]), name=<span class="hljs-string">'gamma'</span>, trainable=<span class="hljs-keyword">True</span>)
<span class="hljs-comment"># 计算此次批量的均值和方差</span>
axises = np.arange(len(x.shape) - <span class="hljs-number">1</span>)
batch_mean, batch_var = tf.nn.moments(x, axises, name=<span class="hljs-string">'moments'</span>)
<span class="hljs-comment"># 滑动平均做衰减</span>
ema = tf.train.ExponentialMovingAverage(decay=<span class="hljs-number">0.5</span>)
<span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">mean_var_with_update</span><span class="hljs-params">()</span>:</span>
ema_apply_op = ema.apply([batch_mean, batch_var])
<span class="hljs-keyword">with</span> tf.control_dependencies([ema_apply_op]):
<span class="hljs-keyword">return</span> tf.identity(batch_mean), tf.identity(batch_var)
<span class="hljs-comment"># train_phase 训练还是测试的flag</span>
<span class="hljs-comment"># 训练阶段计算runing_mean和runing_var,使用mean_var_with_update()函数</span>
<span class="hljs-comment"># 测试的时候直接把之前计算的拿去用 ema.average(batch_mean)</span>
mean, var = tf.cond(train_phase, mean_var_with_update,
<span class="hljs-keyword">lambda</span>: (ema.average(batch_mean), ema.average(batch_var)))
normed = tf.nn.batch_normalization(x, mean, var, beta, gamma, <span class="hljs-number">1e-3</span>)
<span class="hljs-keyword">return</span> normed</code></pre><p>至于此行代码tf.nn.batch_normalization()就是简单的计算batchnorm过程啦,代码如下: <br>这个函数所实现的功能就如此公式:</p><pre class="prettyprint" name="code"><code class="hljs python has-numbering"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">batch_normalization</span><span class="hljs-params">(x,
mean,
variance,
offset,
scale,
variance_epsilon,
name=None)</span>:</span>
<span class="hljs-keyword">with</span> ops.name_scope(name, <span class="hljs-string">"batchnorm"</span>, [x, mean, variance, scale, offset]):
inv = math_ops.rsqrt(variance + variance_epsilon)
<span class="hljs-keyword">if</span> scale <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">None</span>:
inv *= scale
<span class="hljs-keyword">return</span> x * inv + (offset - mean * inv
<span class="hljs-keyword">if</span> offset <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-keyword">None</span> <span class="hljs-keyword">else</span> -mean * inv)</code></pre><h3><a name="t6"></a><a target="_blank"></a>第四节:Batchnorm的优点</h3><p>主要部分说完了,接下来对BatchNorm做一个总结:</p><ul><li>没有它之前,需要小心的调整学习率和权重初始化,但是有了BN可以放心的使用大学习率,但是使用了BN,就不用小心的调参了,较大的学习率极大的提高了学习速度,</li><li>Batchnorm本身上也是一种正则的方式,可以代替其他正则方式如dropout等</li><li>另外,个人认为,batchnorm降低了数据之间的绝对差异,有一个去相关的性质,更多的考虑相对差异性,因此在分类任务上具有更好的效果。</li></ul><blockquote> <p>注:或许大家都知道了,韩国团队在2017NTIRE图像超分辨率中取得了top1的成绩,主要原因竟是去掉了网络中的batchnorm层,由此可见,BN并不是适用于所有任务的,在image-to-image这样的任务中,尤其是超分辨率上,图像的绝对差异显得尤为重要,所以batchnorm的scale并不适合。</p></blockquote><p>参考文献: <br>【1】<a href="http://blog.csdn.net/zhikangfu/article/details/53391840" target="_blank">http://blog.csdn.net/zhikangfu/article/details/53391840</a> <br>【2】<a href="http://geek.csdn.net/news/detail/160906" target="_blank">http://geek.csdn.net/news/detail/160906</a> <br>【3】 <a href="https://www.zhihu.com/question/53133249" target="_blank">https://www.zhihu.com/question/53133249</a></p><br> </div>
</div>
(PS:
BatchNormalization解决什么问题呢?
主要是为了解决反向传播过程中的梯度爆炸和梯度消失,更多请参考:
深度学习中 Batch Normalization为什么效果好?
)
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/137923.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】:
Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】:
官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...