1. 剪切拼接加强数据
MixUp / CutMix 可以提高泛化能力,常用于分类任务。
import torch
import numpy as np
def rand_bbox(size, lam):
# size: [B, C, H, W]
W = size[2]
H = size[3]
cut_rat = np.sqrt(1.0 - lam)
cut_w = int(W * cut_rat)
cut_h = int(H * cut_rat)
cx = np.random.randint(W)
cy = np.random.randint(H)
x1 = np.clip(cx - cut_w // 2, 0, W)
y1 = np.clip(cy - cut_h // 2, 0, H)
x2 = np.clip(cx + cut_w // 2, 0, W)
y2 = np.clip(cy + cut_h // 2, 0, H)
return x1, y1, x2, y2
def mixup_data(x, y, alpha=1.0):
lam = np.random.beta(alpha, alpha) if alpha > 0 else 1.0
idx = torch.randperm(x.size(0))
mixed_x = lam * x + (1 - lam) * x[idx]
y_a, y_b = y, y[idx]
return mixed_x, y_a, y_b, lam
def cutmix_data(x, y, alpha=1.0):
lam = np.random.beta(alpha, alpha) if alpha > 0 else 1.0
idx = torch.randperm(x.size(0))
y_a, y_b = y, y[idx]
x1, y1, x2, y2 = rand_bbox(x.size(), lam)
x[:, :, x1:x2, y1:y2] = x[idx, :, x1:x2, y1:y2]
lam = 1 - ((x2 - x1) * (y2 - y1) / (x.size(-1) * x.size(-2)))
return x, y_a, y_b, lam
训练时可随机切换:
for epoch in range(10):
modelnet.train()
for images, labels in train_loader:
if np.random.rand() < 0.5:
images, y_a, y_b, lam = mixup_data(images, labels, alpha=1.0)
else:
images, y_a, y_b, lam = cutmix_data(images, labels, alpha=1.0)
outputs = modelnet(images)
loss = lam * criterion(outputs, y_a) + (1 - lam) * criterion(outputs, y_b)
optimizer.zero_grad()
loss.backward()
optimizer.step()
2. 微调预训练模型时的核心改动
2.1 数据预处理
- 训练集:随机增强
- 测试集:只做确定性预处理
- 预训练模型通常使用
224x224和 ImageNet 归一化
from torchvision import transforms, datasets
train_transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.RandomHorizontalFlip(0.5),
transforms.RandomCrop(224, padding=16),
transforms.ToTensor(),
transforms.Normalize((0.485, 0.456, 0.406),
(0.229, 0.224, 0.225)),
])
test_transform = transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize((0.485, 0.456, 0.406),
(0.229, 0.224, 0.225)),
])
2.2 模型分类头
- ResNet:改
model.fc - ViT:改
model.heads.head
import torch.nn as nn
from torchvision.models import resnet18, ResNet18_Weights
from torchvision.models import vit_b_16, ViT_B_16_Weights
num_classes = 10
resnet = resnet18(weights=ResNet18_Weights.IMAGENET1K_V1)
resnet.fc = nn.Linear(resnet.fc.in_features, num_classes)
vit = vit_b_16(weights=ViT_B_16_Weights.IMAGENET1K_V1)
vit.heads.head = nn.Linear(vit.heads.head.in_features, num_classes)
2.3 冻结策略
推荐两阶段训练:
- 先只训练分类头
- 再逐步解冻后几层
def freeze_all(model):
for p in model.parameters():
p.requires_grad = False
freeze_all(resnet)
for p in resnet.fc.parameters():
p.requires_grad = True
freeze_all(vit)
for p in vit.heads.parameters():
p.requires_grad = True
2.4 训练循环
device = torch.device("mps" if torch.backends.mps.is_available() else
"cuda" if torch.cuda.is_available() else "cpu")
model = model.to(device)
for epoch in range(10):
model.train()
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
model.eval()
correct, total = 0, 0
with torch.no_grad():
for images, labels in test_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
pred = outputs.argmax(dim=1)
total += labels.size(0)
correct += (pred == labels).sum().item()
print(f"Epoch {epoch+1}, Acc: {100 * correct / total:.2f}%")
3. 优化器选择与参数建议
3.1 常用优化器对比
| 优化器 | 适用场景 | 推荐学习率 | 关键参数 |
|---|---|---|---|
| SGD | 从头训练、CNN、稳定收敛 | 0.1 ~ 0.01 |
momentum=0.9, weight_decay=5e-4 |
| Adam | 小模型、快速收敛、调参简单 | 1e-3 ~ 1e-4 |
betas=(0.9, 0.999) |
| AdamW | ViT、Transformer、微调预训练模型 | 3e-4 ~ 1e-5 |
weight_decay=0.01 ~ 0.05 |
| RMSprop | 部分 CNN / 旧模型 | 1e-3 |
alpha=0.99, momentum=0.9 |
3.2 SGD 调参建议
适合 CNN 和从零训练。
optimizer = torch.optim.SGD(
model.parameters(),
lr=0.1,
momentum=0.9,
weight_decay=5e-4,
nesterov=True
)
建议:
- 学习率通常从
0.1起 - 配合
StepLR或CosineAnnealingLR - 数据集较小时可把
lr降到0.01
3.3 Adam 调参建议
适合快速试验和中小模型。
optimizer = torch.optim.Adam(
model.parameters(),
lr=1e-3,
betas=(0.9, 0.999),
weight_decay=0.0
)
建议:
- 从头训练可用
1e-3 - 如果 loss 波动大,可降到
3e-4 - 一般不建议大 weight decay
3.4 AdamW 调参建议
适合 ViT / Transformer / 预训练微调。
optimizer = torch.optim.AdamW(
model.parameters(),
lr=3e-4,
betas=(0.9, 0.999),
weight_decay=0.05
)
建议:
- ViT 微调首选
- 头部层可稍大学习率
- 主干层可更小学习率
- 常配合 warmup
3.5 RMSprop 调参建议
适合一些早期 CNN 任务。
optimizer = torch.optim.RMSprop(
model.parameters(),
lr=1e-3,
alpha=0.99,
momentum=0.9,
weight_decay=1e-4
)
建议:
- 学习率一般从
1e-3起 - 对梯度噪声较敏感,通常不如 AdamW 常用
4. 优化器使用建议
CIFAR10 + CNN
- 首选:
SGD + momentum - 次选:
Adam
CIFAR10 + ViT / Transformer
- 首选:
AdamW - 学习率通常比 CNN 更小
预训练模型微调
- 头部层:
1e-3 ~ 1e-4 - 主干层:
1e-4 ~ 1e-5 - 先冻结主干,再逐步解冻
5. 查看模型结构
打印整体结构
print(modelnet)
查看所有子模块
for name, module in modelnet.named_modules():
print(name, "->", module.__class__.__name__)
查看参数形状和是否可训练
for name, p in modelnet.named_parameters():
print(name, p.shape, "trainable=", p.requires_grad)
使用 torchinfo 查看摘要
先安装:
pip install torchinfo
然后:
from torchinfo import summary
summary(modelnet, input_size=(1, 3, 32, 32))
6. 训练建议总结
- 从头训练:优先
SGD - 微调 ViT:优先
AdamW - 只训分类头:学习率可大一些
- 解冻主干:学习率要小
- 数据增强不要过强,否则会影响收敛
- 测试集不要使用随机增强
7. ResNet 和 ViT 结构图
7.1 ResNet18 结构图
flowchart TD
A[输入图像<br/>3×224×224] --> B[Conv1 + BN + ReLU]
B --> C[MaxPool]
C --> D1[Residual Block × 2]
D1 --> D2[Residual Block × 2]
D2 --> D3[Residual Block × 2]
D3 --> D4[Residual Block × 2]
D4 --> E[Global Avg Pool]
E --> F[全连接层]
F --> G[分类输出]
ResNet 特点:
- 使用残差连接,缓解深层网络退化问题
- 适合从头训练和迁移学习
- 在 CIFAR10 上通常需要把输入改为
224×224或使用专门的 CIFAR 版 ResNet
7.2 ViT-B/16 结构图
flowchart TD
A[输入图像<br/>3×224×224] --> B[切分为 16×16 Patch]
B --> C[Patch Embedding]
C --> D[加上 CLS Token]
D --> E[加入位置编码]
E --> F[Transformer Encoder × 12]
F --> G[取 CLS Token]
G --> H[MLP 分类头]
H --> I[分类输出]
ViT 特点:
- 把图像当作 token 序列处理
- 依赖较大的数据集或预训练
- 常配合
AdamW和较小学习率 - 输入通常要求
224×224
7.3 两者对比
| 模型 | 核心思路 | 结构特点 | 常用优化器 |
|---|---|---|---|
| ResNet | 卷积 + 残差 | 局部特征提取强,训练稳定 | SGD / Adam |
| ViT | Patch + Transformer | 全局建模强,更依赖数据量 | AdamW |
7.4 选择建议
- 数据量较小:优先 ResNet
- 想尝试 Transformer:优先 ViT
- 从头训练:ResNet 更稳
- 预训练微调:ViT 效果通常更好
8. LoRA 微调 Transformer
LoRA 适合在冻结大部分参数的情况下,只训练少量低秩适配层,显著降低显存和训练成本。
8.1 安装依赖
pip install peft transformers
8.2 代码示例
下面以 Hugging Face 的 Transformer 为例:
import torch
import torch.nn as nn
from transformers import AutoModelForSequenceClassification
from peft import LoraConfig, TaskType, get_peft_model
device = torch.device("mps" if torch.backends.mps.is_available() else
"cuda" if torch.cuda.is_available() else "cpu")
# 1) 加载基础模型
base_model = AutoModelForSequenceClassification.from_pretrained(
"bert-base-uncased",
num_labels=10
)
# 2) 冻结原始参数
for p in base_model.parameters():
p.requires_grad = False
# 3) 配置 LoRA
lora_config = LoraConfig(
task_type=TaskType.SEQ_CLS,
r=8, # 低秩维度,常用 4/8/16
lora_alpha=16, # 一般取 2r 或 4r
lora_dropout=0.1, # 0.05 ~ 0.1 常见
target_modules=["query", "key", "value", "dense"]
)
# 4) 注入 LoRA
model = get_peft_model(base_model, lora_config)
model = model.to(device)
# 5) 只优化可训练参数
optimizer = torch.optim.AdamW(
filter(lambda p: p.requires_grad, model.parameters()),
lr=2e-4,
weight_decay=0.01
)
criterion = nn.CrossEntropyLoss()
print(model.print_trainable_parameters())
8.3 训练方式
for epoch in range(10):
model.train()
for batch in train_loader:
inputs = {k: v.to(device) for k, v in batch.items() if k != "labels"}
labels = batch["labels"].to(device)
outputs = model(**inputs)
loss = criterion(outputs.logits, labels)
optimizer.zero_grad()
loss.backward()
optimizer.step()
8.4 参数建议
r=4/8/16:越大可训练能力越强,但参数更多lora_alpha=2r 或 4r:常用经验值lora_dropout=0.05~0.1:防止过拟合lr=1e-4 ~ 3e-4:LoRA 通常可用较大学习率target_modules:要按模型结构调整
8.5 适用建议
- 文本 Transformer:适合直接用
peft + transformers - ViT / 视觉 Transformer:也可以用 LoRA,但需要确认模块名
- 数据量较小:LoRA 通常比全量微调更稳