用预训练模型识别图像
这是什么:预训练模型(pre-trained model) = 别人在海量数据上训好、免费公开的模型,你拿来直接用。
为什么用:上一章看懂了 CNN 如何从边缘学到物体。但从零训练一个大 CNN 需要上百万张标注图和好几天的 GPU——零基础的我们怎么办?答案是站在巨人的肩膀上。
如何用:本章先讲清"预训练 + 微调"这套组合,再装好 PyTorch,十几行代码识别一张图。
预训练 + 微调
预训练(pre-training):研究者把模型在海量数据(如 ImageNet:128 万张图、1000 个类别)上训好并免费公开。此时模型已认得边缘、纹理、零件——而底层能力是通用的,任何看图任务都用得上。
微调(fine-tuning):拿到预训练模型后,把最后那层"宣布答案"的全连接层换成你自己的(比如 3 类宠物),再用你那几百张图小规模训几轮。底层基本不动,只学"怎么把认得的零件拼成你的类别"。
这就是迁移学习:大数据学通用,小数据学专用。
为什么迁移学习能用?
| 维度 | 从零训练 | 预训练 + 微调 |
|---|---|---|
| 数据需求 | 100 万+ 张标注图 | 几百~几千张即可 |
| 训练时间 | 几天到几周(需 GPU 集群) | 几分钟到几小时(单卡可跑) |
| 底层特征 | 自己学边缘、纹理 | 直接复用已学好的边缘、纹理 |
| 适合场景 | 有海量数据和算力 | 绝大多数中小型看图任务 |
安装工具
PyTorch 是目前最流行的深度学习框架,torchvision 是它的图像工具包:
pip install torch torchvision
(安装包较大,需要几分钟)
十几行代码识别一张图
用预训练 ResNet18 识别一张图片(把 cat.jpg 换成你自己的图)。首次运行会自动下载约 45 MB 的模型文件,请耐心等待;输出为示意,你的类别名和百分比会略有不同:
import torch
from PIL import Image
from torchvision import transforms
from torchvision.models import resnet18, ResNet18_Weights
# 1. 加载 ImageNet 预训练模型,并切换到"只预测"模式
weights = ResNet18_Weights.DEFAULT
model = resnet18(weights=weights).eval()
# 2. 预处理:按模型当初"吃饭的口味"处理图片(缩放、归一化)
preprocess = 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]),
])
# 3. 读图 → 预处理 → 加一维变成"一批 1 张"
img = Image.open("cat.jpg").convert("RGB")
batch = preprocess(img).unsqueeze(0)
# 4. 预测,并把原始分数变成概率
with torch.no_grad():
probs = model(batch).softmax(dim=1)
# 5. 取概率最大的前 5 名
top = probs[0].topk(5)
for score, idx in zip(top.values.tolist(), top.indices.tolist()):
print(f"{weights.meta['categories'][idx]}: {score:.1%}")
你会看到(输出为示意):
Egyptian cat: 84.3%
tabby: 7.2%
tiger cat: 4.1%
lynx: 1.6%
Persian cat: 0.9%
十几行,模型就认出了"这是一只猫"——完全没有自己训练。
代码在做什么
| 代码 | 作用 |
|---|---|
ResNet18_Weights.DEFAULT |
指定官方最新的预训练权重 |
.eval() |
切到推理模式,关掉只在训练时用的行为(如 Dropout、BatchNorm 的训练模式) |
Normalize(...) |
用训练时同样的均值方差归一化(呼应第 1 章) |
unsqueeze(0) |
单图变一批:〔通道, 高, 宽〕→〔1, 通道, 高, 宽〕 |
softmax |
把任意分数变成总和为 1 的概率 |
topk(5) |
取概率最大的 5 个类别 |
torch.no_grad() |
推理时不计算梯度,省显存和时间 |
❓ 常见问题
Q1:首次运行下载模型很慢,能离线吗?
可以。先在有网的机器运行一次,模型权重会缓存到 ~/.cache/torch/hub/checkpoints/,之后拷贝到无网环境也能用。
Q2:ResNet18 识别不了我自己的分类(如"我的两只猫")?
ResNet18 训练的是 ImageNet 的 1000 类(有猫狗车等通用类别),但没有"你家的黑猫""你家的白猫"这种细分。这时候需要第 4 章《实战:微调出自己的图片分类器》。
Q3:为什么 cat.jpg 必须转 RGB?
有些 PNG 是透明通道(RGBA),有些 JPG 本身就是 RGB。.convert("RGB") 统一成 3 通道,避免模型输入通道数不匹配报错。
⚠️ 新手翻车现场
翻车一:忘记 .eval()
# 错误:直接用 model,没调 eval()
model = resnet18(weights=weights)
probs = model(batch).softmax(dim=1)
问题:模型还在训练模式,Dropout 会随机丢弃神经元,BatchNorm 会用当前 batch 的统计量,导致同一张图多次推理结果不一样。
修正:推理前务必调 .eval(),并配合 torch.no_grad()。
翻车二:图片没归一化就直接喂模型
# 错误:只 ToTensor(),没 Normalize()
preprocess = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(), # 只把 0~255 变成 0~1,没按 ImageNet 均值方差标准化
])
问题:模型是用 ImageNet 的均值方差训练的,输入没有同样的归一化,预测结果会很差。
修正:加上 transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])。
🎯 什么时候用预训练模型
- 自己没有上百万张标注图
- 没有 GPU 集群,只能用单卡甚至 CPU
- 任务是通用的图像分类(猫狗、车、花、食品等)
- 需要快速原型,先跑通效果再说
📝 本章小结
- 预训练模型 = 别人在海量数据上训好的通用视觉能力
- 微调 = 换最后一层,用少量数据学自己的类别
- ResNet18 是经典轻量模型,适合入门
- 推理前务必
.eval()+torch.no_grad(),输入要按 ImageNet 标准归一化 - 记忆口诀:预训练学通用,微调学专用;预处理照原样,eval 配 no_grad。
动手改
- 找 3 张不同物体的照片各识别一次,看看第一名准不准
- 把
topk(5)改成topk(1),只输出最可能的类别和概率 - 思考:如果任务是识别"自家两只猫",哪些层可以原样保留?哪一层必须换掉?
下一章:实战:训练自己的图片分类器,亲手微调出属于你的模型 🐸