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 冻结策略

推荐两阶段训练:

  1. 先只训练分类头
  2. 再逐步解冻后几层
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
  • 配合 StepLRCosineAnnealingLR
  • 数据集较小时可把 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 通常比全量微调更稳