跳至内容
迁移学习与模型微调

迁移学习与模型微调

从头训练一个深度学习模型需要海量数据和算力。迁移学习的核心思路是:用别人已经学好的特征作为起点,只在自己的任务上做小幅调整。这是现代深度学习最重要的实践技巧之一。

为什么迁移学习有效

在 ImageNet 上训练的 ResNet,浅层学到的是边缘、颜色、纹理,这些是通用视觉特征,对几乎所有图像任务都有用。微调就是:保留通用底层特征,只让顶层适应新任务

预训练模型(在 100 万张图像上训练好)
  ├── 底层卷积(边缘/颜色)── 冻结,不更新
  ├── 中层卷积(纹理/形状)── 可选冻结
  └── 顶层全连接           ── 替换 + 重新训练

两种基本策略:

  • 特征提取(Feature Extraction):冻结所有层,只训练新的分类头。数据极少时用。
  • 全量微调(Full Fine-tuning):先训练分类头,再解冻全部层一起训练,学习率极小。数据够用时精度更高。

图像分类迁移学习(torchvision)

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import models, transforms, datasets
from torch.utils.data import DataLoader

# 1. 加载预训练模型(自动下载 ImageNet 权重)
model = models.resnet50(weights=models.ResNet50_Weights.DEFAULT)

# 2a. 策略一:特征提取(冻结所有层)
for param in model.parameters():
    param.requires_grad = False

# 替换最后的分类头(原来是 ImageNet 的 1000 类,改成自己的类别数)
num_classes = 5
model.fc = nn.Sequential(
    nn.Dropout(0.5),
    nn.Linear(model.fc.in_features, num_classes)
)
# 此时只有 model.fc 的参数 requires_grad=True

# 2b. 策略二:分层学习率微调(更推荐)
# 底层用更小的学习率,顶层用较大的学习率
param_groups = [
    {"params": model.layer1.parameters(), "lr": 1e-5},
    {"params": model.layer2.parameters(), "lr": 1e-5},
    {"params": model.layer3.parameters(), "lr": 1e-4},
    {"params": model.layer4.parameters(), "lr": 1e-4},
    {"params": model.fc.parameters(),     "lr": 1e-3},
]
optimizer = optim.AdamW(param_groups, weight_decay=1e-2)

# 3. 数据增强(必须用 ImageNet 的均值/标准差)
train_transform = transforms.Compose([
    transforms.RandomResizedCrop(224),
    transforms.RandomHorizontalFlip(),
    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2),
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
])
val_transform = transforms.Compose([
    transforms.Resize(256),
    transforms.CenterCrop(224),
    transforms.ToTensor(),
    transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]),
])

# 4. 训练(两阶段:先训分类头,再解冻全部微调)
def train_phase1(model, train_loader, val_loader, epochs=10):
    """阶段一:只训练分类头"""
    for param in model.parameters():
        param.requires_grad = False
    for param in model.fc.parameters():
        param.requires_grad = True

    optimizer = optim.AdamW(model.fc.parameters(), lr=1e-3, weight_decay=1e-2)
    scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs)
    criterion = nn.CrossEntropyLoss()

    device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
    model  = model.to(device)
    best_acc = 0.0

    for epoch in range(epochs):
        model.train()
        for X, y in train_loader:
            X, y = X.to(device), y.to(device)
            optimizer.zero_grad()
            nn.CrossEntropyLoss()(model(X), y).backward()
            optimizer.step()
        scheduler.step()

        # 验证
        model.eval()
        correct = total = 0
        with torch.no_grad():
            for X, y in val_loader:
                X, y = X.to(device), y.to(device)
                pred = model(X).argmax(1)
                correct += (pred == y).sum().item()
                total   += len(y)
        acc = correct / total
        print(f"Phase1 Epoch {epoch+1}: Val Acc = {acc:.4f}")
        if acc > best_acc:
            best_acc = acc
            torch.save(model.state_dict(), "phase1_best.pt")

    return best_acc


def train_phase2(model, train_loader, val_loader, epochs=20):
    """阶段二:解冻全部层,用极小学习率微调"""
    for param in model.parameters():
        param.requires_grad = True

    # 分层学习率
    param_groups = [
        {"params": [p for n, p in model.named_parameters()
                    if "layer" in n and int(n.split(".")[0][-1]) <= 2], "lr": 1e-5},
        {"params": model.layer3.parameters(), "lr": 5e-5},
        {"params": model.layer4.parameters(), "lr": 1e-4},
        {"params": model.fc.parameters(),     "lr": 5e-4},
    ]
    optimizer = optim.AdamW(param_groups, weight_decay=1e-2)
    scheduler = optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=epochs)
    # ... 训练循环同上

Hugging Face:NLP 迁移学习

对于文本任务,Hugging Face Transformers 是标准工具,几行代码完成微调:

from transformers import (
    AutoTokenizer, AutoModelForSequenceClassification,
    Trainer, TrainingArguments
)
from datasets import Dataset
import torch

# 1. 加载预训练模型(自动选择分词器和模型权重)
model_name = "bert-base-chinese"  # 中文任务
# model_name = "Qwen/Qwen2.5-0.5B"  # 也可用国产模型

tokenizer = AutoTokenizer.from_pretrained(model_name)
model     = AutoModelForSequenceClassification.from_pretrained(
    model_name, num_labels=2  # 二分类
)

# 2. 准备数据集
texts  = ["这个产品太棒了!", "非常失望,质量很差", "还行,一般般", "强烈推荐!"]
labels = [1, 0, 0, 1]

def tokenize(examples):
    return tokenizer(examples["text"], truncation=True, padding="max_length",
                     max_length=128)

dataset = Dataset.from_dict({"text": texts, "label": labels})
dataset = dataset.map(tokenize, batched=True)
train_ds, val_ds = dataset.train_test_split(test_size=0.2).values()

# 3. 训练配置
training_args = TrainingArguments(
    output_dir="./results",
    num_train_epochs=3,
    per_device_train_batch_size=16,
    per_device_eval_batch_size=32,
    learning_rate=2e-5,
    weight_decay=0.01,
    warmup_ratio=0.1,
    lr_scheduler_type="cosine",
    evaluation_strategy="epoch",
    save_strategy="epoch",
    load_best_model_at_end=True,
    fp16=torch.cuda.is_available(),    # 自动开启混合精度
    report_to="none",
)

trainer = Trainer(
    model=model,
    args=training_args,
    train_dataset=train_ds,
    eval_dataset=val_ds,
)
trainer.train()
trainer.save_model("./fine_tuned_model")

LoRA:参数高效微调(大模型场景)

全量微调 70B 的模型需要 140GB+ 显存,大多数人负担不起。LoRA(Low-Rank Adaptation)的思路:冻结原始权重,只训练低秩分解的小矩阵,参数量只有原来的 0.1%–1%,效果接近全量微调。

原始权重矩阵 W(d×k,冻结)
额外训练两个小矩阵:A(d×r)和 B(r×k),r≪d
前向传播:output = W·x + BA·x
只训练 A 和 B,参数量 = r×(d+k),比 d×k 小几百倍
from peft import get_peft_model, LoraConfig, TaskType

lora_config = LoraConfig(
    task_type=TaskType.SEQ_CLS,
    r=16,                               # 低秩维度(越大效果越好,参数越多)
    lora_alpha=32,                      # 缩放因子,通常设为 r 的 2 倍
    lora_dropout=0.1,
    target_modules=["query", "value"],  # 在哪些层加 LoRA(注意力的 Q/V 矩阵)
)

# 用 PEFT 包装原始模型
model = AutoModelForSequenceClassification.from_pretrained("bert-base-chinese", num_labels=2)
lora_model = get_peft_model(model, lora_config)

# 查看参数量
lora_model.print_trainable_parameters()
# trainable params: 294,912 || all params: 102,660,610 || trainable%: 0.29

迁移学习策略选择

根据「数据量 × 任务与预训练任务的相似度」来选策略:
数据量任务相似推荐策略
少(< 1K 样本)只训练分类头,冻结全部底层
少(< 1K 样本)只训练分类头 + 最顶层 1-2 层
中等(1K–10K)两阶段微调(先头,再解冻)
中等(1K–10K)全量微调,较大学习率
多(> 10K)任意全量微调 或 LoRA(大模型)

一句话小结

迁移学习 = 用预训练模型的特征作起点,只在自己的任务上微调。视觉用 torchvision 的 ResNet/EfficientNet;NLP 用 Hugging Face;大模型用 LoRA 省参数。从两阶段微调(先训头,再解冻)开始,是数据有限时最稳健的策略。

最后更新于