预训练模型
知识点回顾:
- 预训练的概念
- 常见的分类预训练模型
- 图像预训练模型的发展史
- 预训练的策略
- 预训练代码实战:resnet18
作业:
- 尝试在cifar10对比如下其他的预训练模型,观察差异,尽可能和他人选择的不同
- 尝试通过ctrl进入resnet的内部,观察残差究竟是什么
知识点 1:预训练(Pre-training)的概念
预训练是深度学习中的一种迁移学习策略,核心逻辑是 "先通用学习,再特定任务适配",具体定义和价值如下:
核心定义:在大规模通用数据集(如 ImageNet)上,先训练一个基础模型,让模型学习到通用的特征提取能力(如边缘、纹理、物体部件等);之后将这个 "预训练好的模型" 作为起点,在小规模特定任务数据集(如自己的分类任务)上进行微调(Fine-tuning),以快速适配目标任务。
本质区别:与 "从零训练(Training from Scratch)" 相比,预训练相当于让模型 "站在巨人的肩膀上"------ 无需从原始像素开始学习基础特征,直接复用通用特征,再优化任务相关的特定特征。
核心价值:
解决小数据问题:当目标任务数据量较小时,从零训练易过拟合,预训练的通用特征可提供有效初始化,提升模型泛化能力;
降低计算成本:大规模数据集预训练需大量算力,但一次预训练可多次复用(微调成本远低于从头训练);
提升模型性能:通用数据集包含更丰富的特征模式,预训练模型的初始参数更优,微调后易达到更高精度。
python
| **模型** | **年份** | **提出团队** | **关键创新点** | **层数** | **参数量** | **ImageNet Top-5错误率** | **典型应用场景** | **预训练权重可用性** |
|----------------|----------|--------------------|--------------------------------------------------------------------------------|----------|------------|--------------------------|------------------------------|------------------------------|
| **LeNet-5** | 1998 | Yann LeCun等 | 首个CNN架构,卷积层+池化层+全连接层,Sigmoid激活函数 | 7 | ~60K | N/A | 手写数字识别(MNIST) | 无(历史模型) |
| **AlexNet** | 2012 | Alex Krizhevsky等 | ReLU激活函数、Dropout、数据增强、GPU训练 | 8 | 60M | 15.3% | 大规模图像分类 | PyTorch/TensorFlow官方支持 |
| **VGGNet** | 2014 | Oxford VGG团队 | 统一3×3卷积核、多尺度特征提取、结构简洁 | 16/19 | 138M/144M | 7.3%/7.0% | 图像分类、目标检测基础骨干网络 | PyTorch/TensorFlow官方支持 |
| **GoogLeNet** | 2014 | Google | Inception模块(多分支并行卷积)、1×1卷积降维、全局平均池化 | 22 | 5M | 6.7% | 大规模图像分类 | PyTorch/TensorFlow官方支持 |
| **ResNet** | 2015 | 何恺明等 | 残差连接(解决梯度消失)、Batch Normalization | 18/50/152| 11M/25M/60M | 3.57%/3.63%/3.58% | 图像/视频分类、检测、分割 | PyTorch/TensorFlow官方支持 |
| **DenseNet** | 2017 | Gao Huang等 | 密集连接(每层与后续所有层相连)、特征复用、参数效率高 | 121/169 | 8M/14M | 2.80% | 小数据集、医学图像处理 | PyTorch/TensorFlow官方支持 |
| **MobileNet** | 2017 | Google | 深度可分离卷积(减少75%计算量)、轻量级设计 | 28 | 4.2M | 7.4% | 移动端图像分类/检测 | PyTorch/TensorFlow官方支持 |
| **EfficientNet** | 2019 | Google | 复合缩放(同时优化深度、宽度、分辨率)、NAS搜索最佳配置 | B0-B7 | 5.3M-66M | 2.6% (B7) | 高精度图像分类(资源受限场景)| PyTorch/TensorFlow官方支持 |
上图的层数,代表该模型不同的版本resnet有resnet18、resnet50、resnet152;efficientnet有efficientnet-b0、efficientnet-b1、efficientnet-b2、efficientnet-b3、efficientnet-b4等
其中ImageNet Top - 5 准确率是图像分类任务里的一种评估指标 ,用于衡量模型在 ImageNet 数据集上的分类性能,模型对图像进行分类预测,输出所有类别(共 1000 类 )的概率,取概率排名前五的类别,只要这五个类别里包含人工标注的正确类别,就算预测正确。
模型架构演进关键点总结
1. **深度突破**:从LeNet的7层到ResNet152的152层,残差连接解决了深度网络的训练难题。 ----没上过我复试班cv部分的自行去了解下什么叫做残差连接,很重要!
2. **计算效率**:GoogLeNet(Inception)和MobileNet通过结构优化,在保持精度的同时大幅降低参数量。
3. **特征复用**:DenseNet的密集连接设计使模型能更好地利用浅层特征,适合小数据集。
4. **自动化设计**:EfficientNet使用神经架构搜索(NAS)自动寻找最优网络配置,开创了AutoML在CNN中的应用。
预训练模型使用建议
| **任务需求** | **推荐模型** | **理由** |
|----------------------------|--------------------|--------------------------------------------------------------------------|
| 快速原型开发 | ResNet50/18 | 结构平衡,预训练权重稳定,社区支持完善 |
| 移动端部署 | MobileNetV3 | 参数量小,计算高效,专为移动设备优化 |
| 高精度分类(资源充足) | EfficientNet-B7 | 目前ImageNet准确率领先,适合GPU/TPU环境 |
| 小数据集或特征复用需求 | DenseNet | 密集连接设计减少过拟合,特征复用能力强 |
| 多尺度特征提取 | Inception-ResNet | 结合Inception多分支和ResNet残差连接,适合复杂场景 |
这些模型的预训练权重均可通过主流框架(如PyTorch的`torchvision.models`、Keras的`applications`模块)直接加载,便于快速迁移到新任务。
预训练的效果依赖于 "数据、任务、模型、训练" 四大维度的策略设计,核心策略如下:
数据策略:决定预训练的 "特征通用性"
数据集选择:优先选择 "规模大、分布广、与目标任务相似" 的数据集(如通用任务用 ImageNet-1K/21K,医学图像用 CheXpert,遥感图像用 NWPU-RESISC45);
**数据增强:**通过随机翻转、裁剪、旋转、颜色抖动、MixUp/CutMix 等增强,扩大数据多样性,避免预训练模型过拟合(如 ImageNet 预训练常用 "随机水平翻转 + 随机裁剪");
数据清洗: 剔除数据集中的标注错误(如 ImageNet 存在约 5% 标注错误),避免错误信息影响预训练特征质量。
任务策略:决定预训练的 "特征导向"
**监督预训练(传统):**以 "图像分类" 为预训练任务(如给模型输入图像,输出 1000 类标签),学习 "从图像到类别" 的判别性特征,适合后续分类、检测等判别性任务;
自监督预训练(当前主流): 无需人工标注,让模型从无标签数据中自学习特征,更适合数据稀缺场景,常见任务包括:
对比学习(如 MoCo、SimCLR):通过 "同一图像的不同增强版本为正样本,其他图像为负样本",让模型学习相似图像的特征聚集;
掩码建模(如 MAE、BEiT):随机掩码部分图像 patch(如掩码 75%),让模型预测掩码区域的像素或语义,学习全局特征关联;
对比学习适合细粒度特征,掩码建模适合全局特征,当前大型预训练模型(如 ViT-L/16)多采用 "自监督预训练 + 监督微调" 的两步策略。
模型策略:决定预训练的 "特征提取能力"
模型初始化:优先选择已验证的基础架构(如 ResNet18/50、ViT-B),避免从零设计复杂模型(风险高、成本大);
正则化策略:加入 Dropout(随机失活)、Weight Decay(权重衰减)、Label Smoothing(标签平滑),防止预训练模型过拟合;
模型适配:根据数据集分辨率调整模型输入尺寸(如 ImageNet 预训练输入 224x224,高分辨率任务预训练输入 384x384)。
训练策略:决定预训练的 "收敛效率与稳定性"
优化器选择:常用 SGD(带动量,如 momentum=0.9)或 AdamW(适合 Transformer,权重衰减集成在优化器中);
学习率调度:采用 "余弦退火" 或 "阶梯衰减"(如 ImageNet 预训练用 "初始学习率 0.1,每 30 轮衰减为 1/10"),避免学习率过高导致不收敛或过低导致收敛慢;
批次大小(Batch Size):尽可能大(如 ImageNet 预训练用 256/512 batch),配合梯度累积(Gradient Accumulation)解决显存不足问题,提升训练稳定性。
ResNet18 的预训练完整代码(以 "CIFAR-10 数据集" 为例,模拟小规模通用数据集预训练,后续可微调至其他任务):
步骤 1:环境准备与导入库
python
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms
from torchvision.models import resnet18 # PyTorch内置ResNet18
import matplotlib.pyplot as plt
# 设备配置(GPU优先)
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(f"使用设备:{device}")
步骤 2:数据加载与增强(CIFAR-10 数据集,10 类,5 万训练图)
python
# 数据增强:训练集用强增强,验证集仅Resize和归一化
train_transform = transforms.Compose([
transforms.RandomResizedCrop(224), # 随机裁剪到224x224(ResNet输入尺寸)
transforms.RandomHorizontalFlip(p=0.5), # 随机水平翻转
transforms.ColorJitter(brightness=0.2, contrast=0.2), # 颜色抖动
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], # ImageNet均值(通用归一化)
std=[0.229, 0.224, 0.225])
])
val_transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
])
# 加载数据集
train_dataset = datasets.CIFAR10(root='./data', train=True, download=True, transform=train_transform)
val_dataset = datasets.CIFAR10(root='./data', train=False, download=True, transform=val_transform)
# 数据加载器
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True, num_workers=4)
val_loader = DataLoader(val_dataset, batch_size=64, shuffle=False, num_workers=4)
步骤 3:定义 ResNet18 模型(修改输出层适配 CIFAR-10)
PyTorch 内置的 ResNet18 默认输出 1000 类(适配 ImageNet),需修改最后全连接层为 10 类(CIFAR-10):
python
def build_resnet18(num_classes=10):
# 加载预训练权重(可选:若已有预训练权重,可加载后微调;此处为"从零预训练",故pretrained=False)
model = resnet18(pretrained=False)
# 修改最后一层全连接层(输入维度512,输出维度num_classes)
in_features = model.fc.in_features
model.fc = nn.Linear(in_features, num_classes)
return model.to(device)
# 初始化模型
model = build_resnet18(num_classes=10)
步骤 4:配置预训练参数(损失函数、优化器、学习率调度)
python
# 1. 损失函数:分类任务用交叉熵损失
criterion = nn.CrossEntropyLoss()
# 2. 优化器:SGD+动量+权重衰减
optimizer = optim.SGD(model.parameters(), lr=0.1, momentum=0.9, weight_decay=5e-4)
# 3. 学习率调度:每15轮衰减为原来的0.1
lr_scheduler = optim.lr_scheduler.StepLR(optimizer, step_size=15, gamma=0.1)
步骤 5:预训练循环(训练 + 验证)
python
def train_one_epoch(model, train_loader, criterion, optimizer, device):
model.train() # 训练模式(启用Dropout等)
total_loss = 0.0
total_correct = 0
total_samples = 0
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() # 更新参数
# 统计指标
total_loss += loss.item() * images.size(0)
_, predicted = torch.max(outputs, 1) # 取预测概率最大的类别
total_correct += (predicted == labels).sum().item()
total_samples += images.size(0)
# 计算 epoch 级指标
avg_loss = total_loss / total_samples
accuracy = total_correct / total_samples
return avg_loss, accuracy
def validate(model, val_loader, criterion, device):
model.eval() # 验证模式(关闭Dropout等)
total_loss = 0.0
total_correct = 0
total_samples = 0
with torch.no_grad(): # 禁用梯度计算,节省显存和时间
for images, labels in val_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
# 统计指标
total_loss += loss.item() * images.size(0)
_, predicted = torch.max(outputs, 1)
total_correct += (predicted == labels).sum().item()
total_samples += images.size(0)
avg_loss = total_loss / total_samples
accuracy = total_correct / total_samples
return avg_loss, accuracy
# 开始预训练(共50轮)
num_epochs = 50
train_losses, train_accs = [], []
val_losses, val_accs = [], []
for epoch in range(num_epochs):
print(f"Epoch [{epoch+1}/{num_epochs}]")
# 训练
train_loss, train_acc = train_one_epoch(model, train_loader, criterion, optimizer, device)
train_losses.append(train_loss)
train_accs.append(train_acc)
print(f"Train - Loss: {train_loss:.4f}, Acc: {train_acc:.4f}")
# 验证
val_loss, val_acc = validate(model, val_loader, criterion, device)
val_losses.append(val_loss)
val_accs.append(val_acc)
print(f"Val - Loss: {val_loss:.4f}, Acc: {val_acc:.4f}")
# 更新学习率
lr_scheduler.step()
# 保存预训练模型(后续可用于微调)
torch.save(model.state_dict(), "resnet18_pretrained_cifar10.pth")
print("预训练模型已保存!")
# 绘制训练曲线(可选,直观查看训练过程)
plt.figure(figsize=(12, 4))
plt.subplot(1, 2, 1)
plt.plot(train_losses, label="Train Loss")
plt.plot(val_losses, label="Val Loss")
plt.xlabel("Epoch")
plt.ylabel("Loss")
plt.legend()
plt.subplot(1, 2, 2)
plt.plot(train_accs, label="Train Acc")
plt.plot(val_accs, label="Val Acc")
plt.xlabel("Epoch")
plt.ylabel("Accuracy")
plt.legend()
plt.show()
作业
python
from torchvision.models import resnet18
import torch
# 加载ResNet-18模型
model = resnet18()
# 打印网络结构(观察layer1~layer4由残差块组成)
print(model)