3. 模型评估与性能分析:用数据说话
模型压缩完了,效果怎么样?不能光靠感觉。我见过太多同学,压缩完模型一看 mAP 掉了 0.5% 就慌了,其实根本没搞清楚瓶颈在哪。这一章,我们就来聊聊怎么用 FLOPs、Params、FPS、mAP 这些硬指标,给模型做一次全面体检。
3.1 四大核心指标,各司其职
先看一张总览表,把这四个指标的关系理清楚:
| 指标 | 全称 | 衡量什么 | 单位 | 压缩后趋势 |
|---|---|---|---|---|
| FLOPs | 浮点运算次数 | 计算复杂度 | GFLOPs | ↓ 减少 |
| Params | 参数量 | 模型大小 | M (百万) | ↓ 减少 |
| FPS | 每秒帧数 | 推理速度 | fps | ↑ 提升 |
| mAP | 平均精度均值 | 检测精度 | % | ↓ 轻微下降 |
说白了,FLOPs 和 Params 是模型本身的属性,FPS 是运行时的表现,mAP 是最终效果。四者之间没有的线性关系,这也是评估的难点所在。
3.2 FLOPs 与 Params:理论复杂度分析
我个人习惯,拿到一个压缩后的模型,第一件事就是算 FLOPs 和 Params。这两个指标能帮你快速判断压缩方法是否有效。
3.2.1 怎么算?用工具
别手算,用现成的库。PyTorch 生态里,我常用 thop 或者 ptflops。举个例子:
# 安装:pip install thop
import torch
from thop import profile, clever_format
# 加载你的 YOLOv11 模型(假设已经定义好)
model = YOLOv11() # 你的模型实例
input = torch.randn(1, 3, 640, 640) # 标准输入
flops, params = profile(model, inputs=(input,))
flops, params = clever_format([flops, params], "%.3f")
print(f"FLOPs: {flops}, Params: {params}")
# 输出示例:FLOPs: 128.456G, Params: 25.300M
3.2.2 压缩前后的对比
假设我们对 YOLOv11 做了剪枝和量化,结果可能像这样:
| 模型版本 | FLOPs (G) | Params (M) | 压缩率 |
|---|---|---|---|
| 原始 YOLOv11 | 128.5 | 25.3 | - |
| 剪枝 30% | 89.9 | 17.7 | 30% |
| 剪枝 + INT8 量化 | 22.5 | 4.4 | 82% |
你看,剪枝主要降参数量,量化主要降 FLOPs。两者结合效果最好。但注意,FLOPs 降了 82%,FPS 不一定能提升 5 倍,因为还有内存带宽、算子优化等瓶颈。
3.3 FPS:真实推理速度
FLOPs 再低,跑不快也是白搭。FPS 才是用户能感知到的指标。我曾在项目中遇到一个坑:模型在 PC 上跑 120 FPS,换到 Jetson Orin 上只有 15 FPS。为什么?因为没考虑硬件特性。
3.3.1 怎么测 FPS?
测 FPS 要模拟真实部署环境。别用 PyTorch 的 eager 模式测,要用 TensorRT 或 ONNX Runtime。下面是一个标准流程:
- 模型导出:将 PyTorch 模型转成 ONNX 或 TensorRT 引擎。
- 预热:跑 100 次推理,让 GPU 进入稳定状态。
- 正式测试:连续跑 1000 次,记录总时间。
- 计算 FPS:1000 / 总时间(秒)。
import time
import numpy as np
import tensorrt as trt
# 假设已经加载了 TensorRT 引擎
engine = load_engine("yolov11.trt")
context = engine.create_execution_context()
# 预热
for _ in range(100):
context.execute_v2(bindings)
# 正式测试
times = []
for _ in range(1000):
start = time.time()
context.execute_v2(bindings)
end = time.time()
times.append(end - start)
fps = 1000 / sum(times)
print(f"FPS: {fps:.2f}")
3.3.2 影响 FPS 的隐藏因素
- Batch Size:batch size 越大,吞吐量越高,但延迟也会增加。边缘设备通常用 batch=1。
- 内存带宽:量化后的模型虽然计算量小了,但如果内存带宽不够,数据搬运会成为瓶颈。
- 算子支持:某些自定义算子(如 SiLU)在 TensorRT 上可能没有高效实现,导致降速。
3.4 mAP:精度不能丢
压缩的代价通常是精度下降。mAP 就是用来衡量这个代价的。YOLOv11 官方用的是 COCO 数据集上的 mAP@0.5:0.95,但实际项目中,我更关注 mAP@0.5,因为很多场景下 IoU=0.5 就够用了。
3.4.1 精度与速度的权衡
我习惯画一张 精度-速度曲线,横轴是 FPS,纵轴是 mAP。不同压缩策略会形成不同的曲线。比如:
- 剪枝:mAP 下降平缓,FPS 提升线性。
- 量化:mAP 可能突然掉点(尤其是 INT4),但 FPS 提升显著。
- 蒸馏:mAP 保持最好,但 FPS 提升有限。
你的任务就是找到那个“甜点”——mAP 下降可接受,FPS 满足需求。
3.5 定位性能瓶颈:一张图看懂
下面这张 SVG 图,是我总结的瓶颈定位流程。每次评估完,我都会走一遍这个逻辑:
这个流程我用了很多项目,屡试不爽。核心思路是:先看理论,再看实际,最后验证效果。每一步都有明确的判断标准,不会盲目优化。
3.6 实战案例:一个完整的评估报告
假设我们做了一个剪枝 + 量化的 YOLOv11 模型,下面是完整的评估数据:
| 指标 | 原始模型 | 压缩后模型 | 变化 | 是否达标 |
|---|---|---|---|---|
| FLOPs | 128.5 G | 22.5 G | ↓ 82.5% | ✅ 是 |
| Params | 25.3 M | 4.4 M | ↓ 82.6% | ✅ 是 |
| FPS (TensorRT) | 45 fps | 210 fps | ↑ 4.7x | ✅ 是 |
| mAP@0.5 | 72.3% | 69.8% | ↓ 2.5% | ⚠️ 临界 |
| mAP@0.5:0.95 | 51.2% | 47.6% | ↓ 3.6% | ❌ 否 |
3.7 避坑指南
最后,分享几个我踩过的坑:
- 别信单次测试:FPS 至少测 3 次取平均,每次 1000 帧以上。系统负载波动会影响结果。
- 注意输入尺寸:评估 FLOPs 时用的输入尺寸,必须和部署时一致。我见过有人用 640x640 评估,部署时用 320x320,结果 FPS 翻倍了,但 mAP 也掉了。
- 量化后的 mAP 波动:INT8 量化后,mAP 可能上下波动 0.5%。这不是 bug,是量化噪声。多测几次取平均。
- 硬件差异:同一个模型,在 A100 上跑 500 FPS,在 Jetson Nano 上可能只有 10 FPS。评估一定要在目标硬件上进行。