引言
推荐系统是「预测用户会喜欢什么」——电商、视频、新闻、音乐都在用。本文按工业界真实架构推进:先建 User-Item 交互矩阵(一切的地基),再讲协同过滤(User/Item 相似度)、矩阵分解(SVD/ALS,隐因子模型)、基于内容的推荐,然后是召回-粗排-精排的工程架构与评估指标(准确率/召回率/NDCG),最后攻冷启动与新物品问题。
前置:/ml-supervised-regression/(预测思路)、/ml-feature-engineering/(特征处理)。评估指标见 /ml-model-evaluation/。
目录
- 1. 推荐系统全景:召回-排序-重排
- 2. User-Item 交互矩阵
- 3. 相似度计算:余弦、皮尔逊与杰卡德
- 4. 协同过滤:User-based 与 Item-based
- 5. 矩阵分解:SVD 与 ALS
- 6. 基于内容的推荐
- 7. 召回层实战:多种召回策略
- 8. 精排层:CTR 预估模型
- 9. 评估指标与冷启动
- 10. 速查表
- 延伸阅读
1. 推荐系统全景:召回-排序-重排
工业级推荐是「漏斗」——候选几百万,最终展示几个:
全量物品(百万级)
→ 召回(粗选,几百~几千) 高速,宽松
→ 精排(CTR 预估,逐条打分) 准,模型
→ 重排(多样性/规则,几十) 业务约束
→ 展示
| 阶段 | 目标 | 方法 |
|---|---|---|
| 召回 | 别漏掉可能喜欢的 | 协同过滤/矩阵分解/向量召回 |
| 精排 | 把最可能的排最前 | CTR 模型(LR/Wide&Deep/GBDT) |
| 重排 | 多样性/业务规则 | 打散、去重、保位置 |
心智:先广召回防漏、再精排序排准、最后重排管体验——别在一层追求完美。
2. User-Item 交互矩阵
一切推荐的输入:用户 × 物品的交互矩阵:
物品1 物品2 物品3 物品4
用户A 5 ? 3 ?
用户B 4 2 ? 5
用户C ? ? 1 4
? = 未知交互(我们要预测的)
评分(显式)或点击/购买次数(隐式)
两种交互:
| 类型 | 含义 | 例子 |
|---|---|---|
| 显式 | 用户主动表达 | 评分 1-5 星 |
| 隐式 | 行为推断偏好 | 点击/购买/播放 |
import pandas as pd
# 用户-物品-交互值 长表 → 矩阵
df = pd.DataFrame({'user': ['A','A','B','B','C','C'],
'item': ['i1','i3','i1','i2','i3','i4'],
'rating': [5,3,4,2,1,4]})
matrix = df.pivot(index='user', columns='item', values='rating')
print(matrix)
关键认知:矩阵高度稀疏(用户只接触过 0.1% 物品)——推荐就是「填补 ? 值」。
3. 相似度计算:余弦、皮尔逊与杰卡德
推荐的核心度量:相似度——三种主流:
import numpy as np
def cosine(a, b):
"""余弦相似度:向量夹角,范围 [-1, 1]"""
return np.dot(a, b) / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-9)
def pearson(a, b):
"""皮尔逊:余弦 - 各自均值(去评分尺度差异)"""
a_c, b_c = a - a.mean(), b - b.mean()
return cosine(a_c, b_c)
def jaccard(a, b):
"""杰卡德:交集 / 并集(只看"是否交互")"""
return len(set(a) & set(b)) / len(set(a) | set(b))
| 相似度 | 适用 | 特点 |
|---|---|---|
| 余弦 | 评分向量 | 简单常用 |
| 皮尔逊 | 评分 | 消去「爱打高分」的偏差 |
| 杰卡德 | 隐式交互(0/1) | 不看数值 |
记忆:评分用余弦/皮尔逊,行为 0/1 用杰卡德。
4. 协同过滤:User-based 与 Item-based
协同过滤(Collaborative Filtering):「相似的人喜欢相似的东西」。
User-based:找与你口味相近的用户,推荐他们喜欢的:
找相似用户(与你评分向量最接近的 10 人)
→ 汇总他们喜欢的、你还没看过的物品
→ 按相似度加权排序
Item-based:推荐「与你喜欢物品相似」的物品:
找到你打过高分的物品
→ 找与它们相似的物品(物品相似度预计算)
→ 推荐
Item-based 是电商主流(物品相似稳定、可离线算、可解释):
import pandas as pd
from sklearn.metrics.pairwise import cosine_similarity
# 物品相似度矩阵(对矩阵列算余弦)
item_sim = cosine_similarity(matrix.T.fillna(0))
# item_sim[i][j] = 物品 i 与 j 的相似度
def recommend_item_based(matrix, user, item_sim, top_k=5):
"""给 user 推荐:加权汇总其高评分物品的相似物品"""
ratings = matrix.loc[user].fillna(0)
scores = {}
for item in ratings.index:
if ratings[item] > 0:
for other, sim in enumerate(item_sim[matrix.columns.get_loc(item)]):
if matrix.loc[user, matrix.columns[other]] == 0:
scores[matrix.columns[other]] = \
scores.get(matrix.columns[other], 0) + ratings[item] * sim
return sorted(scores, key=scores.get, reverse=True)[:top_k]
记忆:User-based 找相似人、Item-based 找相似物——物品相似稳定、可预计算,工程上更优。
5. 矩阵分解:SVD 与 ALS
矩阵分解(隐因子模型):把稀疏矩阵拆成「用户因子 × 物品因子」:
R (用户×物品) ≈ U (用户×k) × V^T (k×物品)
R[u][i] = U[u]·V[i] = 用户 u 对隐因子 k 的偏好 × 物品 i 在这些因子上的成分
k = 隐因子数(如"动作片/剧情片"这类隐含维度,无需人工标注)。
# 用 Surprise 库做 SVD(最简)
from surprise import Dataset, Reader, SVD
from surprise.model_selection import train_test_split
data = Dataset.load_from_df(
df[['user', 'item', 'rating']],
Reader(rating_scale=(1, 5)))
trainset, testset = train_test_split(data, test_size=0.2)
model = SVD(n_factors=20, lr_all=0.005, reg_all=0.02)
model.fit(trainset)
pred = model.predict('A', 'i4') # 预测用户 A 对物品 i4 的评分
| 方法 | 场景 | 特点 |
|---|---|---|
| SVD | 中小数据 | sklearn/Surprise 易用 |
| ALS | 大规模分布式 | Spark MLlib,交替最小二乘 |
| Funk-SVD | 推荐基准 | 经典,只分解已知评分 |
矩阵分解优点:捕捉潜在偏好、可处理稀疏、嵌入可做向量召回。
记忆:矩阵分解 = 学「用户/物品的隐因子向量」——分数就是两向量点积;ALS 是它的分布式版本。
6. 基于内容的推荐
协同过滤只看行为;基于内容看「物品本身 + 用户画像」:
# 物品特征:标签 / 类别 / 文本关键词
items = {'i1': ['动作', '科幻'], 'i2': ['爱情', '剧情'], 'i3': ['动作', '武侠']}
# 用户画像 = 其喜欢物品特征的加权汇总
def user_profile(user, items, ratings):
profile = {}
for item, r in ratings[user].items():
for feat in items[item]:
profile[feat] = profile.get(feat, 0) + r
return profile
# 推荐:给物品特征与用户画像打点积
对比:
| 维度 | 协同过滤 | 基于内容 |
|---|---|---|
| 数据 | 用户-物品行为 | 物品属性 + 用户画像 |
| 冷启动新用户 | ❌ 无行为无法推荐 | ✅ 有画像即可 |
| 冷启动新物品 | ✅ 有交互即可 | ❌ 需特征 |
| 惊喜度 | 高(跨类发现) | 低(只推荐同类) |
| 可解释 | 中等 | 高(“因为你喜欢动作片”) |
记忆:协同看「谁喜欢了它」,内容看「它是什么」——两者互补,工业常混合。
7. 召回层实战:多种召回策略
召回要求「快 + 全」——多个策略并行取并集:
召回源(各自独立、并行):
热门召回 → 全局/类目热门(兜底,防冷门缺货)
协同过滤召回 → Item-based 相似物品
矩阵分解召回 → 用户向量 × 物品向量,Top-N 最近邻
向量召回 → Embedding 相似(深度召回,见下)
内容召回 → 用户画像匹配
→ 汇总、去重、截断 Top 500 送精排
向量召回(Embedding-based):把用户/物品都映射到向量,近邻即候选:
import numpy as np
# 简化:user/item 因子向量(来自矩阵分解/深度模型)
user_vec = { 'A': np.array([0.9, 0.2, 0.1]) }
item_vec = { 'i1': np.array([0.8, 0.1, 0.2]), 'i4': np.array([0.7, 0.3, 0.05]) }
# 余弦相似度取 Top-N(工业用 Faiss/向量数据库加速)
def recall(user, k=10):
sims = [(i, float(np.dot(user_vec[user], v))) for i, v in item_vec.items()]
return sorted(sims, key=lambda x: x[1], reverse=True)[:k]
| 召回策略 | 优点 | 缺点 |
|---|---|---|
| 热门 | 兜底、快 | 个性化弱 |
| 协同过滤 | 个性化、可解释 | 冷启动差 |
| 矩阵分解 | 隐因子、可向量化 | 需要足够数据 |
| 向量召回 | 表达强、可扩展 | 需要训练嵌入 |
记忆:召回 = 多路并行取并集——热门兜底 + 个性化主流,向量召回是新兴主力。
8. 精排层:CTR 预估模型
精排目标:预估「点击率(CTR)」,把最可能点击的排最前:
特征:用户特征(年龄/城市/历史偏好)
物品特征(类目/价格/热度)
交互特征(用户×物品统计、上下文)
→ 模型输出 P(点击)
精排模型演进:
| 模型 | 思想 |
|---|---|
| LR(逻辑回归) | 线性 + 特征交叉手写,可解释 |
| GBDT(树模型) | 自动找非线性特征组合 |
| Wide & Deep | Wide 记忆 + Deep 泛化 |
| DeepFM | FM 特征交叉 + 深度网络 |
| DIN/序列模型 | 行为序列建模 |
# 简化:LR/GBDT 精排(特征工程见 /ml-feature-engineering/)
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import GradientBoostingClassifier
# 特征:user_age, item_cat, item_price, hist_ctr, hour...
X = feature_matrix # (样本, 特征)
y = click_label # 0/1
model = GradientBoostingClassifier(n_estimators=100, learning_rate=0.1)
model.fit(X_train, y_train)
rank_score = model.predict_proba(X_test)[:, 1] # 排序分数
精排评估:不只准确率——更关心排序质量(NDCG,见下节)与线上 CTR 提升。
记忆:精排 = CTR 预估模型——特征决定上限,Wide&Deep 这类模型兼顾记忆与泛化。
9. 评估指标与冷启动
评估指标(推荐 ≠ 分类):
import numpy as np
def ndcg(relevant_ranks, k=10):
"""NDCG:排序质量——相关物品越靠前,分越高"""
dcg = sum(rel / np.log2(i + 2) for i, rel in enumerate(relevant_ranks[:k]))
idcg = sum(1 / np.log2(i + 2) for i in range(min(k, sum(relevant_ranks))))
return dcg / idcg if idcg else 0
def recall_at_k(pred_items, true_items, k=10):
"""Recall@K:前 K 个推荐命中真实喜欢的比例"""
hits = len(set(pred_items[:k]) & set(true_items))
return hits / max(len(true_items), 1)
| 指标 | 衡量 | 适用 |
|---|---|---|
| Precision@K / Recall@K | 命中率 | 召回/粗排 |
| NDCG@K | 排序质量(位置加权) | 精排 |
| AUC | CTR 模型区分度 | 精排 |
| 覆盖率/多样性 | 生态健康 | 全局 |
冷启动(Cold Start)——最经典难题:
| 场景 | 问题 | 解法 |
|---|---|---|
| 新用户 | 无行为 | 热门推荐 + 注册画像 + 探索(随机试探) |
| 新物品 | 无交互 | 内容特征召回 + 多臂老虎机探索 |
| 新平台 | 无数据 | 人工/编辑推荐起步,攒数据 |
铁律:冷启动靠「探索」补——热门兜底 + 随机试探 + 画像特征,纯协同过滤对冷启动无能为力。
10. 速查表
| 需求 | 做法 |
|---|---|
| 数据表示 | User-Item 交互矩阵 |
| 相似度 | 评分→余弦/皮尔逊,行为→杰卡德 |
| 相似推荐 | Item-based 协同过滤(可预计算) |
| 隐因子 | 矩阵分解(SVD/ALS) |
| 无行为新用户 | 基于内容 + 热门兜底 |
| 大规模召回 | 向量召回(Faiss/向量库) |
| 精排 | CTR 模型(GBDT / Wide&Deep) |
| 评估 | Recall@K / NDCG@K / AUC |
| 冷启动 | 热门 + 探索 + 画像 |
| 系统架构 | 召回(多路) → 精排 → 重排 |
一句话记忆:一切始于 User-Item 矩阵;相似度是基石——Item-based 稳定、矩阵分解出隐因子、向量召回扩规模;精排用 CTR 模型;评估看 NDCG/Recall@K;冷启动靠热门兜底加探索——漏斗式架构缺一不可。
延伸阅读
- /ml-supervised-classification/ — 分类与 AUC 基础
- /ml-feature-engineering/ — 精排特征工程
- /ml-model-evaluation/ — 指标与调参
- /ml-time-series/ — 兴趣随时间演化的建模
- [[ai-ml]] — 推荐算法深度专题
- [[database]] — 向量数据库与召回存储
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。