机器学习算法入门:基础概念详解(一个外包老兵的踩坑实录)
上周五晚上十点半,我还在工位上对着 Vim 编辑器发呆——不是在写 Rust,而是在给一个“综合智能前端推荐系统”打补丁。这项目是去年双11前接的,甲方爸爸嘴上说“要个轻量级的小功能”,结果需求文档越写越像《深度学习圣经》。更离谱的是,他们连数据清洗都没做,直接扔给我一堆 CSV 文件,字段名还是中文拼音缩写,比如 yhlx(用户类型)、splx(商品类型)……
作为一个在外包公司摸爬滚打四年的老油条,我早就习惯了这种“既要马儿跑又要马儿不吃草”的戏码。但这次真的有点绷不住了:产品经理在周一晨会上拍胸脯说“下周上线”,测试同事已经在群里@我问接口文档了,而我连训练集都还没跑通。
无奈之下,只能硬着头皮捡起半年前为了跳槽刷题时顺手看的机器学习笔记。没想到,这一捡,还真让我搞出点东西来。今天这篇博客,不讲高深理论,就聊聊我在实际项目里怎么用最基础的算法搞定这个“前端智能推荐”需求的——顺便吐吐槽、避避坑。
背景:甲方要的“智能”,其实很原始
先说清楚业务场景。这是一个电商类 H5 前端页面,需要根据用户历史行为(点击、加购、下单)实时推荐商品。听起来很高大上?其实核心逻辑就是:相似用户喜欢的东西,你也可能喜欢。
甲方最初提的需求是“用 AI 算法做个性化推荐”,但当我问清楚预算和数据规模后,心凉了半截:
- 用户行为日志只有三个月,总共不到 50 万条
- 没有用户画像,只有设备 ID 和行为类型
- 商品库也就 2000 多个 SKU
这种数据量,别说 Transformer 了,连 XGBoost 都有点杀鸡用牛刀。最后我和 PM 达成共识:先用最简单的协同过滤(Collaborative Filtering) 跑个 MVP,能上线就行。
插一句:外包项目最重要的不是技术多牛,而是按时交付。别跟我扯什么 SOTA(State-of-the-Art),deadline 才是第一生产力。
从零开始:用 scikit-learn 搞定基础模型
虽然我是个 Vim 党,平时写 Rust 写得飞起,但机器学习这块我还是老老实实用 Python + Jupyter Notebook(别骂了,真香)。毕竟生态摆在那,scikit-learn 几行代码就能跑通流程。
首先得把数据处理干净。那个 yhlx 字段其实是用户分群标签,但有 30% 是空值。我直接用众数填充(别笑,外包项目哪有时间搞 fancy imputation):
# 数据预处理:简单粗暴但有效
import pandas as pd
from sklearn.impute import SimpleImputer
df = pd.read_csv("user_behavior.csv")
imputer = SimpleImputer(strategy="most_frequent")
df["yhlx"] = imputer.fit_transform(df[["yhlx"]]).flatten()
接着构造用户-商品交互矩阵。这里有个坑:原始数据是行为日志(一行一条记录),但协同过滤需要的是用户对商品的评分或交互强度。我按如下规则量化:
| 行为类型 | 权重 |
|---|---|
| 点击 | 1 |
| 加购 | 3 |
| 下单 | 5 |
# 构造交互矩阵
df["weight"] = df["action"].map({"click": 1, "add_cart": 3, "buy": 5})
interaction_matrix = df.pivot_table(
index="user_id",
columns="item_id",
values="weight",
fill_value=0
)
然后就是重头戏:选算法。
算法选择:KNN vs SVD,谁更适合小数据?
我试了两种经典方法:
- User-Based KNN:找相似用户,推荐他们喜欢的商品
- SVD(奇异值分解):矩阵分解,降维后预测缺失值
结果如下(在验证集上的 Recall@10):
| 算法 | Recall@10 | 训练时间 | 代码复杂度 |
|---|---|---|---|
| User-KNN | 0.28 | 3s | ⭐ |
| SVD | 0.31 | 8s | ⭐⭐ |
看起来 SVD 略好,但考虑到我们还要部署到前端(通过 API 返回推荐结果),延迟才是关键。KNN 虽然准度稍低,但预测快、内存占用小,还能用 sklearn.neighbors.NearestNeighbors 做近似搜索加速。
最终我选了 KNN —— 在工程世界里,80 分的方案只要能跑起来,就是 100 分。
from sklearn.neighbors import NearestNeighbors
# 训练 KNN 模型
model = NearestNeighbors(metric="cosine", algorithm="brute")
model.fit(interaction_matrix.values)
def recommend(user_id, top_k=10):
user_idx = interaction_matrix.index.get_loc(user_id)
distances, indices = model.kneighbors(
[interaction_matrix.iloc[user_idx]],
n_neighbors=top_k+1 # +1 因为自己也算一个邻居
)
# 找出邻居们喜欢但当前用户没交互过的商品
neighbor_items = interaction_matrix.iloc[indices[0][1:]].sum(axis=0)
user_items = interaction_matrix.loc[user_id]
recommendations = neighbor_items[user_items == 0].nlargest(top_k)
return recommendations.index.tolist()
集成到前端:别让算法成为性能瓶颈
模型跑通只是第一步。真正的挑战是怎么把它塞进前端流程里。
我们的 H5 页面是 Vue 写的(没错,外包公司啥都写),每次用户进入首页就要拉推荐列表。如果每次请求都实时计算,服务器早就崩了。
解决方案:离线训练 + 在线缓存。
- 每天凌晨 2 点跑一次训练脚本,生成每个用户的 Top 50 推荐列表
- 存入 Redis,key 为
rec:user:{user_id} - 前端请求时直接查缓存,毫秒级响应
运维同事一开始还担心 Redis 内存不够,结果算下来 10 万用户 × 50 个商品 ID ≈ 40MB,连他监控面板的一个像素都占不满。
那天上线后,测试妹子居然夸我“这次接口好快”。我表面微笑,心里想:要是知道我背后只用了 KNN,怕是要惊掉下巴。
踩过的坑 & 心得总结
不要迷信“AI”二字
很多业务问题根本不需要复杂模型。这次项目用规则引擎(比如“买了 A 的人也买了 B”)其实效果差不多,但甲方非要“算法”才显得高级。懂的都懂。数据质量 > 算法复杂度
我花了 70% 时间在清洗数据,30% 时间调模型。如果数据本身垃圾,再牛的算法也是 Garbage In, Garbage Out。前端也需要懂一点算法逻辑
有次前端同事把推荐列表做了二次排序(按商品价格),结果转化率暴跌。后来我们约定:算法输出的就是最终顺序,前端只负责展示。跨团队协作,边界感很重要。Rust 还是太年轻(暂时)
虽然最近沉迷 Rust,但 ML 生态还是 Python 的天下。不过我已经在用polars做数据处理了,比 pandas 快不少,等linfa成熟了或许能全栈 Rust 化?(做梦中)
最后:外包人的跳槽自救指南
写这篇文章的时候,我刚刷完 LeetCode 第 150 题,Rust 的 async 也撸得差不多了。说实话,如果不是为了跳槽,我才懒得研究这些。但在外包公司待久了,技术容易停滞——天天修 Bug、改 UI、对接各种奇葩 API。
学点机器学习,不是为了转行当算法工程师(那得 PhD 起步),而是让自己在“全栈”之外多一张牌。现在面试官一听你会“端到端落地推荐系统”,眼睛都亮了。
所以啊,别管甲方多离谱,项目多烂,只要能从中榨出一点技术成长,就不算白干。
对了,上周那个推荐系统上线后,GMV 提升了 2.3%。PM 在群里发了个 200 块红包,我抢到了 8 块 6。
但没关系,简历上又能写一行:“主导设计并落地基于协同过滤的前端智能推荐模块”。
这,才是外包人的终极浪漫。

评论 0