深夜的「陈记小馆」门口排着长队,老板老陈却对着本子发愁——他必须判断每一个路过的客人,有多大几率点一碗招牌牛肉面。这,就是推荐系统每天都在做的 CTR 预估。客人会不会下单,绝不能只看“牛肉面”这一个因素:深夜、下雨、客人怕辣、附近没有别的店……每个因素单独看都平平无奇,可一旦组合起来,结论就完全不同。这正是推荐模型要解决的“特征交互”问题。 老陈想出的办法,是让两位师傅同台献艺:面点老师傅擅长“两两搭配”,知道凌晨两点该少放葱油;年轻创意主厨则擅长把七八种食材揉进一碗汤头,产生奇妙的新口感。前者叫 FM(因子分解机),后者叫 DNN(深度神经网络)。把两位师傅放在同一个灶台上,共享同一套统一切配的食材——也就是共享 Embedding——这道招牌菜,就叫 DeepFM。本篇文章就从这间小馆的后厨讲起,带你亲手端出这道推荐算法里的招牌硬菜。
掌握 DeepFM 的建模思路与工程落地细节,是推荐算法工程师做 CTR 预估的必修课,能让你从“调包跑模型”进阶到“读懂模型并落地部署”。
可视化演示
展示FM一阶二阶特征交互、DNN高阶特征学习、DeepFM共享Embedding架构及模型对比的交互式动画。
本节配有交互动效(进入视口后自动加载,约 3KB)。
FM一阶二阶特征交互 一阶项线性加权各特征贡献 二阶项隐向量内积捕捉特征交叉 FM = Σw_i×x_i + ΣΣ⟨v_i,v_j⟩x_ix_j FM用隐向量内积替代显式交叉参数
特征交互问题
老陈的待客本上密密麻麻记着各种信息:现在是深夜、外面下着雨、客人是常客、他上次点了微辣……这些信息单独看,哪一条都不足以决定“他会不会点牛肉面”。真正让老陈拍板的,是这些信息在脑子里飞快组合的结果——比如“深夜 + 下雨 + 常客”,几乎等于自动下单。推荐系统面临的处境一模一样:用户是否点击,取决于多个特征的共同作用。
CTR 预估的任务,就是根据一条样本的特征向量 \(\mathbf{x}\),预测用户点击的概率 \(P(y=1|\mathbf{x})\)。在工业界,特征大多是类别型离散值,比如“性别=男”“时段=深夜”。为了让数学公式能处理它们,工程师会做 one-hot 编码:把每个取值展开成一个很长的向量,只有对应位置是 1,其余全是 0。这就导致特征向量极其稀疏且高维——就像一张几十万行的勾选清单,每行只有一个格子被打上勾。
如果用线性模型,模型只能学到每个特征单独的权重,“深夜”和“年轻用户”各自加一分,永远表达不出“深夜的年轻用户”这个组合的爆发力。如果人工构造所有两两组合特征,那特征数量会变成天文数字,训练与存储都不可行。于是,让模型自己学会“特征交互”,成了推荐系统的核心课题。
这就像老陈不会傻到去培训厨师把所有 200 种食材的两两组合都练一遍——人力根本不够。他需要一种更聪明的办法:让模型从数据里自动领悟,哪些组合值得重视,哪些组合只是噪音。
- 特征向量\(\mathbf{x}\) 代表一条样本的所有特征拼成的向量,是模型输入的起点。
- 单个特征取值\(x_i\) 表示第 i 个特征——经过 one-hot 后,它通常是 0 或 1。
- 稀疏性n 可能高达百万级,但非零的 \(x_i\) 很少,绝大多数维度都是 0。
FM 二阶交互
面点老师傅的绝活,是“单样食材本味”与“经典搭配表”两件事。他知道牛肉的醇厚、面条的筋道,也背得下番茄炒蛋、葱爆牛肉这些两两绝配;而搭配的默契程度,只需要用一个数字打分。FM 要做的,正是把这种本事数学化。
FM 首先给每个特征 \(x_i\) 配一个一阶权重 \(w_i\),再加上一个全局偏移 \(w_0\)——这构成了信号的“基线味道”;更重要的是,它给每个特征都学习一个低维隐向量 \(\mathbf{v}_i\),两个特征的交叉强度就用它们隐向量的内积 \(\langle \mathbf{v}_i, \mathbf{v}_j \rangle\) 来表示。为什么不用一个独立的权重 \(w_{ij}\)?因为特征组合的数量是 O(n²) 级别,稀疏数据下绝大多数组合从未出现过,根本学不出可靠参数。把 \(w_{ij}\) 分解成两个向量内积后,参数总量降到 O(nk),且只要单独出现过某个特征,它的隐向量就能被训练,交叉权重也随之“泛化”到从未见过的组合上。
1. 全局偏差:\(w_0\) 是所有样本的基准分数,相当于“不管什么菜,先给个基础分”。
2. 一阶线性部分:每个特征单独贡献的分数之和,刻画“单样食材本身的味道”。
3. 二阶交叉部分:所有两两特征组合的贡献之和,刻画“番茄配鸡蛋”这种默契。
4. 隐向量内积:\(\langle \mathbf{v}_i, \mathbf{v}_j \rangle\) 用两个向量的内积代替一个独立权重,是参数因子分解的核心。
老师傅的搭配表不是死记硬背 200 × 200 项,而是记住每种食材的“性格向量”:牛肉咸香、番茄酸甜、葱油提鲜……两个食材合不合拍,把向量点乘一下便知。这就是 FM 的智慧。
共享 Embedding
后厨的中央厨房里有一台切配机,把每种食材都切成统一规格的小丁。无论是面点师傅做经典搭配,还是创意主厨做融合菜,用的都是同一袋“标准牛肉丁”“标准胡萝卜丁”。口味体系完全一致,不会出现“这个菜里的牛肉是三厘米块,那个菜里是两厘米粒”的混乱。在 DeepFM 里,这台机器就是共享 Embedding 层。
每个类别型特征(比如“用户 ID=1024”“商品类目=零食”)经过 one-hot 编码后只有一个位置是 1。Embedding 层做的事情,就是按特征 ID 去查一张“向量表”:表里每一行是一个低维稠密向量(比如 32 维或 64 维),把特征 ID 映射到这一行。因为 one-hot 向量与查表矩阵相乘的结果,恰好等于“取出第 i 行”,所以这个过程也叫查表映射。
1. 稀疏特征取值:\(x_i\) 表示第 i 个 one-hot 特征,绝大多数为 0。
2. 嵌入向量:\(\mathbf{v}_i\) 是特征 i 对应的稠密向量,记录了该特征的“性格”。
3. 查表结果:\(\mathbf{e}_i\) 是取出的稠密向量——当 \(x_i=1\) 时就是 \(\mathbf{v}_i\),相当于查表。
共享的关键在于:FM 支路的二阶交叉用这组向量,DNN 支路的高阶学习也用这组向量。两个子网络从同一套表示中提取信息,反向传播时梯度会同时更新这份共享表,互相促进。如果两位厨师的备菜标准不一样,做出来的菜就会“打架”;共享 Embedding 保证了整个后厨说同一种语言。
DNN 高阶学习
年轻的创意主厨不满足于两两搭配——他把牛肉、番茄、泡椒、陈皮、豆瓣酱……七八种食材层层叠加、慢火浓缩,产生一种无法一一拆解的复合鲜味。这种复杂风味,用“哪个食材和哪个食材的配对”根本说不清楚。在数学上,这就是高阶特征交互。
DNN 支路做的事,是把共享 Embedding 产出的那些稠密向量拼接或加和成一个长向量,然后送进多层全连接网络。每一层网络都对上一层的输出做线性变换加非线性激活,层与层之间的组合,等价于以隐式方式让多个特征发生交互——第 2 层可能组合了第 1 层的若干输出,第 3 层又组合了第 2 层的输出。经过足够深的网络,三阶、四阶甚至更高阶的特征关系都可以被表达。
1. 上一层输出:\(\mathbf{a}^{(l)}\) 是第 l 层神经元的激活向量,代表当前“半成品风味”。
2. 线性变换:\(\mathbf{W}^{(l)} \mathbf{a}^{(l)} + \mathbf{b}^{(l)}\) 是仿射变换,相当于把上一层的信号重新调配比例。
3. 非线性激活:\(\sigma(\cdot)\) 通常是 ReLU 等激活函数,给模型引入非线性,否则多层网络等价于一层。
创意主厨说不出“第几克泡椒配第几克牛肉产生了什么化合物”,但当他把食材交给时间与火候——也就是网络的深度与非线性——复合鲜味自然涌现。FM 负责精确的两两搭配,DNN 负责说不清道不明的融合风味,两者互补。
DeepFM 融合
老陈做了个大胆决定:把两个厨房并成一个大开间。面点师傅和创意主厨各做各的,互不干扰,但最后把两碗合成一道招牌套餐端给客人。经典口味负责不出错,创意负责出彩——这就是 DeepFM 的架构哲学。
具体来说,DeepFM 的输入先经过共享 Embedding 层,得到所有特征的稠密向量;随后兵分两路:FM 支路计算一阶项和二阶交叉项,DNN 支路把 Embedding 展平后送入多层网络计算高阶隐式特征;两条支路的输出直接相加,再经过 Sigmoid 函数压缩到 0 到 1 之间,作为点击概率。
1. FM 支路得分:\(\hat{y}_{FM}(\mathbf{x})\) 是 FM 的一阶加二阶输出,刻画可解释的经典搭配。
2. DNN 支路得分:\(y_{DNN}(\mathbf{x})\) 是高阶隐式特征的综合输出,捕捉复杂融合风味。
3. 并行相加:两条支路的分数直接求和,FM 与 DNN 同时贡献、地位对等。
4. Sigmoid 输出:\(\sigma(\cdot)\) 把任意实数压到 0~1 区间,输出即为点击概率。
这个“相加”看似简单,却体现了精心设计:FM 保底解决稀疏场景下最常见的二阶交互,DNN 负责挖掘更复杂的高阶关系。共享 Embedding 让两路从同一套表示出发,端到端地用交叉熵损失直接优化——不需要像早期 FNN 那样先预训练 FM 再训练 DNN。经典与创意同灶,成就了这道招牌菜。
对比与部署
老陈的招牌菜火了,隔壁面馆和分子料理店也来“借鉴”。隔壁面馆用“层层叠叠拉面”的方法做交叉——每一层都把面皮重新折叠、再次擀开,这就像 DCN 的 Cross 网络。分子料理店则用精密仪器,精确控制每一个食材分子的反应——这就像 xDeepFM 的 CIN 模块。
DCN 的 Cross 网络每一层都保留初始特征 \(x_0\),再让当前状态与初始特征做“逐元素相乘”的显式交叉,叠加多层后能显式建模有限阶的特征组合。但 DCN 缺少类似 DNN 的深层隐式表达,且本质上仍是“标量级”操作。xDeepFM 提出的 CIN(Compressed Interaction Network)则更进一步:它在向量维度上做显式高阶交叉,每一层的输出都是“向量级”的交互结果,可以精确控制要交叉到几阶。DeepFM 与 xDeepFM 名字相近,但 DeepFM 的高阶交互靠 DNN 隐式完成,xDeepFM 的高阶交互是显式逐维构造的,两者并不相同。
1. 初始特征:\(\mathbf{x}_0\) 是交叉网络在第一层保留的原始 Embedding 拼接向量,每层都参考它。
2. 线性变换:\(\mathbf{W}_l \mathbf{x}_l + \mathbf{b}_l\) 对当前状态做线性映射,产生新的交叉中间量。
3. 逐元素相乘:\(\odot\) 让初始特征与中间量逐维相乘,这就是“显式交叉”的核心操作。
4. 残差连接:加上 \(\mathbf{x}_l\) 保证信息不丢失,避免深层网络退化。
回到现实部署:训练 DeepFM 的完整流程大致是——收集用户行为日志 → 特征工程(ID 化、分桶、构造交叉记忆)→ 构造正负样本 → 训练并评估 AUC/GAUC → 导出模型 → 上线在线推理服务。工程上要特别关注 Embedding 表的内存占用、线上推理的延迟优化(比如把 Embedding 查表结果预热到内存缓存),以及根据新日志定期增量更新模型。不同流派各有擅长,但 DeepFM 之所以成为经典,正是因为它用最简洁的“经典搭配 + 创意融合”结构,在效果与工程复杂度之间找到了黄金平衡点。
AI 应用场景
信息流广告 CTR 预估
阶段:离线训练 + 在线推理
解决的问题:广告平台需要预测用户是否会点击某条广告,传统 LR 模型依赖人工构造交叉特征,业务人员要手写大量组合规则,既费人力又容易遗漏。DeepFM 自动学习二阶与高阶特征交叉,显著减少人工特征工程。
具体案例:某广告平台把用户画像(年龄、兴趣标签、设备型号)与广告属性(行业、创意形式、投放时段)做 one-hot 后送入 DeepFM,线上 CTR 提升约 15%,AUC 提升 3 个百分点,同时上线周期从两周缩短到三天。
短视频推荐精排
阶段:召回后的精排打分
解决的问题:短视频平台从海量候选视频中粗筛出几百个后,需要用高精度模型精排。DeepFM 对“用户-视频”特征对打分,输出用户点击/完播的概率作为排序依据。
具体案例:某短视频 App 把多路召回结果输入 DeepFM,特征包括用户静态画像、历史观看序列、视频内容标签、当前上下文(时间、网络环境),精排后的平均播放时长提升 8%。
电商商品个性化排序
阶段:商品流排序的模型服务
解决的问题:电商场景用户与商品特征极其稀疏——一个用户只买过几十种商品,一个商品只有有限的曝光记录。DeepFM 的共享 Embedding 让冷门商品也能借由相似商品的向量获得泛化能力。
具体案例:某电商平台把用户行为序列、商品类目、价格带、促销类型等特征送入 DeepFM,作为主排序模型之一,在双十一大促期间依然保持稳定的点击率和 GMV 转化率。
Python 演示
import torchimport torch.nn as nnimport torch.optim as optim class DeepFM(nn.Module): """DeepFM: FM 与 DNN 并行融合的 CTR 预估模型""" def __init__(self, feature_size, embed_size, hidden_sizes): super(DeepFM, self).__init__() # 一阶线性权重(特征 ID -> 权重) self.first_order = nn.Embedding(feature_size, 1) # 共享 Embedding(FM 二阶与 DNN 共用) self.shared_emb = nn.Embedding(feature_size, embed_size) # DNN 深层网络 self.deep_layers = nn.ModuleList() input_dim = embed_size for hidden in hidden_sizes: self.deep_layers.append(nn.Linear(input_dim, hidden)) input_dim = hidden self.deep_out = nn.Linear(input_dim, 1) def forward(self, x): # 共享 Embedding 查表: (batch, fields, embed) emb = self.shared_emb(x) # FM 一阶部分 first = self.first_order(x).sum(dim=1) # FM 二阶部分: 0.5 * ((sum v)^2 - sum(v^2)) sum_sq = emb.sum(dim=1) ** 2 sq_sum = (emb ** 2).sum(dim=1) second = 0.5 * (sum_sq - sq_sum).sum(dim=1) # DNN 高阶部分 deep_in = emb.reshape(emb.size(0), -1) for layer in self.deep_layers: deep_in = torch.relu(layer(deep_in)) dnn_out = self.deep_out(deep_in).squeeze(1) # FM 与 DNN 并行相加, 再经 sigmoid 输出 CTR logits = first + second + dnn_out return torch.sigmoid(logits) def main(): # 模拟 8 个特征, 每条样本有 3 个特征字段 torch.manual_seed(42) model = DeepFM(feature_size=8, embed_size=4, hidden_sizes=[8, 8]) optimizer = optim.Adam(model.parameters(), lr=0.01) x = torch.randint(0, 8, (64, 3)) y = torch.randint(0, 2, (64,)).float() for epoch in range(50): optimizer.zero_grad() pred = model(x).squeeze(1) loss = nn.functional.binary_cross_entropy(pred, y) loss.backward() optimizer.step() if epoch % 10 == 0: print("epoch", epoch, "loss", round(loss.item(), 4)) if __name__ == "__main__": main()
self.shared_emb = nn.Embedding(feature_size, embed_size)建立共享 Embedding 表,FM 与 DNN 都从这里取特征向量,这是 DeepFM 的关键枢纽。first = self.first_order(x).sum(dim=1)用 Embedding 查表取出每个特征的一阶权重并求和,得到 FM 的一阶项。second = 0.5 * (sum_sq - sq_sum).sum(dim=1)利用数学恒等变换把 O(n²) 的二阶交叉化简为 O(nk) 的向量运算,代码里不出现任何双层循环。deep_in = emb.reshape(emb.size(0), -1)把多个字段的 Embedding 展平拼接,作为 DNN 部分的输入向量。logits = first + second + dnn_outFM 支路与 DNN 支路并行相加,体现 DeepFM 的融合方式。return torch.sigmoid(logits)Sigmoid 把融合分数压到 0~1,输出 CTR 点击概率。
常见误区
误区DeepFM 叫“FM 与 DNN 融合”,那不就是先把 FM 训练好、再训练 DNN,最后把两个模型的预测结果做加权平均吗?
正解DeepFM 不是集成学习,而是单一模型的内部并行结构。FM 支路和 DNN 支路共享同一个 Embedding 层,一起做反向传播、端到端联合优化,全程只有一个损失函数。就像两位厨师共用一个灶台、一套食材,而不是各自开两家店再拼桌。
为何常见:“融合”这个词容易让人联想到模型集成,而忽略了 DeepFM 架构里“共享参数 + 联合训练”的本质。
误区DNN 不是万能函数逼近器吗?那我把原始 one-hot 稀疏特征直接灌进 DNN,不也能学出高维交叉吗?
正解千万不能直接灌。原始 one-hot 特征动辄百万维,直接进入第一层全连接会让参数量爆炸,而且每个权重只能被极少数样本更新,学不出泛化。DeepFM 的做法是先用共享 Embedding 把稀疏特征压缩成几十维稠密向量,再送入 DNN。
为何常见:直觉上觉得“DNN 够强”,忽略了高维稀疏输入带来的维度灾难与严重过拟合。
误区看到公式里有两层求和符号,就以为代码里必须写双重 for 循环遍历所有 i < j 的特征组合,复杂度 O(n²)。
正解FM 可以通过恒等变换把二阶项改写为 \(\frac{1}{2}((\sum v_i x_i)^2 - \sum (v_i x_i)^2)\),计算复杂度从 O(n²) 降为 O(nk)。Python 演示代码正是用这种向量化技巧实现的。
为何常见:只看了公式没看推导,“双重求和”的第一印象根深蒂固。
误区DeepFM 里不是有 DNN 吗?那我把隐藏层加到 10 层、每层 1024 个神经元,效果肯定更好。
正解DNN 层数过深会带来过拟合、训练不稳、推理延迟高。CTR 场景特征稀疏、噪声大,过深的网络很容易把记忆性噪声学进去。实践中 2~3 层隐藏层通常就够,要结合 AUC 和线上效果综合调参。
为何常见:深度学习“越深越强”的印象在社会上过于深入人心,忽略了推荐场景数据稀疏的现实。
误区xDeepFM 名字里也有 DeepFM,所以只是 DeepFM 加了点小改动的版本,核心结构完全一样。
正解两者高阶特征的学习方式根本不同:DeepFM 的 DNN 是“隐式”高阶交叉,没有显式的交叉阶数概念;xDeepFM 的 CIN 是“显式”向量级高阶交叉,可以精确控制交叉到第几阶。名字相近但设计哲学不同。
为何常见:模型命名都以 DeepFM 为后缀,让人误以为只是工程优化,没注意到数学结构上的差异。
面试题
Q1. DeepFM 中 FM 部分的核心职责是?
- A.学习一阶与二阶特征交互← 正确答案
- B.学习三阶以上的深层交叉
- C.独立生成全连接网络参数
- D.完成用户物品的向量检索
答案:A。FM 在 DeepFM 中负责建模一阶线性项与二阶特征交叉,DNN 负责高阶隐式交叉。选项 B 是 DNN 的职责;C、D 与 FM 无关。就像面点老师傅只负责经典两两搭配,不负责分子料理。
Q2. DeepFM 的 FM 与 DNN 组件关系是?
- A.两个组件并行融合输出结果← 正确答案
- B.FM 为前置 DNN 做数据过滤
- C.DNN 输出作为 FM 的输入项
- D.两个组件交替更新各自参数
答案:A。DeepFM 是并行结构:共享 Embedding 后 FM 与 DNN 各算各的,输出直接相加再过 Sigmoid。B、C 描述的都是串行关系,D 的交替训练也不是 DeepFM 的做法——它是端到端联合训练。
Q3. 关于 DeepFM 共享 Embedding 的说法,正确的有哪些?
- A.FM 与 DNN 共用同一个低维嵌入表← 正确答案
- B.模型无需 FM 预训练即可端到端优化← 正确答案
- C.每个子网络拥有独立的 Embedding 表
- D.共享会让 FM 与 DNN 训练目标互相割裂
答案:AB。共享 Embedding 是 DeepFM 的核心:一份嵌入表同时服务 FM 与 DNN,梯度会同时更新它,无需像 FNN 那样先预训练 FM。C 错在“独立”,D 错在“割裂”——共享恰恰让两路目标统一。
Q4. DeepFM 最擅长处理的特征类型是?
- A.稀疏高维的类别型离散特征← 正确答案
- B.稠密连续的数值型评分特征
- C.像素级别的图像纹理特征图
- D.词级别的语义上下文特征串
答案:A。DeepFM 的 FM 与 Embedding 机制天生适合 one-hot 后的稀疏离散特征,比如用户 ID、商品类目、时段。图像和文本需要 CNN/RNN 等结构,不属于 DeepFM 的主场。
Q5. 与 DCN 的 Cross 网络相比,DeepFM 的差异化特点是什么?
- A.用 FM 模块完成二阶交互表示← 正确答案
- B.使用循环神经网络捕获时序
- C.完全抛弃低维嵌入表示层
- D.只在特征比特位做逻辑操作
答案:A。DCN 用 Cross 网络做显式有限阶交叉,DeepFM 则用 FM 做二阶交叉、DNN 做高阶隐式交叉。B、C、D 都不是 DeepFM 的设计。两者都依赖 Embedding,D 完全是干扰项。
Q6. DeepFM 中 DNN 部分的能力有哪些?
- A.自动学习高阶非线性特征组合← 正确答案
- B.通过多层网络捕获复杂语义交互← 正确答案
- C.替代 FM 完成全部二阶交叉计算
- D.使用激活函数引入非线性表达← 正确答案
答案:ABD。DNN 通过多层堆叠与非线性激活隐式学习高阶特征交互,A、B、D 正确。C 错误:二阶交叉由 FM 支路负责,DNN 不替代它,两者是分工而非竞争关系。
Q7. FM 参数因子分解能大幅降低复杂度的原因是?
- A.交叉权重表示成隐向量的内积← 正确答案
- B.每对特征都保留独立权重参数
- C.只计算一阶特征的线性加权和
- D.用树模型替代线性分类器
答案:A。FM 把每个特征对独有的权重 w_ij 分解为 v_i 与 v_j 的内积,参数从 O(n²) 降到 O(nk),且能泛化到未出现的特征对。B 恰恰是因子分解要避免的原始做法。
Q8. 实际部署 DeepFM 做 CTR 预估时,第一件要做的事是?
- A.构造并清洗特征批次样本数据← 正确答案
- B.直接加载预训练权重上线服务
- C.将特征维度压缩到一维数值
- D.删除所有稀疏类别特征字段
答案:A。任何推荐模型的落地都是从数据与特征工程开始的:采集日志、ID 化、分桶、构造样本。没有高质量训练数据,模型权重无从谈起;B、C、D 都是错误步骤。
Q9. 关于 xDeepFM 与 DeepFM 的差异,正确的是?
- A.CIN 模块显式建模高阶特征交叉← 正确答案
- B.DeepFM 高阶依赖 DNN 隐式学习← 正确答案
- C.两者在结构逻辑上完全同构等价
- D.向量级逐维交叉比标量更精细← 正确答案
答案:ABD。xDeepFM 的 CIN 在向量维度上显式构造高阶交叉,DeepFM 的 DNN 则是隐式学习高阶特征。A、B 正确。C 错在两模型结构并不相同。D 正确:CIN 的逐维压缩操作确实比 DCN 的标量级交叉更精细。
Q10. DeepFM 在输出层使用哪个函数得到点击概率?
- A.Sigmoid 压缩结果到零到一区间← 正确答案
- B.Softmax 对所有类别做归一化概率
- C.Tanh 将结果映射到负一到一区间
- D.ReLU 直接输出原始非负数值
答案:A。CTR 预估是二分类问题,输出层用 Sigmoid 把融合分数压缩到 0~1 作为点击概率。Softmax 用于多分类,Tanh 输出含负值,ReLU 不产生概率,都不合适。