4. 软标签与硬标签结合

任务说明

本案例实现知识蒸馏中软标签与硬标签损失的混合训练。教师模型为预训练ResNet18,学生模型为轻量CNN。通过加权求和软标签损失(KL散度)与硬标签损失(交叉熵),并调整权重系数α,观察学生模型在CIFAR-10验证集上的准确率变化。

环境依赖

需要 torch、torchvision、numpy。若本机无GPU,代码自动回退CPU运行(速度较慢,可将训练轮数调小验证流程)。

核心实现

import torch
import torch.nn as nn
import torch.nn.functional as F
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader

# ---------- 模型定义 ----------
class TeacherNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.backbone = torchvision.models.resnet18(weights=None, num_classes=10)
    def forward(self, x):
        return self.backbone(x)

class StudentNet(nn.Module):
    def __init__(self):
        super().__init__()
        self.features = nn.Sequential(
            nn.Conv2d(3, 32, 3, padding=1), nn.ReLU(),
            nn.MaxPool2d(2),
            nn.Conv2d(32, 64, 3, padding=1), nn.ReLU(),
            nn.MaxPool2d(2),
            nn.Conv2d(64, 128, 3, padding=1), nn.ReLU(),
            nn.MaxPool2d(2),
        )
        self.classifier = nn.Sequential(
            nn.Flatten(),
            nn.Linear(128*4*4, 256), nn.ReLU(),
            nn.Linear(256, 10)
        )
    def forward(self, x):
        return self.classifier(self.features(x))

# ---------- 数据加载 ----------
transform = transforms.Compose([
    transforms.RandomCrop(32, padding=4),
    transforms.RandomHorizontalFlip(),
    transforms.ToTensor(),
    transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])
trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = DataLoader(trainset, batch_size=128, shuffle=True)
testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)
testloader = DataLoader(testset, batch_size=256, shuffle=False)

# ---------- 训练准备 ----------
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
teacher = TeacherNet().to(device)
student = StudentNet().to(device)

# 加载预训练教师权重(此处用随机权重演示流程,实际使用请替换为训练好的权重)
# 正式实验请加载真实教师模型:teacher.load_state_dict(torch.load('teacher.pth'))
teacher.eval()
for p in teacher.parameters():
    p.requires_grad = False

optimizer = torch.optim.Adam(student.parameters(), lr=1e-3)
criterion_ce = nn.CrossEntropyLoss()
criterion_kl = nn.KLDivLoss(reduction='batchmean')
T = 4.0          # 温度
alpha = 0.7      # 软标签损失权重(硬标签权重为 1-alpha)

# ---------- 训练循环 ----------
def train_one_epoch():
    student.train()
    total_loss = 0.0
    for images, labels in trainloader:
        images, labels = images.to(device), labels.to(device)
        with torch.no_grad():
            teacher_logits = teacher(images)
        student_logits = student(images)

        # 硬标签损失
        loss_hard = criterion_ce(student_logits, labels)
        # 软标签损失(温度缩放后KL散度)
        loss_soft = criterion_kl(
            F.log_softmax(student_logits / T, dim=1),
            F.softmax(teacher_logits / T, dim=1)
        ) * (T * T)
        # 混合损失
        loss = alpha * loss_soft + (1 - alpha) * loss_hard

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
    return total_loss / len(trainloader)

# ---------- 验证函数 ----------
def evaluate():
    student.eval()
    correct = 0
    total = 0
    with torch.no_grad():
        for images, labels in testloader:
            images, labels = images.to(device), labels.to(device)
            outputs = student(images)
            _, predicted = torch.max(outputs, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
    return 100.0 * correct / total

# ---------- 执行训练 ----------
if __name__ == '__main__':
    epochs = 5
    for epoch in range(epochs):
        loss = train_one_epoch()
        acc = evaluate()
        print(f'Epoch {epoch+1}/{epochs} | Loss: {loss:.4f} | Val Acc: {acc:.2f}%')
    print('训练完成')

运行与验证

直接执行脚本即可开始训练。代码会先下载CIFAR-10数据集(首次运行约需数分钟),随后进行5轮训练并输出每轮验证准确率。

验证通过条件:

  • 程序无报错完整跑完5轮训练;
  • 每轮输出的Loss值呈下降趋势;
  • 最终验证准确率高于随机猜测(10%),且随训练轮数增加准确率逐步提升。

如需对比不同α值的影响,可修改代码中alpha = 0.7为其他值(如0.3、0.5、0.9)后重新运行,观察验证准确率变化。

关键说明

本案例使用随机初始化的教师模型仅用于演示蒸馏流程可运行性。实际应用中,教师模型需预先在数据集上训练至较高准确率,并将权重保存后在此处加载(替换代码中注释部分)。温度T控制软标签的平滑程度,α控制两类损失的相对重要性,二者均为可调超参数。