从单片机到AI:一个嵌入式老狗的机器学习入门血泪史

监控面板盯梢人
2025-12-27 06:14
阅读 281

去年底,我们成都办公室接了个“小”需求——给客户做个智能工单分类系统。说是“小”,但产品经理在站会上轻描淡写地说:“能不能用AI自动识别用户反馈是硬件故障还是软件bug?”
我当时正调试一块STM32板子,手一抖差点把示波器探头插进USB口。我是搞嵌入式的啊!连Python都只用来写过串口脚本!

但没办法,团队就5个人,后端主力刚提了离职,领导看着我这个“会点C又懂点Linux”的老油条说:“你不是最近在学Go吗?Go也能跑ML,试试?”
得,又是“能者多劳”。为了不被扔去维护十年前的Springboot祖传代码(那玩意儿前端还是JSP写的),我硬着头皮啃起了机器学习。


硬件人眼中的“特征”和“模型”

在嵌入式领域,我们常说“信号特征提取”——比如从ADC采样数据里算出RMS值、FFT频谱峰值。这其实和机器学习里的特征工程异曲同工。只不过以前我在STM32上用C写个滑动平均滤波要抠半天内存,现在在Python里一行df['rms'] = np.sqrt(np.mean(df['signal']**2))就搞定,感动到想哭。

工单分类的需求其实很明确:输入一段用户描述(比如“设备开不了机,指示灯不亮”),输出类别(硬件/软件/其他)。典型的文本分类问题。

我一开始天真地以为:找个开源模型,喂点数据,调个API完事。结果现实狠狠打了脸——

  • 第一天:用sklearn的CountVectorizer + LogisticRegression,准确率68%
  • 第三天:换成TF-IDF,72%
  • 第五天:试了朴素贝叶斯,反而跌到65%,当时真的想砸键盘
  • 第七天:组长问进度,我支支吾吾说“还在调参”,他幽幽来一句:“隔壁组用BERT微调,92%了”

救命!我连Transformer是啥都还没搞明白!


别卷了,先搞懂基础概念

痛定思痛,我决定回炉重造。翻出周志华的《机器学习》(俗称“西瓜书”),结合实战重新理解几个核心概念:

1. 特征(Feature)不是你想加就能加

在嵌入式里,加个传感器意味着改PCB、重做EMC测试;在ML里,加特征看似简单,实则暗坑无数。

# 初版特征:只用了词频
from sklearn.feature_extraction.text import CountVectorizer
vectorizer = CountVectorizer()
X = vectorizer.fit_transform(raw_texts)

# 后来加了:文本长度、标点符号数量、是否含"重启"等关键词
import re
def extract_features(text):
    return {
        'length': len(text),
        'exclamations': text.count('!'),
        'has_reboot': 1 if '重启' in text else 0,
        # ... 其他手工特征
    }

但很快发现:手工特征泛化能力差。用户说“设备罢工了”和“机器死活起不来”语义相同,但关键词完全不同。这时候才理解为什么NLP要走向词向量(Word Embedding)。

2. 模型选择:没有银弹,只有trade-off

模型 训练速度 准确率 可解释性 资源消耗
逻辑回归 ⚡️快 🟡中等 ✅高 💧低
随机森林 🐢慢 🟢高 🟡中 💧中
BERT 🐌极慢 🔥极高 ❌黑盒 💦高

作为前嵌入式工程师,我对资源极度敏感。我们的服务最终要部署在4核8G的云服务器上(还得和Springboot后端共享资源!),BERT直接出局。最后折中选了SVM + TF-IDF,准确率85%,推理延迟<50ms,勉强过关。


和Springboot前端联调的那些坑

模型训练完只是开始,集成到现有系统才是地狱

我们的架构是:

前端(Vue) → Springboot API → Python ML服务(gRPC)

坑1:序列化地狱

Springboot用Jackson序列化JSON,Python用pickle保存模型。第一次联调时,前端传了个带emoji的工单:“设备🔥了!”,后端直接500。

解决方案:统一用UTF-8,并在Springboot Controller加过滤:

@PostMapping("/classify")
public ResponseEntity<?> classify(@RequestBody Map<String, String> payload) {
    String text = payload.get("text").replaceAll("[^\\x00-\\x7F]", ""); // 过滤非ASCII
    // 调用gRPC...
}

坑2:冷启动延迟

Python服务首次加载模型要8秒!前端请求超时。运维小哥差点把我挂墙上。

骚操作:用Go写了个轻量代理层(毕竟我是Go转岗选手嘛),预热模型:

// 启动时加载模型
var model *ml.Model
func init() {
    model = ml.LoadModel("ticket_classifier.pkl")
}

// HTTP handler
func classifyHandler(w http.ResponseWriter, r *http.Request) {
    text := r.FormValue("text")
    result := model.Predict(text)
    json.NewEncoder(w).Encode(result)
}

然后Springboot通过HTTP调用这个Go服务,比gRPC省事多了(别告诉架构师)。


给嵌入式/后端转AI的同学几点建议

  1. 别一上来就搞深度学习
    我见过太多人直接上PyTorch,结果连交叉验证都不会做。先用sklearn把传统模型玩透,理解偏差-方差权衡、过拟合这些基础概念。

  2. 数据比模型重要100倍
    我们最初的数据集只有200条标注数据,准确率死活上不去。后来发动全组人手动标注了2000条,准确率直接+15%。脏活累活躲不掉

  3. 部署思维要前置
    在嵌入式行业养成的习惯救了我:时刻想着资源限制。别在本地用32G内存跑通就以为万事大吉,想想线上环境能不能扛住。

  4. 善用现有轮子
    最终方案其实用了HuggingFace的distilbert-base-chinese(轻量版BERT),但只做特征提取,分类器还是逻辑回归。既享受了预训练模型的语义能力,又控制了资源消耗。


写在最后

现在这套系统已经上线三个月,每天处理3000+工单,准确率稳定在89%。虽然比不上隔壁组的92%,但胜在稳定、省资源。上周五下班前,产品经理居然夸我:“你们AI做得不错嘛!” —— 这是我今年听过最动听的话。

从焊电路板到调学习率,跨度有点大,但底层逻辑相通:都是在约束条件下寻找最优解。STM32的RAM有限,云服务器的CPU也有限;示波器看波形,TensorBoard看loss曲线。

如果你也是半路出家学AI,别慌。记住:所有复杂的模型,都始于一个简单的for循环。就像我当年在51单片机上写的第一个LED闪烁程序——万丈高楼平地委,干就完了!

(PS:最近在研究怎么把TinyML塞进ESP32,要是成功了再写篇《在2MB Flash上跑神经网络是什么体验》,敬请期待~)

评论 0

最热最新
暂无评论
匿名用户Lv.1
0
影响力
0
文章
0
粉丝