推荐系统(三)—— 深度学习基础模型
从 MLP 到 Embedding,再到 NeuMF、YouTube DNN、Wide & Deep —— 用渐进的方式讲清深度学习推荐系统的每一块基石,附经过原文核对的架构图和可直接运行的 PyTorch 代码。
2016 年 6 月,Google 在一篇短短一页的会议论文里悄悄改写了推荐系统的版图。那篇论文叫 Wide & Deep Learning,描述的模型当时正在 Google Play 应用商店里跑——一个用户量上十亿的产品。一年之内,主流厂商都把深度模型推上了线;到 2019 年,行业默认值已经变了:矩阵分解只是 baseline,不再是系统。
发生了什么?多层神经网络带来了四件经典方法做不到的事:
- 表征是学出来的。Embedding 取代 one-hot,模型从点击数据里端到端地长出语义。
- 交互是非线性的。两层带 ReLU 的 MLP 能拟合 XOR;点积不行。
- 多模态可以融合。文本、图像、行为序列都走同一条梯度。
- 优化是端到端的。再也不用人手设计交叉特征,损失函数自己挑。
本文沿着 dot(p_u, q_i) 一路走到 NeuMF、YouTube DNN 和 Wide & Deep。架构都按原论文核对过,每一步配可直接跑的 PyTorch 代码。
你将建立直觉的部分
- MLP 直觉:为什么"Linear + ReLU"叠起来就是一台通用的交互引擎。
- Embedding:不只是
nn.Embedding怎么调,而是为什么梯度会把相似 ID 拉到一起。 - NeuMF(He 等,WWW 2017):两条路径,一个目标。
- YouTube DNN(Covington 等,RecSys 2016):当今几乎所有大规模推荐系统的两阶段模板。
- Wide & Deep(Cheng 等,DLRS 2016):教科书级别的"记忆 + 泛化"融合。
前置知识
- 熟悉 PyTorch 基础(
nn.Module、autograd、DataLoader)。 - 看过本系列第二篇 ,理解矩阵分解和隐式/显式反馈。
- 基本线性代数:点积、矩阵向量乘法。
一、为什么是深度学习,为什么是现在
经典方法撞到的天花板

上面的数字是公开 CTR 数据集(Criteo、Avazu、隐式反馈版的 MovieLens-1M)上的典型表现。结论很一致:每多一层非线性就多换来几个 AUC 百分点,而几个 AUC 百分点在 GMV 上意义重大。
要看清为什么,得先看清每种经典方法到底能表达什么。
矩阵分解用一个点积来预测评分:
$$\hat{r}_{ui} = \mathbf{p}_u^\top \mathbf{q}_i$$直白讲:每个用户、每个物品都是一个短向量,预测就是它们的"对齐程度"。漂亮,但是线性的——没法表达"我同时喜欢科幻和动作,但单独一个都不行"这种交互。
**因子分解机(FM)**加上了二阶交互:
$$\hat{y}(\mathbf{x}) = w_0 + \sum_i w_i x_i + \sum_{i协同过滤直接绕过建模,找"和你像的人喜欢什么”。在矩阵稀疏到一定程度之前都好用——而生产环境里,矩阵总会稀疏。
三者的共同短板是:最多到二阶,并且都需要人来设计正确的特征。
深度学习补上的部分
带非线性的两层网络在理论上是通用函数逼近器。落到实践,就是:
- 一个建在 Embedding 之上的 MLP,可以拟合数据所支撑的任意阶交互。
- 同一套骨干可以同时吃图像(CNN)、文本(Transformer)、序列(RNN)—— 联合训练。
- 冷启动有了抓手:新物品只要有内容(标题、图像),预训练编码器就能给它一个像样的初始向量。
代价是更多算力、更差的可解释性、更多超参。第七节会讲让这笔买卖划算的工程纪律。
二、MLP 直觉:从点积到非线性交互
在认识具体模型之前,先内化一件事:MLP 比点积多给了你什么。
点积 $\mathbf{p}^\top \mathbf{q} = \sum_k p_k q_k$ 是把每个维度的乘积加起来。它对称、线性,无法表达"特征 A 只在特征 B 也激活时才有用"。
把 $[\mathbf{p}; \mathbf{q}]$ 拼起来过一层 Linear → ReLU → Linear:
ReLU 会根据"哪些输入维度组合被激活"来开关每个隐藏单元。隐藏单元够多,就是通用逼近定理。交互形式不再是写死的公式,而是学出来的。
把"点积换成 MLP"这一刀就是种子,NeuMF、YouTube DNN、Wide & Deep 都从这里长出来。
三、Embedding:从稀疏 ID 到语义表征的桥梁
One-hot 是敌人
想象一个目录:1000 万用户、100 万物品。one-hot 编码下,每个用户是一个 1000 万维的向量,只有一个位置是 1。三件事同时出问题:
- 存储和计算爆炸——光一个用户输入就是 40 MB 的浮点向量。
- 信息密度塌陷——99.99999% 的位置都是零。
- 任意两个 one-hot 的距离都相等:$\|\mathbf{e}_i - \mathbf{e}_j\|_2 = \sqrt{2}$。用户 42 和用户 43 的距离,跟用户 42 和用户 9,999,999 的距离没区别。
Embedding 一次性解掉这三件事。它把每个 ID 映射到一个 64 维左右的稠密向量。训练完之后,口味相似的用户在这个 64 维空间里挨着。
类比:one-hot 像一本通讯录,每个人都住在一座孤岛上,岛与岛距离相等。Embedding 是地理学家把岛重排,让相关的人住的岛挨在一起。“找相似用户"突然就变成了一次最近邻查询。
Embedding 到底是什么
数学上,Embedding 层就是一个可学习的矩阵 $\mathbf{P} \in \mathbb{R}^{m \times d}$,第 $i$ 行就是用户 $i$ 的向量。“查表"操作 $\mathbf{p}_i = \mathbf{P}[i, :]$ 等价于 $\mathbf{P}^\top \mathbf{e}_i$,但实现成行索引来跑得快。
代码上一行就够:
| |
梯度是怎么教会 Embedding 有意义的
Embedding 一开始是随机的。它能变得有语义,全靠梯度推它。
拿一个点击预测的损失来说:用户 $u$ 点了物品 $i$,损失就告诉优化器"让 $\mathbf{p}_u^\top \mathbf{q}_i$ 变大”。反向传播会把 $\mathbf{p}_u$ 微微往 $\mathbf{q}_i$ 的方向推,反之亦然。在百万次点击之后,这一条简单规则就能产出令人惊讶的结构:受众重叠的物品会聚在一起,常被一起买的物品会聚在一起,同一个艺术家的歌会聚在一起。
你从没告诉模型"流派"是什么。是梯度的几何结构自己发现了它。
把它学到的东西画出来

把训练好的物品 Embedding 矩阵用 t-SNE 投到二维,通常会看到这样一幅图:同类物品紧紧聚成一团,混合类物品在相关簇之间架起桥梁(“科幻动作"就坐在科幻和动作之间),长尾物品散落在外围。这是判断模型"到底有没有学到东西"的最佳诊断。
维度怎么选
| 物品规模 | 推荐 $d$ | 备注 |
|---|---|---|
| <10 万 | 8 – 32 | 再大就过拟合 |
| 10 万 – 100 万 | 32 – 64 | 多数场景的甜点 |
| 100 万 – 1 亿 | 64 – 128 | 128 之上收益递减 |
| 网级 | 128 – 256 | 只有交互上百亿才合算 |
YouTube 论文里、后续工作里反复印证的一条经验法则:从 $d = 32$ 起步,翻倍直到验证 AUC 提升不到 ~0.5%,停。 内存和服务延迟随 $d$ 线性增长,质量却是凹的。
一个干净、可复用的 Embedding 层
| |
多字段类别特征(用户 ID、物品 ID、类目、城市……)每个字段一张表,再 stack 起来:
| |
这个 [batch, num_fields, dim] 的张量,就是下面所有模型期望的输入形状。
四、神经协同过滤(NCF 与 NeuMF)
想法:把点积换成更聪明的东西
He、Liao、Zhang、Nie、Hu、Chua 在 WWW 2017 上提出 NCF。卖点朴素得让人意外:矩阵分解其实是网络的特例,把它一般化就能做得更好。
论文里一共有三兄弟:
- GMF(Generalized Matrix Factorization):可学习权重的点积。
- MLP:纯粹的拼接 + 深度网络,不预设"内积"这种归纳偏置。
- NeuMF:把 GMF 和 MLP 融合起来,每条路径用各自独立的 Embedding。
实践里 NeuMF 才是真正重要的那个。下面这张架构图忠于原论文。
NeuMF 架构

从下往上看:
- 每边两张 Embedding 表。GMF 和 MLP 不共享 Embedding——论文里强调这一点很关键。每条路径学自己擅长的表征。
- GMF 路径:元素积 $\mathbf{p}_u^\text{GMF} \odot \mathbf{q}_i^\text{GMF}$。在它上面再乘一个可学习权重,就是点积的推广。
- MLP 路径:把 $[\mathbf{p}_u^\text{MLP}; \mathbf{q}_i^\text{MLP}]$ 拼起来,过 2–3 层 Dense + ReLU(典型尺寸:$128 \to 64 \to 32$)。
- 融合:把两条路径的输出拼起来,再投影成一个标量过 sigmoid:
对隐式反馈(点击、播放、购买),损失是二元交叉熵:
$$\mathcal{L} = -\sum_{(u, i) \in \mathcal{D}^+ \cup \mathcal{D}^-} \big[ y_{ui} \log \hat{y}_{ui} + (1 - y_{ui}) \log(1 - \hat{y}_{ui}) \big]$$负样本集 $\mathcal{D}^-$ 由采样得到——通常每个正样本配 4 个负样本。
NeuMF 端到端实现
| |
NeuMF 训练里容易被忽略的细节
论文里有三件事经常被人忘掉:
- 两条路径分别预训练。先单独训 GMF,再单独训 MLP,最后把两份权重拿来初始化 NeuMF。论文里光这个 trick 就有 ~2% 的 AUC 提升。
- 负样本比例要选对。“1 正 4 负"是经典默认;1:1 欠拟合,1:10 浪费算力。
- bias 不要做 L2。给偏置项加 weight decay 会掉点,要在优化器的参数组里把它们排除。
五、YouTube DNN:撑起半个互联网的两阶段流水线
Covington、Adams、Sargin(RecSys 2016)是深度学习时代被引最多的工业推荐论文。它的"召回(candidate generation)+ 排序(ranking)“两阶段拆分,是后来几乎所有大规模推荐系统的模板:抖音、Spotify、Pinterest、Instagram、淘宝。
为什么要两阶段
每次请求都给十亿个视频打分根本不现实。给十亿个物品都喂丰富特征实时打分也不现实。解法是:
- 召回阶段用便宜的模型和便宜的特征,把候选集从百万缩到几百。
- 排序阶段用很重的模型和丰富特征,对这几百个候选精打分,挑 top-K。
架构

召回被建模成一个"超级多分类"问题:给定用户当前状态,预测他下一个会看的视频是哪一个,类别数等于候选视频总数(百万级)。用户塔把最近看过的视频 Embedding 平均池化、和人口学特征拼接,过三层 ReLU($1024 \to 512 \to 256$),输出一个 256 维的用户向量。训练用 sampled softmax;上线时把视频 Embedding 灌进 ANN 索引(HNSW、ScaNN),用户向量就是一次最近邻查询——十亿物品也只要个位数毫秒。
排序是一个更深的前馈网络($1024 \to 512 \to 256 \to 128$),吃的特征也多得多:曝光视频自身的 Embedding、同频道历史观看的 Embedding、距离上次观看的时间、在 feed 中的位置、语言匹配等等。关键是输出头用了带权重的 logistic 回归,目标是预测期望观看时长,而不是点击。论文证明这个目标比纯 CTR 更对齐长期满意度。
YouTube 论文里值得抄的设计
三个老化得很好的选择:
- 用平均池化压缩用户最近行为。便宜、并行、强 baseline。序列模型(第六篇)只有在数据足够多时才赢得了它。
- 召回当分类做,不要当回归做。Sampled softmax + ANN 上线,是行业的统治性范式。
- 认真选标签。“观看时长”、“长点击”(停留 >30 秒)、“完播"都比裸点击泛化得好。损失函数就是产品规格。
PyTorch 里召回塔的骨架:
| |
训练时把这个用户向量和一组采样的候选视频 Embedding 拼起来过 softmax;上线时直接对 ANN 索引做点积。这一座塔,就是当今工业级召回的主力机型。
六、Wide & Deep:记忆遇上泛化
洞察
Cheng 等人(DLRS 2016)注意到,深度模型虽然泛化好,但有时会"过度推荐”——因为 Embedding 把一切都抹平了,它会推一些"听起来合理但其实不对"的物品。反过来,带交叉特征的线性模型能完美记住具体共现,但没法外推。
类比:记忆是那个会跟你说"你喜欢 Inception,那一定喜欢 Tenet"的朋友——具体、准确,但永远不冒险。泛化是那个会跟你说"你喜欢烧脑悬疑片,试试 Primer"的朋友——视野更宽,偶尔翻车,但能给你惊喜。一个好的推荐系统应该同时是这两个朋友。
他们的解法:联合训练,两边的分数在 sigmoid 之前直接相加。
架构

Wide 部分:在原始稀疏特征和人手设计的交叉特征 $\phi(\mathbf{x})$ 上的线性层,比如 installed_app=Pandora AND impression_app=YouTube。输出 $\hat{y}^w = \mathbf{w}^\top [\mathbf{x}, \phi(\mathbf{x})] + b$。
Deep 部分:每个类别字段都过 Embedding,拼接后过 3 层 ReLU($256 \to 128 \to 64$)。输出 $\hat{y}^d$。
联合输出头:$\hat{y} = \sigma(\hat{y}^w + \hat{y}^d)$。两边都从同一个损失收到梯度,由优化器决定各自贡献多少。
论文用了一个刻意的拆分:Wide 端用带 L1 的 FTRL(稀疏、可解释、自动选特征),Deep 端用 AdaGrad(稠密、平滑)。这种"两套优化器"的设置很关键——一套吃到底会掉点。
实现
| |
实践里你也可以一把 Adam 训完,模型也能跑出来——但 Wide+Deep 的联合损失才是模型真正叫 Wide & Deep 的原因。它不是把两个独立训完的模型 ensemble 在一起。 Wide 和 Deep 的参数能看到彼此的梯度,这种交互正是要害。
直系后辈
Wide & Deep 衍生出了一整支族系,把人手交叉特征自动化掉:
| 模型 | “Wide"被替换为 | 年份 |
|---|---|---|
| DeepFM | FM 层,自动学二阶交叉 | 2017 |
| DCN | Cross Network,每多一阶只多 $O(d)$ 参数 | 2017 |
| xDeepFM | CIN(Compressed Interaction Network),显式高阶交叉 | 2018 |
| AutoInt | 在特征 Embedding 上做自注意力 | 2019 |
本系列第四篇 会一一讲它们。
七、训练纪律:决定上面这些到底好不好用
架构对只是必要条件,远不是充分条件。下面这些细节经常比"换模型"更能动 AUC。

负采样
对隐式反馈,每个用户都有上千"负样本”(没看到过的物品)。三种采样策略,按精细程度排序:
- 均匀随机。默认,意外地难被打败,召回阶段尤其稳。
- 按热度加权。热门物品被采到的概率更大——用户略过一个爆款,是很强的负信号。
- batch 内 / 困难负样本。把同一个 batch 里别人的正样本当自己的负样本;或者周期性从当前模型里捞高分负样本。提升判别力,但温度要调好。
| |
优化器、调度、正则
90% 以上的推荐模型都能用的合理默认:
| |
- Dropout 0.2 – 0.3,加在 MLP 塔里。再大就掉排序质量。
- 梯度裁剪(
torch.nn.utils.clip_grad_norm_(model.parameters(), 5.0)):看到 loss 跳就开。 - Early stopping:监控验证 AUC,patience 5–10 个 epoch。
一个完整的训练循环
| |
指标要对得上任务
| 场景 | 主指标 | 它真正度量的是 |
|---|---|---|
| CTR 预测 | AUC | 模型能不能把正样本排在负样本前面 |
| CTR 预测 | LogLoss | 预测概率是否校准 |
| Top-K 召回 | Recall@K、HitRate@K | 正确物品有没有出现在前 K 个里 |
| Top-K 排序 | NDCG@K | 正确物品排得有多靠前 |
| 评分预测 | RMSE | 预测分数的平方误差均值 |
如果你用 RMSE 衡量 CTR、用 AUC 衡量评分预测,那就拧错了优化的旋钮。一开始就选定,写下来,永远别偷偷换。
八、常见问题,认真回答
Q:Embedding 维度该多大? 1M 以下物品规模从 32 起步,再大从 64 起步。翻倍直到验证 AUC 提升不到 ~0.5%。$d$ 超过 256 几乎都会过拟合,除非你有上百亿次交互。
Q:是不是永远应该用 NeuMF 取代 MF? 不是。在 ~100 万次交互以下,调好正则的 MF 经常打过 NeuMF——深度模型在那个数据量下会过拟合。NeuMF 真正稳定胜出,是在 ~1000 万次交互、且有丰富 side feature 之上。
Q:Wide & Deep 能不能只要 Deep,不要 Wide? 可以,很多人就是这么做的(DeepFM、DCN)。Wide 端给纯 Deep 模型不能给的东西,是对高基数共现的精确记忆——“装了 X 应用的用户也会装 Y 应用”。如果你的业务依赖这种具体共现被精确捕捉,留着 Wide 端。否则用自动化的交叉网络(DeepFM、DCN)通常更划算。
Q:YouTube DNN 在我只有几百万用户的场景里要不要用? 两阶段在 ~10 万物品以下属于杀鸡用牛刀。先用一个 ranker 对全目录打分。当全目录打分跟不上你的延迟预算时,再上两阶段。
Q:全新物品(没任何交互)怎么处理? 按效果递减排:
- 基于内容初始化——用预训练编码器(BERT、CLIP)从物品文本/图像算出 Embedding。
- 类目均值初始化——取同类目物品 Embedding 的平均。
- 多臂老虎机探索——把新物品给一小部分流量,攒初始信号。
Q:怎么防止深度推荐模型过拟合? 分层防御:Embedding 上加 $10^{-5}$ 的 weight decay,MLP 里 0.2–0.3 的 dropout,验证 AUC 上的 early stopping,实在不行减小 $d$。只在验证指标真的能跟着动的时候才加复杂度。
Q:怎么加速训练?
80/20 列表:先上 GPU(10–100 倍),再加大 batch(吃满 GPU),再混合精度(torch.cuda.amp,~2 倍),最后 DataLoader(num_workers > 0)。所有特征离线预计算,永远不要在训练循环里做 join。
九、走到这里我们看到了什么
深度学习没有发明推荐系统,它改了三个约束:
- 表征是学出来的,不是设计出来的。Embedding 取代 one-hot,梯度发现你写不出来的结构。
- 交互是任意阶的,不再止步于二阶。MLP 拟合数据所支撑的任何形式。
- 流水线是端到端的,不再是几段拼起来。损失从预测一路流回原始 ID。
NeuMF 证明了"学出来的交互"打得过"写死的内积”。YouTube DNN 展示了如何在十亿物品的尺度上把召回和排序拆开做。Wide & Deep 告诉我们记忆和泛化最好联合学,而不是分别学完再 ensemble。
本系列第四篇 接着往下走:CTR 预估模型如何把 Wide & Deep 还在依赖的人手交叉自动化掉,包括 DeepFM、DCN、xDeepFM、AutoInt、FiBiNet。
系列导航
本文是推荐系统系列的第 3 篇,共 16 篇。