📑 本页目录(点开跳转)
01 · 10 分钟跑通一个推荐系统
⏱ 10 分钟 | ⭐ 核心 | 🔨 有代码,必须动手
🎯 一句话
推荐的核心逻辑只有一句:「你喜欢 A,而 B 和 A 很像,所以推 B 给你」。这一节你就把它写出来。
😤 先说清楚:不需要准备任何东西
- ❌ 不用装库(numpy 都不用)
- ❌ 不用下数据集
- ❌ 不用懂数学
- ✅ 只要一个能跑 Python 的地方
没装 Python? 打开 Google Colab,新建笔记本,粘代码,按 Shift+Enter。15 秒搞定。
🔨 动手:复制这段代码,跑它
# -*- coding: utf-8 -*-
"""10 分钟推荐系统 —— 不用装任何库,不用下载任何数据"""
# ============ 第 1 步:数据 ============
# 8 个用户,对 10 部电影的评分(1-5 分,0 表示没看过)
movies = ["流浪地球", "星际穿越", "泰坦尼克", "教父", "低俗小说",
"你的名字", "千与千寻", "复仇者联盟", "肖申克救赎", "疯狂动物城"]
ratings = {
"小明": [5, 5, 0, 0, 0, 4, 4, 5, 0, 3],
"小红": [0, 0, 5, 0, 0, 5, 5, 0, 4, 5],
"小刚": [5, 4, 0, 5, 5, 0, 0, 4, 5, 0],
"小美": [0, 0, 5, 0, 0, 4, 5, 0, 5, 5],
"阿强": [4, 5, 0, 4, 4, 0, 0, 5, 4, 0],
"阿丽": [0, 3, 4, 0, 0, 5, 5, 0, 4, 4],
"老王": [5, 5, 0, 5, 4, 0, 3, 5, 5, 0],
"小张": [3, 0, 5, 0, 0, 5, 4, 0, 0, 5],
}
# ============ 第 2 步:算物品之间有多像 ============
def similarity(a, b):
"""余弦相似度:两个物品的评分向量夹角越小 = 越像。返回 0~1"""
dot = sum(x * y for x, y in zip(a, b))
na = sum(x * x for x in a) ** 0.5
nb = sum(y * y for y in b) ** 0.5
return dot / (na * nb) if na and nb else 0.0
# 把「每个用户对电影 i 的评分」抽成一列,就是电影 i 的向量
users = list(ratings.keys())
item_vectors = [[ratings[u][i] for u in users] for i in range(len(movies))]
# 10x10 的相似度表
sim = [[similarity(item_vectors[i], item_vectors[j]) for j in range(len(movies))]
for i in range(len(movies))]
# ============ 第 3 步:给某个人推荐 ============
def recommend(user, top_n=3):
my_ratings = ratings[user]
scores = {}
for j in range(len(movies)):
if my_ratings[j] > 0: # 看过的不推
continue
# 打分 = Σ(我给看过的电影的分 × 那部电影和它的相似度)
score = sum(my_ratings[i] * sim[i][j] for i in range(len(movies)) if my_ratings[i] > 0)
scores[movies[j]] = score
return sorted(scores.items(), key=lambda x: -x[1])[:top_n]
# ============ 第 4 步:看结果 ============
for user in users:
seen = [movies[i] for i, r in enumerate(ratings[user]) if r >= 4]
print(f"\n【{user}】喜欢:{'、'.join(seen)}")
print(" 推荐给他:")
for name, score in recommend(user):
print(f" → {name} (推荐分 {score:.2f})")
🎉 你应该看到这个
【小刚】喜欢:流浪地球、星际穿越、教父、低俗小说、复仇者联盟、肖申克救赎
推荐给他:
→ 千与千寻 (推荐分 10.35)
→ 你的名字 (推荐分 6.47)
→ 疯狂动物城 (推荐分 5.96)
【小张】喜欢:泰坦尼克、你的名字、千与千寻、疯狂动物城
推荐给他:
→ 肖申克救赎 (推荐分 12.71)
→ 星际穿越 (推荐分 8.23)
→ 复仇者联盟 (推荐分 5.99)
恭喜。这就是一个真的推荐系统。
它叫 ItemCF(基于物品的协同过滤),亚马逊靠它做了十几年推荐,至今仍在无数产品里跑着。
🧠 它到底干了什么?(3 步,看图)
第 1 步:把「电影」变成一串数字
────────────────────────────────
小明 小红 小刚 小美 阿强 阿丽 老王 小张
星际穿越 [ 5 0 4 0 5 3 5 0 ] ← 这就是「星际穿越」这部电影
千与千寻 [ 4 5 0 5 0 5 3 4 ] ← 这就是「千与千寻」
💡 一部电影 = 「所有人对它的评分」组成的一串数字。
两部电影被同一批人喜欢 → 这两串数字长得像 → 这两部电影像。
第 2 步:算「像不像」
────────────────────────────────
余弦相似度 = 两串数字的夹角
完全一样方向 → 1.0(超级像)
完全没关系 → 0.0(毫无关系)
第 3 步:加权打分
────────────────────────────────
小刚给「星际穿越」打了 4 分
「星际穿越」和「千与千寻」相似度 0.6
↓
「千与千寻」得分 += 4 × 0.6 = 2.4
把小刚看过的每一部都这样算一遍、加起来 → 总分最高的就是推荐结果
💡 人话翻译:「你给高分的电影,它们像谁,我就推谁。你给的分越高、越像,推荐分就越高。」
🔍 一个你可能没注意的坑(很重要)
看小红和小美的结果——她们明明都是「文艺+动画」口味,系统却给她们推了星际穿越和流浪地球。
为什么?
因为小明既喜欢科幻也喜欢动画,他一个人就把这两个圈子「连」起来了。而且科幻片被打分的人多,向量「更长」,天然得分高。
这个现象叫 热门偏置(Popularity Bias):
越热门的物品越容易被推荐,越容易被推荐就越热门 —— 一个恶性循环。
这是推荐系统最经典的问题之一,工业界花了大量精力解决它(第 11 节讲怎么治)。
你在第 10 分钟就撞见了一个真实的工业难题。👏
🔨 玩一下(可选,但很有用)
改代码,看结果怎么变。这比读十页文字有用:
- 给自己加一行:在
ratings里加"你": [...],填自己的口味,看推什么 - 改成推 5 个:
recommend(user, top_n=5) - 试试治热门偏置:把打分那行改成除以物品被评分次数
python count = sum(1 for u in users if ratings[u][j] > 0) score = score / (count ** 0.5) # 惩罚热门物品再跑一遍,小红的推荐变了吗?
🔗 这一章连到哪里
| 去哪 | 为什么 |
|---|---|
| ML基础 01 | 同样的「10 分钟先跑通」思路,那边是监督学习版 |
| 数学原理 04 | ItemCF 本质是 kNN —— 那一章讲它为什么在高维会失效 |
| 上线之后 02 | 你算出的相似度好看,不代表线上有效 |
✅ 检查点
不用写答案,心里过一遍。答不上来就往上翻:
- 「星际穿越」这部电影,在代码里被表示成了什么?
- 为什么用「余弦相似度」而不是直接比大小?
- 为什么看过的电影要跳过?
- 热门偏置是什么意思?
👀 答案
- 被表示成一个「谁看过它」的向量——矩阵里它那一列(或那一行)。⭐ 注意:代码完全不知道它是科幻片、不知道诺兰导演、甚至不知道它是电影,它只知道「哪些用户和它有过交互」。这就是协同过滤的全部信息来源。
- 因为要比的是「方向」不是「大小」。直接比大小的话,看过 500 部电影的重度用户会和所有人都「很相似」,仅仅因为他的向量长。余弦相似度先做了归一化,问的是「口味重合的比例」而不是「重合的绝对数量」。
- 因为推荐已经看过的东西没有价值——用户不会再看一遍,这个坑位就浪费了。⚠️ 而且它会让离线指标虚高:模型很容易学会「推他看过的」,分数很好看,线上毫无效果。
- 热门物品因为交互多,会和几乎所有东西都「相似」,于是被推给所有人 → 它更热 → 更容易被推。⭐ 富者愈富,长尾内容永无出头之日。这是协同过滤的结构性缺陷,不是 bug——第 11 章的重排和多样性约束就是为了对抗它。
🛑 可以停在这里
你今天已经完成了最难的一步:开始了。
这个 40 行的程序,剩下 17 节全是在回答同一个问题的升级版:
「真实世界有 3 亿用户、1 亿个物品、要在 200 毫秒内出结果,这 40 行怎么变成能扛住的系统?」
⚡ 走神救援(隔天回来看这个就够)
我写了个 ItemCF:把电影表示成「所有人对它的评分向量」→ 算两两余弦相似度 → 用户喜欢的电影像谁就推谁。发现了热门偏置问题。⭐ 这一章真正的价值不是那段代码,是它暴露的两个问题:热门偏置(热门物品和谁都像,于是被推给所有人 → 富者愈富)和没有验证(你不知道推得好不好)。这两个问题分别通向第 11 节的重排和第 12 节的评估。另外注意:这里用余弦相似度而不是直接比大小,是因为要比的是口味方向不是活跃程度 —— 不归一化的话,看过 500 部电影的人会和所有人都「相似」。
下一节 👉 02-推荐系统到底是什么.md