前言
最近看到Group Normalization
的论文,主要提到了四个特征归一化方法:Batch Norm
、Layer Norm
、Instance Norm
、Group Norm
。此外,论文还提到了Local Response Normalization(LRN)
、Weight Normalization(WN)
、Batch Renormalization(BR)
。
国际惯例,参考博客:
Group_Normalization-Tensorflow
动机
BN
是在小批数据中用均值和方差归一化,能够保证很深的网络能够收敛,但是BN
需要足够大的batch size
,比较小的batch
对批量数据的统计特征估算不准确,降低BN
的batch size
就会提升模型误差。
Group
的思想有很多:AlexNet
将模型部署到两块GPU
;ResNeXt
测试了depth
、width
、groups
对网络的效果,建议在相似计算消耗的前提下,较大的group
能提升准确率;MobileNet
和Xception
测试了depth-wise
卷积的效果,也就是group
数与channel
数相同;ShuffleNet
尝试了交换group
特征,即channel
随机交换。这些方法都包括将channel
划分为不同的group
,所以作者就想到了group
做Normalization
。
作者认为,DNN的channels
特征并非是非结构化的,比如第一层卷积,其中一个滤波器与他的水平翻转滤波器,对同一张图片的响应,可能得到相似的分布。如果第一层卷积近似学习到了这对滤波器,那么这些滤波器对应的channel
就可以被一起归一化了。文章还说明了,除了类似这样的卷积核可以导致group
,其它的比如频率、形状、亮度、纹理等,都可能具有联系,都可以被group
。
理论
通常归一化的标准公式就是:
x
^
=
x
−
μ
i
σ
i
\hat{x}=\frac{x-\mu_i}{\sigma_i}
x^=σix−μi
其中
μ
\mu
μ是均值,
σ
\sigma
σ是方差,假设均值和方差都是从集合
S
i
S_i
Si中计算得到的,那么
μ
i
=
1
m
∑
k
∈
S
i
x
k
σ
i
=
1
m
∑
k
∈
S
i
(
x
k
−
μ
i
)
2
+
ϵ
\begin{aligned} \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} \end{aligned}
μiσi=m1k∈Si∑xk=m1k∈Si∑(xk−μi)2+ϵ
假设其中某个卷积层的特征图树木为
(
N
,
C
,
H
,
W
)
(N,C,H,W)
(N,C,H,W),分别代表批中样本索引、特征图通道索引、特征图高、宽,设它们的索引是
(
N
i
,
C
j
,
H
,
W
)
(N_i,C_j,H,W)
(Ni,Cj,H,W)代表第
i
i
i个样本的第
j
j
j个特征图。
那么
Batch Norm
对应的计算均值和方差的数据集合为: S i = { k ∣ k C = C i } S_i= \{k|k_C=C_i\} Si={k∣kC=Ci} ;意思是将当前批所有数据的具有相同通道索引的特征图划分为一组,每组单独归一化,这样组集合就是:
( N , C 1 , H , W ) , ( N , C 2 , H , W ) , ⋯ , ( N , C j , H , W ) , ⋯ (N,C_1,H,W),(N,C_2,H,W),\cdots,(N,C_j,H,W),\cdots (N,C1,H,W),(N,C2,H,W),⋯,(N,Cj,H,W),⋯Layer Norm
对应的计算均值和方差的数据集合为: S i = { k ∣ k N = N i } S_i= \{k|k_N = N_i \} Si={k∣kN=Ni};意思是将当前批每个数据的所有通道划分为一组,每组单独归一化,这样组集合就是:
( N 1 , C , H , W ) , ( N 2 , C , H , W ) , ⋯ , ( N i , C , H , W ) , ⋯ (N_1,C,H,W),(N_2,C,H,W),\cdots,(N_i,C,H,W),\cdots (N1,C,H,W),(N2,C,H,W),⋯,(Ni,C,H,W),⋯Instance Norm
对应的计算均值和方差的数据集合为: S = { k ∣ k n = N i , k C = C j } S=\{k|k_n=N_i,k_C=C_j\} S={k∣kn=Ni,kC=Cj};意思是将当前批每个数据的每个通道单独划分一组,也就是每个特征图自己归一化,这样组集合就是:
( N 1 , C 1 , H , W ) , ( N 2 , C 1 , H , W ) , ⋯ , ( N i , C j , H , W ) , ⋯ (N_1,C_1,H,W),(N_2,C_1,H,W),\cdots,(N_i,C_j,H,W),\cdots (N1,C1,H,W),(N2,C1,H,W),⋯,(Ni,Cj,H,W),⋯Group Norm
对应的计算均值和方差的数据集合为: S = { k ∣ k N = N i , ⌊ k C C / G ⌋ = ⌊ C i C / G ⌋ } S=\{k| k_N=N_i, \lfloor \frac{k_C}{C/G} \rfloor = \lfloor \frac{C_i}{C/G} \rfloor \} S={k∣kN=Ni,⌊C/GkC⌋=⌊C/GCi⌋};意思是将每个样本对应的所有通道划分为 G G G组,每组单独归一化,假设每组被划分后有两个通道,组集合就是:
第一组: ( N 1 , C 1 , H , W ) , ( N 1 , C 2 , H , W ) (N_1,C_1,H,W),(N_1,C_2,H,W) (N1,C1,H,W),(N1,C2,H,W)
⋮ \vdots ⋮
第p组: ( N i , C j , H , W ) , ( N i , C j + 1 , H , W ) (N_i,C_j,H,W),(N_i,C_{j+1},H,W) (Ni,Cj,H,W),(Ni,Cj+1,H,W)
⋮ \vdots ⋮
当然,为了弥补损失掉的表达能力,上述所有的Normalization
方法都必须学习一个线性变换:
y
i
=
γ
x
i
^
+
β
y_i=\gamma \hat{x_i}+\beta
yi=γxi^+β
其中 γ \gamma γ和 β \beta β是可训练的缩放与偏移值,是针对每个通道的。
代码
第三方实现
github
上有人在tensorflow
中实现过,戳这里
def group_norm(x, G=32, eps=1e-5, scope='group_norm') :
with tf.variable_scope(scope) :
N, H, W, C = x.get_shape().as_list()
G = min(G, C)
x = tf.reshape(x, [N, H, W, G, C // G])
mean, var = tf.nn.moments(x, [1, 2, 4], keep_dims=True)
x = (x - mean) / tf.sqrt(var + eps)
gamma = tf.get_variable('gamma', [1, 1, 1, C], initializer=tf.constant_initializer(1.0))
beta = tf.get_variable('beta', [1, 1, 1, C], initializer=tf.constant_initializer(0.0))
x = tf.reshape(x, [N, H, W, C]) * gamma + beta
return x
调用方法也很简单:
from ops import *
x = conv(x)
x = group_norm(x)
tensorflow官方实现
官方实现戳这里
定义的函数:
def group_norm(inputs,
groups=32,
channels_axis=-1,
reduction_axes=(-3, -2),
center=True,
scale=True,
epsilon=1e-6,
activation_fn=None,
param_initializers=None,
reuse=None,
variables_collections=None,
outputs_collections=None,
trainable=True,
scope=None,
mean_close_to_zero=False):
分组的核心代码:
# Manually broadcast the parameters to conform to the number of groups.
params_shape_broadcast = ([1] * len(axes_before_channels) +
[groups, channels // groups] +
[1] * len(axes_after_channels))
# Reshape the input by the group within the channel dimension.
inputs_shape = (axes_before_channels + [groups, channels // groups] +
axes_after_channels)
inputs = array_ops.reshape(inputs, inputs_shape)
可以发现,不管是个人实现,还是官方实现,最主要的就是对channels
进行groups
分组。
后记
GN
主要就是针对batch size
时,BN
归一化不准确而提出的,如果我们的机器能满足batch size
足够大,那就没必要用GN
了。
博客已同步至微信公众号,不定时更新计算机视觉、机器学习、深度学习理论与代码实现,有问题欢迎评论或者微信公众号私聊