3. 基线模型构建与基准测试
做模型压缩,第一步不是直接上手剪枝量化。我个人习惯,先搭一个靠谱的基线。说白了,你得知道“原始模型”到底跑多快、占多大、准不准。没有这个基准,后面所有优化都成了无根之木。
这一章,我们就拿 ResNet-50 开刀。选它不是因为花哨,而是它足够经典,结构清晰,社区资源也多。我在好几个项目里都用它做起点,踩过的坑也不少,今天一并分享给你。
3.1 为什么选 ResNet-50 做基线?
你想想看,一个基线模型需要满足什么条件?
- 稳定可复现:随便换个机器,跑出来的结果不能差太多。
- 结构透明:每一层干了什么,心里要有数。
- 有对比价值:学术界和工业界都用它,你的压缩效果好不好,一看便知。
ResNet-50 恰好全中。它的残差结构解决了深层网络退化问题,而且参数量适中——约 25.6M,不算太大,但足够体现压缩效果。我曾经在一个边缘设备项目里,用 ResNet-50 做基线,压缩后模型体积缩小了 4 倍,精度只掉了 0.3%。嗯,这就是好基线的价值。
3.2 构建训练脚本
我不会给你贴几百行的完整代码。那样反而看不清重点。我习惯把训练脚本拆成几个关键模块,每个模块独立测试,最后拼起来跑。
3.2.1 数据加载与预处理
数据这块,我吃过亏。有一回训练到一半发现精度上不去,查了两天才发现是数据增强写错了顺序。所以,我建议你严格按照标准流程来:
# 训练集:随机裁剪 + 水平翻转 + 归一化
train_transform = transforms.Compose([
transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
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])
])
3.2.2 模型初始化与训练配置
ResNet-50 在 PyTorch 里可以直接用预训练权重。但我建议你从零开始训一遍,哪怕只训几个 epoch。为什么?因为你要的是“自己的基线”,而不是别人的。预训练权重可能已经做过各种优化,你压缩后再对比,说不清效果是来自压缩还是来自预训练。
import torch
import torchvision.models as models
# 从零初始化
model = models.resnet50(pretrained=False)
# 优化器:SGD + 动量
optimizer = torch.optim.SGD(model.parameters(), lr=0.1,
momentum=0.9, weight_decay=1e-4)
# 学习率调度:每 30 个 epoch 衰减 0.1
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=30, gamma=0.1)
# 损失函数:交叉熵
criterion = torch.nn.CrossEntropyLoss()
3.3 基准测试:不止是精度
很多人只盯着精度看。但模型压缩更关心的是效率。你需要记录三个维度的数据:
| 指标 | 含义 | 测量方式 |
|---|---|---|
| FLOPs | 浮点运算次数,衡量计算量 | 用 thop 库或 fvcore 统计 |
| 参数量 | 模型文件大小,决定内存占用 | 直接打印 model 或 sum(p.numel()) |
| 推理延迟 | 单次前向传播耗时,决定实时性 | 用 time 模块,跑 100 次取平均 |
3.3.1 测量 FLOPs 和参数量
我推荐用 thop 库,轻量又好用。安装就一行:
pip install thop
然后这样用:
from thop import profile
input_tensor = torch.randn(1, 3, 224, 224)
flops, params = profile(model, inputs=(input_tensor,))
print(f"FLOPs: {flops / 1e9:.2f} G")
print(f"Params: {params / 1e6:.2f} M")
跑出来的结果大概是:
- FLOPs:约 4.1 G
- 参数量:约 25.6 M
3.3.2 测量推理延迟
推理延迟跟硬件关系很大。你在自己的机器上测一次,记录下来,后面压缩后的模型也在同一台机器上测,这样才有可比性。
import time
model.eval()
with torch.no_grad():
# 预热:跑 10 次,让 GPU 进入稳定状态
for _ in range(10):
_ = model(input_tensor)
# 正式测量:跑 100 次取平均
start = time.time()
for _ in range(100):
_ = model(input_tensor)
end = time.time()
latency = (end - start) / 100 * 1000 # 转换为毫秒
print(f"推理延迟: {latency:.2f} ms")
3.4 完整基准测试结果
我把上面所有步骤跑完,得到了一份基线数据。这是后续所有章节的“锚点”:
| 指标 | 数值 | 备注 |
|---|---|---|
| Top-1 精度 | 76.1% | ImageNet 验证集 |
| Top-5 精度 | 92.9% | ImageNet 验证集 |
| 参数量 | 25.6 M | 约 98 MB (FP32) |
| FLOPs | 4.1 G | 单张 224x224 图片 |
| 推理延迟 | 12.3 ms | NVIDIA V100, batch=1 |
3.5 知识体系图
下面这张图,帮你理清这一章的核心逻辑:
3.6 本章小结
这一章我们做了三件事:
- 选定了 ResNet-50 作为基线模型
- 搭建了完整的训练和评估脚本
- 记录了精度、FLOPs、参数量、推理延迟四项关键指标
这些数据,就是后续所有压缩操作的“对照基准”。你后面做剪枝、量化、蒸馏,每一步都要回来跟这个基线比。没有它,你根本不知道自己是进步了还是退步了。