3. 模型选型与基准测试

模型选型这事儿,说白了就是给产线挑一个「能干又省钱」的伙计。我刚开始做工业缺陷检测移植时,总想着模型越大精度越高,结果上了嵌入式设备直接卡死——帧率不到1FPS,产线根本没法用。后来才明白,选模型不是选精度最高的,而是选能在目标硬件上跑得动的。

这一章,我们就聊聊怎么挑轻量化网络,怎么算FLOPs和参数量,以及怎么在PC上先跑一轮基准测试。

3.1 轻量化网络的选择标准

工业场景下,模型选型我一般看三个硬指标:推理速度、模型体积、精度损失。你想想看,产线上每张图片的检测时间是有上限的,比如要求100ms以内,那模型推理就不能超过80ms,还得留20ms给前后处理。

我个人习惯把选型流程拆成四步:

  1. 定硬件边界:先搞清楚目标设备是什么——是树莓派、Jetson Nano,还是RK3588?不同设备的算力天差地别。
  2. 定精度底线:缺陷检测的漏检率不能超过多少?比如0.5%以内,那模型精度就不能低于某个阈值。
  3. 定体积上限:模型文件能不能塞进Flash?一般嵌入式设备Flash就几百MB,模型太大根本放不下。
  4. 定延迟上限:单帧推理时间必须小于产线节拍时间。

核心原则:选型不是选最好的,而是选「刚好够用」的。多出来的算力,不如留给图像预处理和后处理。

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:模型做一次推理需要多少次浮点运算,决定了计算量和推理速度。

怎么算?我一般用两个工具:

  1. torchsummary:看参数量和每层输出尺寸。
  2. 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 知识结构总览

下面这张图,把模型选型到基准测试的整个流程串起来了:

模型选型与基准测试流程 模型选型与基准测试流程 需求分析 候选模型 指标计算 硬件边界 精度底线 体积上限 延迟上限 MobileNetV3 EfficientNet-Lite ShuffleNetV2 GhostNet 参数量 FLOPs 模型体积 理论计算量 PC端基准测试 平均延迟 最大延迟 吞吐量 精度验证 内存占用 CPU/GPU利用率 最终模型决策

这张图把整个流程分成了四个阶段:需求分析、候选模型、指标计算、基准测试。每个阶段都有明确的输入和输出,最终汇聚到模型决策。

3.6 小结

模型选型这事儿,没有银弹。我踩过的坑不少,总结下来就一句话:先定硬件边界,再算理论指标,最后用基准测试说话。别迷信FLOPs,也别只看参数量,实际跑一跑,什么都清楚了。

下一轮,我们就要把选好的模型往嵌入式设备上移植了。到时候,你会遇到更多「惊喜」——嗯,到时候再说。