欢迎来到theFlyer的博客—希望你有不一样的感悟
前言:好记性不如烂笔头,不得不承认记忆力严重下降,看了不少遍的东西还是记不住,还是需要在理解和实践。
一. Dropout
深度学习模型常常使用丢弃法(dropout)来应对过拟合问
题。丢弃法有⼀些不同的变体。本节中提到的丢弃法特指倒置丢弃法(inverted dropout)。
这是一个单隐藏层的多层感知机。其中,输入个数为4,隐藏单元个数为5,且隐藏单元的
h
i
=
f
(
x
1
w
1
+
x
2
w
2
+
x
3
w
3
+
x
4
w
4
+
b
i
)
h_i = f(x_1w_1+x_2w_2+x_3w_3+x_4w_4+b_i)
hi=f(x1w1+x2w2+x3w3+x4w4+bi)
这里
f
f
f是激活函数,
x
1
,
.
.
.
,
x
4
x_1,...,x_4
x1,...,x4是输入,隐藏层
i
i
i的权重参数为
w
1
,
.
.
.
,
w
4
w_1,...,w_4
w1,...,w4,偏差参数为
b
i
b_i
bi。使用Dropout时,隐藏单元有一定概率被丢弃掉。假设丢弃概率为p,那么有p的概率
h
i
h_i
hi被清零,有1-p得到概率
h
i
h_i
hi会除以1-p做拉伸。丢弃概率是超参数,设随机变量
ζ
i
\zeta_i
ζi为0和1的概率分布为p和1-p。使用Dropout时,我们重新计算新的隐藏单元
h
i
′
=
ζ
i
(
1
−
p
)
h
i
.
h_{i}^{'} = \frac {\zeta_i} {(1-p)} h_i.
hi′=(1−p)ζihi.由于
E
(
ζ
i
)
=
1
−
p
E (\zeta_i)=1-p
E(ζi)=1−p,因此
E
(
h
i
′
)
=
E
(
ζ
i
)
1
−
p
h
i
E (h_{i}^{'})=\frac {E (\zeta_i)}{1-p}h_i
E(hi′)=1−pE(ζi)hi
所以Dropout不改变其输入的期望值。当使用Dropout时,可能如上图的
h
2
h_2
h2和
h
5
h_5
h5被清零。这时输出值的计算不在依赖
h
2
h_2
h2和
h
5
h_5
h5,在反向传播时,与这两个隐藏单元相关的权重的梯度均为0。由于在训练中,隐藏层神经元的丢弃是随机的,即每一个神经元都可能被清零。这样输出层的计算都无法过度依赖隐藏层中任何一个神经元,从而在训练模型时起到正则化的作用,并可以用来应对过拟合。在测试模型时,我们为了拿到更加确定性的结果,一般不使用Dropout。
def dropout(X, drop_prob):
assert 0 <= drop_prob <= 1
keep_prob = 1 - drop_prob
# 这种情况下把全部元素都丢弃。
if keep_prob == 0:
return X.zeros_like()
mask = nd.random.uniform(0,1,X.shape) <keep_prob
return mask * X / keep_prob
二. BatchNomalization
全连接层和卷积层做批量归⼀化的⽅法稍有不同。
对全连接层做批量归⼀化
通常将批量归⼀化层置于全连接层中的仿射变换和激活函数之间。设全连接层的输⼊为 u,权重参数和偏差参数分别为 W 和 b,激活函数为ϕ。设批量归⼀化的操作符为 BN。那么,使⽤批量归⼀化的全连接层的输出为
ϕ
(
B
N
(
x
)
)
ϕ(BN(x))
ϕ(BN(x))其中批量归⼀化输⼊ x 由仿射变换
x
=
W
u
+
b
x = Wu + b
x=Wu+b得到。考虑⼀个由 m 个样本组成的小批量,仿射变换的输出为⼀个新的小批量
B
=
{
x
(
1
)
,
.
.
.
,
x
(
m
)
}
B = \{ x^{(1)} ,...,x^{(m)} \}
B={x(1),...,x(m)}。它们正是批量归⼀化层的输⼊。对于小批量B 中任意样本 x^{(i)}∈ R d ,1 ≤ i ≤ m,批量归⼀化层的输出同样是 d 维向量
y
(
i
)
=
B
N
(
x
(
i
)
)
,
y^{(i)} = BN(x^{(i)} ),
y(i)=BN(x(i)),可以由以下几步求得。首先,对小批量的 B 求均值和⽅差:
µ
B
←
1
m
∑
i
=
1
m
x
(
i
)
,
µ_B ← \frac {1} {m} \sum_{i=1}^mx^{(i)} ,
µB←m1i=1∑mx(i),
σ
B
2
←
1
m
∑
i
=
1
m
(
x
(
i
)
−
µ
B
)
2
,
σ^2_B ←\frac {1} {m}\sum_{i=1}^m(x^{(i)}-µ_B)^2 ,
σB2←m1i=1∑m(x(i)−µB)2,其中的平⽅计算是按元素求平⽅。接下来,我们使⽤按元素开⽅和按元素除法对
x
(
i
)
x^{(i)}
x(i) 标准化:
x
(
i
)
←
x
(
i
)
−
µ
B
σ
B
2
+
ϵ
x^{(i)}←\frac {x^{(i)}-µ_B} {\sqrt{σ^2_B + ϵ}}
x(i)←σB2+ϵx(i)−µB这⾥ ϵ > 0 是⼀个很小的常数,保证分⺟⼤于 0。在上⾯标准化的基础上,批量归⼀化层引⼊了两个可以学习的模型参数,拉升(scale)参数 γ 和偏移(shift)参数 β。这两个参数和 x (i) 形状
相同,皆为 d 维向量。它们与
x
(
i
)
x^{(i)}
x(i)分别做按元素乘法(符号 ⊙)和加法计算:
y
(
i
)
←
γ
⊙
x
^
(
i
)
+
β
.
y (i) ← γ ⊙ \hat{x} ^{(i)} + β.
y(i)←γ⊙x^(i)+β.
⾄此,得到了
x
(
i
)
x^{(i)}
x(i) 的批量归⼀化的输出
y
(
i
)
y^(i)
y(i) 。值得注意的是,可学习的拉升和偏移参数保留了不对 $ \hat{x} ^{(i)}$ 做批量归⼀化的可能:此时只需学出
γ
=
σ
B
2
+
ϵ
γ = \sqrt{σ^2_B + ϵ}
γ=σB2+ϵ 和
β
=
µ
B
β = µ_B
β=µB 。可以对此这样理解:如果批量归⼀化⽆益,理论上学出的模型可以不使⽤批量归⼀化。
对卷积层做批量归⼀化
对卷积层来说,批量归⼀化发⽣在卷积计算之后、应⽤激活函数之前。如果卷积计算输出多个通道,我们需要对这些通道的输出分别做批量归⼀化,且每个通道都拥有独⽴的拉升和偏移参数,且均为标量。设小批量中有 m 个样本。在单个通道上,假设卷积计算输出的⾼和宽分别为 p 和 q。我们需要对该通道中 m × p × q 个元素同时做批量归⼀化。对这些元素做标准化计算时,我们使⽤相同的均值和⽅差,即该通道中 m × p × q 个元素的均值和⽅差。
预测时的批量归⼀化
使⽤批量归⼀化训练时,我们可以将批量⼤小设的⼤⼀点,从而使批量内样本的均值和⽅差的计算都较为准确。将训练好的模型⽤来预测时,我们希望模型对于任意输⼊都有确定的输出。因此,单个样本的输出不应取决于批量归⼀化所需要的随机小批量中的均值和⽅差。⼀种常⽤的⽅法是通过移动平均估算整个训练数据集的样本均值和⽅差,并在预测时使⽤它们得到确定的输出。可⻅,和Dropout⼀样,批量归⼀化层在训练模式和预测模式下的计算结果也是不⼀样的。
def batch_norm(X,gamma,beta,moving_mean,moving_var,eps,momentum):
if not autogad.is_training():
#如果是预测模式,直接使用传入的移动平均所得的均值和方差
x_hat = (x -moving_mean) / nd.sqrt(moving_var +eps)
else:
assert len(X.shape) in (2,4)
if len(X.shape)==2:
#使用全连接层的情况,计算 特征维上的均值和方差。
mean = X.mean(axis=0)
var = ((x-mean)**2).mean(axis=0)
else:
#使用二维卷积层的情况,计算通道维上(axis=1)的均值和方差,这里需要
#保持x的形状以便后面可以做广播运算
mean = X.mean(axis =(0,2,3),kepdims=true)
var = ((x-mean)**2).mean(axis=(0,2,3),kepdims=True)
# 训练模式下用当前的均值和方差做标准化
X_hat=(x-mean) / nd.sqrt(var +eps)
# 更新移动平均的均值和方差
moving_mean = momentum * moving_mean+(1.0 - momentum) *mean
moving_var = momentum * moving_var+(1.0 - momentum) *var
Y = gamma* X_hat +beta
return Y,moving_mean,moving_var
接下来⾃定义⼀个 BatchNorm 层。它保存参与求梯度和迭代的拉升参数 gamma 和偏移参数 beta,同时也维护移动平均得到的均值和⽅差,以能够在模型预测时使⽤。BatchNorm 实例所需指定的 num_features 参数对于全连接层为输出个数,对于卷积层则为输出通道数。该实例所需指定的 num_dims 参数对于全连接层和卷积层分别为 2 和 4。
Class BatchNorm(nn.Block);
def __init__(self,num_features,num_dims,**kwargs):
super(BatchNorm.self).__init__(**kwargs)
if num_dim == 2:
shape = (1,num_features)
else:
shape = (1,num_features.1.1)
#参与梯度和迭代的拉升和偏移参数,分别初始化0和1.
self.gamma = self.params.get('gamma', shape=shape, init=init.One())
self.beta = self.params.get('beta', shape=shape, init=init.Zero())
# 不参与求梯度和迭代的变量,全在 CPU 上初始化成 0。
self.moving_mean = nd.zeros(shape)
self.moving_var = nd.zeros(shape)
def forward(self, X):
# 如果 X 不在 CPU 上,将 moving_mean 和 moving_var 复制到 X 所在设备上。
if self.moving_mean.context != X.context:
self.moving_mean = self.moving_mean.copyto(X.context)
self.moving_var = self.moving_var.copyto(X.context)
# 保存更新过的 moving_mean 和 moving_var。
Y, self.moving_mean, self.moving_var = batch_norm(X, self.gamma.data(), self.beta.data(), self.moving_mean,self.moving_var, eps=1e-5, momentum=0.9)
return Y
注释
参考MXnet文档
错过秋招,目前正在找工作,有点小愁。
~~学习如逆水行舟,不进则退。