Batch Nomalization,Group Normalization,Cross-Iteration Batch Normalization分析

BatchNomalization

发表于第32届机器学习国际会议 PMLR,2015年

前言

由于深度神经网络每层输入的分布在训练过程中随着前一层的参数变化而变化,因此训练深度神经网络很复杂。由于需要较低的学习率和仔细的参数初始化,这会减慢训练速度,并且使得训练具有饱和非线性的模型变得非常困难。我们将这种现象称为内部协变量偏移,并通过BN层输入来解决该问题。Batch Normalization 允许我们使用更高的学习率,并且在初始化时不那么小心。它还充当正则化器,在某些情况下消除了 Dropout 的需要。

covariate shift(协变量偏移)的提出

层输入分布的变化带来了一个问题,因为层需要不断适应新的分布。当学习系统的输入分布发生变化时,据说会经历协变量转移(Shimodaira,2000)。这通常是通过域适应来处理的(Jiang,2008)。

协变量转移的提出以及一种处理方法的论文分别如下

Shimodaira, Hidetoshi,2000
《Improving predictive inference under covariate shift by weighting the log-likelihood function》
Journal of Statistical Planning and Inference,90(2):227–244, October 2000.

Jiang, Jing.
《A literature survey on domain adaptation of statistical classifiers》, 2008.

作者提出协变量转移的概念可以扩展得到学习系统之外,以应用于其部分,例如自网络或一个层。
考虑一个网络的计算
l = F 2 ( F 1 ( u , Θ 1 ) , Θ 2 ) l=F_2(F_1(u,\Theta_1),\Theta_2) l=F2(F1(u,Θ1),Θ2)
其中 F 1 F_1 F1 F 2 F_2 F2是任意转换,并且要学习参数 Θ 1 , Θ 2 \Theta_1,\Theta_2 Θ1,Θ2以最小化 l o s s loss loss l l l
学习 Θ 2 \Theta_2 Θ2可以看作是将输入 x = F 1 ( u , Θ 1 ) x=F_1(u,\Theta_1) x=F1(u,Θ1)输入到子网络中
l = F 2 ( x , Θ 2 ) l=F_2(x,\Theta_2) l=F2(x,Θ2)
Θ 2 ← Θ 1 − α m ∑ i = 1 m ∂ F 2 ( x i , Θ 2 ) ∂ Θ 2 \Theta_2\leftarrow\Theta_1-\frac{\alpha}{m}\sum_{i=1}^m\frac{\partial F_2(x_i,\Theta_2)}{\partial \Theta_2} Θ2Θ1mαi=1mΘ2F2(xi,Θ2)
b a t c h s i z e batch size batchsize m m m,学习率为 α \alpha α
以上公式表明梯度下降步骤与输入为 x x x的独立网络 F 2 F_2 F2完全等效。

因此,使训练更有效率的输入分布特性(例如,在训练和测试数据之间具有相同的分布)也适用于训练子网络。因此,随着时间的推移, x x x的分布保持固定是有利的

这样 Θ 2 \Theta_2 Θ2 不必为了补偿 x x x的分布的变化来重新调整

作者指出,子网络输入的固定分布也会对子网络外的层产生积极影响。作者举了一个例子,考虑一个具有 s i g m o i d sigmoid sigmoid的激活函数 z = g ( W u + b ) z=g(Wu+b) z=g(Wu+b),其中 u u u是层输入,权重矩阵 W W W和偏置向量 b b b是要学习的层参数,激活函数为
g = 1 1 + e − x g=\frac{1}{1+e^{-x}} g=1+ex1
随着 ∣ x ∣ |x| x增加, g ‘ ( x ) g^`(x) g(x)趋于0,这意味着对于除了低维的所有维度的 x = W u + b x=Wu+b x=Wu+b,流向 u u u的梯度将会消失,模型会缓慢训练。
然而由于 x x x W , b W,b W,b和下面所有层的参数的影响,在训练期间对这些参数的更改可能会将 x x x的许多维度移动到非线性的饱和状态并减慢收敛速度。随着网络深度的增加,这种效果会被放大。 在实践中,饱和问题和由此产生的梯度消失通常通过使用RELU和仔细的初始化和小的学习率来解决。
然而,如果我们可以确保非线性输入的分布在网络训练时保持更稳定,那么优化器就不太可能陷入饱和状态,并且训练会加速。

作者提出BatchNormlization,BN减少了内部协变量偏移.(使网络输入的分布不那么容易产生变化,更稳定)。BN通过减少梯度对参数尺度或其初始值的依赖性,BN对通过网络的梯度流也有有益的影响。

这使我们能够使用更高的学习率而没有发散的风险。此外,批量归一化使模型正则化并减少了对 Dropout 的需求。最后,批量归一化通过防止网络陷入饱和模式,使使用饱和非线性成为可能。

减少covariate shift(协变量偏移)

作者将内部协变量偏移定义为由于训练期间网络参数的变化而导致的网络激活分布的变化。为了改进训练,作者寻求减少内部协变量偏移。随着训练的进行,通过固定层输入 x x x的分布,我们期望提高训练速度。
众所周知(LeCun et al., 1998b; Wiesler & Ney, 2011)如果输入被白化,网络训练收敛得更快——即线性变换为具有零均值和单位方差,并且去相关。(白化指去除数据的冗余信息)。由于每一层都遵守下面的层产生的输入,因此对每一层的输入实现相同的白化将是有利的。通过对每一层的输入进行白化,我们将朝着实现输入的固定分布迈出一步,从而消除内部协变量偏移的不良影响。

作者认为对每层输入的完全白化代价太高昂,且并非处处可微,给反向传播带来困难,作者进行了两个简化。。
第一个是通过使其具有零均值和1方差独立地归一化每个标量特征,而不是联合白化层输入和输出中的特征。对于具有 d d d维输入 x = ( x ( 1 ) . . . ( x ( d ) ) x=(x^{(1)}...(x^{(d)}) x=(x(1)...(x(d)),我们将对每个维度进行归一化。
x ^ ( k ) = x ( k ) − E [ x ( k ) ] V a r [ x ( k ) ] \widehat{x}^{(k)}=\frac{x^{(k)}-E[x^{(k)}]}{\sqrt{Var[x^{(k)}]}} x (k)=Var[x(k)] x(k)E[x(k)]
其中期望和方差是在训练数据集上计算的。如 (LeCun et al., 1998b) 所示,即使特征没有去相关,这种归一化也会加速收敛。

关于去相关,归一化,白化,去均值等机器学习的思想请查看:去相关、归一化、去均值、白化
更深刻的机器学习关于去相关如何实现请查看:【机器学习】降维——PCA(非常详细)

请注意,简单地对层的每个输入进行归一化可能会改变层可以表示的内容。例如,对 sigmoid 的输入进行归一化会将它们限制在非线性的线性范围内。为了解决这个问题,我们确保插入到网络中的变换可以代表身份变换。为了完成这个目的,我们为每个激活 x ( k ) x^{(k)} x(k)引入一对参数 γ ( k ) , β ( k ) \gamma^{(k)},\beta^{(k)} γ(k),β(k),它们缩放和移动归一化值:
y ( k ) = γ ( k ) x ^ ( k ) + β ( k ) . y^{(k)}=\gamma^{(k)}\widehat{x}^{(k)}+\beta^{(k)}. y(k)=γ(k)x (k)+β(k).
这些参数与原始模型参数一起学习,并恢复网络的表示能力。实际上,通过设置 y ( k ) = V a r [ x ( k ) ] y^{(k)}=\sqrt{Var[x^{(k)}]} y(k)=Var[x(k)] b e t a ( k ) = E [ x ( k ) ] beta^{(k)}=E[x^{(k)}] beta(k)=E[x(k)],我们可以恢复原始激活,如果这是最佳选择的话。
在这里插入图片描述
反向传播链式法则:
在这里插入图片描述
BN 变换是一种将归一化激活引入网络的可微变换。这确保在模型训练时,层可以继续学习表现出较少内部协变量偏移的输入分布,从而加速训练。此外,应用于这些归一化激活的学习仿射变换允许 BN 变换表示身份变换并保留网络容量。

推理时BN的处理

依赖于小批量的激活归一化允许有效的训练,但在推理过程中既不必要也不可取;确定性地,我们希望输出仅依赖于输入。为此,一旦网络经过训练,我们使用归一化:
x ^ = x − E [ x ] V a r [ x ] + ϵ \widehat{x}=\frac{x-E[x]}{\sqrt{Var[x]+\epsilon}} x =Var[x]+ϵ xE[x]
忽略 ϵ \epsilon ϵ,这些归一化后的激活具有于训练期间相同的均值0和方差1.作者使用无偏方差估计 V a r [ x ] = m m − 1 ⋅ E B [ σ B 2 ] Var[x]=\frac{m}{m-1}\cdot E_B[\sigma^2_B] Var[x]=m1mEB[σB2],其中期望超过训练大小为m的小批量并且 σ B 2 \sigma^2_B σB2是他们的样本方差

下面算法总结了训练Batch-Nomalization网络的过程
在这里插入图片描述

BN的好文章

基础 | batchnorm原理及代码详解

Group Normalization

European Conference on Computer Vision (ECCV), 2018 (Oral). Best Paper Honorable Mention
在这里插入图片描述

前言

在这里插入图片描述
尽管 BN 取得了巨大的成功,但由于其沿批次维度进行归一化的独特行为也导致了 BN 的缺点。特别是,BN 需要以足够大的批量大小工作,BN 的使用通常需要这些系统在模型设计和批量大小之间进行折衷。
GN 作为一个层,将通道划分为组并对每个组内的特征进行归一化(图 2)。 GN 不利用批量维度,其计算与批量大小无关。
在这里插入图片描述
已经提出了几种归一化方法 来避免利用批量维度。层归一化 (LN) 沿通道维度运行,实例归一化 (IN)执行类似 BN 的计算,但仅针对每个样本(图 2)。权重归一化(WN) 建议对过滤器权重进行归一化,而不是对特征进行操作。这些方法没有受到批量维度引起的问题的影响,但它们在许多视觉识别任务中无法接近 BN 的准确性。我们在其余部分的上下文中提供了与这些方法的比较。我们的归一化方法没有解决批量统计计算,而是本质上避免了这种计算。

GN原理

我们首先描述特征归一化的一般公式,然后在这个公式中呈现 GN。一系列特征归一化方法,包括 BN、LN、IN 和 GN,执行以下计算:
x ^ i = 1 σ i ( x i − μ i ) , − ( 1 ) \widehat{x}_i=\frac{1}{\sigma_i}(x_i-\mu_i),-(1) x i=σi1(xiμi),(1)
这里 x x x是一个层(通道层)计算的特征, i i i是一个索引。在2D图像的情况下, i = ( i N , i C , i H , i W ) i=(i_N,i_C,i_H,i_W) i=(iN,iC,iH,iW)是一个以 ( N , C , H , W ) (N,C,H,W) (N,C,H,W)为顺序的4D矢量,其中 N N N是批处理轴, C C C是通道轴, H H H W W W是空间高度和宽度轴.
(1)中的 μ \mu μ σ \sigma σ是平均值和标准差,计算公式为:
μ i = 1 m ∑ k ∈ S i x k , σ i 1 m ∑ k ∈ S i ( x k − μ i ) 2 + ϵ , − ( 2 ) \mu_i=\frac{1}{m}\sum_{k\in S_i}x_k,\sigma_i\sqrt{\frac{1}{m}\sum_{k\in S_i}(x_k-\mu_i)^2+\epsilon},-(2) μi=m1kSixk,σim1kSi(xkμi)2+ϵ ,(2)
其中 ϵ \epsilon ϵ是一个小常数。 S i S_i Si是计算均值和标准差的像素集(在BN则是批处理里的同位置的像素集),并且 m m m是该集的大小(在BN则是batch_size)。许多类型的特征归一化方法主要区别在于集合 S i S_i Si如何定义,讨论如下:

Batch Norm回顾

对于Batch Normalization,集合 S i S_i Si定义为:
S i = { k ∣ k C = i C } , − ( 3 ) S_i=\{k|k_C=i_C\},-(3) Si={kkC=iC},(3)
其中 i C ( k C i_C(k_C iC(kC)表示沿 C C C轴的 i ( k ) i(k) i(k)的子索引。
注:这里 i C i_C iC表示第C个通道的子索引 i i i,子索引指的是(N,H,W)索引,代表第 i i i个batch

公式(3)意味着BN在这个C通道的所有像素都会被一起归一化,对于每个通道,BN沿着(N,H,W)轴计算 μ \mu μ σ \sigma σ

Layer Norm回顾

对于Layer Norm,集合 S i S_i Si定义为:
S i = { k ∣ k N = i N } , − ( 4 ) S_i=\{k\mid k_N=i_N\},-(4) Si={kkN=iN},(4)
注:这里 i N i_N iN表示第N个批次的子索引 i i i,子索引指的是(C,H,W)索引,代表第 i i i个通道。
公式(4)意味着LN对每个example沿(C,H,W)轴计算 μ \mu μ σ \sigma σ

Instance Norm回顾

对于Instance Norm,集合 S i S_i Si定义为:
S i = { k ∣ k N = i N , k C = i C } . − ( 5 ) S_i=\{k\mid k_N=i_N,k_C=i_C\}.-(5) Si={kkN=iN,kC=iC}.(5)
公式(5)意味着IN对每个example和通道沿着(H,W)轴计算 μ \mu μ σ \sigma σ
注:IN对特定批次N和通道C在空间域上进行归一化。
在这里插入图片描述

BN、LN和IN的总结

BN、LN和IN的方法都学习了一种准线性变换,以补偿可能丧失的表征能力:
y i = γ x ^ i + β , − ( 6 ) y_i=\gamma \widehat{x}_i+\beta,-(6) yi=γx i+β,(6)
其中 γ \gamma γ β \beta β是可训练的标量和偏移量
注: γ \gamma γ β \beta β均由 i C i_C iC索引,这里简写了。由 i C i_C iC索引意味着公式(6)的补偿机制会在批次维度N生效。

GN的提出

Group Norm.形式上,一个GN层计算 μ \mu μ σ \sigma σ的集合 S i S_i Si被定义为:
S i = { k ∣ k N = i N , ⌊ k C C / G ⌋ = ⌊ i C C / G ⌋ } . − ( 7 ) S_i=\{k\mid k_N=i_N,\lfloor\frac{k_C}{C/G}\rfloor=\lfloor\frac{i_C}{C/G}\rfloor\}.-(7) Si={kkN=iN,C/GkC=C/GiC}.(7)
参数介绍:

  • G G G是组数,是一个预定义的超参,默认情况 G = 32 G=32 G=32
  • C / G C/G C/G是每组的通道数。
  • ⌊ ⋅ ⌋ \lfloor\cdot\rfloor 是向下取整操作
  • 假定每组通道沿 C C C轴按顺序存储, ⌊ k C C / G ⌋ = ⌊ i C C / G ⌋ \lfloor\frac{k_C}{C/G}\rfloor=\lfloor\frac{i_C}{C/G}\rfloor C/GkC=C/GiC意味着索引 i i i k k k位于同一组通道中
  • GN沿 ( H , W ) (H,W) (H,W)轴和一组 C C C通道计算 μ \mu μ σ \sigma σ

图2最右边是两组 G = 2 G=2 G=2的简单情况,每组有3个通道

GN的 S i S_i Si由公式(7)给出,一个GN层由等式(1)、(2)和(6)定义。
具体来说,同一个组的像素经过相同的 μ \mu μ σ \sigma σ一起归一化。GN还学习每个通道的 γ \gamma γ β \beta β

注:GN的归一化和LN很像,同是在通道维度 C C C进行计算 μ \mu μ σ \sigma σ,但区别是GN对通道进行了分组。

GN和LN,IN的相关性

LN、IN和GN都沿batch轴执行独立计算。GN的两种极端情况相当于LN和IN(见图2)
注:LN相当于GN分组G=1的情况,IN相当于GN分组G=C(通道数)的情况,这是两个极端.

与LN的关系

如果将组G设为1,则GN变为LN.假设LN的层中的所有通道都做出“相似的贡献”。与LN中研究的是完全连接层的情况不同,LN的论文指名,在卷积存在的情况下,该假设可能不太有效。(LN是研究RNN等网络提出的归一化层)
GN比LN收到的限制更小,因为假设每组通道(而不是所有通道)服从共享的均值和方差;该模型仍然可以灵活地为每个组学习不同的分布。这导致GN对LN的表征能力得到改善。

与IN的关系

如果将组设为C,则GN变为IN。但IN只能依赖空间维度来计算均值和方差,因此错过了利用通道依赖性的机会。

GN实现

GN可以通过PyTorch和TensorFlow中支持自动区分的几行代码轻松实现.TensorFlow实现代码如下:
在这里插入图片描述
事实上,在TensorFlow中,我们只需指定如何沿归一化方法定义的适当轴计算均值和方差(“矩”)

GN实验

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
GN的验证误差稍高意味着GN失去了BN的一些正则化能力。这是可以理解的,因为BN的均值和方差计算引入了随机分批抽样引起的不确定性,这有助于正则化[26]。GN(和LN/in)中缺少这种不确定性。但是,GN与适当的正则化器相结合可能会改善结果。这可能是一个未来的研究课题
在这里插入图片描述
在这里插入图片描述
小批量训练时,BN的误差变得相当高.GN更稳定,对batch size不敏感.GN在32到2的大批量范围内具有非常相似的曲线(受随机变化的影响)。以上这些结果表明,batch均值和方差估计可能过于随机和不准确,尤其是当他们是在4或2幅图像上计算时。然而,如果从1张图像计算统计数据,则这种随机性消失,在这种情况下,BN在训练时变得与in相似,我们发现IN的结果(28.4%)比批量为2(34.7%)的BN更好。

Cross-Iteration Batch Normalization

发表于IEEE/CVF计算机视觉和模式识别,CVPR 2021年

前言

BN存在一个问题,当在小批量大小的情况下训练显著降低了效率。为了解决这个问题,作者提出了Cross-Iteration Batch Normalization(CBN),其中联合使用来自多个最近迭代的示例来提高估计质量。在多次迭代中计算统计数据的一个挑战是,由于网络权重的变化,来自不同迭代的网络激活无法相互比较。因此,我们通过提出的基于泰勒多项式的技术来补偿网络权重变化,从而可以准确估计统计数据并有效地应用批量归一化。

BN的缺点以及CBN的提出

BN假设通常适用于大批量数据,在小批量机制中会失效,而且由于设备显存或者内存限制,批量大小也会收到限制,总之数据集大小和设备资源要求都会限制批量的大小。

为了改进小批量机制中的统计估计,已经提出了几种替代的归一化器,有Layer Normalization(LN) ,Instance Normalization (IN) 和Group Normalization (GN) 来计算通道维度上的均值和方差,与批量大小无关。然而,不同的通道归一化技术往往适用于不同的任务,这取决于所涉及的通道集,尽管GN是为检测任务而涉及,但较慢的推理速度限制其实际应用。另一方面,synchronized BN (SyncBN) 通过在多个 GPU 上处理更大的批量大小来产生一致的改进。这些性能提升是以跨设备同步所需的额外开销为代价的。

关于上述提到的LN,IN以及GN的论文分析:
-------------------------------待更新--------------------------------------------------

一个很少探索的估计更好统计数据的方向是通过最近多次训练迭代的例子来计算它们,而不是像以前的技术那样只从当前迭代计算。这可以显着扩大从中获得均值和方差的数据池。然而,这种方法存在一个明显的缺点,即由于网络权重的变化,来自不同迭代的激活值无法相互比较。如图 1 所示,直接计算多次迭代的统计数据,我们称之为 Naive CBN,导致精度较低。
在这里插入图片描述
在本文中,我们提出了一种补偿迭代之间网络权重变化的方法,以便可以有效地使用先前迭代中的示例来改进批量归一化。思路动机来源于作者观察到网络权重在连续训练迭代之间逐渐而不是突然变化,这要归功于随机梯度下降 (SGD) 的迭代性质。因此,最近迭代的样本的均值和方差可以通过低阶泰勒多项式很好地近似为当前网络权重,该多项式定义在统计数据的梯度上,相对于网络权重。将最近多次迭代的补偿均值和方差与当前迭代的均值和方差进行平均,以产生更好的统计估计值。

通常,可以对三个组件执行归一化:输入数据、隐藏激活和网络参数。
LN 的动机是为序列模型探索更合适的统计数据,而 IN 以类似于 BN 的方式执行归一化,但仅对每个实例进行统计。 GN 实现了 IN 和 LN 之间的平衡,通过沿通道维度将特征分成多个组并计算每个组内的均值和方差进行归一化。BIN 引入了一种可学习的方法,用于在规范化和维护风格信息之间自动切换,在风格转移任务上享受 BN 和 IN 的优势。跨 GPU 批量归一化(CGBN 或 SyncBN)[18] 将 BN 扩展到多个 GPU,目的是增加有效批量大小。虽然提供了更高的准确性,但它为训练过程引入了同步开销。Kalman Normalization (KN) [29] 提出了一种卡尔曼滤波程序,用于根据网络层的观察统计数据和先前层的计算统计数据估计网络层的统计数据。
Batch Renormalization (BRN) 是第一次尝试利用最近迭代的统计数据进行归一化。它不会补偿来自最近迭代的统计数据,而是降低了来自远距离迭代的统计数据的重要性。然而,这种降低权重的启发式不会使结果统计数据“正确”,因为最近迭代的统计数据不是当前网络权重。 BRN 可以被视为我们的 Naive CBN 基线的特殊版本(没有泰勒多项式近似),其中远距离迭代被降权。

最近的工作还研究了网络参数的规范化。在Weight Normalization (WN) [22] 中,通过将权重向量重新参数化为其长度和方向来改进网络权重的优化。Weight Standardization (WS) [19] 而是根据权重的一阶和二阶矩重新参数化权重,以便平滑优化问题的损失情况。为了结合多种归一化技术的优点,Switchable Normalization(SN)[16]和Sparse Switchable Normalization(SSN)[24]利用可微学习在不同的归一化方法之间进行切换。

提议的 CBN 采用激活归一化方法,旨在减轻 BN 的小批量依赖性。与现有技术不同,它提供了一种跨多个训练迭代有效聚合统计数据的方法。

CBN的原理

为了解决小批量 BN 的问题,一种简单的方法是计算当前和先前迭代的均值和方差。
然而,统计第 ( t − τ ) (t-\tau) (tτ)次迭代的 μ t − τ ( θ t − τ ) \mu_{t-\tau}(\theta_{t-\tau}) μtτ(θtτ) v t − τ ( θ t − τ ) v_{t-\tau}(\theta_{t-\tau}) vtτ(θtτ)是基于网络权重 θ t − τ \theta_{t-\tau} θtτ下计算的,这会使它们在当前迭代中过时。因此,直接聚合来自多次迭代的统计数据会产生不准确的均值和方差估计,从而导致性能显着下降。(注: θ t \theta_t θt表示第 t t t个小批量中某个层的的网络权重, μ \mu μ v v v分别表示均值和方差)

由于基于梯度的训练的性质,我们观察到网络权重在连续迭代之间平滑变化。
这允许我们通过泰勒多项式从现成的 μ t − τ ( θ t − τ ) \mu_{t-\tau}(\theta_{t-\tau}) μtτ(θtτ) v t − τ ( θ t − τ ) v_{t-\tau}(\theta_{t-\tau}) vtτ(θtτ)中近似 μ t − τ ( θ t ) \mu_{t-\tau}(\theta_t) μtτ(θt) v t − τ ( θ t ) v_{t-\tau}(\theta_t) vtτ(θt),即:
在这里插入图片描述
作者分析了 μ , v \mu,v μ,v对于层满足 r < l r<l r<l的F范数均满足:
在这里插入图片描述
在这里插入图片描述
以此在ImageNet数据集上分析了当前层和之前的层之间的梯度信息,如下图
在这里插入图片描述
作者根据这个经验证据在第 l l l层截断了这些部分梯度信息。
因此,作者进一步近似方程5和方程6得出:
在这里插入图片描述
其中 ∂ μ t − τ l ( θ t − τ ) / ∂ θ t − τ l \partial \mu^l_{t-\tau}(\theta_{t-\tau})/\partial \theta^l_{t-\tau} μtτl(θtτ)/θtτl ∂ v t − τ l ( θ t − τ ) / ∂ θ t − τ l \partial v^l_{t-\tau}(\theta_{t-\tau})/\partial \theta^l_{t-\tau} vtτl(θtτ)/θtτl的简单实现需要 O ( C o u t × C o u t × C i n × K ) O(C_{out}\times C_{out}\times C_{in}\times K) O(Cout×Cout×Cin×K)的计算开销,其中 C o u t C_{out} Cout C i n C_{in} Cin分别表示第 l l l层的输出和输入通道维度, K K K表示 θ t − τ l \theta^l_{t-\tau} θtτl的核大小。

作者给出了有效实现的证明逻辑过程:
C o u t C_{out} Cout C i n C_{in} Cin分别表示第 l l l层的输出和输入通道维度, K K K表示 θ t − τ l \theta^l_{t-\tau} θtτl的核大小。 μ t − τ l \mu^l_{t-\tau} μtτl v t − τ l v^l_{t-\tau} vtτl在通道中是 C o u t C_{out} Cout维度,并且 θ t − τ l \theta^l_{t-\tau} θtτl是一个 C o u t × C i n × K C_{out}\times C_{in} \times K Cout×Cin×K维度的张量。
∂ μ t − τ l ( θ t − τ ) / ∂ θ t − τ l \partial \mu^l_{t-\tau}(\theta_{t-\tau})/\partial \theta^l_{t-\tau} μtτl(θtτ)/θtτl ∂ v t − τ l ( θ t − τ ) / ∂ θ t − τ l \partial v^l_{t-\tau}(\theta_{t-\tau})/\partial \theta^l_{t-\tau} vtτl(θtτ)/θtτl的简单实现需要 O ( C o u t × C o u t × C i n × K ) O(C_{out}\times C_{out}\times C_{in}\times K) O(Cout×Cout×Cin×K)的计算开销,作者发现该操作可以在 O ( C o u t × C i n × K ) O(C_{out}\times C_{in}\times K) O(Cout×Cin×K)中有效地实现,这归功于 μ \mu μ v v v特征响应的平均。

这里作者推导出 ∂ μ t − τ l ( θ t − τ ) / ∂ θ t − τ l \partial \mu^l_{t-\tau}(\theta_{t-\tau})/\partial \theta^l_{t-\tau} μtτl(θtτ)/θtτl的有效实现。 ∂ v t − τ l ( θ t − τ ) / ∂ θ t − τ l \partial v^l_{t-\tau}(\theta_{t-\tau})/\partial \theta^l_{t-\tau} vtτl(θtτ)/θtτl的推导大致相同。
首先通过删除不相关的迭代符号简化一下公式,让 μ l \mu^l μl θ l \theta^l θl分别表示 μ t − τ l ( θ t − τ ) \mu^l_{t-\tau}(\theta_{t-\tau}) μtτl(θtτ) θ t − τ l \theta^l_{t-\tau} θtτl
前向传播的逐元素计算可以计算为
μ j l = 1 m ∑ i = 1 m x i , j l , − − − − − − − − − − − ( 13 ) \mu^l_j=\frac{1}{m}\sum^m_{i=1}x^l_{i,j},-----------(13) μjl=m1i=1mxi,jl,(13)
其中, μ j l \mu^l_j μjl表示 μ l \mu^l μl的第 j j j个通道, x i , j l x^l_{i,j} xi,jl表示第 i i i个示例中的第 j j j个通道。 x i , j l x^l_{i,j} xi,jl计算为:
x i , j l = ∑ n = 1 C i n ∑ k = 1 K θ j , n , k l ⋅ y i + o f f s e t ( k ) , n l − 1 , − − − − − − − − − − − ( 14 ) x^l_{i,j}=\sum^{C_{in}}_{n=1}\sum^K_{k=1}\theta^l_{j,n,k}\cdot y^{l-1}_{i+offset(k),n},-----------(14) xi,jl=n=1Cink=1Kθj,n,klyi+offset(k),nl1,(14)
其中, n n n k k k分别枚举输入特征维度和卷积核索引, o f f s e t ( k ) offset(k) offset(k)表示执行第 k k k个核时的空间偏移,并且 y l − 1 y^{l-1} yl1是第 ( l − 1 ) (l-1) (l1)层的输出。
∂ μ l / ∂ θ l ∈ R C o u t × C o u t × C i n × K \partial\mu^l/\partial\theta^l\in\mathbb R^{C_{out}\times C_{out}\times C_{in}\times K} μl/θlRCout×Cout×Cin×K的元素计算如下,考虑方程(13)和方程(14):
在这里插入图片描述
因此, [ ∂ μ l ∂ θ l ] j , q , p , η [\frac{\partial\mu^l}{\partial\theta^l}]_{j,q,p,\eta} [θlμl]j,q,p,η仅在 j = q j=q j=q时采取非零值。
这个操作可以在 O ( C i n × K ) O(C_{in}\times K) O(Cin×K)中有效地实现。类似地, ∂ v l / ∂ θ l \partial v^l/\partial\theta^l vl/θl的计算可以控制在 O ( C o u t × C i n × K ) O(C_{out}\times C_{in}\times K) O(Cout×Cin×K)

在这里插入图片描述
在这里插入图片描述

在补偿网络权重变化后,我们将最近 k-1 次迭代的统计数据与当前迭代 t 的统计数据进行聚合,以获得 CBN 中使用的统计数据:
在这里插入图片描述
其中 μ t − τ l ( θ t ) \mu^l_{t-\tau}(\theta_t) μtτl(θt) v t − τ l ( θ t ) v^l_{t-\tau}(\theta_t) vtτl(θt)是从方程(7)和方程(8)计算出来的。在方程(10)中, v ˉ t , k l ( θ t ) \bar{v}^l_{t,k}(\theta_t) vˉt,kl(θt) v t − τ l ( θ t ) v^l_{t-\tau}(\theta_t) vtτl(θt) μ t − τ l ( θ t ) 2 \mu^l_{t-\tau}(\theta_t)^2 μtτl(θt)2在每次迭代中的最大值确定,因为 v t − τ l ( θ t ) ≥ μ t − τ l ( θ t ) 2 v^l_{t-\tau}(\theta_t) \geq\mu^l_{t-\tau}(\theta_t)^2 vtτl(θt)μtτl(θt)2应该适用于有效的估计,但可能会违反方程(7)和方程(8)中的泰勒多项式近似。最后, μ ˉ t , k l ( θ t ) \bar{\mu}^l_{t,k}(\theta_t) μˉt,kl(θt) σ ˉ t , k l ( θ t ) \bar{\sigma}^l_{t,k}(\theta_t) σˉt,kl(θt)用于在当前迭代中相对应的特征响应 { x t , i l ( θ t ) } i = 1 m \{x^l_{t,i}(\theta_t)\}^m_{i=1} {xt,il(θt)}i=1m进行归一化
在这里插入图片描述
使用 CBN,用于计算当前迭代统计的有效示例数是原始 BN 的 k 倍。在训练中,损失梯度反向传播到当前迭代的网络权重和激活值,即 θ t l \theta^l_t θtl x t , i l ( θ t ) x^l_{t,i}(\theta_t) xt,il(θt)。先前迭代的那些是固定的并且不接收梯度。因此,CBN 在反向传播中的计算成本与 BN 相同
用 CBN 替换网络中的 BN 模块只会导致计算开销和内存占用的轻微增加。对于计算,额外的开销主要来自计算偏导数 ∂ μ t − τ ( θ t − τ ) ∂ θ t − τ l \frac{\partial\mu_{t-\tau}(\theta_{t-\tau})}{\partial\theta^l_{t-\tau}} θtτlμtτ(θtτ) ∂ v t − τ ( θ t − τ ) ∂ θ t − τ l \frac{\partial v_{t-\tau}(\theta_{t-\tau})}{\partial\theta^l_{t-\tau}} θtτlvtτ(θtτ)对于整个网络的开销。
在这里插入图片描述
所提出的CBN中的一个关键超参数是用于统计估计的最近迭代的时间窗口大小 k k k。更宽的窗口扩大了示例集,但对于更远的迭代,示例质量变得越来越低,因为网络参数 θ t \theta_t θt θ t − τ \theta_{t-\tau} θtτ的差异变得更加显着,并且使用低阶泰勒多项式补偿得不太好。根据经验,我们发现CBN在各种设置和任务中,当窗口大小高达 k = 8 k = 8 k=8时是有效的。唯一的技巧是在训练开始时,当网络权重变化很快时,窗口大小应该保持较小。因此,我们为窗口大小引入了一个长度为 T b u r n − i n T_{burn-in} Tburnin的老化周期,其中 k = 1 k = 1 k=1并且CBN退化为原始 BN。在我们的实验中,老化周期默认在ImageNet图像分类上设置为25个时期,在COCO目标检测上设置为3个时期。
在这里插入图片描述
作者实现了二阶泰勒近似补偿的方式,发现并没有比一阶泰勒近似补偿获得更好的性能,表明一阶近似足以在ImageNet上进行图像分类,因此CBN默认采用一阶泰勒近似补偿。作者也实现了更多CBN层来补偿,但使用更多层并不会进一步提高性能,反而会消耗更多的FLOP,因此CBN默认采取一层补偿。

CBN算法流程

在这里插入图片描述

CBN官方源码

from __future__ import division

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.nn.parameter import Parameter


class CBatchNorm2d(nn.Module):
    def __init__(self, num_features, eps=1e-5, momentum=0.1, affine=True,
                 track_running_stats=True,
                 buffer_num=0, rho=1.0,
                 burnin=0, two_stage=True,
                 FROZEN=False, out_p=False):
        super(CBatchNorm2d, self).__init__()
        self.num_features = num_features
        self.eps = eps
        self.momentum = momentum
        self.affine = affine
        self.track_running_stats = track_running_stats

        self.buffer_num = buffer_num
        self.max_buffer_num = buffer_num
        self.rho = rho
        self.burnin = burnin
        self.two_stage = two_stage
        self.FROZEN = FROZEN
        self.out_p = out_p

        self.iter_count = 0
        self.pre_mu = []
        self.pre_meanx2 = []  # mean(x^2)
        self.pre_dmudw = []
        self.pre_dmeanx2dw = []
        self.pre_weight = []
        self.ones = torch.ones(self.num_features).cuda()

        if self.affine:
            self.weight = Parameter(torch.Tensor(num_features))
            self.bias = Parameter(torch.Tensor(num_features))
        else:
            self.register_parameter('weight', None)
            self.register_parameter('bias', None)
        if self.track_running_stats:
            self.register_buffer('running_mean', torch.zeros(num_features))
            self.register_buffer('running_var', torch.ones(num_features))
        else:
            self.register_parameter('running_mean', None)
            self.register_parameter('running_var', None)
        self.reset_parameters()

    def reset_parameters(self):
        if self.track_running_stats:
            self.running_mean.zero_()
            self.running_var.fill_(1)
        if self.affine:
            self.weight.data.uniform_()
            self.bias.data.zero_()

    def _check_input_dim(self, input):
        if input.dim() != 4:
            raise ValueError('expected 4D input (got {}D input)'
                             .format(input.dim()))

    def _update_buffer_num(self):
        if self.two_stage:
            if self.iter_count > self.burnin:
                self.buffer_num = self.max_buffer_num
            else:
                self.buffer_num = 0
        else:
            self.buffer_num = int(self.max_buffer_num * min(self.iter_count / self.burnin, 1.0))

    def forward(self, input, weight):
        # deal with wight and grad of self.pre_dxdw!
        self._check_input_dim(input)
        y = input.transpose(0, 1)
        return_shape = y.shape
        y = y.contiguous().view(input.size(1), -1)

        # burnin
        if self.training and self.burnin > 0:
            self.iter_count += 1
            self._update_buffer_num()

        if self.buffer_num > 0 and self.training and input.requires_grad:  # some layers are frozen!
            # cal current batch mu and sigma
            cur_mu = y.mean(dim=1)
            cur_meanx2 = torch.pow(y, 2).mean(dim=1)
            cur_sigma2 = y.var(dim=1)
            # cal dmu/dw dsigma2/dw
            dmudw = torch.autograd.grad(cur_mu, weight, self.ones, retain_graph=True)[0]
            dmeanx2dw = torch.autograd.grad(cur_meanx2, weight, self.ones, retain_graph=True)[0]
            # update cur_mu and cur_sigma2 with pres
            mu_all = torch.stack([cur_mu, ] + [tmp_mu + (self.rho * tmp_d * (weight.data - tmp_w)).sum(1).sum(1).sum(1) for tmp_mu, tmp_d, tmp_w in zip(self.pre_mu, self.pre_dmudw, self.pre_weight)])
            meanx2_all = torch.stack([cur_meanx2, ] + [tmp_meanx2 + (self.rho * tmp_d * (weight.data - tmp_w)).sum(1).sum(1).sum(1) for tmp_meanx2, tmp_d, tmp_w in zip(self.pre_meanx2, self.pre_dmeanx2dw, self.pre_weight)])
            sigma2_all = meanx2_all - torch.pow(mu_all, 2)

            # with considering count
            re_mu_all = mu_all.clone()
            re_meanx2_all = meanx2_all.clone()
            re_mu_all[sigma2_all < 0] = 0
            re_meanx2_all[sigma2_all < 0] = 0
            count = (sigma2_all >= 0).sum(dim=0).float()
            mu = re_mu_all.sum(dim=0) / count
            sigma2 = re_meanx2_all.sum(dim=0) / count - torch.pow(mu, 2)

            self.pre_mu = [cur_mu.detach(), ] + self.pre_mu[:(self.buffer_num - 1)]
            self.pre_meanx2 = [cur_meanx2.detach(), ] + self.pre_meanx2[:(self.buffer_num - 1)]
            self.pre_dmudw = [dmudw.detach(), ] + self.pre_dmudw[:(self.buffer_num - 1)]
            self.pre_dmeanx2dw = [dmeanx2dw.detach(), ] + self.pre_dmeanx2dw[:(self.buffer_num - 1)]

            tmp_weight = torch.zeros_like(weight.data)
            tmp_weight.copy_(weight.data)
            self.pre_weight = [tmp_weight.detach(), ] + self.pre_weight[:(self.buffer_num - 1)]

        else:
            x = y
            mu = x.mean(dim=1)
            cur_mu = mu
            sigma2 = x.var(dim=1)
            cur_sigma2 = sigma2

        if not self.training or self.FROZEN:
            y = y - self.running_mean.view(-1, 1)
            # TODO: outside **0.5?
            if self.out_p:
                y = y / (self.running_var.view(-1, 1) + self.eps)**.5
            else:
                y = y / (self.running_var.view(-1, 1)**.5 + self.eps)
            
        else:
            if self.track_running_stats is True:
                with torch.no_grad():
                    self.running_mean = (1 - self.momentum) * self.running_mean + self.momentum * cur_mu
                    self.running_var = (1 - self.momentum) * self.running_var + self.momentum * cur_sigma2
            y = y - mu.view(-1, 1)
            # TODO: outside **0.5?
            if self.out_p:
                y = y / (sigma2.view(-1, 1) + self.eps)**.5
            else:
                y = y / (sigma2.view(-1, 1)**.5 + self.eps)

        y = self.weight.view(-1, 1) * y + self.bias.view(-1, 1)
        return y.view(return_shape).transpose(0, 1)

    def extra_repr(self):
        return '{num_features}, eps={eps}, momentum={momentum}, affine={affine}, ' \
               'buffer={max_buffer_num}, burnin={burnin}, ' \
               'track_running_stats={track_running_stats}'.format(**self.__dict__)

Normalization总结

归一化的目的是让输入的数据具有相同的分布,便于网络的训练,但具有相同的分布,但这些方法都有各自的优点,BN的优点利用随机分批抽样引起的不确定性,GN利用通道分组维度,IN利用空间维度,LN利用通道维度,他们都在各自的计算区间上进行归一化。CBN采用泰勒去估计时序上的BN参数,同样利用随机分批抽样引起的不确定性。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值