1. 对数据部分做归一化
主要且常用的归一化操作有BN,LN,IN,GN,示意图如图所示。
图中的蓝色部分,表示需要归一化的部分。其中两维 C C C和 N N N分别表示 c h a n n e l channel channel和 b a t c h batch batch s i z e size size,第三维表示 H H H, W W W,可以理解为该维度大小是 H ∗ W H*W H∗W,也就是拉长成一维,这样总体就可以用三维图形来表示。可以看出 B N BN BN的计算和 b a t c h batch batch s i z e size size相关(蓝色区域为计算均值和方差的单元),而 L N LN LN、 B N BN BN和 G N GN GN的计算和 b a t c h batch batch s i z e size size无关。同时 L N LN LN和 I N IN IN都可以看作是 G N GN GN的特殊情况( L N LN LN是 g r o u p group group=1时候的 G N GN GN, I N IN IN是 g r o u p = C group=C group=C时候的 G N GN GN)。
Batch Normalization;
B N BN BN的简单计算步骤为:
-
沿着通道计算每个batch的均值 μ = 1 m ∑ i = 1 m x i \mu=\frac{1}{m} \sum_{i=1}^{m} x_{i} μ=m1∑i=1mxi。
-
沿着通道计算每个 b a t c h batch batch的方差 δ 2 = 1 m ∑ i = 1 m ( x i − μ B ) 2 \delta^{2}=\frac{1}{m} \sum_{i=1}^{m}\left(x_{i}-\mu_{\mathcal{B}}\right)^{2} δ2=m1∑i=1m(xi−μB)2。
-
对x做归一化,
-
加入缩放和平移变量 γ \gamma γ和 β \beta β ,归一化后的值, y i ← γ x ^ i + β y_{i} \leftarrow \gamma \widehat{x}_{i}+\beta yi←γx i+β
B N BN BN适用于判别模型中,比如图片分类模型。因为 B N BN BN注重对每个 b a t c h batch batch进行归一化,从而保证数据分布的一致性,而判别模型的结果正是取决于数据整体分布。但是 B N BN BN对 b a t c h s i z e batchsize batchsize的大小比较敏感,由于每次计算均值和方差是在一个 b a t c h batch batch上,所以如果 b a t c h s i z e batchsize batchsize太小,则计算的均值、方差不足以代表整个数据分布。(其代码见下BN的前向与反向的代码详解!)
在训练过程之中,我们主要是通过滑动平均这种Trick的手段来控制变量更新的速度。
def batchnorm_forward(x, gamma, beta, bn_param):
"""
Input:
- x: (N, D)维输入数据
- gamma: (D,)维尺度变化参数
- beta: (D,)维尺度变化参数
- bn_param: Dictionary with the following keys:
- mode: 'train' 或者 'test'
- eps: 一般取1e-8~1e-4
- momentum: 计算均值、方差的更新参数
- running_mean: (D,)动态变化array存储训练集的均值
- running_var:(D,)动态变化array存储训练集的方差
Returns a tuple of:
- out: 输出y_i(N,D)维
- cache: 存储反向传播所需数据
"""
mode = bn_param['mode']
eps = bn_param.get('eps', 1e-5)
momentum = bn_param.get('momentum', 0.9)
N, D = x.shape
# 动态变量,存储训练集的均值方差
running_mean = bn_param.get('running_mean', np.zeros(D, dtype=x.dtype))
running_var = bn_param.get('running_var', np.zeros(D, dtype=x.dtype))
out, cache = None, None
# TRAIN 对每个batch操作
if mode == 'train':
sample_mean = np.mean(x, axis = 0)
sample_var = np.var(x, axis = 0)
x_hat = (x - sample_mean) / np.sqrt(sample_var + eps)
out = gamma * x_hat + beta
cache = (x, gamma, beta, x_hat, sample_mean, sample_var, eps)
#滑动平均(影子变量)这种Trick的引入,目的是为了控制变量更新的速度,防止变量的突然变化对变量的整体影响,这能提高模型的鲁棒性。
running_mean = momentum * running_mean + (1 - momentum) * sample_mean
running_var = momentum * running_var + (1 - momentum) * sample_var
# TEST:要用整个训练集的均值、方差
elif mode == 'test':
x_hat = (x - running_mean) / np.sqrt(running_var + eps)
out = gamma * x_hat + beta
else:
raise ValueError('Invalid forward batchnorm mode "%s"' % mode)
bn_param['running_mean'] = running_mean
bn_param['running_var'] = running_var
return out, cache
下面来一个背诵版本:
因此, B N BN BN的反向传播代码如下:
def batchnorm_backward(dout, cache):
"""
Inputs:
- dout: 上一层的梯度,维度(N, D),即 dL/dy
- cache: 所需的中间变量,来自于前向传播
Returns a tuple of:
- dx: (N, D)维的 dL/dx
- dgamma: (D,)维的dL/dgamma
- dbeta: (D,)维的dL/dbeta
"""
x, gamma, beta, x_hat, sample_mean, sample_var, eps = cache
N = x.shape[0]
dgamma = np.sum(dout * x_hat, axis = 0)
dbeta = np.sum(dout, axis = 0)
dx_hat = dout * gamma
dsigma = -0.5 * np.sum(dx_hat * (x - sample_mean), axis=0) * np.power(sample_var + eps, -1.5)
dmu = -np.sum(dx_hat / np.sqrt(sample_var + eps), axis=0) - 2 * dsigma*np.sum(x-sample_mean, axis=0)/ N
dx = dx_hat /np.sqrt(sample_var + eps) + 2.0 * dsigma * (x - sample_mean) / N + dmu / N
return dx, dgamma, dbeta
那么为啥要用 B N BN BN呢? B N BN BN的作用如下:
-
B N BN BN加快网络的训练与收敛的速度
在深度神经网络中中,如果每层的数据分布都不一样的话,将会导致网络非常难收敛和训练。如果把每层的数据都在转换在均值为零,方差为1 的状态下,这样每层数据的分布都是一样的训练会比较容易收敛。
-
控制梯度爆炸防止梯度消失
以 s i g m o i d sigmoid sigmoid函数为例, s i g m o i d sigmoid sigmoid函数使得输出在 [ 0 , 1 ] [0,1] [0,1]之间,实际上当 输入过大或者过小,经过sigmoid函数后输出范围就会变得很小,而且反向传播时的梯度也会非常小,从而导致梯度消失,同时也会导致网络学习速率过慢;同时由于网络的前端比后端求梯度需要进行更多次的求导运算,最终会出现网络后端一直学习,而前端几乎不学习的情况。Batch Normalization (BN) 通常被添加在每一个全连接和激励函数之间,使数据在进入激活函数之前集中分布在0值附近,大部分激活函数输入在0周围时输出会有加大变化。
同样,使用了 B N BN BN之后,可以使得权值不会很大,不会有梯度爆炸的问题。
-
防止过拟合
在网络的训练中,BN的使用使得一个 m i n i b a t c h minibatch minibatch中所有样本都被关联在了一起,因此网络不会从某一个训练样本中生成确定的结果,即同样一个样本的输出不再仅仅取决于样本的本身,也取决于跟这个样本同属一个 b a t c h batch batch的其他样本,而每次网络都是随机取 b a t c h batch batch,比较多样,可以在一定程度上避免了过拟合。
Instance Normalization
IN适用于生成模型中,比如图片风格迁移。因为图片生成的结果主要依赖于某个图像实例,所以对整个 b a t c h batch batch归一化不适合图像风格化中,在风格迁移中使用$Instance $ N o r m a l i z a t i o n Normalization Normalization。不仅可以加速模型收敛,并且可以保持每个图像实例之间的独立。
当然,其前向反向的推导与 B N BN BN相似,无非是维度的问题了~
Instance Norm代码如下所示:
def Instancenorm(x, gamma, beta):
# x_shape:[B, C, H, W]
results = 0.
eps = 1e-5
x_mean = np.mean(x, axis=(2, 3), keepdims=True)
x_var = np.var(x, axis=(2, 3), keepdims=True0)
x_normalized = (x - x_mean) / np.sqrt(x_var + eps)
results = gamma * x_normalized + beta
return results
Layer Normalization
L N LN LN是指对同一张图片的同一层的所有通道进行 N o r m a l i z a t i o n Normalization Normalization操作。与上面的计算方式相似,计算均值与方差,在计算缩放和平移变量 γ \gamma γ和 β \beta β。 L N LN LN 主要用在 N L P NLP NLP任务中,当然,像 T r a n s f o r m e r Transformer Transformer中存在的就是 L N LN LN。
def Layernorm(x, gamma, beta):
# x_shape:[B, C, H, W]
results = 0.
eps = 1e-5
x_mean = np.mean(x, axis=(1, 2, 3), keepdims=True)
x_var = np.var(x, axis=(1, 2, 3), keepdims=True0)
x_normalized = (x - x_mean) / np.sqrt(x_var + eps)
results = gamma * x_normalized + beta
return results
为什么RNN中不使用BN?
R N N RNN RNN可以展开成一个隐藏层共享参数的 M L P MLP MLP,随着时间片的增多,展开后的 M L P MLP MLP的层数也在增多,最终层数由输入数据的时间片的数量决定,所以 R N N RNN RNN是一个动态的网络。在 R N N RNN RNN网络中,一个 b a t c h batch batch的数据中,通常各个样本的长度都是不同的。往往在最后时刻,只有少量样本有数据,基于这个样本的统计信息不能反映全局分布,所以这时 B N BN BN的效果并不好。
而当将 L N LN LN添加到 C N N CNN CNN之后,实验结果发现 L N LN LN破坏了卷积学习到的特征,模型无法收敛,所以在 C N N CNN CNN之后使用 B N BN BN是一个更好的选择。
对于 L N LN LN与 B N BN BN 而言, B N BN BN取的是不同样本的同一个特征,而 L N LN LN取的是同一个样本的不同特征。在 B N BN BN和 L N LN LN都能使用的场景中, B N BN BN的效果一般优于 L N LN LN,原因是基于不同数据,同一特征得到的归一化特征更不容易损失信息。但是有些场景是不能使用 B N BN BN的,例如 b a t c h s i z e batchsize batchsize较小或者在 R N N RNN RNN中,这时候可以选择使用 L N LN LN, L N LN LN得到的模型更稳定且起到正则化的作用。 R N N RNN RNN能应用到小批量和 R N N RNN RNN中是因为 L N LN LN的归一化统计量的计算是和 b a t c h s i z e batchsize batchsize没有关系的。
Group Normalization
G r o u p Group Group N o r m a l i z a t i o n Normalization Normalization( G N GN GN)是针对 B a t c h Batch Batch $ Normalization ( B N ) 在 (BN)在 (BN)在batch$ $ size 较 小 时 错 误 率 较 高 而 提 出 的 改 进 算 法 , 因 为 较小时错误率较高而提出的改进算法,因为 较小时错误率较高而提出的改进算法,因为BN 层 的 计 算 结 果 依 赖 当 前 层的计算结果依赖当前 层的计算结果依赖当前batch 的 数 据 , 当 的数据,当 的数据,当batch$ $ size 较 小 时 ( 比 如 2 、 4 这 样 ) , 该 较小时(比如2、4这样),该 较小时(比如2、4这样),该batch$数据的均值和方差的代表性较差,因此对最后的结果影响也较大。
其中, G N GN GN是将通道数 C C C分成 G G G份,每份 C / / G C//G C//G,当 G = 1 G=1 G=1时,每份 G G G个,所以为一整块的 C C C,即为 L N LN LN 。当 G = C G=C G=C时,每份只有 1 1 1个,所以为 I N IN IN。
G N GN GN是指对同一张图片的同一层的某几个(不是全部)通道一起进行 N o r m a l i z a t i o n Normalization Normalization操作。这几个通道称为一个 G r o u p Group Group。计算相应的均值以及方差,计算缩放和平移变量 γ \gamma γ和 β \beta β。
其代码如下所示:
def GroupNorm(x, gamma, beta, G=16):
# x_shape:[B, C, H, W]
# gamma, beta, scale, offset : [1, c, 1, 1]
# G: num of groups for GN
results = 0.
eps = 1e-5
x = np.reshape(x, (x.shape[0], G, x.shape[1]/16, x.shape[2], x.shape[3]))
x_mean = np.mean(x, axis=(2, 3, 4), keepdims=True)
x_var = np.var(x, axis=(2, 3, 4), keepdims=True0)
x_normalized = (x - x_mean) / np.sqrt(x_var + eps)
results = gamma * x_normalized + beta
return results
Switchable Normalization
S N SN SN中表明了一个观点: B N BN BN层的成功和协方差什么的没有关联!证明这种层输入分布稳定性与 B N BN BN 的成功几乎没有关系。相反,实验发现 B N BN BN 会对训练过程产生更重要的影响:它使优化解空间更加平滑了。这种平滑使梯度更具可预测性和稳定性,从而使训练过程更快。
S N SN SN的具体做法可以从图中看出来:
论文中认为:
-
第一,归一化虽然提高模型泛化能力,然而归一化层的操作是人工设计的。在实际应用中,解决不同的问题原则上需要设计不同的归一化操作,并没有一个通用的归一化方法能够解决所有应用问题;
-
第二,一个深度神经网络往往包含几十个归一化层,通常这些归一化层都使用同样的归一化操作,因为手工为每一个归一化层设计操作需要进行大量的实验。
而与强化学习不同, S N SN SN使用可微分学习,为一个深度网络中的每一个归一化层确定合适的归一化操作。
S N SN SN算法是为三组不同的 μ k \mu_{k} μk 以及 σ k \sigma_{k} σk 分别学习三个总共6个标量值( w k w_{k} wk 和 w k ′ w_{k}^{\prime} wk′ ), h n c i j h_{n c i j} hncij表示一个输入维度为 n , c , i , j n,c,i,j n,c,i,j的特征图, h ^ n c i j \hat{h}_{n c i j} h^ncij为归一化之后的特征图。
其中 Ω = { i n , l n , b n } \Omega=\{i n, l n, b n\} Ω={
in,ln,bn} 。在计算 ( μ ln , σ ln ) (\mu_{\ln }, \sigma_{\ln }) (μln,σln) 和 ( μ b n , σ b n ) (\mu_{b n}, \sigma_{b n}) (μbn,σbn) 时,我们可以使用 ( μ i n , σ i n ) (\mu_{i n}, \sigma_{i n}) (μin,σin) 作为中间变量以减少计算量。
μ i n = 1 H W ∑ i , j H , W h n c i j , σ i n 2 = 1 H W ∑ i , j H , W ( h n c i j − μ i n ) 2 \mu_{\mathrm{in}}=\frac{1}{H W} \sum_{i, j}^{H, W} h_{n c i j}, \quad \sigma_{\mathrm{in}}^{2}=\frac{1}{H W} \sum_{i, j}^{H, W}\left(h_{n c i j}-\mu_{\mathrm{in}}\right)^{2} μin=HW1∑i,jH,Whncij,σin2=HW1∑i,jH,W(hncij−μin)2
μ l n = 1 C ∑ c = 1 C μ i n , σ l n 2 = 1 C ∑ c = 1 C ( σ i n 2 + μ i n 2 ) − μ l n 2 \mu_{\mathrm{ln}}=\frac{1}{C} \sum_{c=1}^{C} \mu_{\mathrm{in}}, \quad \sigma_{\mathrm{ln}}^{2}=\frac{1}{C} \sum_{c=1}^{C}\left(\sigma_{\mathrm{in}}^{2}+\mu_{\mathrm{in}}^{2}\right)-\mu_{\mathrm{ln}}^{2} μln=C1∑c=1Cμin,σln2=C1∑c=1C(σin2+μin2)−μln2
μ b n = 1 N ∑ n = 1 N μ i n , σ b n 2 = 1 N ∑ n = 1 N ( σ i n 2 + μ i n 2 ) − μ b n 2 \mu_{\mathrm{bn}}=\frac{1}{N} \sum_{n=1}^{N} \mu_{\mathrm{in}}, \quad \sigma_{\mathrm{bn}}^{2}=\frac{1}{N} \sum_{n=1}^{N}\left(\sigma_{\mathrm{in}}^{2}+\mu_{\mathrm{in}}^{2}\right)-\mu_{\mathrm{bn}}^{2} μbn=N1∑n=1Nμin,σbn2=N1∑n=1N(σin2+μin2)−μbn2
w k w_{k} wk 是通过softmax计算得到的激活函数:
w k = e λ k ∑ z ∈ i n , ln , b n e λ z w_{k}=\frac{e^{\lambda_{k}}}{\sum_{z \in \mathrm{in}, \ln , \mathrm{bn}} e^{\lambda_{z}}} \quad wk=∑z∈in,ln,bneλzeλk and k ∈ { i n , ln , b n } \quad k \in\{\mathrm{in}, \ln , \mathrm{bn}\} k∈{
in,ln,bn}
代码如下:
def SwitchableNorm(x, gamma, beta, w_mean, w_var):
# x_shape:[B, C, H, W]
results = 0.
eps = 1e-5
mean_in = np.mean(x, axis=(2, 3), keepdims=True)
var_in = np.var(x, axis=(2, 3), keepdims=True)
mean_ln = np.mean(x, axis=(1, 2, 3), keepdims=True)
var_ln = np.var(x, axis=(1, 2, 3), keepdims=True)
mean_bn = np.mean(x, axis=(0, 2, 3), keepdims=True)
var_bn = np.var(x, axis=(0, 2, 3), keepdims=True)
mean = w_mean[0] * mean_in + w_mean[1] * mean_ln + w_mean[2] * mean_bn
var = w_var[0] * var_in + w_var[1] * var_ln + w_var[2] * var_bn
x_normalized = (x - mean) / np.sqrt(var + eps)
results = gamma * x_normalized + beta
return results
值得一提的是在测试的时候,在 S N SN SN的 B N BN BN部分,它使用的是一种叫做**批平均(**batch average)的方法,它分成两步:1.固定网络中的 S N SN SN层,从训练集中随机抽取若干个批量的样本,将输入输入到网络中;2.计算这些批量在特定SN层的 μ \mu μ 和 σ \sigma σ 的平均值,它们将会作为测试阶段的均值和方差。实验结果表明,在 S N SN SN中批平均的效果略微优于滑动平均。
2. 对模型权重做归一化
Weight Normalization
W e i g h t Weight Weight $ Normalization(WN) 是 在 权 值 的 维 度 上 做 的 归 一 化 。 是在权值的维度上做的归一化。 是在权值的维度上做的归一化。WN$做法是将权值向量 w w w在其欧氏范数和其方向上解耦成了参数向量 v v v和参数标量 g g g 后使用 S G D SGD SGD分别优化这两个参数。
W N WN WN也是和样本量无关的,所以可以应用在 b a t c h s i z e batchsize batchsize较小以及 R N N RNN RNN等动态网络中;另外 B N BN BN使用的基于 m i n i − b a t c h mini-batch mini−batch的归一化统计量代替全局统计量,相当于在梯度计算中引入了噪声。而 W N WN WN则没有这个问题,所以在生成模型与强化学习等噪声敏感的环境中 W N WN WN的效果也要优于 B N BN BN。
W N WN WN的计算过程:
对于神经网络而言,一个节点的计算过程可以表达为:
y = ϕ ( w ⋅ x + b ) y=\phi(\mathbf{w} \cdot \mathbf{x}+b) y=ϕ(w⋅x+b)
其中 w w w是与该神经元连接的权重,通过损失函数与梯度下降对网络进行优化的过程就是求解最优 w w w的过程。将 w w w的长度与方向解耦,可以将 w w w表示为:
w = g ∥ v ∥ v w=\frac{g}{\|v\|} v w=∥v∥gv
y = ϕ ( w ∗ x + b ) y=\phi(w * x+b) y=ϕ(w∗x+b)
w w w 为与该神经元连接的权重,通过损失函数与梯度下降对网络进行优化的过程就是求解最优 w w w 的
过程。将 w w w 的长度与方向解塊,可以将 w w w 表示为
w = g ∥ v ∥ v w=\frac{g}{\|v\|} v w=∥v∥gv
其中 g g g 为标量,其大小等于 w w w 的模长, v ∥ v ∥ \frac{v}{\|v\|} ∥v∥v 为与 w w w 同方向的单位向量,此时,原先训练过程 中 w w w 的学习转化为 g g g 和 v v v 的学习。假设损失函数以 L L L 表示,则 L L L 对 g g g 和 v v v 的梯度可以分别
表示为,
∇ g L = ∇ g w ∗ ( ∇ w L ) T = ∇ w L ∗ v T ∥ v ∥ \nabla_{g} L=\nabla_{g} w *\left(\nabla_{w} L\right)^{T}=\frac{\nabla_{w} L * v^{T}}{\|v\|} ∇gL=∇gw∗(∇wL)T=∥v∥∇wL∗vT
∇ v L = ∇ v w ∗ ∇ w L = ∂ g ∗ v ∥ v ∥ ∂ v ∗ ∇ w L = g ∗ ∥ v ∥ ∥ v ∥ 2 ∗ ∇ w L − g ∗ v ∗ ∂ ∥ v ∥ ∂ v ∥ v ∥ 2 ∗ ∇ w L \nabla_{v} L=\nabla_{v} w * \nabla_{w} L=\frac{\partial \frac{g * v}{\|v\|}}{\partial v} * \nabla_{w} L=\frac{g *\|v\|}{\|v\|^{2}} * \nabla_{w} L-\frac{g * v * \frac{\partial\|v\|}{\partial v}}{\|v\|^{2}} * \nabla_{w} L ∇vL=∇vw∗∇wL=∂v∂∥v∥g∗v∗∇wL=∥v∥2g∗∥v∥∗∇wL−∥v∥2g∗v∗∂v∂∥v∥∗∇wL
因为
∂ ∥ v ∥ v = ∂ ( v T ∗ v ) 0.5 ∂ v = 0.5 ∗ ( v T ∗ v ) − 0.5 ∗ ∂ ( v T ∗ v ) ∂ v = v ∥ v ∥ \frac{\partial\|v\|}{v}=\frac{\partial\left(v^{T} * v\right)^{0.5}}{\partial v}=0.5 *\left(v^{T} * v\right)^{-0.5} * \frac{\partial\left(v^{T} * v\right)}{\partial v}=\frac{v}{\|v\|} v∂∥v∥=∂v∂(vT∗v)0.5=0.5∗(vT∗v)−0.5∗∂v∂(vT∗v)=∥v∥v
所以
∇ v L = g ∥ v ∥ ∗ ∇ w L − g ∗ ∇ g L ∥ v ∥ 2 ∗ v = g ∥ v ∥ ∗ M w ∗ ∇ w L \nabla_{v} L=\frac{g}{\|v\|} * \nabla_{w} L-\frac{g * \nabla_{g} L}{\|v\|^{2}} * v=\frac{g}{\|v\|} * M_{w} * \nabla_{w} L ∇vL=∥v∥g∗∇wL−∥v∥2g∗∇gL∗v=∥v∥g∗Mw∗∇wL
其中 M w = I − w ∗ w T ∥ w ∥ 2 M_{w}=I-\frac{w * w^{T}}{\|w\|^{2}} Mw=I−∥w∥2w∗wT,与向量点乘可以投影任意向量至 w w w 的补空间。相对于原先的 ∇ w L , ∇ v L \nabla_{w} L, \quad \nabla_{v} L ∇wL,∇vL进行了 g ∥ v ∥ \frac{g}{\|v\|} ∥v∥g的缩放与 M w M_{w} Mw的投影。这两者都对优化过程起到作用。
对于 v v v而言, v new = v + Δ v v^{\text {new }}=v+\Delta v vnew =v+Δv, 因为 Δ v ∝ ∇ v L \Delta v \propto \nabla_{v} L Δv∝∇vL ,所以 Δ v \Delta v Δv 与 v v v 正交。
假设, ∥ Δ v ∥ ∥ v ∥ = c \frac{\|\Delta v\|}{\|v\|}=c ∥v∥∥Δv∥=c, 则 ∥ v n e w ∥ = ∥ v ∥ 2 + c 2 ∥ v ∥ 2 = 1 + c 2 ∥ v ∥ ≥ ∥ v ∥ , ∇ v L \left\|v^{n e w}\right\|=\sqrt{\|v\|^{2}+c^{2}\|v\|^{2}}=\sqrt{1+c^{2}}\|v\| \geq\|v\|, \quad \nabla_{v} L ∥vnew∥=∥v∥2+c2∥v∥2=1+c2∥v∥≥∥v∥,∇vL 可以影响 v v v 模长的增长,同时 v v v 的模长也影响 ∇ v L \nabla_{v} L ∇vL 的大小。
因此,我们可以得到:
- g ∥ v ∥ \frac{g}{\|\mathbf{v}\|} ∥v∥g表明 W N WN WN会对权重梯度进行 g ∥ v ∥ \frac{g}{\|\mathbf{v}\|} ∥v∥g的缩放。
- M w ∇ w L M_{\mathbf{w}} \nabla_{\mathbf{w}} L Mw∇wL表明WN会将梯度投影到一个远离于 ∇ w L \nabla_{\mathbf{w}} L ∇wL的方向。
代码可以参考:
import torch.nn as nn
import torch.nn.functional as F
# 以一个简单的单隐层的网络为例
class Model(nn.Module):
def __init__(self, input_dim, output_dim, hidden_size):
super(Model, self).__init__()
# weight_norm
self.dense1 = nn.utils.weight_norm(nn.Linear(input_dim, hidden_size))
self.dense2 = nn.utils.weight_norm(nn.Linear(hidden_size, output_dim))
def forward(self, x):
x = self.dense1(x)
x = F.leaky_relu(x)
x = self.dense2(x)
return x
Spectral Normalization
首先看下这个图,了解下一个数学概念叫做Lipschitz 连续性:
L i p s c h i t z Lipschitz Lipschitz 条件限制了函数变化的剧烈程度,即函数的梯度。在一维空间中,很容易看出y=sin(x) 是1-Lipschitz的,它的最大斜率是 1。
如 y = x y=x y=x与 y = − x y=-x y=−x的斜率是 1 1 1与 − 1 -1 −1, s i n ( x ) sin(x) sin(x)求导是 c o s x ( x ) cosx(x) cosx(x),值域为 [ 0 , 1 ] [0,1] [0,1].
在 G A N GAN GAN中,假设我们有一个判别器 D : I → R D: I \rightarrow \mathbb{R} D:I→R ,其中 I I I是图像空间。如果判别器是 K − L i p s c h i t z c o n t i n u o u s K-Lipschitz continuous K−Lipschitzcontinuous 的,那么对图像空间中的任意 x 和 y ,有:
∥ D ( x ) − D ( y ) ∥ ≤ K ∥ x − y ∥ \|D(x)-D(y)\| \leq K\|x-y\| ∥D(x)−D(y)∥≤K∥x−y∥
其中 ∥ ⋅ ∥ \|\cdot\| ∥⋅∥为 L 2 L_{2} L2 norm,如果$ K$ 取到最小值,那么$ K$ 被称为$ Lipschitz$ $ constant$。
首先抛出结论:
矩阵 A A A除以它的 spectral norm ( A T A A^{T} A ATA最大特征值的开根号 λ 1 \sqrt{\lambda_{1}} λ1)可以使其具有1-Lipschitz continuity。
那么 S p e c t r a l Spectral Spectral N o r m a l i z a t i o n Normalization Normalization的具体做法如下:
-
将神经网络的每一层的参数 W W W 作$ SVD$ 分解,然后将其最大的奇异值限定为 1 1 1,满足 1 − L i p s c h i t z 1-Lipschitz 1−Lipschitz条件。
在每一次更新 W W W之后都除以 W W W最大的奇异值。 这样,每一层对输入 x x x 最大的拉伸系数不会超过 1 1 1。
经过Spectral Norm之后,神经网络的每一层 g l ( x ) g_{l}(x) gl(x) 权重,都满足
g l ( x ) − g l ( y ) x − y ≤ 1 \frac{g_{l}(x)-g_{l}(y)}{x-y} \leq 1 x−ygl(x)−gl(y)≤1
对于整个神经网络 f ( x ) = g N ( g N − 1 ( … g 1 ( x ) … ) ) f(x)=g_{N}\left(g_{N-1}\left(\ldots g_{1}(x) \ldots\right)\right) f(x)=gN(gN−1(…g1(x)…))自然也就满足利普希茨连续性了 -
在每一次训练迭代中,都对网络中的每一层都进行 S V D SVD SVD分解,是不现实的,尤其是当网络权重维度很大的时候。我们现在可以使用一种叫做 p o w e r power power i t e r a t i o n iteration iteration的算法。
Power iteration 是用来近似计算矩阵最大的特征值( d o m i n a n t dominant dominant $ eigenvalue$ 主特征值)和其对应的特征向量(主特征向量)的。
假设矩阵 A A A是一个 n n n x n n n的满秩的方阵,它的单位特征向量为 v 1 , v 2 , … v n v_{1}, v_{2}, … v_{n} v1,v2,…vn,对于的特征值为 λ 1 , λ 2 , … λ n \lambda_{1}, \lambda_{2}, …\lambda_{n} λ1,λ2,…λn。那么任意向量 x = x 1 ∗ v 1 + x 2 ∗ v 2 + … x n ∗ v n x=x_{1} * v_{1} + x_{2} * v_{2} + … x_{n} * v_{n} x=x1∗v1+x2∗v2+…xn∗vn。则有:
A x = A ( x 1 ⋅ ν 1 + x 2 ⋅ ν 2 + … + x n ⋅ ν n ) = x 1 ( A ν 1 ) + x 2 ( A ν 2 ) + … + x n ( A ν n ) = x 1 ( λ 1 ν 1 ) + x 2 ( λ 2 ν 2 ) + … + x n ( λ n ν n ) Ax=A(x1⋅ν1+x2⋅ν2+…+xn⋅νn)=x1(Aν1)+x2(Aν2)+…+xn(Aνn)=x1(λ1ν1)+x2(λ2ν2)+…+xn(λnνn)Ax=A(x1⋅ν1+x2⋅ν2+…+xn⋅νn)=x1(Aν1)+x2(Aν2)+…+xn(Aνn)=x1(λ1ν1)+x2(λ2ν2)+…+xn(λnνn)
我们通过 k k k次迭代:
A k x = x 1 ( λ 1 k ν 1 ) + x 2 ( λ 2 k ν 2 ) + … + x n ( λ n k ν n ) = λ 1 k [ x 1 ν 1 + x 2 ( λ 2 λ 1 ) k ν 2 + … + x n ( λ n λ 1 ) k ν n ] Akx=x1(λk1ν1)+x2(λk2ν2)+…+xn(λknνn)=λk1[x1ν1+x2(λ2λ1)kν2+…+xn(λnλ1)kνn]
同样,我们可以得到:
具体的代码实现过程中,可以随机初始化一个噪声向量代入公式 (13) 。由于每次更新参数的step size 很小,矩阵 W 的参数变化都很小,矩阵可以长时间维持不变。
因此,可以把参数更新的 s t e p step step 和求矩阵最大奇异值的 s t e p step step 融合在一起,即每更新一次权重 W W W ,更新一次和,并将矩阵归一化一次。
代码如下:
import torch
from torch.optim.optimizer import Optimizer, required
import torch.nn.functional as F
from torch import nn
from torch import Tensor
from torch.nn import Parameter
def l2normalize(v, eps=1e-12):
return v / (v.norm() + eps)
class SpectralNorm(nn.Module):
def __init__(self, module, name='weight', power_iterations=1):
super(SpectralNorm, self).__init__()
self.module = module
self.name = name
self.power_iterations = power_iterations
if not self._made_params():
self._make_params()
def _update_u_v(self):
u = getattr(self.module, self.name + "_u")
v = getattr(self.module, self.name + "_v")
w = getattr(self.module, self.name + "_bar")
height = w.data.shape[0]
for _ in range(self.power_iterations):
v.data = l2normalize(torch.mv(torch.t(w.view(height,-1).data), u.data))
u.data = l2normalize(torch.mv(w.view(height,-1).data, v.data))
# sigma = torch.dot(u.data, torch.mv(w.view(height,-1).data, v.data))
sigma = u.dot(w.view(height, -1).mv(v))
setattr(self.module, self.name, w / sigma.expand_as(w))
def _made_params(self):
try:
u = getattr(self.module, self.name + "_u")
v = getattr(self.module, self.name + "_v")
w = getattr(self.module, self.name + "_bar")
return True
except AttributeError:
return False
def _make_params(self):
w = getattr(self.module, self.name)
height = w.data.shape[0]
width = w.view(height, -1).data.shape[1]
u = Parameter(w.data.new(height).normal_(0, 1), requires_grad=False)
v = Parameter(w.data.new(width).normal_(0, 1), requires_grad=False)
u.data = l2normalize(u.data)
v.data = l2normalize(v.data)
w_bar = Parameter(w.data)
del self.module._parameters[self.name]
self.module.register_parameter(self.name + "_u", u)
self.module.register_parameter(self.name + "_v", v)
self.module.register_parameter(self.name + "_bar", w_bar)
def forward(self, *args):
self._update_u_v()
return self.module.forward(*args)
大家好,我是灿视。目前是位算法工程师 + 创业者 + 奶爸的时间管理者!
我曾在19,20年联合了各大厂面试官,连续推出两版《百面计算机视觉》,受到了广泛好评,帮助了数百位同学们斩获了BAT等大小厂算法Offer。现在,我们继续出发,持续更新最强算法面经。
我曾经花了4个月,跨专业从双非上岸华五软工硕士,也从不会编程到进入到百度与腾讯实习。
欢迎加我私信,点赞朋友圈,参加朋友圈抽奖活动。如果你想加入<百面计算机视觉交流群>,也可以私我。
转载:https://blog.csdn.net/weixin_38646522/article/details/116764640