想象一间百年药铺:坐诊三十年的老药工,脑子里装着上千张熟方子。病人一进门说“咳嗽、有痰、还发烧”,他眼皮都不用抬就能背出三味药——可你若问一个从没见过的古怪症状组合,他也只能摇头。旁边那位刚从医学院毕业的年轻药师正好相反:他背的都是药理学原理,什么分子结合什么受体、什么结构起什么作用。遇到没见过的症状组合,他能照原理推出一张新方子,却常常想不起十年前那位老顾客最爱喝什么药。 推荐系统也面临一模一样的难题。用户和商品之间,既有大量“反复出现、规律固定”的历史组合——比如装完美颜类 App 的用户常常又去装修图类 App;也有无数“从没见过、必须靠推理”的新组合——比如一位刚注册的新用户与一个刚上架的新 App。只靠记忆,模型遇到新情况就抓瞎;只靠泛化,模型又会把熟客的偏好记混。Google 在 2016 年提出的 Wide & Deep 推荐模型,正是让“老药工”和“年轻药师”在同一张诊台上坐诊,把记忆与泛化两副本领捏成一张方子。 这篇文章就来拆解这张方子:为什么记忆和泛化缺一不可?Wide 部分怎么做到“死记”?Deep 部分怎么做到“活推”?两套思路又是怎么联合训练、一起部署、并最终被业务效果验证的。

岗位关联

搞懂 Wide & Deep,你就掌握了推荐系统在“记住用户习惯”与“探索新规律”之间做权衡的核心思路,这是推荐、广告、搜索从业者绕不开的一课。

目录
  1. 可视化演示
  2. 模型背景
  3. Wide记忆
  4. Deep泛化
  5. 联合训练
  6. 特征工程
  7. 实现细节
  8. 效果评估
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

展示Google Wide & Deep推荐模型双路架构:Wide部分线性记忆特征交叉,Deep部分Embedding泛化未知组合,联合训练输出预测。

本节配有交互动效(进入视口后自动加载,约 3KB)。

Wide & Deep联合架构 Wide:线性交叉记忆 Deep:Embedding泛化 P(y|x)=σ(w_wide^T x + w_deep^T a^(l_f)) 双路联合训练兼顾记忆与泛化

模型背景

药铺里两位医生各怀绝技,可如果店主人只能留一位,他一定左右为难。留下老药工,熟客天天排队,新客却常常被误诊;留下年轻药师,疑难杂症有思路,老顾客却觉得自己被怠慢了。其实店主人想要的,从来不是二选一——而是让两位医生同一时间、同一诊台、同一张处方笺上合作。

推荐系统也是这个道理。在 Google Play 应用商店的推荐场景里,团队要预测的是“用户看到某个候选 App 时会不会安装”。传统大规模线性模型擅长记忆:如果日志里反复出现“装了美颜 App 后又装修图 App”的规律,线性模型能把这个共现关系牢牢抓住。可它一旦遇到新用户、新 App 这类从未出现的组合,就无能为力了。反过来,深度模型通过 embedding 学习用户和 App 的语义向量,能对没见过的组合给出合理预测,但这些“推理”往往把那些高价值却低频的记忆稀释掉。

Google 团队在 2016 年发表的 Wide & Deep 模型,核心思想就是用一条通路同时容纳两者:Wide 侧是广义线性模型,负责“记忆”训练数据中反复出现的特征组合;Deep 侧是带嵌入层的多层全连接网络,负责“泛化”到未出现的特征组合。两个部分的分数相加,过一层 sigmoid,得到最终的点击或安装概率。这套思路后来被广泛迁移到搜索排序、广告点击率预估、信息流推荐等场景,许多后世的推荐架构都延续了“记忆与泛化并行”的设计哲学。

对药铺来说,老药工和年轻药师各有局限,但一张联合处方的力量远超任何一个人在单打独斗。Wide & Deep 从诞生那天起,就是想明白了一个朴素的道理:把不同性质的能力放在同一个模型里,让它们互相补位,而不是互相竞争。

Wide记忆

老药工的看家本领是什么?不是推导,而是见过。他脑海里存着几千个“症状组合 → 药方”的条目,看到咳嗽带痰,就想到某张方子;看到发热无汗,又想到另一张方子。这种记忆的边界非常清晰:见过的组合,判断又快又准;没见过的组合,他连猜的勇气都没有。

Wide 部分的设计思路正是如此。它本质是一个广义线性模型,对输入特征做加权求和。为了让模型具备“记住组合”的能力,Wide 侧使用了一种叫交叉乘积变换的技巧:把两个或多个原始特征是否同时出现,编码成一个新的 0/1 特征。比如“用户已经安装美颜类 App”且“当前候选 App 是修图类 App”,这个交叉特征的值就为 1,否则为 0。模型一旦在训练数据里看到这个组合反复共现,就会把对应权重调高,下次再遇到时给出强烈的正向信号——这就是 Wide 的“记忆机制”。

老药工的记忆优点在于可控、可解释:你说想让模型记住哪些组合,就给哪些组合做交叉。缺点也同样明显——每多记一条,就要多一个维度;而一旦某个组合在日志里只出现过三五次,这条“记忆”就变成了噪声。所以 Wide 侧适合那些业务上明确、频次高、规律强的组合,也正因为如此,药铺里才必须再坐一位年轻药师。

ϕk(x)=∏i=1dxicki,cki∈{0,1}\phi_k(\mathbf{x})=\prod_{i=1}^{d} x_i^{c_{ki}},\qquad c_{ki}\in\{0,1\}
交叉乘积变换的含义是:只在若干个指定特征同时出现的条件下,新特征才等于 1,否则为 0。这相当于给模型准备了一本“组合词典”。
  1. 输入向量这里的 \(\mathbf{x}\) 是用户、候选 App 以及上下文的完整特征向量,每个 \(x_i\) 表示第 \(i\) 个特征是否出现。
    x∈Rd\mathbf{x}\in\mathbb{R}^d
  2. 指示系数\(c_{ki}\) 决定第 \(k\) 个交叉特征是否包含第 \(i\) 个原始特征,取值为 0 表示不参与,1 表示参与。
    cki∈{0,1}c_{ki}\in\{0,1\}
  3. 连乘运算\(\prod\) 把所有参与交叉的特征乘起来;因为特征都是 0/1,所以只有当所有参与特征都等于 1 时,连乘结果才为 1。
    ϕk(x)=∏i=1dxicki\phi_k(\mathbf{x})=\prod_{i=1}^{d} x_i^{c_{ki}}
  4. 记忆生效\(\phi_k(\mathbf{x})=1\) 时表示“这个组合出现了”,模型直接给这个组合分配一个可学习的权重,从而记住它的倾向。
    得分贡献=wwideTϕ(x)\text{得分贡献}=\mathbf{w}_{\text{wide}}^T\phi(\mathbf{x})

Deep泛化

年轻药师从不出售“见过”的方子,他出售的是原理。退烧药为什么能退烧?因为它作用于前列腺素合成的某个酶;抗过敏药为什么让人犯困?因为它还可能穿过血脑屏障影响组胺受体。只要掌握了原理,哪怕遇到一种从未见过的复方搭配,他也能通过底层机制推测出可能的相互作用——这就是“泛化”。

Deep 部分想做的事一模一样。它先把每个用户、每个 App、每类上下文都映射成一个低维稠密向量,也就是 embedding。相似的 User 在向量空间里挨得近,相似的 App 也彼此靠近。接着,这些向量被拼接起来,送入多个全连接隐藏层。每一层都在不同抽象层级上对特征做组合——底层组合的是细粒度属性,高层组合出的是更抽象的偏好。当模型见过了“用户 A 喜欢动作游戏”和“用户 B 与用户 A 相似”之后,面对“用户 B 会不会喜欢某款新动作游戏”这个从没见过的问题,Deep 也能通过向量相近性给出合理推断。

Deep 的核心优势是“自动”:它不需要人类手写交叉组合,而是让网络自己学习特征间的高阶交互。但代价也很现实——它需要海量数据来把 embedding 和网络参数练稳。细看这个模型,年轻药师的推理能力,终究是在一次次问诊中被磨出来的;要是药铺里只有他,遇到熟悉的病症他反而可能推翻老经验、开出一堆复杂检查。所以,必须把 Wide 和 Deep 放在一起,用同一个目标去联合训练。

a(l+1)=f(W(l)a(l)+b(l))\mathbf{a}^{(l+1)}=f\left(\mathbf{W}^{(l)}\mathbf{a}^{(l)}+\mathbf{b}^{(l)}\right)
这是深度网络中第 \(l+1\) 层的激活计算公式:上一层的输出向量乘上权重矩阵、加上偏置,再经过非线性激活函数,得到这一层的表示。
  1. 上一层激活\(\mathbf{a}^{(l)}\) 表示第 \(l\) 层的输出向量,原始输入特征经过嵌入后成为第 0 层表示。
    a(l)∈Rnl\mathbf{a}^{(l)}\in\mathbb{R}^{n_l}
  2. 权重矩阵\(\mathbf{W}^{(l)}\) 把上一层的 \(n_l\) 维向量线性变换为 \(n_{l+1}\) 维空间。
    W(l)∈Rnl×nl+1\mathbf{W}^{(l)}\in\mathbb{R}^{n_l\times n_{l+1}}
  3. 偏置向量\(\mathbf{b}^{(l)}\) 为每个输出维度提供一个可学习的偏移量,让神经元不必从零开始。
    b(l)∈Rnl+1\mathbf{b}^{(l)}\in\mathbb{R}^{n_{l+1}}
  4. 激活函数\(f(\cdot)\) 通常是 ReLU,它将线性变换结果做非线性映射,使得网络能够表达复杂的特征交互。
    f(z)=max⁡(0,z)f(z)=\max(0,z)
  5. 叠加传播计算完成后,\(\mathbf{a}^{(l+1)}\) 又成为下一层的输入,信息就这样逐层抽象、逐层泛化。
    a(l+1)=f(W(l)a(l)+b(l))\mathbf{a}^{(l+1)}=f(\mathbf{W}^{(l)}\mathbf{a}^{(l)}+\mathbf{b}^{(l)})

联合训练

两位医生怎么合作?一种笨办法是:老药工先看,给出一个方子;年轻药师再看,给出另一个方子;最后抓药时把两包药混在一起。这种“先各看各的、最后平均”的方式,本质上是 ensemble,两个方子互相不知道对方在想什么。Wide & Deep 的做法不是这样——它让两人对同一病人同时把脉,互相牵制,最后只诞生一张共同署名的药方。

技术上说,Wide 部分的线性打分和 Deep 部分的网络打分在输出层直接相加,再接一个 sigmoid 函数,得到预测概率。整个模型只有一个交叉熵损失函数,反向传播时,梯度会同时流向 Wide 侧的权重、Deep 侧的网络参数和 embedding 表。也就是说,任何一个分支做错了,都会被同一个“疗效反馈”纠正;任何一个分支做对了,也会帮助另一个分支调整自己的坐标。

训练时还有一个细节:Wide 侧和 Deep 侧使用了不同的优化器。Wide 侧常用 FTRL 算法,配合 L1 正则,让庞大的交叉特征权重保持稀疏且稳定;Deep 侧则用 AdaGrad,适配深度网络参数的分步更新节奏。优化器不同,但损失函数唯一——两人开的方子可以被不同笔迹书写,却必须签在同一张处方笺上,这正是联合训练和“两个模型做平均”的本质区别。

P(Y=1∣x)=σ(wwideT[x,ϕ(x)]+wdeepTa(lf)+b)P(Y=1\mid\mathbf{x})=\sigma\left(\mathbf{w}_{\text{wide}}^T[\mathbf{x},\phi(\mathbf{x})]+\mathbf{w}_{\text{deep}}^T\mathbf{a}^{(l_f)}+b\right)
联合预测的完整表达式:Wide 侧对原始特征与交叉特征打分,Deep 侧对最后一层隐藏表示打分,两边相加后过 sigmoid,得到点击概率。
  1. Sigmoid 映射\(\sigma(\cdot)\) 将任意实数得分压缩到 0 到 1 之间,作为点击概率。
    σ(z)=11+e−z\sigma(z)=\frac{1}{1+e^{-z}}
  2. Wide 侧打分\(\mathbf{w}_{\text{wide}}^T[\mathbf{x},\phi(\mathbf{x})]\) 是线性模型对原始特征与交叉特征的加权求和,精度高但依赖已见组合。
    wwideT[x,ϕ(x)]\mathbf{w}_{\text{wide}}^T[\mathbf{x},\phi(\mathbf{x})]
  3. Deep 侧打分\(\mathbf{w}_{\text{deep}}^T\mathbf{a}^{(l_f)}\) 把深度网络最后一层学到的抽象表示映射为一个分值,负责对未见组合做推理。
    wdeepTa(lf)\mathbf{w}_{\text{deep}}^T\mathbf{a}^{(l_f)}
  4. 全局偏置\(b\) 是整体模型的偏置项,代表数据集中正样本的基础发生率。
    +b+b
  5. 联合概率两部分相加后经过 sigmoid,得到最终预测 \(P(Y=1\mid\mathbf{x})\),训练时用它和真实标签计算交叉熵损失。
    y^=σ(Wide得分+Deep得分+b)\hat{y}=\sigma(\text{Wide得分}+\text{Deep得分}+b)

特征工程

要让两位医生发挥各自所长,给他们的“养料”必须各有侧重。老药工看的是方子库:只收录那些经过验证、反复出现的经典组合。年轻药师看的是药材大全:每种药都单独建档,标明性味归经。如果给老药工一本十万字的药材百科,他背不下来;如果给年轻药师只发几本常见方书,他的推理也无从展开。

Wide 侧的核心输入是人工构造的交叉特征。在 Google Play 场景中,最有价值的是“用户已安装 App × 候选 App”这类共现特征——它直接复现了用户历史上表现出过的兴趣组合。这类特征要筛选,不能贪多:只对高频、有业务含义的组合做交叉,同时设置最低出现频次(比如至少出现 10 次),否则学到的权重只是噪声。

Deep 侧的输入则更“原生态”:用户 ID、App ID、设备类型、请求时段等原始稀疏特征和连续特征。这些特征不需要人工交叉,而是由模型自己通过 embedding 映射成稠密向量,再在多层网络里自动组合。为了让 Deep 学得更稳,连续特征要归一化到相近尺度,分类特征要建立稳定的 ID 映射,训练样本要随机打散——这些都是数据预处理的基本功。

记忆特征和泛化特征的关系,很像方子库和药材大全的关系:方子库是过去的经验结晶,负责保证下限;药材大全是未来的推理素材,负责打开上限。特征工程真正要回答的问题,并不是“哪个特征更高级”,而是“哪一个组合必须被记住、哪一类规律应该被悟出”。

实现细节

药铺正式开张,得有完整的坐诊流程:挂号分诊、把脉问诊、合方抓药、煎煮送达。Wide & Deep 作为 Google Play 的线上推荐模型,同样需要一套可落地的工程架构,而论文里恰好记录了它的实现细节。

网络结构上,Deep 侧为每个分类特征准备一张 embedding 表,维度取 32,把用户 ID、已安装 App 列表、候选 App 等映射成稠密向量,拼接后输入三层全连接网络,每层 1024 个神经元,激活函数用 ReLU。Wide 侧则把构造好的交叉特征直接拼成大向量,输入广义线性层。两条分支在输出层汇合,计算点击概率和损失。超参数方面,embedding 维度、隐藏层宽度、Mini-batch 大小、FTRL 与 AdaGrad 的学习率,都会通过小规模实验确定。

训练技巧上,Google 团队用 TensorFlow 将 Wide 和 Deep 两个子图拼进同一个计算图中。每次迭代,模型从用户行为日志中采样一批样本,前向计算联合得分与交叉熵损失,反向传播时自动为两侧计算梯度,再分别交给 FTRL 和 AdaGrad 优化器更新。线上 serving 时,不需要分别部署两个模型再合并结果——一次前向计算就能同时得到两侧分数,这大大降低了线上延迟和运维成本。

整套工程流程给我们的启示是:两位医生开方再神,挂号、转诊、抓药的环节也必须顺滑。Wide & Deep 的价值不仅在于模型设计简洁,更在于它在工业化系统里天然好部署——一张处方笺走到底,谁也不用单独排队。

效果评估

联合坐诊的效果好不好,不能只听两位医生自夸,要看复诊率和好评率。Google Play 团队也对 Wide & Deep 做了严格的离线与线上评估:离线用 AUC 等指标衡量排序质量,线上则直接看 App 安装率这类业务指标。论文报告的结论是,联合模型相比纯 Wide 或纯 Deep 的基线,线上安装率提升了约 3.9%。

为什么联合模型能赢?关键就在于它同时照顾了两类用户行为:对反复出现的兴趣组合,Wide 的记忆保证了“不犯错”;对那些从未见过的新组合,Deep 的泛化保证了“不失手”。这个效果也启发了一条重要的权衡法则:如果业务中历史规律强、长尾特征少,可以把模型的记忆侧做得更重;如果新用户新商品多、特征长尾明显,则应该让泛化侧承担更多责任。Wide & Deep 提供的正是一个可调节的框架,而不是一个固定的答案。

评估时还要留个心眼:离线指标要对样本按时间切分,防止特征泄露;上线实验要采用严格的 A/B 分流,保证实验组与控制组只有模型不同。回到药铺的比喻,老药工与年轻药师的合作模式,最终是靠患者的长期口碑站住了脚——面对熟悉的病,记忆让它稳;面对陌生的病,泛化让它灵。一张能同时容纳“熟”与“新”的诊台,才是推荐系统真正想要的。

提升率=CTRWideDeep−CTRBaselineCTRBaseline×100%\text{提升率}=\frac{\text{CTR}_{\text{WideDeep}}-\text{CTR}_{\text{Baseline}}}{\text{CTR}_{\text{Baseline}}}\times100\%
联合模型带来的业务提升,用线上点击率的相对变化来衡量:分子是联合模型与基线模型点击率之差,分母是基线点击率,乘以 100% 换算成百分比。
  1. 分子差值\(\text{CTR}_{\text{WideDeep}}-\text{CTR}_{\text{Baseline}}\) 是实验组与对照组的点击率绝对差距。
    Δ=CTRWideDeep−CTRBaseline\Delta=\text{CTR}_{\text{WideDeep}}-\text{CTR}_{\text{Baseline}}
  2. 相对比除以基线点击率,把绝对差距转成相对提升,便于在不同流量规模下比较。
    ΔCTRBaseline\frac{\Delta}{\text{CTR}_{\text{Baseline}}}
  3. 百分比换算乘以 100% 后得到业务上常用的“提升率”指标。
    提升率=ΔCTRBaseline×100%\text{提升率}=\frac{\Delta}{\text{CTR}_{\text{Baseline}}}\times100\%

AI 应用场景

应用商店 App 推荐

阶段:候选集排序

解决的问题:传统协同过滤只记得住高频共现组合,遇到新上架 App 或新用户,排序结果失去依据。Wide 侧记录“已装 App × 候选 App”的高频交叉,Deep 侧用嵌入向量理解 App 的功能语义,使新 App 也能被分配合理曝光。

具体案例:Google Play 的推荐系统每日处理数亿次“用户 × 候选 App”请求。用户历史安装列表与候选 App 做交叉特征进入 Wide 分支,用户和 App 的 ID 特征进入 Deep 分支做 embedding。最终联合模型将线下 AUC 和线上安装率同时提升,验证了记忆与泛化并行的有效性。

广告点击率预估

阶段:在线广告预估

解决的问题:广告场景特征极度稀疏,且“用户 × 广告主”的共现关系带有很强的重复性。纯线性模型记性好但泛化弱,纯深度模型泛化强但容易丢失精确历史信号。Wide & Deep 把高频共现交给 Wide,把广告文本、用户兴趣等语义信息交给 Deep,兼顾稳定与探索。

具体案例:某信息流广告平台在训练 CTR 模型时,将“用户行业 × 广告行业”的交叉特征放入 Wide 侧,将用户行为序列和广告标题的嵌入表示放入 Deep 侧。上线两周后,广告点击率相对基线提升 2.1%,同时冷启动广告的曝光效率显著改善。

信息流内容推荐

阶段:内容打分排序

解决的问题:资讯 App 中,用户对固定栏目有强偏好(如总是点开体育频道),又存在跨领域的兴趣迁移(如读完汽车测评后开始关注旅行内容)。Wide 记忆强栏目偏好,Deep 捕获跨领域兴趣迁移,两者联合打分提升整体阅读时长。

具体案例:某新闻 App 将“用户常读频道 × 候选文章频道”作为 Wide 交叉特征,同时把用户阅读过的文章标题经过嵌入表示送入 Deep。实践发现,联合模型在人均阅读篇数上比纯 Deep 模型高出 4.7%,验证了精细记忆对于留存类指标的重要价值。

Python 演示

python86 行
import numpy as np  def sigmoid(z):    return 1.0 / (1.0 + np.exp(-np.clip(z, -20, 20)))  def make_wide_vec(user, item, n_users, n_items, n_cross=128):    """构造 Wide 侧输入:用户 one-hot + 商品 one-hot + 交叉特征"""    v = np.zeros(n_users + n_items + n_cross)    v[user] = 1.0    v[n_users + item] = 1.0    v[n_users + n_items + (user * 31 + item) % n_cross] = 1.0    return v  class WideAndDeep:    """numpy 实现的小型 Wide & Deep 推荐模型"""     def __init__(self, n_wide, n_cat, emb_dim=8, hidden_dim=16, seed=42):        rng = np.random.default_rng(seed)        self.n_wide = n_wide        self.w_wide = np.zeros(n_wide)        self.emb = rng.normal(0, 0.1, size=(n_cat, emb_dim))        self.W1 = rng.normal(0, 0.1, size=(emb_dim, hidden_dim))        self.b1 = np.zeros(hidden_dim)        self.w2 = rng.normal(0, 0.1, size=hidden_dim)        self.b2 = 0.0     def _deep_logit(self, x_cat):        h0 = self.emb[x_cat].sum(axis=0)        z1 = np.tanh(h0 @ self.W1 + self.b1)        return z1 @ self.w2 + self.b2, z1, h0     def predict(self, x_wide, x_cat):        logit = self.w_wide @ x_wide        logit += self._deep_logit(x_cat)[0]        return sigmoid(logit)     def train(self, X_wide, X_cat, y, epochs=5, lr=0.05):        for _ in range(epochs):            for xw, xc, yi in zip(X_wide, X_cat, y):                d_logit, z1, h0 = self._deep_logit(xc)                p = sigmoid(self.w_wide @ xw + d_logit)                grad = p - yi                # Deep 输出层梯度                self.w2 -= lr * grad * z1                self.b2 -= lr * grad                # Deep 隐藏层梯度                dz1 = (1 - z1 ** 2) * grad * self.w2                self.W1 -= lr * np.outer(h0, dz1)                self.b1 -= lr * dz1                # 嵌入梯度                d_emb = dz1 @ self.W1.T                for idx in xc:                    self.emb[idx] -= lr * d_emb                # Wide 梯度                self.w_wide -= lr * grad * xw     def evaluate(self, X_wide, X_cat, y):        preds = np.array([self.predict(xw, xc) for xw, xc in zip(X_wide, X_cat)])        return np.mean((preds > 0.5) == y)  def main():    n_users, n_items, n = 50, 50, 800    rng = np.random.default_rng(0)    users = rng.integers(0, n_users, n)    items = rng.integers(0, n_items, n)    # 记忆性组合:特定用户组与商品组共现    memo = (users * 3 + items) % 17 == 0    # 泛化性规律:热门商品更易被点击    favorite = items % 7 == 0    p = 0.2 + 0.6 * favorite + 0.4 * memo    y = (rng.random(n) < p).astype(int)     X_cat = np.stack([users, items + n_users], axis=1)    X_wide = np.stack([make_wide_vec(u, i, n_users, n_items) for u, i in zip(users, items)])     model = WideAndDeep(n_wide=X_wide.shape[1], n_cat=n_users + n_items)    model.train(X_wide, X_cat, y, epochs=5, lr=0.05)    print("Wide&Deep 准确率: %.3f" % model.evaluate(X_wide, X_cat, y))  if __name__ == "__main__":    main()
目的:用 numpy 从零实现一个迷你 Wide & Deep 模型,演示 Wide 线性分支与 Deep 嵌入网络如何共享损失、联合训练。
  1. make_wide_vec(user, item, n_users, n_items, n_cross=128)构造 Wide 侧输入向量,在用户 one-hot 和商品 one-hot 之后拼接一段交叉特征。
  2. self.w_wide = np.zeros(n_wide)Wide 侧的权重向量,初始化为零,靠梯度逐步“记住”特征组合。
  3. self.emb = rng.normal(0, 0.1, size=(n_cat, emb_dim))为每个用户和商品分配一个低维稠密向量,是 Deep 泛化的起点。
  4. _deep_logit(x_cat)把用户和商品的 embedding 相加,送入一层隐藏网络,得到 Deep 分支的分数。
  5. logit = self.w_wide @ xw计算 Wide 侧线性得分,与 Deep 得分相加后过 sigmoid,体现联合预测。
  6. grad = p - yi对交叉熵损失求导后,每个样本的梯度就是预测值与标签之差。
  7. self.emb[idx] -= lr * d_emb梯度逐层回传后更新嵌入表,这是 Deep 部分学习泛化规律的机制。
  8. self.w_wide -= lr * grad * xwWide 侧同步更新权重,保证记忆分支不偷懒。

常见误区

误区很多人把 Wide & Deep 理解成“先训练一个逻辑回归,再训练一个神经网络,预测时把两个模型的分数平均一下”。看论文里的结构图,两条分支各走各的,很容易这么想。

正解Wide & Deep 是联合训练的单模型。两个分支共享同一个损失函数,梯度同时回传到 Wide 权重和 Deep 参数、embedding 表中。就像老药工和年轻药师不是各开各的方子再混合,而是必须对同一病人共同把脉、共同签名——这才是“联合”的本意。

为何常见:结构图上有两条独立通路线,加上两个分支用了不同优化器,让人误以为它们是两个独立模型。

误区深度网络又有 embedding 又有多层非线性,理论上能自动学习任何特征交叉,那为什么还需要一个简单的线性模型?

正解深度网络擅长“泛化”却不擅长“精确记忆”。对于稀疏又高价值的特征组合,比如“用户装过 A 类 App 且此刻在看 B 类 App”,Embedding 会在相似性中把它模糊掉。论文实验显示,联合模型超过纯 Deep 模型,正说明 Wide 的记忆能力不可替代。

为何常见:深度学习在图像、文本等领域太成功,容易让人低估线性模型在稀疏特征记忆上的价值。

误区既然 Wide 负责记忆,那干脆把用户和商品的全排列都做成交叉特征,让模型记下所有可能组合,岂不是更稳妥?

正解交叉特征越多,特征空间越稀疏,很多组合在整个训练集里只出现几次,权重学出来全是噪声。正确做法是设置最小频次阈值,只对高频、有明确业务语义的组合做交叉,其他交给 Deep 泛化。

为何常见:抓住“Wide 负责记忆”这一点后,很容易把“记忆”机械地理解为“数量越多越好”。

误区有人认为 Wide 侧应该用分类损失,Deep 侧用回归损失,最后把两个损失加权求和再优化。

正解Wide & Deep 只有一个统一损失,例如点击场景的交叉熵。这个损失同时约束两个分支,通过链式法则把梯度分发给两侧。两侧可以用不同优化器、不同正则,但它们的“疗效”是由同一个医生总负责的。

为何常见:Wide 用 FTRL、Deep 用 AdaGrad,让新手误以为两侧各自在训练自己的任务。

误区画结构图时,两个分支看起来共用同一个输入层,有人就认为把同一批特征原样喂给两边就行。

正解设计上要分工。手工交叉特征应该主要进 Wide 侧,让线性模型直接利用它们;原始高维稀疏 ID 特征和连续特征进 Deep 侧做 embedding。若两边喂一样的离散 ID,Wide 侧只是白白增加维度,Deep 侧也无法发挥自动交叉优势。

为何常见:大量简化版示意图只画了一个共享输入,容易误导读者忽略两侧“养料”不同。

面试题

Q1. Wide & Deep 中 Wide 部分的基础模型是什么?

单选题 · easy

  • A.深度神经网络
  • B.广义线性模型← 正确答案
  • C.梯度提升决策树
  • D.K 均值聚类模型

答案:B。Wide 部分是广义线性模型,加上交叉乘积变换后负责记忆特征组合。Deep 部分才是神经网络;决策树和聚类不属于 Wide & Deep 的组件。

Q2. 以下哪些是 Deep 部分能够“泛化”到未见组合的关键?

多选题 · easy

  • A.将离散 ID 映射成低维嵌入向量← 正确答案
  • B.多层全连接网络做非线性变换← 正确答案
  • C.人工构造的海量精确交叉特征
  • D.只保留训练集里出现过的样本

答案:AB。嵌入向量让相似特征在空间中靠近,多层网络自动学习高阶交互,这两者带来了泛化能力。人工精确交叉特征是 Wide 侧的典型武器;只保留见过的样本违背了泛化目标。

Q3. Wide 部分的交叉乘积变换,主要作用是什么?

单选题 · mid

  • A.对特征做降维
  • B.精确记住稀疏特征的共现组合← 正确答案
  • C.增加模型的非线性表达能力
  • D.自动处理缺失值

答案:B。交叉乘积变换把“哪些特征同时出现”编码成 0/1 特征,让线性模型直接学习该组合的权重,本质是一种记忆机制。降维和非线性分别是嵌入层和激活函数的事。

Q4. 关于 Wide & Deep 的联合训练,以下说法正确的是?

单选题 · mid

  • A.整个模型只有一个损失函数← 正确答案
  • B.Wide 和 Deep 各有一个损失函数
  • C.只有 Deep 部分有损失函数
  • D.损失函数只在 Wide 部分生效

答案:A。联合训练的核心是共享同一个损失,比如交叉熵。反向传播时梯度会同时流向 Wide 权重和 Deep 参数,因此两个分支被同一个目标约束,而不是各自为政。

Q5. 关于 Wide 与 Deep 的训练优化器,正确的说法有?

多选题 · mid

  • A.Wide 侧重常用 FTRL 算法← 正确答案
  • B.Deep 侧常用 AdaGrad 优化器← 正确答案
  • C.两侧必须使用完全相同的优化器
  • D.两侧可以设置不同的正则化方式← 正确答案

答案:ABD。论文中 Wide 用 FTRL 配 L1 保持稀疏,Deep 用 AdaGrad 适配网络更新。两者优化器不同、正则也可以不同,但共享同一个损失函数,因此 C 错误。

Q6. Wide & Deep 模型训练完成后的预测过程是?

单选题 · hard

  • A.先跑 Wide 再跑 Deep,串行计算
  • B.同时计算两分支输出,相加后过 sigmoid← 正确答案
  • C.随机选择一个分支输出作为预测
  • D.分别预测后取最大值作为结果

答案:B。预测时两个分支并行计算各自得分,在输出层相加,再经 sigmoid 映射成概率。串行或取最大值都不是 Wide & Deep 的定义。

Q7. Deep 部分的输入特征通常是什么?

单选题 · easy

  • A.仅人工构造的交叉特征
  • B.原始稀疏 ID 特征和连续特征← 正确答案
  • C.只允许使用数值标签特征
  • D.训练样本对应的最终标签

答案:B。Deep 侧输入的是用户 ID、商品 ID、上下文等原始特征,由嵌入层自行降维和学习关系。人工交叉特征留给 Wide 侧,标签则用于计算损失。

Q8. 以下哪些说法符合 Wide & Deep 论文的结论?

多选题 · hard

  • A.联合模型比纯 Deep 模型在线上安装率更高← 正确答案
  • B.Deep 部分负责精确记忆历史规律
  • C.Wide 部分需要人工设计交叉特征← 正确答案
  • D.Wide 部分在冷启动场景表现优于 Deep

答案:AC。论文报告联合模型安装率提升明显,因此 A 正确。Deep 负责泛化而非记忆,B 错误。Wide 的交叉特征依赖人工设计,C 正确;冷启动场景下没有足够历史共现,Wide 会失效,D 错误。

Q9. 为什么 Wide & Deep 不能被看作是 ensemble 方法?

单选题 · mid

  • A.两个分支共享损失,联合优化而非独立训练← 正确答案
  • B.两个分支各自用了不同的优化器
  • C.该模型本质上是一个线性模型
  • D.两分支预测结果相加后再取平均

答案:A。ensemble 的典型特征是多个模型独立训练后组合。Wide & Deep 的两个分支在同一个计算图中被同一个损失函数约束,因此是联合训练。B 是事实但并非关键区别,D 描述也不符合模型行为。

Q10. 在 Google Play 场景中,“用户已安装 App × 候选 App”这类交叉特征通常放在哪一侧?

单选题 · hard

  • A.Deep 部分,通过嵌入自动学习
  • B.Wide 部分,作为显式交叉特征← 正确答案
  • C.两侧同时放完全相同的副本
  • D.不放模型,只做离线统计

答案:B。这类共现特征具有明确业务含义和高频价值,Wide 侧能直接学习其权重,实现精确记忆。Deep 侧负责从原始 ID 中泛化,不必重复放置相同副本,D 则完全浪费了特征价值。