计算机视觉实战:从被产品追着改需求,到搞定一个商品识别小模型
大家好,我是阿哲,在腾讯做客户端开发快三年了,主要搞微信小程序相关业务。说白了,就是天天和 wx.request、setData、onLoad 这些 API 打交道,偶尔还得和产品经理“友好沟通”一下需求合理性。最近一年,我们组开始尝试把一些轻量级的 AI 能力集成进小程序——比如扫一扫识物、拍照搜同款之类的。这活儿本来该算法团队干,但你知道的,大厂里“全栈化”是种玄学,前端要懂点后端,客户端也得会点 CV(Computer Vision),不然开会连 PPT 都看不懂。
去年双11前,产品突然甩过来一个 PRD:“用户拍一张商品图,我们要在 2 秒内返回相似商品列表。” 我当时就懵了:我一个写 JS 的,咋还让我搞图像识别? 但领导一句“你不是喜欢研究底层原理嘛”,直接给我安排上了。行吧,为了 KPI,硬着头皮上。
为啥非得自己训模型?不能调现成 API 吗?
其实一开始我也想偷懒,直接调腾讯云的图像识别 API。但一测发现两个问题:
- 延迟太高:走公网 + 云端推理,平均 800ms+,加上网络抖动,偶尔飙到 2s+,用户体验直接崩。
- 成本爆炸:双11预估流量 500w 次/天,按次计费的话,财务看了报表怕是要报警。
于是技术方案很快定下来:前端采集图片 → 小程序上传 → 后端轻量模型本地推理 → 返回结果。重点在于“轻量”——模型必须小、快、准,最好能塞进 Docker 容器跑在边缘节点上。
这时候我才意识到:客户端开发的尽头,真的是算法工程化。
算法选型:MobileNetV3 vs EfficientNet-Lite,谁才是真·轻量王者?
我翻遍了 TensorFlow Lite 和 PyTorch Mobile 的文档,对比了几种主流轻量模型:
| 模型 | 参数量 (MB) | 推理速度 (ms, CPU) | Top-1 Acc (%) | 是否支持量化 |
|---|---|---|---|---|
| MobileNetV2 | 14.3 | 45 | 72.0 | ✅ |
| MobileNetV3-Small | 5.7 | 28 | 67.4 | ✅ |
| EfficientNet-Lite0 | 4.9 | 32 | 74.4 | ✅ |
| ResNet-18 | 46.8 | 120+ | 70.1 | ❌(太大) |
数据来自 ImageNet,实测环境:Intel Xeon E5-2686v4(我们测试机配置)。
一眼看去,EfficientNet-Lite0 准确率最高,但速度略逊于 MobileNetV3-Small。不过我们业务场景不是通用分类,而是商品细粒度识别(比如区分 iPhone 14 Pro 和 14 Pro Max),准确率优先级更高。
但!坑来了——EfficientNet-Lite0 在 TensorFlow Lite 上的量化支持有点拉胯,官方 demo 跑起来一堆 warning。而 MobileNetV3-Small 社区生态成熟,连微信小程序官方文档都拿它当例子。
最终拍板:先用 MobileNetV3-Small 快速验证,不行再切 EfficientNet。
数据集:别信公开数据集,真实业务全是脏数据
我天真地以为,下载个 ImageNet 子集或者 Open Images 就能开训。结果第一次 demo 给产品看,人家随手拍了个模糊反光的手机壳,模型输出 “apple fruit” —— 当时我真的想砸键盘。
现实教训:业务场景的数据分布,和公开数据集天差地别。
我们紧急启动了“土法炼钢”:
- 内部爬取商城商品图(约 10w 张)
- 发动全组同事用手机拍办公桌上的物品(水杯、键盘、工牌…)
- 甚至去华强北蹲点拍山寨耳机(别问,问就是业务需要)
最后清洗出 8.2w 张高质量样本,覆盖 1200 个 SKU。标签体系按三级类目设计:电子产品 > 手机 > iPhone 14 Pro。
开发心得 #1:算法再牛,没有对齐业务的数据,都是耍流氓。
训练 & 调优:那些让我凌晨三点还在调参的日子
环境搭在公司内部的 GPU 集群(感谢运维大哥没锁 quota)。代码基于 PyTorch Lightning,主要流程如下:
# train.py 核心片段
from torchvision.models import mobilenet_v3_small
import torch.nn as nn
class ProductClassifier(nn.Module):
def __init__(self, num_classes=1200):
super().__init__()
self.backbone = mobilenet_v3_small(pretrained=True)
# 替换最后分类层
self.backbone.classifier[3] = nn.Linear(
self.backbone.classifier[3].in_features,
num_classes
)
def forward(self, x):
return self.backbone(x)
# 关键训练技巧
optimizer = torch.optim.AdamW(model.parameters(), lr=1e-3, weight_decay=1e-4)
scheduler = torch.optim.lr_scheduler.CosineAnnealingLR(optimizer, T_max=50)
criterion = nn.CrossEntropyLoss(label_smoothing=0.1) # 防过拟合神器
踩过的坑:
过拟合严重:训练集 acc 95%,验证集只有 68%。
→ 解法:加RandomErasing+CutMix数据增强,效果立竿见影。类别不平衡:iPhone 样本 5000 张,某冷门数据线只有 20 张。
→ 解法:采用ClassBalanced Loss,给稀有类更高权重。量化掉点:FP32 模型 76.2% → INT8 量化后 71.5%。
→ 解法:量化感知训练(QAT)!在训练后期插入 fake quant 节点,让模型提前适应量化噪声。
# QAT 关键代码(PyTorch)
model.train()
model.qconfig = torch.quantization.get_default_qat_qconfig('fbgemm')
torch.quantization.prepare_qat(model, inplace=True)
for epoch in range(10): # 只需微调几轮
train_one_epoch(model, ...)
torch.quantization.convert(model, inplace=True)
开发心得 #2:调参不是玄学,是科学试错。每次实验必须记录 seed、lr、augment policy,否则回头根本不知道哪步有效。
部署上线:从 Docker 到小程序,链路打通有多难?
模型训好了,.pt 文件 15MB,转成 TensorFlow Lite 后只有 3.2MB。部署流程:
小程序拍照 → 压缩至 224x224 → 上传 base64 → Flask API → TFLite 推理 → 返回 top5 商品ID
Flask 服务用 Gunicorn + Gevent 跑,单核 CPU 下 QPS 约 80。压测时发现内存泄漏——原来是每次推理都新建 interpreter 实例。改成全局单例后稳如老狗:
# inference.py
import tensorflow as tf
# 全局加载,避免重复初始化
_interpreter = tf.lite.Interpreter(model_path="model.tflite")
_interpreter.allocate_tensors()
def predict(image_tensor):
input_details = _interpreter.get_input_details()
output_details = _interpreter.get_output_details()
_interpreter.set_tensor(input_details[0]['index'], image_tensor)
_interpreter.invoke()
output = _interpreter.get_tensor(output_details[0]['index'])
return output
上线前夜,测试同学报了个诡异 bug:某些安卓机返回全是 NaN。
查了一晚上,发现是 TFLite 版本兼容性问题——低端机用的旧版 runtime 不支持某些 ops。最终解决方案:在转换模型时加 --target_ops=TFLITE_BUILTINS 强制兼容。
开发心得 #3:线上环境永远比你想象的复杂。测试机 ≠ 用户机,尤其在国内千元机生态下。
效果 & 复盘:准确率 82.3%,但产品又提新需求了…
最终线上指标(灰度 10% 流量):
| 指标 | 目标 | 实际 |
|---|---|---|
| 端到端延迟 | ≤2000ms | 1420ms |
| 识别准确率(Top-1) | ≥75% | 82.3% |
| 服务器 CPU 峰值 | ≤60% | 48% |
老板还算满意,但产品周五下午又来找我:“能不能加个‘以图搜视频’功能?” ……我默默打开了 Boss 直聘。
面试题挑战:如果你面试被问“如何优化移动端 CV 模型”?
现在回想,这段经历完美踩中了大厂常考的几个点。如果面试官问我:
“你们怎么保证模型在低端机上跑得快?”
我会这么答:
- 模型层面:选 MobileNet/EfficientNet 等轻量架构,用 depthwise conv 减少计算量;
- 训练层面:通过知识蒸馏(teacher-student)或神经架构搜索(NAS)进一步压缩;
- 部署层面:TensorFlow Lite / Core ML 量化(INT8)、算子融合、CPU 绑核;
- 工程层面:图片预处理在前端做(小程序 canvas resize),减少传输体积;
关键思维:不要只谈算法,要讲清楚端到端链路优化。
最后的碎碎念
说实话,搞完这个项目,我对“客户端开发”的认知彻底刷新了。以前觉得写好 UI 逻辑、处理好兼容性就 OK,现在发现:现代 App 开发,本质是“体验 × 性能 × 智能”的三角平衡。
虽然过程中被 Bug 折磨到秃头,但看到用户真的用“拍照识物”找到心仪商品时,那种成就感……嗯,大概能抵消三次和产品经理吵架的血压升高吧。
如果你也在被“顺便加个 AI 功能”折磨,别慌。记住:所有复杂的系统,都是从一行 import torch 开始的。
对了,最近在刷 LeetCode 的“图像处理”专题,准备跳槽面试。要是你也想交流 CV 实战 or 小程序性能优化,欢迎评论区唠嗑!(或者私信我吐槽产品经理,我超会的)
附:工具链清单(亲测可用)
- 数据标注:LabelImg + 内部自研半自动打标平台
- 训练框架:PyTorch 1.12 + Lightning
- 模型转换:
torch.onnx.export→tf.import_graph_def→ TFLite Converter - 压测工具:locust + 自定义 image payload generator
- 监控:Prometheus + Grafana(盯死 CPU/内存/QPS)

评论 0