本文基于d2l项目内容整理,探索批量归一化技术在经典LeNet网络中的应用,包括自定义实现和PyTorch高级API两种方式的对比分析。

批量归一化的优势:

与不使用批量归一化的 LeNet 相比,批量归一化操作限制了内部协变量的偏移,使网络在训练时可以支持更大的学习率以提高训练速度。


1. 使用自定义的批量归一化实现

1.1 网络架构设计

BatchNorm 层的插入位置:

为了便于演示,将自定义的BatchNorm应用于最早的卷积神经网络 LeNet 的:

卷积层之后、激活函数之前

全连接层之后、激活函数之前

继续使用training_tools.py中的工具训练评估模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import torch
from torch import nn, optim, Tensor

from batch_norm import BatchNorm
from training_tools import fashionMNIST_loader, Trainer


class LeNetNorm(nn.Module):
def __init__(self):
super().__init__()
self.model = nn.Sequential(
nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5, padding=2), BatchNorm(6), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),

nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5), BatchNorm(16), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),

nn.Flatten(),
nn.Linear(in_features=16 * 5 * 5, out_features=120), BatchNorm(120), nn.Sigmoid(),
nn.Linear(in_features=120, out_features=84), BatchNorm(84), nn.Sigmoid(),
nn.Linear(in_features=84, out_features=10)
)

def forward(self, x) -> Tensor:
return self.model(x)


if __name__ == '__main__':
BATCH_SIZE = 256
EPOCHS_NUM = 20
LEARNING_RATE = 0.9

model = LeNetNorm()
train_loader, test_loader = fashionMNIST_loader(BATCH_SIZE)
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), LEARNING_RATE)
platform = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

with Trainer(model, train_loader, test_loader, criterion, optimizer, platform) as trainer:
trainer.train(EPOCHS_NUM)

1.2 训练结果分析

查看自定义BatchNorm训练过程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
001/20 轮,训练损失:0.7467,训练精度:72.63%,测试损失:0.5786,测试精度:78.79%
002/20 轮,训练损失:0.4560,训练精度:83.42%,测试损失:0.5931,测试精度:78.64%
003/20 轮,训练损失:0.3935,训练精度:85.69%,测试损失:0.5317,测试精度:79.71%
004/20 轮,训练损失:0.3534,训练精度:87.09%,测试损失:0.4948,测试精度:82.67%
005/20 轮,训练损失:0.3278,训练精度:87.98%,测试损失:0.5338,测试精度:79.72%
006/20 轮,训练损失:0.3067,训练精度:88.77%,测试损失:0.5119,测试精度:80.34%
007/20 轮,训练损失:0.2965,训练精度:89.02%,测试损失:0.5917,测试精度:79.18%
008/20 轮,训练损失:0.2863,训练精度:89.48%,测试损失:0.5458,测试精度:80.29%
009/20 轮,训练损失:0.2719,训练精度:89.94%,测试损失:0.3808,测试精度:86.62%
010/20 轮,训练损失:0.2598,训练精度:90.37%,测试损失:0.3530,测试精度:87.19%
011/20 轮,训练损失:0.2532,训练精度:90.56%,测试损失:0.4072,测试精度:86.18%
012/20 轮,训练损失:0.2463,训练精度:90.89%,测试损失:0.4431,测试精度:83.19%
013/20 轮,训练损失:0.2393,训练精度:91.09%,测试损失:0.3236,测试精度:88.37%
014/20 轮,训练损失:0.2312,训练精度:91.41%,测试损失:0.3809,测试精度:86.48%
015/20 轮,训练损失:0.2252,训练精度:91.61%,测试损失:0.5903,测试精度:80.33%
016/20 轮,训练损失:0.2201,训练精度:91.83%,测试损失:0.3459,测试精度:87.68%
017/20 轮,训练损失:0.2157,训练精度:91.94%,测试损失:0.3195,测试精度:88.70%
018/20 轮,训练损失:0.2097,训练精度:92.22%,测试损失:0.4688,测试精度:83.59%
019/20 轮,训练损失:0.2072,训练精度:92.27%,测试损失:0.3301,测试精度:88.55%
020/20 轮,训练损失:0.1993,训练精度:92.52%,测试损失:0.4786,测试精度:85.04%
自定义BatchNorm训练过程可视化
自定义BatchNorm训练过程可视化

2. 使用PyTorch高级API实现

2.1 高级API的优势

PyTorch内置BatchNorm的特点:

为了便于演示,将torch.nn.BatchNorm1dtorch.nn.BatchNorm2d批量归一化高级 API 应用于LeNet网络中:

BatchNorm2d 用于卷积层输出

BatchNorm1d 用于全连接层输出

插入位置:层输出之后、激活函数之前

继续使用training_tools.py中的工具训练评估模型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import torch
from torch import nn, optim, Tensor

from torch.nn import BatchNorm1d, BatchNorm2d
from training_tools import fashionMNIST_loader, Trainer


class LeNetNorm(nn.Module):
def __init__(self):
super().__init__()
self.model = nn.Sequential(
nn.Conv2d(in_channels=1, out_channels=6, kernel_size=5, padding=2), BatchNorm2d(6), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),

nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5), BatchNorm2d(16), nn.Sigmoid(),
nn.AvgPool2d(kernel_size=2, stride=2),

nn.Flatten(),
nn.Linear(in_features=16 * 5 * 5, out_features=120), BatchNorm1d(120), nn.Sigmoid(),
nn.Linear(in_features=120, out_features=84), BatchNorm1d(84), nn.Sigmoid(),
nn.Linear(in_features=84, out_features=10)
)

def forward(self, x) -> Tensor:
return self.model(x)


if __name__ == '__main__':
BATCH_SIZE = 256
EPOCHS_NUM = 12
LEARNING_RATE = 0.9

model = LeNetNorm()
train_loader, test_loader = fashionMNIST_loader(BATCH_SIZE)
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), LEARNING_RATE)
platform = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

with Trainer(model, train_loader, test_loader, criterion, optimizer, platform) as trainer:
trainer.train(EPOCHS_NUM)

2.2 训练结果分析

查看PyTorch高级API训练过程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
001/20 轮,训练损失:0.7319,训练精度:73.59%,测试损失:0.5507,测试精度:79.77%
002/20 轮,训练损失:0.4581,训练精度:83.33%,测试损失:0.5434,测试精度:80.35%
003/20 轮,训练损失:0.3874,训练精度:86.01%,测试损失:0.4753,测试精度:82.32%
004/20 轮,训练损失:0.3445,训练精度:87.58%,测试损失:0.6090,测试精度:78.65%
005/20 轮,训练损失:0.3152,训练精度:88.48%,测试损失:0.5935,测试精度:77.56%
006/20 轮,训练损失:0.2993,训练精度:89.00%,测试损失:0.4295,测试精度:84.09%
007/20 轮,训练损失:0.2863,训练精度:89.49%,测试损失:0.4920,测试精度:83.13%
008/20 轮,训练损失:0.2702,训练精度:90.05%,测试损失:0.3630,测试精度:86.45%
009/20 轮,训练损失:0.2612,训练精度:90.37%,测试损失:0.3293,测试精度:88.18%
010/20 轮,训练损失:0.2516,训练精度:90.73%,测试损失:0.8095,测试精度:72.46%
011/20 轮,训练损失:0.2451,训练精度:90.92%,测试损失:0.3142,测试精度:88.69%
012/20 轮,训练损失:0.2333,训练精度:91.42%,测试损失:0.2834,测试精度:89.40%
013/20 轮,训练损失:0.2266,训练精度:91.52%,测试损失:0.3581,测试精度:86.73%
014/20 轮,训练损失:0.2222,训练精度:91.62%,测试损失:0.3546,测试精度:87.98%
015/20 轮,训练损失:0.2146,训练精度:92.02%,测试损失:0.3224,测试精度:89.06%
016/20 轮,训练损失:0.2088,训练精度:92.15%,测试损失:0.3792,测试精度:86.32%
017/20 轮,训练损失:0.2040,训练精度:92.38%,测试损失:0.3774,测试精度:86.00%
018/20 轮,训练损失:0.1987,训练精度:92.54%,测试损失:0.3286,测试精度:88.51%
019/20 轮,训练损失:0.1935,训练精度:92.78%,测试损失:0.3452,测试精度:87.89%
020/20 轮,训练损失:0.1880,训练精度:92.97%,测试损失:0.7534,测试精度:78.21%
PyTorch高级API训练过程可视化
PyTorch高级API训练过程可视化

3. 性能对比与总结

两种实现方式的性能对比:

自定义BatchNorm实现:

  • 最终训练精度:92.52%
  • 最终测试精度:85.04%
  • 训练轮数:20 轮
  • 学习率:0.9

PyTorch高级API实现:

  • 最终训练精度:92.97%
  • 最终测试精度:78.21%
  • 训练轮数:20 轮
  • 学习率:0.9

批量归一化在LeNet中的作用机制:

解决内部协变量偏移问题

支持更大的学习率设置

加速训练收敛过程

提高模型的训练稳定性

在一定程度上起到正则化作用

实际应用中的选择建议:

推荐使用PyTorch内置API:

  • 更稳定和高效的实现
  • 经过充分优化和测试
  • 支持多种计算设备加速
  • 维护成本更低

自定义实现的适用场景:

  • 学习和理解算法原理
  • 需要特殊定制的场景
  • 研究和实验目的

总结

本文对比了在LeNet网络中使用批量归一化的两种实现方式:

  1. 自定义实现:帮助理解批量归一化的内部机制和算法原理
  2. PyTorch高级API:提供了更稳定、高效的工业级实现

批量归一化通过解决内部协变量偏移问题,使得网络能够使用更大的学习率,从而加速训练过程并提高模型性能。在实际应用中,建议优先使用PyTorch的内置API以获得最佳性能。