个性化推荐系统毕设实战:从协同过滤到实时推荐架构的完整实现
摘要:许多学生在完成“个性化推荐系统毕设”时,常陷入算法堆砌却缺乏工程落地能力的困境。本文基于真实毕设场景,提供一套可部署、可扩展的轻量级推荐系统方案,涵盖离线训练与在线服务解耦、基于用户行为的协同过滤实现、以及使用 FastAPI + Redis 构建低延迟推荐接口。读者将掌握如何平衡算法效果与系统复杂度,并获得可直接用于答辩的完整代码结构与性能优化技巧。
1. 背景痛点:毕设里那些“看起来厉害却跑不起来”的坑
做推荐系统毕设,最容易踩的三个坑:
- 冷启动:论文里写“用内容特征补全”,结果数据里连商品标题都没有,用户画像全靠随机。
- 数据稀疏:10 万用户、5 万商品,交互记录只有 20 万条,稀疏度 99.96%,直接跑深度学习,GPU 风扇转得比风扇还响,AUC 却 0.52。
- 无工程架构:Notebook 里跑通了的模型,放到 Flask 一并发请求就 OOM;老师一句“能上线吗?”直接原地社死。
所以,毕设不是发 KDD,而是“能跑、能抗、能讲”。下面这套“轻量协同过滤 + 在线缓存”方案,我自己在 4 周内从 0 到答辩,服务器成本 60 元/月,老师看完只问了一句“开源吗?”
2. 技术选型:为什么放弃深度学习,拥抱“土味”协同过滤
| 维度 | 深度学习(NNCF) | 基于内存的协同过滤 |
|---|---|---|
| 数据量 | 需要百万级才收敛 | 万级也能出结果 |
| 训练时长 | 2 h+(GPU) | 30 s(CPU) |
| 线上延迟 | 50~100 ms(模型推理) | 10 ms(内存查表) |
| 代码量 | 500+ 行 | 100 行 |
| 答辩难度 | 需要解释调参、网络结构 | 一句“余弦相似度”老师秒懂 |
结论:在“数据小、时间紧、服务器破”的毕设场景,User-Based CF是最优解;等以后有实时流,再上模型也不迟。
3. 核心实现:30 行代码搞定 Top-N 推荐
3.1 数据假设
- 用户行为表
ratings.csv:user_id, item_id, rating, timestamp - 显式评分 1–5,隐式可把 rating 全置 1
3.2 离线训练流程
- 读取 → 去重 → 构建 User-Item 矩阵
- 按皮尔逊系数找 Top-50 邻居
- 对每位用户预生成 Top-20 推荐列表
- 结果落盘
user_rec.pkl,FastAPI 启动时一次性载入内存
3.3 在线服务流程
- 用户 ID 入参 → 查内存字典 → 返回推荐列表
- 若用户冷启动(新 ID),降级到“热门榜”
- 接口加 Redis 缓存,TTL 300 s,防刷
4. 可运行代码(Clean Code 版)
以下脚本可直接python train.py生成模型,uvicorn serve:app拉起服务,依赖仅pandas, scikit-learn, fastapi, uvicorn, redis。
4.1 离线训练 train.py
# train.py import pandas as pd import pickle from sklearn.metrics.pairwise import cosine_similarity RATINGS_PATH = 'ratings.csv' TOPK_USERS = 50 # 邻居数 TOPN_REC = 20 # 推荐长度 OUT_MODEL = 'user_rec.pkl' def build_matrix(df): """构建 User-Item 稀疏矩阵""" matrix = df.pivot_table(index='user_id', columns='item_id', values='rating').fillna(0) return matrix def topk_neighbors(sim_matrix, k=TOPK_USERS): """为每个用户返回最相似的 k 个用户""" neighbors = {} for uid in sim_matrix.index: row = sim_matrix.loc[uid].sort_values(ascending=False) row = row[row.index != uid] # 去掉自己 neighbors[uid] = row.head(k).index.tolist() return neighbors def generate_rec(matrix, neighbors, n=TOPN_REC): """预生成 Top-N 推荐列表""" rec = {} for uid in matrix.index: watched = set(matrix.loc[uid].nonzero()[1]) scores = {} for vid in neighbors[uid]: for iid, score in matrix.loc[vid].items(): if score > 0 and iid not in watched: scores[iid] = scores.get(iid, 0) + score rec[uid] = sorted(scores.items(), key=lambda x: -x[1])[:n] return rec def main(): df = pd.read_csv(RATINGS_PATH) matrix = build_matrix(df) sim = cosine_similarity(matrix) sim = pd.DataFrame(sim, index=matrix.index, columns=matrix.index) neighbors = topk_neighbors(sim) rec = generate_rec(matrix, neighbors) with open(OUT_MODEL, 'wb') as f: pickle.dump(rec, f) print(f'model saved -> {OUT_MODEL}') if __name__ == '__main__': main()4.2 在线服务 serve.py
# serve.py import pickle from fastapi import FastAPI, HTTPException from pydantic import BaseModel import redis rdb = redis.Redis(host='127.0.0.1', port=6379, decode_responses=True) app = FastAPI() rec_dict = pickle.load(open('user_rec.pkl', 'rb')) class RecRequest(BaseModel): user_id: int n: int = 10 @app.post('/rec') def recommend(req: RecRequest): uid = req.user_id n = req.n cache_key = f'rec:{uid}:{n}' # 读缓存 if (cached := rdb.get(cache_key)): return {'user_id': uid, 'items': cached.split(',')} # 查内存 if uid in rec_dict: items = [int(iid) for iid, _ in rec_dict[uid][:n]] else: # 冷启动:热门榜 items = [1313, 5378, 6350, 7368, 7362] # 预计算的全局热门 # 写缓存 rdb.setex(cache_key, 300, ','.join(map(str, items))) return {'user_id': uid, 'items': items}启动命令:
pip install -r requirements.txt python train.py uvicorn serve:app --host 0.0.0.0 --port 80005. 性能与安全:让接口扛得住 1k QPS
缓存策略
- Redis 只存用户最终列表,不存中间矩阵,内存 < 100 MB
- 设置随机 TTL 偏移量,防止雪崩
请求幂等性
- 推荐接口天生只读,不加写操作;如需埋点,用异步队列落盘,不阻塞返回
用户隐私
- 日志脱敏:打印 user_id 哈希后 8 位
- 矩阵文件加文件系统权限 600,禁止匿名下载
6. 生产环境避坑指南
避免全量重算
- 采用“日增量 + 周全量”策略:每天增量更新新增用户,凌晨低峰期再跑一遍全量
并发竞争
- 训练与推理解耦:训练写临时文件
*.tmp,完成后mv原子替换,防止推理读到半写文件
- 训练与推理解耦:训练写临时文件
日志监控
- 在 FastAPI 加
/metrics接口,暴露推荐耗时、缓存命中率 - 使用 Prometheus + Grafana,答辩时可直接放面板截图,老师直呼专业
- 在 FastAPI 加
热更新
- 推荐字典放
global变量,提供/reload管理接口,无需重启服务即可载入新模型
- 推荐字典放
7. 答辩现场:老师最常问的三个问题
“数据稀疏怎么解决?”
答:毕设场景下稀疏度 99%+,采用 User-Based CF 并引入热门榜降级,效果优于盲目加深度网络。“冷启动怎么办?”
答:新用户走热门榜;注册时收集 3 个兴趣标签,即可切换到标签近邻,后续论文可拓展。“如何证明系统能扩展?”
答:接口无状态,横向加机器即可;矩阵预计算,复杂度 O(n²) 在 10 万用户内单机可接受;再大可转 Item-Based 或 Spark 分布式。
8. 下一步思考:实时流与 A/B 测试
把这套系统当成 MVP 已足够毕业,但别止步。可以接着玩:
- 实时行为流:用 Kafka 收集点击事件,5 min 微批更新 Item-Based 增量相似度,做到“刷一次更新一次”
- A/B 测试:在推荐接口埋点,分桶 20% 流量给热门榜,80% 给 CF,用 Click-Through Rate 对比,答辩时直接上实验结果,老师想不给优秀都难
写到这里,代码仓库已开源在 GitHub,标签打好了recommendation-system,graduation-project,fastapi。把 README 截图往论文附录一贴,答辩 PPT 最后一页放二维码,老师只会问:“能不能再帮我们组做一套?”
祝你毕设顺利通过,也欢迎把实时流、A/B 的实验结果告诉我,一起把“土味”CF 玩出花来。