u B = 1 B ∑ i ∈ B x i u_B=\frac{1}{B}\sum\limits_{i \in B}x_i uB=B1i∈B∑xi
σ B 2 = 1 ∣ B ∣ ∑ i ∈ B ( x i − u B ) 2 + ϵ \sigma^2_B=\frac{1}{|B|}\sum\limits_{i\in B}(x_i-u_B)^2+\epsilon σB2=∣B∣1i∈B∑(xi−uB)2+ϵ, ϵ \epsilon ϵ 是1个很小的数,防止方差为0
x i + 1 = γ x i − μ ^ B σ ^ B + β x_{i+1}=\gamma\frac{x_i-\hat{\mu}_B}{\hat{\sigma}_B}+\beta xi+1=γσ^Bxi−μ^B+β, B is mini_batch_data, γ \gamma γ是需要学习的方差, β \beta β是需要学习的期望, μ ^ B \hat{\mu}_B μ^B is mean, σ ^ B \hat{\sigma}_B σ^B is var,
import torch
from torch import nn
from d2l import torch as d2l
def batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentum):
'''
mini_batch_norm
:param X 这个层的输入
:param gamma 需要学习的方差
:param beta 需要学习的期望
:param moving_mean 整个数据集的期望,不是这个小批量数据的期望,做推理时使用
:param moving_var 整个数据集的方差,不是这个小批量数据的方差,做推理时使用
:param eps 有常用值,每个框架都不同,通常取1e-5
:param momentum 用于更新moving_mean和moving_var,有常用值,每个框架都不同,通常取0.9
'''
# 做推理的时候
# 没有开启梯度计算
if not torch.is_grad_enabled():
# 为什么仅仅使用全局的均值和全局方差,做推理可能就1张图片,没有批量的概念
X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)
else:
# x必须等于2或者4
# x=2,全连接层
# x=4,二维卷积层
assert len(X.shape) in (2, 4)
# x=2,全连接层,对特征求均值和方差
if len(X.shape) == 2:
# 默认,按那一维求均值,那一个维度消失
# 按第0维求均值,求每个特征的均值,作用在每个特征上
# !按第0维求均值,运算作用在第2维上
# mean.shape=1xn
mean = X.mean(dim=0)
# 求两个样本的方差
var = ((X - mean) ** 2).mean(dim=0)
# x=4,二维卷积层,对通道求均值和方差
else:
# 保留维度
# 0维,批量大小。1维,前一层输出通道数。2维,高。3维,宽
# !按第0维,2维,3维,求均值,运算作用在第1维上
# 1xnx1x1 四维
mean = X.mean(dim=(0, 2, 3), keepdim=True)
var = ((X - mean) ** 2).mean(dim=(0, 2, 3), keepdim=True)
# mean和var都是在当前mini_batch_data中求出的均值和方差
# X_hat=1xnx1x1
X_hat = (X - mean) / torch.sqrt(var + eps)
moving_mean = momentum * moving_mean + (1.0 - momentum) * mean
moving_var = momentum * moving_var + (1.0 - momentum) * var
# beta,直接把入参加入进来了
# moving_mean和moving_var只在推理的时候有用,训练的时候没用
# gamma.shape=beta.shape=1xnx1x1
Y = gamma * X_hat + beta
return Y, moving_mean.data, moving_var.data
class BatchNorm(nn.Module):
'''
BatchNorm层
'''
def __init__(self, num_features, num_dims):
'''
:param num_features=2,特征量。num_features=4,前1层输出通道数
:param num_dims=2,全连接层。num_dims=4,二维卷积层
'''
super().__init__()
if num_dims == 2:
shape = (1, num_features)
else:
shape = (1, num_features, 1, 1)
# 全1初始化需要学习的方差,如果全0,在公式中乘之后全0
self.gamma = nn.Parameter(torch.ones(shape))
# 全0初始化需要学习的均值
self.beta = nn.Parameter(torch.zeros(shape))
# 全1初始化整个数据集的方差
self.moving_var = torch.ones(shape)
# 全0初始化整个数据集的期望
self.moving_mean = torch.zeros(shape)
def forward(self, X):
# 小批量数据X和整体数据均值要放到同一设备上
if self.moving_mean.device != X.device:
self.moving_mean = self.moving_mean.to(X.device)
self.moving_var = self.moving_var.to(X.device)
Y, self.moving_mean, self.moving_var = batch_norm(
X, self.gamma, self.beta, self.moving_mean,
self.moving_var, eps=1e-5, momentum=0.9)
return Y
应用BatchNorm 于LeNet模型
net = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5),
# 6二维卷积层的输出通道数。num_dims=4 表示作用于二维卷积层的输出通道上
BatchNorm(6, num_dims=4),
nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, kernel_size=5),
# 16二维卷积层的输出通道数。num_dims=4 表示作用于二维卷积层的输出通道上
BatchNorm(16, num_dims=4),
nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Flatten(),
nn.Linear(16*4*4, 120), BatchNorm(120, num_dims=2),
nn.Sigmoid(),
nn.Linear(120, 84),
# 84特征量。num_dims=2 表示作用于全连接层的特征上
BatchNorm(84, num_dims=2),
nn.Sigmoid(),
nn.Linear(84, 10))
在Fashion-MNIST数据集上训练网络
lr, num_epochs, batch_size = 1.0, 10, 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
loss 0.270, train acc 0.900, test acc 0.796
25335.7 examples/sec on cuda:0
# 查看net第1层(batchNorm层)的参数,方差学习参数gamma和均值学习参数beta
net[1].gamma.reshape((-1,)), net[1].beta.reshape((-1,))
(tensor([2.4066, 2.6878, 3.8948, 0.4407, 2.3572, 4.0047], device='cuda:0',
grad_fn=),
tensor([ 0.6224, 0.5743, -3.8525, 0.8712, 2.7259, -2.4347], device='cuda:0',
grad_fn=))
简洁实现
net = nn.Sequential(
nn.Conv2d(1, 6, kernel_size=5),
# 6输出通道数
nn.BatchNorm2d(6),
nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Conv2d(6, 16, kernel_size=5),
# 16输出通道数
nn.BatchNorm2d(16),
nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),
nn.Flatten(),
nn.Linear(256, 120),
# 120特征量
nn.BatchNorm1d(120),
nn.Sigmoid(),
nn.Linear(120, 84),
# 84特征量
nn.BatchNorm1d(84),
nn.Sigmoid(),
nn.Linear(84, 10))
使用相同超参数来训练模型
d2l.train_ch6(net, train_iter, test_iter, num_epochs, lr, d2l.try_gpu())
loss 0.266, train acc 0.902, test acc 0.826
48070.5 examples/sec on cuda:0
总结
query
一旦模型稳定了,收敛就不会变慢
数值稳定性:
!!!数值稳定性:
MLP中可以用batch normalization
batch normalization
batch normalization收敛时间变短
调参num_epochs, batch_size, lr,
xxx normalization