CIFAR-10 与 CIFAR-100:从 10 类到 100 类的数据集迁移实战指南
当你在 CIFAR-10 上训练出一个表现不错的模型后,下一步自然是想挑战更复杂的任务。CIFAR-100 就是这样一个理想的进阶选择——它不仅类别数量增加了 10 倍,还引入了层级标签结构。本文将带你深入理解这两个数据集的差异,并提供从 CIFAR-10 迁移到 CIFAR-100 的完整技术路线。
1. 理解数据集差异:从简单分类到细粒度识别
CIFAR-10 和 CIFAR-100 虽然都是 32x32 像素的彩色图像数据集,但在结构和复杂度上存在显著差异:
| 特性 | CIFAR-10 | CIFAR-100 |
|---|---|---|
| 类别数量 | 10 | 100 |
| 每类样本数 | 6,000 | 600 |
| 训练集总量 | 50,000 | 50,000 |
| 测试集总量 | 10,000 | 10,000 |
| 标签层级 | 单层扁平结构 | 双层结构(20个超类) |
| 典型应用场景 | 基础分类任务 | 细粒度图像识别 |
CIFAR-100 最显著的特点是它的层级标签体系:
- 粗粒度标签(Coarse Labels):20 个超类(如"水生哺乳动物"、"花卉"等)
- 细粒度标签(Fine Labels):100 个子类(如"海豚"、"鲸鱼"属于"水生哺乳动物"超类)
这种结构为模型训练提供了更多可能性。你可以:
- 仅使用细粒度标签进行 100 类分类
- 使用粗粒度标签进行 20 类分类
- 联合训练,利用层级关系提升模型表现
# CIFAR-100 标签加载示例 import torchvision.datasets as datasets cifar100 = datasets.CIFAR100(root='./data', train=True, download=True) print(f"细粒度标签: {cifar100.targets[0]}") print(f"对应粗粒度标签: {cifar100.coarse_targets[0]}")2. 模型架构调整策略
从 10 类扩展到 100 类,模型需要做出相应调整:
2.1 输出层改造
最直接的修改是输出层维度:
import torch.nn as nn # CIFAR-10 输出层 output_layer_10 = nn.Linear(512, 10) # CIFAR-100 输出层改造 output_layer_100 = nn.Linear(512, 100) # 修改输出维度为100但仅仅增加输出维度是不够的,你还需要考虑:
层级分类策略:
class HierarchicalClassifier(nn.Module): def __init__(self, backbone): super().__init__() self.backbone = backbone self.coarse_classifier = nn.Linear(512, 20) # 粗粒度分类 self.fine_classifier = nn.Linear(512, 100) # 细粒度分类 def forward(self, x): features = self.backbone(x) coarse_logits = self.coarse_classifier(features) fine_logits = self.fine_classifier(features) return coarse_logits, fine_logits2.2 损失函数优化
对于层级分类,可以设计复合损失函数:
def hierarchical_loss(coarse_logits, fine_logits, coarse_labels, fine_labels, alpha=0.3): coarse_loss = nn.CrossEntropyLoss()(coarse_logits, coarse_labels) fine_loss = nn.CrossEntropyLoss()(fine_logits, fine_labels) return alpha * coarse_loss + (1-alpha) * fine_loss2.3 网络深度与容量
随着类别增加,建议:
- 增加网络深度(如从 ResNet-18 升级到 ResNet-34)
- 扩大通道数(如从 64 初始通道增加到 128)
- 添加注意力机制(如 SE 模块)
# 通道数扩展示例 def conv3x3(in_planes, out_planes, stride=1): return nn.Conv2d(in_planes, out_planes, kernel_size=3, stride=stride, padding=1, bias=False) # CIFAR-10 常用配置 block_64 = conv3x3(64, 64) # CIFAR-100 扩展配置 block_128 = conv3x3(128, 128)3. 数据增强策略升级
CIFAR-100 由于每类样本更少(仅 600 张),需要更智能的数据增强:
3.1 基础增强组合
from torchvision import transforms # CIFAR-10 常用增强 transform_10 = transforms.Compose([ transforms.RandomHorizontalFlip(), transforms.RandomCrop(32, padding=4), transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010)), ]) # CIFAR-100 增强升级 transform_100 = transforms.Compose([ transforms.RandomHorizontalFlip(), transforms.RandomCrop(32, padding=4), transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2), transforms.RandomRotation(15), transforms.RandomAffine(0, translate=(0.1, 0.1)), transforms.ToTensor(), transforms.Normalize((0.5071, 0.4867, 0.4408), (0.2675, 0.2565, 0.2761)), ])注意:CIFAR-100 使用不同的归一化参数,这是基于其自身数据统计计算的
3.2 高级增强技术
- Cutout:随机遮挡图像区域
class Cutout(object): def __init__(self, length): self.length = length def __call__(self, img): h, w = img.size(1), img.size(2) mask = np.ones((h, w), np.float32) y = np.random.randint(h) x = np.random.randint(w) y1 = np.clip(y - self.length // 2, 0, h) y2 = np.clip(y + self.length // 2, 0, h) x1 = np.clip(x - self.length // 2, 0, w) x2 = np.clip(x + self.length // 2, 0, w) mask[y1:y2, x1:x2] = 0. img = img * torch.from_numpy(mask) return img- Mixup:图像混合增强
def mixup_data(x, y, alpha=1.0): if alpha > 0: lam = np.random.beta(alpha, alpha) else: lam = 1 batch_size = x.size(0) index = torch.randperm(batch_size) mixed_x = lam * x + (1 - lam) * x[index, :] y_a, y_b = y, y[index] return mixed_x, y_a, y_b, lam4. 训练技巧与调优策略
4.1 学习率调度
由于类别增加,需要更谨慎的学习率控制:
# 分层学习率示例 optimizer = torch.optim.SGD([ {'params': model.backbone.parameters(), 'lr': 0.1}, {'params': model.coarse_classifier.parameters(), 'lr': 0.2}, {'params': model.fine_classifier.parameters(), 'lr': 0.2} ], momentum=0.9, weight_decay=5e-4) # 余弦退火调度 scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=200)4.2 类别不平衡处理
CIFAR-100 虽然总体平衡,但某些超类下子类样本可能不均衡:
# 焦点损失 (Focal Loss) class FocalLoss(nn.Module): def __init__(self, gamma=2, alpha=None): super(FocalLoss, self).__init__() self.gamma = gamma self.alpha = alpha def forward(self, inputs, targets): BCE_loss = F.cross_entropy(inputs, targets, reduction='none') pt = torch.exp(-BCE_loss) loss = (1-pt)**self.gamma * BCE_loss if self.alpha is not None: loss = self.alpha[targets] * loss return loss.mean()4.3 知识蒸馏应用
可以利用预训练的 CIFAR-10 模型辅助训练:
def distillation_loss(student_logits, teacher_logits, labels, temp=3, alpha=0.7): soft_teacher = F.softmax(teacher_logits/temp, dim=1) soft_student = F.log_softmax(student_logits/temp, dim=1) kl_div = F.kl_div(soft_student, soft_teacher, reduction='batchmean') * (temp**2) ce_loss = F.cross_entropy(student_logits, labels) return alpha * kl_div + (1-alpha) * ce_loss5. 评估与结果分析
5.1 评估指标
除了常规的准确率,对于 CIFAR-100 建议关注:
- 各类别准确率(特别是相似类别间)
- 混淆矩阵分析
- 粗粒度与细粒度准确率对比
from sklearn.metrics import confusion_matrix, classification_report def evaluate(model, loader): model.eval() all_preds = [] all_labels = [] with torch.no_grad(): for inputs, labels in loader: outputs = model(inputs) _, preds = torch.max(outputs, 1) all_preds.extend(preds.cpu().numpy()) all_labels.extend(labels.cpu().numpy()) print(classification_report(all_labels, all_preds)) cm = confusion_matrix(all_labels, all_preds) plt.figure(figsize=(20,20)) sns.heatmap(cm, annot=True, fmt='d') plt.show()5.2 典型性能基准
| 模型架构 | CIFAR-10 准确率 | CIFAR-100 准确率 |
|---|---|---|
| ResNet-18 | 95.2% | 76.5% |
| ResNet-34 | 95.8% | 78.3% |
| EfficientNet-B0 | 95.1% | 77.8% |
| MobileNetV3 | 94.5% | 75.2% |
提示:这些结果基于标准数据增强和训练方案,使用本文技巧通常可以获得 2-5% 的提升
6. 实战:完整迁移案例
以下是一个完整的 PyTorch 实现示例:
import torch import torchvision import torch.nn as nn import torch.optim as optim from torchvision import transforms, datasets from torch.utils.data import DataLoader # 1. 数据准备 train_transform = transforms.Compose([ transforms.RandomResizedCrop(32, scale=(0.8, 1.0)), transforms.RandomHorizontalFlip(), transforms.ColorJitter(0.2, 0.2, 0.2), transforms.ToTensor(), transforms.Normalize((0.5071, 0.4867, 0.4408), (0.2675, 0.2565, 0.2761)), Cutout(16) ]) test_transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.5071, 0.4867, 0.4408), (0.2675, 0.2565, 0.2761)) ]) train_set = datasets.CIFAR100(root='./data', train=True, download=True, transform=train_transform) test_set = datasets.CIFAR100(root='./data', train=False, download=True, transform=test_transform) train_loader = DataLoader(train_set, batch_size=128, shuffle=True, num_workers=4) test_loader = DataLoader(test_set, batch_size=100, shuffle=False, num_workers=4) # 2. 模型定义 class CIFAR100Model(nn.Module): def __init__(self): super().__init__() self.backbone = torchvision.models.resnet34(pretrained=False, num_classes=100) self.backbone.conv1 = nn.Conv2d(3, 64, kernel_size=3, stride=1, padding=1, bias=False) self.backbone.maxpool = nn.Identity() def forward(self, x): return self.backbone(x) model = CIFAR100Model().cuda() # 3. 训练配置 criterion = nn.CrossEntropyLoss() optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9, weight_decay=5e-4) scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=200) # 4. 训练循环 for epoch in range(200): model.train() for inputs, targets in train_loader: inputs, targets = inputs.cuda(), targets.cuda() # Mixup 增强 inputs, targets_a, targets_b, lam = mixup_data(inputs, targets, alpha=1.0) outputs = model(inputs) loss = lam * criterion(outputs, targets_a) + (1-lam) * criterion(outputs, targets_b) optimizer.zero_grad() loss.backward() optimizer.step() scheduler.step() # 评估 model.eval() correct = 0 total = 0 with torch.no_grad(): for inputs, targets in test_loader: inputs, targets = inputs.cuda(), targets.cuda() outputs = model(inputs) _, predicted = outputs.max(1) total += targets.size(0) correct += predicted.eq(targets).sum().item() print(f'Epoch {epoch}: Acc {100.*correct/total:.2f}%')从 CIFAR-10 迁移到 CIFAR-100 不仅是类别数量的增加,更是对模型理解能力的全面提升。通过合理调整模型架构、优化训练策略并应用高级增强技术,你可以在 CIFAR-100 上获得具有竞争力的结果。记住,细粒度分类的关键在于让模型学会区分细微的视觉差异,这需要数据、模型和训练策略的精心配合。