选题焦虑:把零散的灵感拼成一张可落地的地图
做毕设最怕的不是写代码,而是“选题”本身。很多同学把大量时间花在刷论坛、翻博客,结果越搜越乱:
- 项目太宏大,STM32 跑个 RTOS 就 90% RAM 占用
- 项目太老旧,老师一句“没创新点”直接打回
- 项目资料碎片化,GitHub 上只有一张原理图,代码失踪
把痛点拆下来,无非三件事:信息过载、需求模糊、资源不匹配。AI 辅助推荐不是炫技,而是帮你在 1~2 秒内把“我能做”“我想做”“老师会通过”三件事对齐。
方案选型:规则、TF-IDF 还是协同过滤?
先给结论:在 Cortex-M3/4 这类 64 kB RAM 的小家伙上,别迷信大模型,够用就好。
规则引擎
- 思路:if-else 硬编码“关键词→项目”映射
- 优点:零依赖、可解释
- 缺点:维护噩梦,新增关键词要烧录整包固件
TF-IDF + 余弦相似度
- 思路:把项目简介当文档,向量相似即推荐
- 内存:词典 2 000 词 ≈ 30 kB,加上倒排索引勉强挤得进
- 瓶颈:新词出现要重算 IDF,冷启动 3~4 s,体验打折
轻量级协同过滤(用户-项目共现矩阵分解)
- 思路:只存 0/1 的“点击/收藏”矩阵,用隐语义因子还原
- 内存:100 项目 × 8 因子 = 0.8 kB,可常驻
- 计算:本地矩阵乘法 O(k×n),k=8 时 1 ms 级
- 缺点:需要初始行为日志,否则“冷启动”随机推荐
综合权衡后,我采用“关键词匹配做召回 + 轻量协同过滤做排序”的二级漏斗:
- 召回阶段保证相关,过滤 90% 噪声
- 排序阶段把“热门但已做烂”的项目降权,冷门创新项目提权
端侧实现:把推荐系统塞进 64 kB RAM
数据预处理离线化
- 在 PC 端用 Python 把 1 000 条项目描述分词、去停用词,生成:
- 关键词→项目倒排表(json 转 C 数组)
- 用户-项目交互矩阵(csv 转二进制 0/1)
- 用 Alternating Least Squares 训练隐因子,因子维度 k=8,迭代 10 次即可收敛,RMSE ≈ 0.18
- 把模型参数 quantize 到 int8,体积从 32 kB 压到 8 kB,精度损失 < 2%
运行时三件套
- 关键词哈希表:Perfect Hash 算法保证 O(1) 命中,Flash 只读
- 协同过滤推理:手写定点矩阵乘,无 malloc,栈上完成
- 推荐缓存:LRU 策略保留最近 8 次推荐结果,避免重复计算
内存占用实测(STM32F411 + 128 kB RAM)
- 代码段:24 kB
- 全局只读模型:10 kB
- 运行时栈+缓存:≤ 8 kB
- 留给业务逻辑:> 80 kB,绰绰有余
代码实战:Python 训练 → C 推理 → Arduino 演示
训练脚本(PC 端)
# train_cf.py import pandas as pd from implicit import als # 自己写的极简 ALS,无 numpy 依赖 inter = pd.read_csv('user_project.csv') # 0/1 矩阵 P, Q = als.train(inter, k=8, iter=10, lam=0.05) P.astype('int8').tofile('P_int8.bin') # 用户因子 Q.astype('int8').tofile('Q_int8.bin') # 项目因子推理头文件(model.h)
/* 模型常量 */ #define N_USER 50 #define N_PROJ 100 #define K_LATENT 8 extern const int8_t Q_PROJ[N_PROJ][K_LATENT]; // 项目因子Arduino 演示(推荐核心)
#include "model.h" #include <Arduino.h> struct UserFactor { int8_t v[K_LATENT]; }; int16_t dot8(const int8_t *a, const int8_t *b, uint8_t k){ int16_t s=0; while(k--) s += (*a++) * (*b++); return s; } /* 输入:用户隐因子 u; 返回:top3 项目索引 */ void recommend(const UserFactor &u, uint8_t *topIdx){ int16_t score[N_PROJ]; for(uint16_t i=0;i<N_PROJ;i++) score[i] = dot8(u.v, Q_PROJ[i], K_LATENT); /* 简单选择 top3,不排序,复杂度 O(N*k) */ for(uint8_t t=0;t<3;t++){ int16_t max=-32768; uint8_t idx=0; for(uint16_t i=0;i<N_PROJ;i++){ if(score[i]>max){ max=score[i]; idx=i]; } } topIdx[t]=idx; score[idx]=-32768; // 标记已选 } } void setup(){ Serial.begin(115200); UserFactor me = { .v={12,-7,3,15,0,-2,8,5} }; // 预载因子 uint8_t top[3]; recommend(me, top); for(auto i:top) Serial.println(i); }Clean Code 要点
- 所有魔数(50、100、8)集中宏定义,方便一键调参
- 矩阵乘法手写,避免引入 ARM CMSIS 而增大 10 kB
- 输入输出全部值类型,杜绝动态内存
指标与体验
| 指标 | 实测值 | 说明 |
|---|---|---|
| 冷启动时间 | 380 ms | 从通电到第一次推荐,含模型加载 |
| 单次推荐延迟 | 2.1 ms | 100 项目打分 + top3 选择 |
| Flash 占用 | 34 kB | 含代码+模型 |
| RAM 峰值 | 7.8 kB | 栈局部数组 |
| 准确率 | 0.72@top3 | 人工标注 50 条需求,top3 命中 36 条 |
体验上,把系统做成“串口菜单”形式,学生输入 3~5 个关键词,开发板 1 秒内返回编号与简介,用 OLED 滚动显示,现场演示效果足够惊艳。
生产环境避坑指南
数据漂移
每季度重新跑训练脚本,把新入库的项目和交互日志合并;旧因子权重衰减 0.9,防止热门项目长期霸榜简化模型≠随便压缩
int8 量化前先做 min-max 校准,极端离群值直接截断,否则余弦相似度会畸变,冷门项目永远沉底硬件兼容性
- 8-bit AVR 没有单周期乘法,dot8 改用查表法,牺牲 512 字节 Flash 换 3 倍提速
- 若迁移到 ESP32-C3,RISC-V 指令集无饱和乘法,需开 -march=rv32im 才支持
mul
供电与文件系统
推荐结果想掉电保存,可写进 EEPROM 模拟“本地收藏”,但页写寿命 100 k 次,频繁刷写要平均磨损版权与合规
爬取公开项目摘要时遵守各平台 robots.txt,简介文本仅作学术教学,不二次分发完整 PDF
动手吧:把推荐引擎搬到你的板子上
整套代码已放在 GitHub,默认配置跑在 STM32F4 discovery 板,外接 0.96' OLED。你可以:
- 把关键词词典换成中文分词,支持拼音首字母,方便串口输入
- 把协同过滤换成增量 SVD,用片外 SPI Flash 存参数,重启秒级恢复
- 把推荐结果通过 BLE 发到手机,做成微信小程序,现场答辩扫码即看
毕设不是终点,而是把“需求→模型→部署”跑通的第一圈闭环。祝你把 AI 塞进小小的 MCU,也把自己的灵感塞进更硬核的未来。