1. 实验背景
CIFAR-100 是图像识别领域常用的一个小型数据集,包含了 60,000 张 32×32 的彩色图像,共有 100 个类别(也可归为 20 个超级类别)。训练集有 50,000 张图像,测试集有 10,000 张图像,每个类别各有 600 张图像。由于 CIFAR-100 的类间相似度较高,且图像尺寸较小,给模型的泛化能力带来了一定挑战。它也因此常被用来做深度学习模型的快速验证和对比实验。
本次实验的目标是使用经典的 ResNet 和它的变体 Wide-ResNet 模型在 CIFAR-100 上进行图像分类,并分析模型的表现。在结果评估方面,我们主要关注:
- Top-1 Accuracy:预测结果的第一候选是否与标签相匹配。
- Top-5 Accuracy:预测结果中前 5 个候选是否包含正确标签。
- Super-Class Accuracy:以 20 个超级类别为目标,判断更高层级的分类准确率。
以下重点介绍使用 Wide-ResNet 的部分实验过程和结果。
2. 模型介绍
2.1 ResNet 回顾
ResNet(Residual Network)最早由微软研究院提出,通过使用残差结构(Residual Block)有效缓解了深度神经网络中出现的梯度消失或梯度爆炸等问题。同时,残差结构也让网络能在保持较深层数的同时依旧有良好的训练效果。
2.2 Wide-ResNet 简介
Wide-ResNet 是对 ResNet 的改进版本,论文 Wide Residual Networks 提出可以在增加网络宽度(即 channel 数)而不是盲目加深网络深度的情况下,获得更强的表达能力和更好的性能表现。相比于很深的 ResNet,Wide-ResNet 通常能在较少的训练时间内取得相当甚至更高的准确率。
Wide-ResNet 结构的主要变化有:
- 将每一层的通道数乘以一个 widen factor(例如 2、4、8等),实现网络变宽;
- 在一些实现中,还会配合使用 Dropout 来进一步缓解过拟合。
3. 代码结构
下面展示了本次实验使用的 WideResNet 模型的主要代码。可以看到,对于 Wide ResNet,关键在于残差块 WideBlock
中同时包含了 Batch Normalization、ReLU、卷积操作以及 Dropout,并且通过 shortcut
来实现残差连接。在构建网络时,我们根据 depth
来计算每个阶段的层数,并通过 widen_factor
来指定通道增宽系数。
import torch
import torch.nn as nn
import torch.nn.functional as F
# 权重初始化函数
def weights_init(m):
if isinstance(m, nn.Conv2d) or isinstance(m, nn.Linear):
nn.init.xavier_normal_(m.weight) # 使用 Xavier 初始化
elif isinstance(m, nn.BatchNorm2d):
nn.init.ones_(m.weight)
nn.init.zeros_(m.bias)
# Wide ResNet 的基本块
class WideBlock(nn.Module):
def __init__(self, in_channels, out_channels, stride=1, dropout_rate=0.3):
super(WideBlock, self).__init__()
self.conv1 = nn.Conv2d(in_channels, out_channels, kernel_size=3,
stride=stride, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(out_channels)
self.dropout1 = nn.Dropout(dropout_rate)
self.conv2 = nn.Conv2d(out_channels, out_channels, kernel_size=3,
padding=1, bias=False)
self.bn2 = nn.BatchNorm2d(out_channels)
self.dropout2 = nn.Dropout(dropout_rate)
self.shortcut = nn.Sequential()
if stride != 1 or in_channels != out_channels:
self.shortcut = nn.Sequential(
nn.Conv2d(in_channels, out_channels, kernel_size=1,
stride=stride, bias=False),
nn.BatchNorm2d(out_channels)
)
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
out = self.dropout1(out)
out = self.bn2(self.conv2(out))
out = self.dropout2(out)
out += self.shortcut(x)
out = F.relu(out)
return out
# Wide ResNet 模型
class WideResNet(nn.Module):
def __init__(self, depth, widen_factor, num_classes=100):
super(WideResNet, self).__init__()
self.in_channels = 16
n = (depth - 4) // 6 # 每个 stage 的层数
self.conv1 = nn.Conv2d(3, 16, kernel_size=3, padding=1, bias=False)
self.bn1 = nn.BatchNorm2d(16)
self.layer1 = self._make_layer(WideBlock, 16 * widen_factor, n, stride=1)
self.layer2 = self._make_layer(WideBlock, 32 * widen_factor, n, stride=2)
self.layer3 = self._make_layer(WideBlock, 64 * widen_factor, n, stride=2)
self.avg_pool = nn.AdaptiveAvgPool2d(1)
self.fc = nn.Linear(64 * widen_factor, num_classes)
# 参数初始化
self.apply(weights_init)
def _make_layer(self, block, out_channels, n, stride):
layers = []
layers.append(block(self.in_channels, out_channels, stride))
self.in_channels = out_channels
for _ in range(1, n):
layers.append(block(out_channels, out_channels))
return nn.Sequential(*layers)
def forward(self, x):
out = F.relu(self.bn1(self.conv1(x)))
out = self.layer1(out)
out = self.layer2(out)
out = self.layer3(out)
out = self.avg_pool(out)
out = out.view(out.size(0), -1)
out = self.fc(out)
return out
在代码实现中:
WideBlock
是基础残差结构,每个 Block 中包含两个卷积层、BN 以及 Dropout,用于增宽的部分体现在out_channels
的设计中。WideResNet
按照 (depth-4)/6 计算每个阶段重复的残差块数量,并且通过widen_factor
调整每个阶段的通道数(默认为[16, 32, 64]
变成[16×widen_factor, 32×widen_factor, 64×widen_factor]
)。
4. 实验过程
4.1 数据预处理
CIFAR-100 的图像分辨率较小(32×32),我们通常会对训练集做一些数据增强,常见手段包括:
- 随机裁剪(RandomCrop)
- 随机水平翻转(RandomHorizontalFlip)
- 均值和标准差归一化(Normalize)
import torchvision.transforms as transforms
transform_train = transforms.Compose([
transforms.RandomCrop(32, padding=4),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.5071, 0.4867, 0.4408], [0.2675, 0.2565, 0.2761])
])
transform_test = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize([0.5071, 0.4867, 0.4408], [0.2675, 0.2565, 0.2761])
])
这里的 [0.5071, 0.4867, 0.4408]
和 [0.2675, 0.2565, 0.2761]
分别是 CIFAR-100 统计得到的 RGB 三通道的均值和标准差。
4.2 训练设置
- 优化器:常用的是
SGD
(带动量 Momentum=0.9)或者Adam
。如果使用 SGD,学习率衰减策略可采用多步衰减或 Cosine Annealing。 - 批大小 (batch_size):128 或 256 均可,根据实际显存情况决定。
- 学习率 (lr):从 0.1 或 0.01 开始,有时搭配余弦退火(Cosine Annealing)取得较好效果。
- 训练 epoch:通常 200 次左右即可在 CIFAR-100 上看到较稳定的结果。
4.3 训练流程
大致训练流程如下:
- 加载数据:使用
DataLoader
搭配前述的数据增强方法。 - 实例化模型:如
model = WideResNet(depth=28, widen_factor=10, num_classes=100)
。 - 定义损失函数和优化器:
criterion = nn.CrossEntropyLoss()
,optimizer = torch.optim.SGD(...)
。 - 训练循环:
- 将模型设为训练模式
model.train()
; - 前向传播计算损失;
- 反向传播更新权重;
- (可选)在训练集或验证集上查看中间结果。
- 将模型设为训练模式
- 评估:在测试集上评估
model.eval()
模式,得到 Top-1/Top-5 Accuracy 等。
5. 实验结果与分析
以下是在不同随机种子(Random Seed)下取得的结果表格。可以看到,对 CIFAR-100 的测试准确率(Test Acc)、Top-5 准确率以及超级类别准确率如下:
Random Seed | Test Acc | Top-5 Acc | Super-Class Acc |
---|---|---|---|
4 | 79.05% | 95.33% | 87.21% |
8 | 79.44% | 95.25% | 87.28% |
12 | 79.14% | 95.27% | 87.74% |
可以观察到:
- Test Acc 在三个随机种子下都稳定在 79% 左右,Top-5 Acc 大约在 95%,表明模型在 CIFAR-100 上有相当的区分能力。
- Super-Class Acc 更高一些,因为超级类别只有 20 个,类别难度相对较小,所以准确率在 87%~88% 左右。
- 不同随机种子带来的性能波动很小,说明我们的训练流程相对稳定。
5.1 对比分析
- ResNet vs Wide-ResNet:通常情况下,相同深度下,Wide-ResNet 在 CIFAR-100 上往往能获得略好于标准 ResNet 的精度,且收敛更快。
- 模型深度与宽度:从实验证明,盲目加深网络可能效果并不明显,但增宽通道(适度加大网络容量)往往能带来更显著的效果提升。
- Dropout 影响:在 CIFAR-100 这种数据量有限且类别多的情况下,适当使用 Dropout(如 0.3)能够减少过拟合,提升泛化性能。
5.2最终结果图
6. 心得体会
-
宽度扩展的好处:Wide-ResNet 在相对浅层(如 depth=28)并采用较大宽度(如 widen_factor=10)时,就能取得非常不错的分类结果,而且训练速度更快,与非常深的 ResNet(如 ResNet-100+)相比效率更高。
-
训练策略的重要性:在 CIFAR-100 上,恰当的学习率初始值与衰减策略能够带来较大提升。特别是 余弦退火 (Cosine Annealing) 常常能比简单的 Step LR 取得更好或者至少相当的效果。对小数据集来说,数据增强也是关键环节。
-
随机种子:多次实验对于验证模型效果的稳定性很重要,建议在汇报或撰写文章时,至少使用 3 个以上不同的随机种子(或多次重复实验)来验证模型的平均性能和波动区间。
-
模型可解释性与可视化:尽管我们更关注数值指标,但在实际应用中,可视化中间特征、观察网络对不同类别的判别重点,对于深入理解模型也很有帮助,可以结合 Grad-CAM 等方法进行分析。
7. 总结
在本次实验中,采用 Wide-ResNet 模型对 CIFAR-100 进行了分类任务,取得了接近 79% 的 Top-1 测试准确率和 95% 左右的 Top-5 准确率,超级类别准确率约 87%~88%。实验中我们看到了 增宽网络(而非一味加深)在小尺寸数据上的优势,以及合适的 Dropout、数据增强 和 学习率调度 对模型效果的重要影响。