教程中使用BN后选择将学习率提高了,这是为什么呢?
我的一个猜想是:BN使得函数的值域变小,成为一个更小的区间,而自变量的范围不变,因此学习的步幅变大在值域上的移动并不会太多,但搜索次数可以变少,更便于找到最优解。是这样吗?
俺觉得有道理!紫薯布丁紫薯布丁紫薯布丁紫薯布丁
批量归一化的原理:
批量归一化的手动实现:
class BatchNorm(nn.Module):
def __init__(self, features: int, epsilon=1e-5, momentum=0.9):
"""
批量归一化层
通过每个小批量的均值 running_mean 和方差 running_var 归一化数据,并使用拉伸幅度 γ (gamma) 和偏移参数 β (beta) 学习并
恢复数据的分布特性。
:param features: 特征数。即,全连接层的特征数,或卷积层的通道数
:param epsilon: 小常数。保证在计算标准差时,方差不为零,避免除零错误
:param momentum: 动量系数。每个小批量的均值和方差更新时,依赖于历史统计量的程度,取值范围为 [0, 1]
"""
assert 0 <= momentum <= 1, '动量系数的取值范围为 [0, 1]'
super().__init__()
self.epsilon = epsilon
self.momentum = momentum
self.gamma = nn.Parameter(torch.ones(features)) # 注册可学习参数 gamma:拉伸幅度
self.beta = nn.Parameter(torch.zeros(features)) # 注册可学习参数 beta:偏移
self.register_buffer('running_mean', torch.zeros(features)) # 注册模型状态参数全局均值 running_mean 到缓冲区
self.register_buffer('running_var', torch.ones(features)) # 注册模型状态参数全局方差 running_var 到缓冲区
def forward(self, x: Tensor) -> Tensor:
"""根据输入数据的维度,进行批量归一化"""
if x.dim() == 2: # 输入数据来自全连接层的输出 (batch_size, features)
return self._batch_norm_fc(x)
elif x.dim() == 4: # 输入数据来自卷积层的输出 (batch_size, channels, height, width)
return self._batch_norm_conv(x)
else:
raise ValueError(f'暂不支持的输入形状:{x.shape}')
def _batch_norm_fc(self, x: Tensor) -> Tensor:
"""
对来自全连接层的输入进行批量归一化
计算各个特征的均值与方差(有偏估计,除以 n 而不是 n-1)
"""
if self.training:
mean = x.mean(dim=0, keepdim=True)
var = x.var(dim=0, keepdim=True, unbiased=False)
with torch.no_grad():
self.running_mean = (1 - self.momentum) * self.running_mean + self.momentum * mean
self.running_var = (1 - self.momentum) * self.running_var + self.momentum * var
else:
mean = self.running_mean
var = self.running_var
x_normalized = (x - mean) / torch.sqrt(var + self.epsilon)
out = self.gamma * x_normalized + self.beta
return out
def _batch_norm_conv(self, x: Tensor) -> Tensor:
"""
对来自卷积层的输入进行批量归一化
计算各个通道的均值与方差(有偏估计,除以 n 而不是 n-1)
"""
if self.training:
mean = x.mean(dim=(0, 2, 3), keepdim=True)
var = x.var(dim=(0, 2, 3), keepdim=True, unbiased=False)
with torch.no_grad():
self.running_mean = (1 - self.momentum) * self.running_mean + self.momentum * mean.squeeze()
self.running_var = (1 - self.momentum) * self.running_var + self.momentum * var.squeeze()
else:
mean = self.running_mean[None, :, None, None]
var = self.running_var[None, :, None, None]
x_normalized = (x - mean) / torch.sqrt(var + self.epsilon)
out = self.gamma[None, :, None, None] * x_normalized + self.beta[None, :, None, None]
return out
他这里面的讲解是这样的,但我觉得不太合理,我觉得应该精确平均到每一个特征点样本间的差距,应该是沿着batch维度来求均值。。我记得好像是andrew ng课里面还是哪里说的沿着batch维度做平均,得到CHW的形状
为什么训练的时候不用移动平均值来做BN啊,这样不是更符合整体分布吗??好奇怪。。
个人认为是模型每经过一轮训练,bn层的移动平均值和移动方差以及缩放偏差参数都会改变,而测试集未经过模型训练,这些参数不能完全代表测试集的特征分布,导致输出结果波动。