📘 Day 44 实战作业 (极速版):ResNet 与 迁移学习
1. 作业综述
核心目标:
- 迁移学习:学会调用 ImageNet 预训练的 ResNet18 模型,将其知识迁移到 CIFAR-10 任务上。
- 策略对比 :亲手实验 冻结骨干 (Linear Probing) 和 解冻微调 (Fine-tuning) 两种策略的效果差异。
- 工业级加速 :掌握 混合精度训练 (AMP) 和 分辨率适配 技巧,在保持高精度的同时极大提升训练速度。
涉及知识点:
- ResNet18: 工业界最常用的基准模型。
- Transfer Learning :
pretrained=True,freeze weights. - AMP (Automatic Mixed Precision) :
torch.cuda.amp. - Data Resize: 适配模型输入尺寸。
场景类比:
- 从头训练: 像小学生写作文,词汇量有限,写得慢且水平一般。
- 迁移学习 : 像大学教授写作文,知识渊博。
- 冻结: 教授套模板写(只练最后一层),速度极快。
- 微调: 教授认真推敲每一句话(全网微调),水平最高。
步骤 1:数据准备 (极速配置)
优化策略:
- 分辨率 112x112:比标准的 224x224 少了 75% 的像素,速度提升显著。
- Pin Memory: 锁页内存,加快数据传输。
任务:
- 定义预处理管道 (Resize -> 112)。
- 加载 CIFAR-10 数据集。
py
import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms, models
from torch.utils.data import DataLoader
from torch.cuda.amp import autocast, GradScaler # 混合精度神器
import time
# --- 1. 极速配置 ---
IMG_SIZE = 112 # 降级分辨率加速 (原版 224)
BATCH_SIZE = 128 # 因为图小了,Batch 可以开大点 (原版 32)
NUM_WORKERS = 2 # 数据加载进程数
# 2. 定义预处理
norm_mean = [0.485, 0.456, 0.406]
norm_std = [0.229, 0.224, 0.225]
train_transform = transforms.Compose([
transforms.Resize(IMG_SIZE),
transforms.RandomHorizontalFlip(), # 数据增强
transforms.ToTensor(),
transforms.Normalize(norm_mean, norm_std)
])
test_transform = transforms.Compose([
transforms.Resize(IMG_SIZE),
transforms.ToTensor(),
transforms.Normalize(norm_mean, norm_std)
])
# 3. 加载数据 (开启 pin_memory)
print("📥 正在加载数据...")
train_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=train_transform)
test_dataset = datasets.CIFAR10(root='./data', train=False, download=True, transform=test_transform)
train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True,
num_workers=NUM_WORKERS, pin_memory=True)
test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False,
num_workers=NUM_WORKERS, pin_memory=True)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print(f"✅ 数据准备就绪 | 设备: {device} | 输入尺寸: {IMG_SIZE}x{IMG_SIZE}")
📥 正在加载数据...
✅ 数据准备就绪 | 设备: cuda | 输入尺寸: 112x112
步骤 2:模型构建工厂
任务 :
编写一个函数 create_resnet18,灵活控制是否加载预训练权重、是否冻结骨干。
- Backbone: ResNet18 (去掉最后的全连接层)。
- Head: 新的全连接层 (输出 10 类)。
py
def create_resnet18(pretrained=True, freeze=False):
"""
创建一个适配 CIFAR-10 的 ResNet18
"""
# 1. 加载模型
if pretrained:
# weights='DEFAULT' 自动下载最新权重
model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)
print(f"🔧 [模型创建] 加载 ImageNet 预训练权重 (Freeze={freeze})...")
else:
model = models.resnet18(weights=None)
print(f"✨ [模型创建] 随机初始化权重...")
# 2. 冻结骨干 (Backbone)
if freeze:
for param in model.parameters():
param.requires_grad = False
# 3. 替换 Head (这一层默认是可训练的)
# ResNet18 的 fc 输入特征数是 512
in_features = model.fc.in_features
model.fc = nn.Linear(in_features, 10)
return model
步骤 3:混合精度训练引擎
优化策略:
- 使用
torch.cuda.amp进行半精度 (FP16) 训练。 - 显存占用减半,计算速度翻倍,非常适合 RTX 30 系列显卡。
任务 :
封装 train_one_epoch 和 evaluate 函数,集成 AMP 逻辑。
py
# 初始化梯度缩放器 (AMP 必备)
scaler = GradScaler()
def train_one_epoch(model, loader, criterion, optimizer):
model.train()
running_loss = 0.0
correct = 0
total = 0
for inputs, labels in loader:
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
# --- 核心优化:混合精度上下文 ---
with autocast():
outputs = model(inputs)
loss = criterion(outputs, labels)
# --- 核心优化:缩放梯度反向传播 ---
scaler.scale(loss).backward()
scaler.step(optimizer)
scaler.update()
# 统计
running_loss += loss.item()
_, predicted = outputs.max(1)
total += labels.size(0)
correct += predicted.eq(labels).sum().item()
return running_loss / len(loader), 100. * correct / total
def evaluate(model, loader, criterion):
model.eval()
running_loss = 0.0
correct = 0
total = 0
with torch.no_grad():
for inputs, labels in loader:
inputs, labels = inputs.to(device), labels.to(device)
# 测试时不需要 scaler,但可以用 autocast 加速推理
with autocast():
outputs = model(inputs)
loss = criterion(outputs, labels)
running_loss += loss.item()
_, predicted = outputs.max(1)
total += labels.size(0)
correct += predicted.eq(labels).sum().item()
return running_loss / len(loader), 100. * correct / total
C:\Users\ADVANCE\AppData\Local\Temp\ipykernel_4824\1229741273.py:2: FutureWarning: `torch.cuda.amp.GradScaler(args...)` is deprecated. Please use `torch.amp.GradScaler('cuda', args...)` instead.
scaler = GradScaler()
步骤 4:实验 A - 极速冻结训练 (Linear Probing)
场景 :
只训练最后一层分类器 (Head)。
因为骨干网络参数被锁死,无需计算梯度,速度快到飞起。
预期 :
3 个 Epoch 内,准确率应该能达到 80% - 85%。
py
print("\n=== 实验 A: 冻结骨干 (只练 Head) ===")
start_time = time.time()
# 1. 创建冻结模型
model_frozen = create_resnet18(pretrained=True, freeze=True).to(device)
criterion = nn.CrossEntropyLoss()
# 2. 优化器 (只优化 fc 层,lr 可以大一点)
optimizer = optim.Adam(model_frozen.fc.parameters(), lr=0.001)
# 3. 训练 3 轮
for epoch in range(3):
train_loss, train_acc = train_one_epoch(model_frozen, train_loader, criterion, optimizer)
test_loss, test_acc = evaluate(model_frozen, test_loader, criterion)
print(f"Epoch {epoch+1}: Train Acc: {train_acc:.2f}% | Test Acc: {test_acc:.2f}%")
print(f"⏱️ 实验 A 耗时: {time.time() - start_time:.2f} 秒")
=== 实验 A: 冻结骨干 (只练 Head) ===
🔧 [模型创建] 加载 ImageNet 预训练权重 (Freeze=True)...
C:\Users\ADVANCE\AppData\Local\Temp\ipykernel_4824\1229741273.py:16: FutureWarning: `torch.cuda.amp.autocast(args...)` is deprecated. Please use `torch.amp.autocast('cuda', args...)` instead.
with autocast():
C:\Users\ADVANCE\AppData\Local\Temp\ipykernel_4824\1229741273.py:43: FutureWarning: `torch.cuda.amp.autocast(args...)` is deprecated. Please use `torch.amp.autocast('cuda', args...)` instead.
with autocast():
Epoch 1: Train Acc: 70.42% | Test Acc: 75.63%
Epoch 2: Train Acc: 76.52% | Test Acc: 76.82%
Epoch 3: Train Acc: 77.49% | Test Acc: 76.55%
⏱️ 实验 A 耗时: 111.37 秒
步骤 5:实验 B - 全网微调 (Fine-tuning)
场景 :
解冻所有层,让 ResNet 针对 CIFAR-10 进行自我调整。
这通常能达到最高精度,但计算量大。得益于我们的 AMP 和 Resize 优化,这里也能跑得很快。
关键技巧 :
分层学习率:骨干网络 (Backbone) 用小火慢炖 (1e-4),分类头 (Head) 用大火爆炒 (1e-3)。
预期 :
3 个 Epoch 内,准确率有望突破 90%。
py
print("\n=== 实验 B: 解冻微调 (追求极致精度) ===")
start_time = time.time()
# 1. 创建解冻模型
model_finetune = create_resnet18(pretrained=True, freeze=False).to(device)
# 2. 分层学习率设置
optimizer = optim.Adam([
{'params': model_finetune.conv1.parameters(), 'lr': 1e-4},
{'params': model_finetune.layer1.parameters(), 'lr': 1e-4},
{'params': model_finetune.layer2.parameters(), 'lr': 1e-4},
{'params': model_finetune.layer3.parameters(), 'lr': 1e-4},
{'params': model_finetune.layer4.parameters(), 'lr': 1e-4},
{'params': model_finetune.fc.parameters(), 'lr': 1e-3} # Head 用大一点的 LR
])
# 3. 训练 3 轮
for epoch in range(3):
train_loss, train_acc = train_one_epoch(model_finetune, train_loader, criterion, optimizer)
test_loss, test_acc = evaluate(model_finetune, test_loader, criterion)
print(f"Epoch {epoch+1}: Train Acc: {train_acc:.2f}% | Test Acc: {test_acc:.2f}%")
print(f"⏱️ 实验 B 耗时: {time.time() - start_time:.2f} 秒")
=== 实验 B: 解冻微调 (追求极致精度) ===
🔧 [模型创建] 加载 ImageNet 预训练权重 (Freeze=False)...
C:\Users\ADVANCE\AppData\Local\Temp\ipykernel_4824\1229741273.py:16: FutureWarning: `torch.cuda.amp.autocast(args...)` is deprecated. Please use `torch.amp.autocast('cuda', args...)` instead.
with autocast():
C:\Users\ADVANCE\AppData\Local\Temp\ipykernel_4824\1229741273.py:43: FutureWarning: `torch.cuda.amp.autocast(args...)` is deprecated. Please use `torch.amp.autocast('cuda', args...)` instead.
with autocast():
Epoch 1: Train Acc: 88.53% | Test Acc: 92.06%
Epoch 2: Train Acc: 95.27% | Test Acc: 92.97%
Epoch 3: Train Acc: 97.24% | Test Acc: 93.48%
⏱️ 实验 B 耗时: 173.20 秒
🎓 Day 44 总结:速度与激情的平衡
今天我们不仅学习了迁移学习,还掌握了显卡榨干技巧。
对比结果:
- 从头训练 (Day 41): 20 轮才跑 75%,还慢。
- 冻结训练 (实验 A): 几秒钟跑完,直接 80%+。适合快速验证想法。
- 微调训练 (实验 B): 精度之王,轻松 90%+。配合 AMP 和 Resize 优化,速度也完全可以接受。
工程化经验:
- 遇到训练慢,先问自己:图是不是太大了?是不是没开混合精度?
- 遇到显存爆,先问自己:Batch Size 是不是太大了?能不能减小图片尺寸?
Next Level :
明天(Day 45),我们将走出分类任务的舒适区,挑战计算机视觉皇冠上的明珠 ------ 目标检测 (Object Detection)。我们将学习如何不仅识别出"猫",还能框出"猫在哪里"!🚀