how to

7.5.BN

Aug 6, 2024
notesjulyfun技术学习d2l
9 Minutes
1687 Words

mean 用法

ref: https://blog.csdn.net/qq_40714949/article/details/115485140

dim 写第几维,第几维就会被求和,并消失。

1
x = torch.Tensor([1, 2, 3, 4, 5, 6]).view(2, 3)
2
y_0 = torch.mean(x, dim=0)
3
y_1 = torch.mean(x, dim=1)
4
print(x)
5
print(y_0)
6
print(y_1)
1
tensor([[1., 2., 3.],
2
[4., 5., 6.]])
3
tensor([2.5000, 3.5000, 4.5000])
4
tensor([2., 5.])

BN 层回忆提纲

\mathrm{BN}(\mathbf{x}) = \boldsymbol{\gamma} \odot \frac{\mathbf{x} - \hat{\boldsymbol{\mu}}_\mathcal{B}}{\hat{\boldsymbol{\sigma}}_\mathcal{B}} + \boldsymbol{\beta}

BN 层对上一层的输出进行规范化到 0 均值和 1 方差,同时学习一个缩放系数 gamma(初始为 1)和平移系数 beta(初始为 0)。符合直觉地,全连接层的每个元素对样本维求均值,mean.shape = (元素数) 。卷积层的每个通道对(样本,宽,高)求均值,mean.shape = (通道数)

下面 gamma 和 beta 是待学习参数,momentum 是上一次均值的权重(并不会直接采纳本次求得的均值,形同低通滤波)。

非训练模式时,直接使用先前计算出来的 moving_mean, moving_var.

1
import torch
2
from torch import nn
3
from d2l import torch as d2l
4
5
6
def batch_norm(X, gamma, beta, moving_mean, moving_var, eps, momentum):
7
# 通过is_grad_enabled来判断当前模式是训练模式还是预测模式
8
if not torch.is_grad_enabled():
9
# 如果是在预测模式下,直接使用传入的移动平均所得的均值和方差
10
X_hat = (X - moving_mean) / torch.sqrt(moving_var + eps)
11
else:
12
assert len(X.shape) in (2, 4)
13
if len(X.shape) == 2:
14
# 使用全连接层的情况,计算特征维上的均值和方差
15
mean = X.mean(dim=0)
50 collapsed lines
16
var = ((X - mean) ** 2).mean(dim=0)
17
else:
18
# 使用二维卷积层的情况,计算通道维上(axis=1)的均值和方差。
19
# 这里我们需要保持X的形状以便后面可以做广播运算
20
mean = X.mean(dim=(0, 2, 3), keepdim=True)
21
var = ((X - mean) ** 2).mean(dim=(0, 2, 3), keepdim=True)
22
# 训练模式下,用当前的均值和方差做标准化
23
X_hat = (X - mean) / torch.sqrt(var + eps)
24
# 更新移动平均的均值和方差
25
moving_mean = momentum * moving_mean + (1.0 - momentum) * mean
26
moving_var = momentum * moving_var + (1.0 - momentum) * var
27
Y = gamma * X_hat + beta # 缩放和移位
28
return Y, moving_mean.data, moving_var.data
29
30
class BatchNorm(nn.Module):
31
# num_features:完全连接层的输出数量或卷积层的输出通道数。
32
# num_dims:2表示完全连接层,4表示卷积层
33
def __init__(self, num_features, num_dims):
34
super().__init__()
35
if num_dims == 2:
36
shape = (1, num_features)
37
else:
38
shape = (1, num_features, 1, 1)
39
# 参与求梯度,拉伸和偏移参数,分别初始化成1和0
40
self.gamma = nn.Parameter(torch.ones(shape))
41
self.beta = nn.Parameter(torch.zeros(shape))
42
# 非模型参数的变量初始化为0和1
43
self.moving_mean = torch.zeros(shape)
44
self.moving_var = torch.ones(shape)
45
46
def forward(self, X):
47
# 如果X不在内存上,将moving_mean和moving_var
48
# 复制到X所在显存上
49
if self.moving_mean.device != X.device:
50
self.moving_mean = self.moving_mean.to(X.device)
51
self.moving_var = self.moving_var.to(X.device)
52
# 保存更新过的moving_mean和moving_var
53
Y, self.moving_mean, self.moving_var = batch_norm(
54
X, self.gamma, self.beta, self.moving_mean,
55
self.moving_var, eps=1e-5, momentum=0.9)
56
return Y
57
58
net = nn.Sequential(
59
nn.Conv2d(1, 6, kernel_size=5), BatchNorm(6, num_dims=4), nn.Sigmoid(),
60
nn.AvgPool2d(kernel_size=2, stride=2),
61
nn.Conv2d(6, 16, kernel_size=5), BatchNorm(16, num_dims=4), nn.Sigmoid(),
62
nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),
63
nn.Linear(16*4*4, 120), BatchNorm(120, num_dims=2), nn.Sigmoid(),
64
nn.Linear(120, 84), BatchNorm(84, num_dims=2), nn.Sigmoid(),
65
nn.Linear(84, 10))

由上述示例知,BN 是放在激活函数和卷积或线性层之间。

  • 为什么只检查 moving_mean 所在的显存,而不需要检查 gamma, beta 所在的显存?

chat: 在PyTorch中,nn.Parameter对象自动与模块(nn.Module)注册,并且会随模块一起被移动到指定的设备(CPU或GPU)上。当你调用.to(device).cuda().cpu()方法在一个nn.Module上时,它不仅会移动模块的参数(即由nn.Parameter定义的参数)到指定的设备,还会移动缓冲区(如批量归一化层的运行时均值和方差)。然而,这里的关键点是self.moving_meanself.moving_var并不是通过nn.Parameter定义的,它们是作为普通的torch.Tensor对象存在的,不会自动随着模块的.to(device)调用而移动。

因此,在BatchNorm类的forward方法中,需要手动检查self.moving_meanself.moving_var是否与输入X在同一设备上。如果它们不在同一设备上,就需要手动将它们移动到X所在的设备,以保证计算能够正常进行。这是为了确保所有用于计算的张量都在同一设备上,从而避免因设备不匹配导致的错误。

verified. loss 0.272, train acc 0.899, test acc 0.864

学习到的参数:

1
net[1].gamma.reshape((-1,)), net[1].beta.reshape((-1,))
1
(tensor([2.0412, 4.2506, 2.4837, 2.8996, 0.3911, 3.2350], device='cuda:0',
2
grad_fn=<ViewBackward0>),
3
tensor([-0.7424, 2.6919, 2.7948, -2.8892, -0.3490, 1.4538], device='cuda:0',
4
grad_fn=<ViewBackward0>))

pure pytorch

1
net = nn.Sequential(
2
nn.Conv2d(1, 6, kernel_size=5), nn.BatchNorm2d(6), nn.Sigmoid(),
3
nn.AvgPool2d(kernel_size=2, stride=2),
4
nn.Conv2d(6, 16, kernel_size=5), nn.BatchNorm2d(16), nn.Sigmoid(),
5
nn.AvgPool2d(kernel_size=2, stride=2), nn.Flatten(),
6
nn.Linear(256, 120), nn.BatchNorm1d(120), nn.Sigmoid(),
7
nn.Linear(120, 84), nn.BatchNorm1d(84), nn.Sigmoid(),
8
nn.Linear(84, 10))
1
loss 0.264, train acc 0.904, test acc 0.853

炼金术

从 BN 开始就很难解释网络层作用的原理了。

Exercise

  • 在使用批量规范化之前,我们是否可以从全连接层或卷积层中删除偏置参数?为什么?

    可以,BN 层会消除前一层 $b$ 的影响

  • 比较LeNet在使用和不使用批量规范化情况下的学习率。

    • 绘制训练和测试准确度的提高。

      使用 (10 epochs):

      1
      loss 0.264, train acc 0.904, test acc 0.853

      不使用 BN:

      1
      loss 0.502, train acc 0.807, test acc 0.804
    • 学习率有多高?

      带 BN 学习率 1.0,不带则 0.9

  • 我们是否需要在每个层中进行批量规范化?尝试一下?

    1
    不删除BN层,则loss 0.268, train acc 0.901, test acc 0.861
    2
    只保留第二个和第四个BN层,则loss 0.289, train acc 0.894, test acc 0.787
    3
    只保留第一个和第三个BN层,则loss 0.286, train acc 0.893, test acc 0.820
    4
    只删除第一个BN层,则loss 0.272, train acc 0.899, test acc 0.769
    5
    只删除第二个BN层,则loss 0.269, train acc 0.901, test acc 0.774
    6
    只删除第三个BN层,则loss 0.272, train acc 0.901, test acc 0.857
    7
    只删除第四个BN层,则loss 0.283, train acc 0.895, test acc 0.857
    8
    可以看出删除全连接层后面的批量规范化对结果影响不大,因此实际上是可以删除的
  • 可以通过批量规范化来替换暂退法吗?行为会如何改变?

    1
    似乎是可以的,BN层兼具稳定训练过程和正则化的效果,将全连接阶段的BN层全部替换为弃置概率为0.5的Dropout层, 则loss 0.486, train acc 0.827, test acc 0.763,对比而言精度不如使用BN层的情况,而且train acc与test acc的差距也更大,说明BN反而能够起到比dropout更好更稳定的正则化效果
  • 确定参数beta和gamma,并观察和分析结果。

    通过实验观察模型中gamma和beta的情况。从结果来看,随着网络加深,beta的值逐渐稳定到0附近,而gamma的值则逐步稳定到1到2之间,说明BN层确实有助于稳定中间结果的数据分布

  • 查看高级API中有关BatchNorm的在线文档,以查看其他批量规范化的应用。

  • 研究思路:可以应用的其他“规范化”转换?可以应用概率积分变换吗?全秩协方差估计可以么?

BatchNorm vs LayerNorm

Article title:7.5.BN
Article author:Julyfun
Release time:Aug 6, 2024
Copyright 2025
Sitemap