3. 模型选型与基准测试
模型选型这事儿,说白了就是给产线挑一个「能干又省钱」的伙计。我刚开始做工业缺陷检测移植时,总想着模型越大精度越高,结果上了嵌入式设备直接卡死——帧率不到1FPS,产线根本没法用。后来才明白,选模型不是选精度最高的,而是选能在目标硬件上跑得动的。
这一章,我们就聊聊怎么挑轻量化网络,怎么算FLOPs和参数量,以及怎么在PC上先跑一轮基准测试。
3.1 轻量化网络的选择标准
工业场景下,模型选型我一般看三个硬指标:推理速度、模型体积、精度损失。你想想看,产线上每张图片的检测时间是有上限的,比如要求100ms以内,那模型推理就不能超过80ms,还得留20ms给前后处理。
我个人习惯把选型流程拆成四步:
- 定硬件边界:先搞清楚目标设备是什么——是树莓派、Jetson Nano,还是RK3588?不同设备的算力天差地别。
- 定精度底线:缺陷检测的漏检率不能超过多少?比如0.5%以内,那模型精度就不能低于某个阈值。
- 定体积上限:模型文件能不能塞进Flash?一般嵌入式设备Flash就几百MB,模型太大根本放不下。
- 定延迟上限:单帧推理时间必须小于产线节拍时间。
核心原则:选型不是选最好的,而是选「刚好够用」的。多出来的算力,不如留给图像预处理和后处理。
3.2 MobileNet vs EfficientNet-Lite:怎么选?
目前工业界用得最多的轻量化网络,就是MobileNet系列和EfficientNet-Lite系列。我两个都用过,说说我的感受。
| 对比维度 | MobileNetV3 | EfficientNet-Lite |
|---|---|---|
| 核心思想 | 深度可分离卷积 + 通道注意力 | 复合缩放 + 深度可分离卷积 |
| 参数量 | 2.5M ~ 5.4M | 4.0M ~ 13.0M |
| FLOPs | 60M ~ 220M | 100M ~ 400M |
| 推理速度(Jetson Nano) | ~15ms(224x224) | ~25ms(224x224) |
| 精度(ImageNet Top-1) | ~75.2% | ~76.3% |
| 适用场景 | 极致低延迟、资源极度受限 | 精度要求稍高、算力尚可 |
我在项目中遇到过这样的情况:一个PCB缺陷检测项目,要求单帧推理时间小于20ms。MobileNetV3跑下来18ms,精度损失在可接受范围内;EfficientNet-Lite虽然精度高一点,但跑到28ms,超了。最后选了MobileNetV3,再配合一些剪枝技巧,把延迟压到了15ms。
我的建议:如果目标设备是树莓派4或更低端的MCU,优先考虑MobileNetV3;如果是Jetson Nano或RK3588,EfficientNet-Lite可以试试,但一定要先跑基准测试。
3.3 FLOPs与参数量计算
FLOPs和参数量,这两个指标经常被混淆。我简单解释一下:
- 参数量(Params):模型有多少个可训练参数,决定了模型文件大小和内存占用。
- FLOPs:模型做一次推理需要多少次浮点运算,决定了计算量和推理速度。
怎么算?我一般用两个工具:
- torchsummary:看参数量和每层输出尺寸。
- thop:直接算FLOPs和参数量。
举个例子,算MobileNetV3的FLOPs:
import torch
from thop import profile
from models.mobilenetv3 import mobilenet_v3_small
model = mobilenet_v3_small(pretrained=False)
input = torch.randn(1, 3, 224, 224)
flops, params = profile(model, inputs=(input,))
print(f"FLOPs: {flops / 1e6:.2f}M")
print(f"Params: {params / 1e6:.2f}M")
输出结果:
FLOPs: 62.34M
Params: 2.54M
嗯,这里要注意:FLOPs和实际推理速度不是线性关系。为什么?因为硬件对某些算子有加速,比如NVIDIA的TensorRT对卷积有深度优化,但Depthwise卷积反而可能跑不满。所以FLOPs只能作为参考,最终还是要看实际跑出来的延迟。
避坑指南:我曾经只看FLOPs选模型,结果MobileNetV3的FLOPs比EfficientNet-Lite低,但实际跑起来因为Depthwise卷积在GPU上效率不高,反而比EfficientNet-Lite慢。所以一定要在目标硬件上跑基准测试。
3.4 在PC端跑基准测试
基准测试的目的,是在上嵌入式设备之前,先摸清模型的「底牌」。我一般分三步走:
3.4.1 准备测试数据集
用产线上实际采集的图片,至少1000张。不要用公开数据集,因为工业缺陷的分布和公开数据集差太远。
import os
import cv2
import numpy as np
def load_test_images(data_dir, img_size=(224, 224)):
images = []
for fname in os.listdir(data_dir):
img = cv2.imread(os.path.join(data_dir, fname))
img = cv2.resize(img, img_size)
img = img.astype(np.float32) / 255.0
images.append(img)
return np.array(images)
test_images = load_test_images("./defect_samples/")
print(f"Loaded {len(test_images)} images")
3.4.2 跑推理并统计指标
我习惯统计三个指标:平均延迟、最大延迟、吞吐量。注意要跑多次取平均,因为第一次推理有预热过程。
import time
import torch
model.eval()
with torch.no_grad():
# 预热
dummy = torch.randn(1, 3, 224, 224)
for _ in range(10):
_ = model(dummy)
# 正式测试
latencies = []
for img in test_images:
tensor = torch.from_numpy(img).unsqueeze(0)
start = time.time()
_ = model(tensor)
latencies.append((time.time() - start) * 1000)
avg_latency = np.mean(latencies)
max_latency = np.max(latencies)
throughput = 1000 / avg_latency
print(f"Avg Latency: {avg_latency:.2f}ms")
print(f"Max Latency: {max_latency:.2f}ms")
print(f"Throughput: {throughput:.2f} FPS")
3.4.3 对比多个模型
把候选模型都跑一遍,结果整理成表格:
| 模型 | 参数量 | FLOPs | 平均延迟 | 吞吐量 | 精度(mAP) |
|---|---|---|---|---|---|
| MobileNetV3-Small | 2.54M | 62M | 12.3ms | 81.3 FPS | 92.1% |
| MobileNetV3-Large | 5.48M | 219M | 18.7ms | 53.5 FPS | 94.5% |
| EfficientNet-Lite0 | 4.01M | 100M | 22.1ms | 45.2 FPS | 93.8% |
| EfficientNet-Lite1 | 5.42M | 150M | 28.4ms | 35.2 FPS | 94.2% |
小技巧:PC上的基准测试结果,和嵌入式设备上的结果一般有个比例关系。比如PC上跑20ms的模型,在Jetson Nano上大概跑60-80ms。这个比例因硬件而异,建议先拿一个模型做标定。
3.5 知识结构总览
下面这张图,把模型选型到基准测试的整个流程串起来了:
这张图把整个流程分成了四个阶段:需求分析、候选模型、指标计算、基准测试。每个阶段都有明确的输入和输出,最终汇聚到模型决策。
3.6 小结
模型选型这事儿,没有银弹。我踩过的坑不少,总结下来就一句话:先定硬件边界,再算理论指标,最后用基准测试说话。别迷信FLOPs,也别只看参数量,实际跑一跑,什么都清楚了。
下一轮,我们就要把选好的模型往嵌入式设备上移植了。到时候,你会遇到更多「惊喜」——嗯,到时候再说。