本文基于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%
|
2. 使用PyTorch高级API实现
2.1 高级API的优势
PyTorch内置BatchNorm的特点:
为了便于演示,将torch.nn.BatchNorm1d
和torch.nn.BatchNorm2d
批量归一化高级 API 应用于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 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%
|
3. 性能对比与总结
两种实现方式的性能对比:
自定义BatchNorm实现:
- 最终训练精度:92.52%
- 最终测试精度:85.04%
- 训练轮数:20 轮
- 学习率:0.9
PyTorch高级API实现:
- 最终训练精度:92.97%
- 最终测试精度:78.21%
- 训练轮数:20 轮
- 学习率:0.9
实际应用中的选择建议:
推荐使用PyTorch内置API:
- 更稳定和高效的实现
- 经过充分优化和测试
- 支持多种计算设备加速
- 维护成本更低
自定义实现的适用场景:
- 学习和理解算法原理
- 需要特殊定制的场景
- 研究和实验目的
总结
本文对比了在LeNet网络中使用批量归一化的两种实现方式:
- 自定义实现:帮助理解批量归一化的内部机制和算法原理
- PyTorch高级API:提供了更稳定、高效的工业级实现
批量归一化通过解决内部协变量偏移问题,使得网络能够使用更大的学习率,从而加速训练过程并提高模型性能。在实际应用中,建议优先使用PyTorch的内置API以获得最佳性能。