推荐系统深度实战:从协同过滤、召回排序到深度学习与多目标精排的工程全解

推荐系统是现代互联网最隐秘也最赚钱的 AI 应用:它不直接"生成"内容,却决定了用户看到的世界。从抖音的沉浸式 feed,到电商的"猜你喜欢",再到广告的精准投放,背后都是一套精密的多阶段漏斗。本文从第一性原理拆解推荐系统的四阶段范式,给出协同过滤、向量召回、Wide&Deep / DeepFM 排序与重排的可落地实现,并附上一份可直接对照的生产陷阱清单。

一、第一性原理:为什么推荐必须是"漏斗"

推荐系统的核心矛盾是海量候选(百万~十亿物品)× 严格时延(百毫秒内返回)× 个性化精度。你不可能对十亿物品逐一跑一遍精排模型,于是工程上必然分层过滤,每层用更贵的模型处理更少的候选——这就是经典的召回 → 粗排 → 精排 → 重排四阶段漏斗。

阶段 候选规模 时延预算 模型复杂度 目标
召回 (Recall) 全库 1e6~1e9 ~10-50ms 轻量(向量/规则) 高召回、不漏
粗排 (Pre-rank) ~1e3~1e4 ~10ms 双塔/小模型 快速截断
精排 (Rank) ~1e2~1e3 ~30-50ms 深度模型 精准打分
重排 (Re-rank) ~数十 ~10ms 规则/Listwise 多样性、业务

理解这个漏斗是优化一切推荐系统的前提:在召回层追求"不漏",在精排层追求"准",在重排层追求"好"(多样性、合规、业务目标),各层职责不可混淆。

二、召回层:从协同过滤到向量召回

2.1 协同过滤:最朴素却最永恒的基线

协同过滤(CF)的核心是"物以类聚、人以群分"。Item-CF 基于共现矩阵找相似物品,实现简单、可解释、冷启动友好。


import numpy as np
from collections import defaultdict

def build_item_cf(train: list[tuple[int, int]], k: int = 20):
    """基于用户-物品正反馈(点击/购买)构建 Item-CF 相似度。"""
    user_items = defaultdict(set)
    cooc = defaultdict(lambda: defaultdict(int))
    item_cnt = defaultdict(int)
    for uid, iid in train:
        user_items[uid].add(iid)
        item_cnt[iid] += 1
    for uid, items in user_items.items():
        items = list(items)
        for i in items:
            for j in items:
                if i != j:
                    cooc[i][j] += 1
    sim = {}
    for i, neighbors in cooc.items():
        scored = []
        for j, cij in neighbors.items():
            # 改进余弦:惩罚热门物品,缓解哈利波特效应
            denom = np.sqrt(item_cnt[i] * item_cnt[j])
            scored.append((j, cij / denom))
        sim[i] = sorted(scored, key=lambda x: -x[1])[:k]
    return sim

def recall_by_cf(sim, user_hist: set, topn: int = 100):
    score = defaultdict(float)
    for iid in user_hist:
        for j, s in sim.get(iid, []):
            if j not in user_hist:
                score[j] += s
    return sorted(score.items(), key=lambda x: -x[1])[:topn]

注意"哈利波特效应":全局爆款物品与一切物品共现极高,会污染相似度。必须做热门惩罚(如上式分母)或引入 ItemCF 的归一化变体(Swing、似然比)。

2.2 向量召回:用 Embedding 突破共现稀疏

当物品/用户规模极大、共现极稀疏时,CF 力不从心。向量召回把用户与物品映射到同一向量空间,用 ANN(HNSW / IVF-PQ)做近邻检索——这正是本站 向量数据库内核工程一文所剖析的近似最近邻检索的生产落点。


import numpy as np
# 离线:双塔模型产出 user_emb / item_emb(维度 d=128)
# 在线:用 FAISS 做 TopK ANN
def ann_recall(item_embs: np.ndarray, user_emb: np.ndarray, topk: int = 100):
    # item_embs: [N, d], user_emb: [d]
    # 真实生产用 faiss.IndexHNSWFlat + nprobe,此处用精确内积演示语义
    sims = item_embs @ user_emb
    idx = np.argpartition(-sims, topk)[:topk]
    return idx[np.argsort(-sims[idx])]

向量召回的优势是能捕捉语义相似("无线耳机"召回"蓝牙耳麦"),与 CF 的共现互补,实践中常多路召回并行(CF 一路 + 向量一路 + 热门一路 + 地理一路),再送入粗排融合。

三、排序层:从 LR 到 Wide&Deep 与 DeepFM

3.1 特征工程仍是地基

排序模型的输入是稠密/稀疏混合特征:用户画像(年龄、地域)、上下文(时段、设备)、物品特征(类目、价格)、交叉统计(该用户对该类目的历史 CTR)。特征一致性(离线训练与在线推理用同一套特征管道)是排序模型上线的头号坑。

3.2 Wide & Deep: memorization 与 generalization 兼得

Google 提出的 Wide&Deep 是工业排序的奠基架构:Wide 部分记忆高频交叉(记忆历史行为模式),Deep 部分泛化到未见组合。


import torch, torch.nn as nn

class WideDeep(nn.Module):
    def __init__(self, wide_dim, cat_dims, emb_dim=16, hidden=(256,128,64)):
        super().__init__()
        self.wide = nn.Linear(wide_dim, 1, bias=False)            # 记忆
        self.embeddings = nn.ModuleList(
            [nn.Embedding(c, emb_dim) for c in cat_dims])         # 类别特征嵌入
        deep_in = sum(emb_dim for _ in cat_dims) + wide_dim
        layers = []
        for h in hidden:
            layers += [nn.Linear(deep_in, h), nn.ReLU(), nn.BatchNorm1d(h), nn.Dropout(0.1)]
            deep_in = h
        self.deep = nn.Sequential(*layers, nn.Linear(deep_in, 1))
    def forward(self, wide_x, cat_x):
        e = torch.cat([emb(cat_x[:, i]) for i, emb in enumerate(self.embeddings)], dim=1)
        deep_x = torch.cat([wide_x, e], dim=1)
        return torch.sigmoid(self.wide(wide_x) + self.deep(deep_x))

3.3 DeepFM:让 FM 自动学交叉

Wide&Deep 的 Wide 部分仍需人工设计交叉特征。DeepFM 用 FM 层自动学习二阶特征交叉,免去人工特征工程,是更省心的工业默认选择之一。其 FM 部分与 Deep 部分共享嵌入,结构上更优雅。

四、重排层:从精准到"好"

精排给出每个物品的独立分数,但用户看到的是一个列表,需要兼顾多样性、新颖性与业务约束。

  • 多样性:MMR(最大边际相关)或 DPP(行列式点过程)打散相似物品,避免首页全是同一类目。
  • 业务规则:置顶履约、降权低毛利、合规过滤、去重。
  • Listwise 重排:用生成式/序列模型直接产出最优顺序(如 DLCM、PRM),而非逐点重排。

import numpy as np
def mmr_rerank(items, scores, sim_matrix, lambda_=0.7, topn=10):
    """MMR:在相关性与多样性之间权衡,lambda 越大越偏相关性。"""
    selected, candidate = [], set(range(len(items)))
    while len(selected) < topn and candidate:
        best, best_score = None, -1e9
        for i in candidate:
            max_sim = max([sim_matrix[i][j] for j in selected], default=0)
            mmr = lambda_ * scores[i] - (1 - lambda_) * max_sim
            if mmr > best_score:
                best_score, best = mmr, i
        selected.append(best); candidate.discard(best)
    return selected

五、评估:离线指标与在线指标必须双轨

指标 类型 定义 说明
Recall@K 离线 前 K 结果命中相关集比例 召回层核心
NDCG@K 离线 考虑排序位置的归一化折损累计增益 精排核心
MAP 离线 平均精度均值 多相关项场景
CTR 在线 点击/曝光 业务北极星之一
CVR 在线 转化/点击 电商/广告核心
人均时长 在线 停留时长 内容平台核心
多样性指数 在线 列表内类目/作者熵 体验护栏

离线指标永远只是代理:NDCG 涨 1 个点,线上 CTR 未必涨。必须配合在线 A/B 实验(AA 校验、显著性检验、新奇效应剔除)才能下结论。

六、冷启动:推荐系统的阿喀琉斯之踵

  • 用户冷启动:新用户无行为,靠人口属性/设备/地理做默认召回,快速积累行为后切换到个性化;用"兴趣探针"(主动展示多样性内容)加速探索(Exploration)。
  • 物品冷启动:新物品无曝光数据,靠内容特征(类目、文本 Embedding)进向量召回,辅以"冷启动流量池"保量试探。
  • 探索-利用(E&E):用 Thompson Sampling / UCB / 疫苗法在短期收益与长期探索间平衡,避免系统陷入信息茧房。

七、生产陷阱清单

陷阱 典型表现 根因 对策
特征穿越 离线 NDCG 虚高、线上无提升 训练用了推理时不可得的未来特征 严格时间戳切分 + 特征可用时间校验
线上线下特征不一致 离线好、线上崩 两套特征管道 特征平台统一,推理复用训练算子
哈利波特效应 推荐被爆款淹没 共现未做热门惩罚 改进余弦 / Swing / 似然比
召回不足 精排再强也救不回 召回漏掉了好物品 多路召回 + 召回层独立评估 Recall@K
E&E 缺失 信息茧房、新内容无曝光 纯利用策略 引入 Thompson Sampling / 冷启动流量池
评估指标错配 离线涨、业务跌 离线指标非业务代理 离线+在线双轨,A/B 显著性校验
重排忽略多样性 首页同质化、用户流失 只看逐点分数 MMR/DPP + 多样性护栏

八、与 LLM / RAG 的桥接

推荐系统并非与本站其它主题割裂:

  • 与向量数据库:向量召回就是 ANN 检索,本站 向量数据库内核工程:从 IVF-PQ 到 HNSW 再到 DiskANN一文剖析的索引结构正是召回层的引擎。
  • 与 LLM / RAG:生成式推荐正成为新范式——用 LLM 理解用户意图与物品语义,用 RAG 注入实时库存/上下文,再用传统排序做精排融合,弥合"语义理解"与"工业级时延/规模"的鸿沟。
  • 与多目标:现代精排普遍多目标(点击、时长、转化、留存),需 Pareto 最优或 ESMM/MMOE 这类多任务模型,与本站"多智能体/多目标优化"的工程思想同源。

九、总结

推荐系统的工程本质,是用分层漏斗在"规模、时延、精度"的不可能三角中求解:召回层用向量与共现"不漏",排序层用深度学习"打准",重排层用多样性与业务规则把"准"变成"好"。落地时务必守住三条生命线——特征一致性、召回独立评估、离线在线双轨实验——并时刻警惕冷启动与信息茧房。当这套体系跑顺,推荐才真正从"猜你喜欢"进化为"懂你所需"。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿
网站二维码

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部