步行街的尽头,阿东开了一家奶茶店。他每天会想三件事:有多少人从橱窗前经过?有多少人推门进来?又有多少人掏钱买单?经过的叫曝光,进门的叫点击,买单的叫转化。这三个数字一层层变少,像一只漏斗。阿东过去只在“已经进门”的客人身上记笔记——谁爱加珍珠、谁点半糖——但他渐渐发现,这套笔记拿去预测街上所有路人时总是失准:因为进门的客人本来已经“偏好这家店”了,用他们的口味代表所有路人,自然有偏;更何况一天进店的人本来就不多,真正买单的更是寥寥几个,笔记薄得可怜。ESMM 就是一篇教算法“站在整条街上思考”的论文:它让两个店员——一个管门口(CTR 塔)、一个管柜台(CVR 塔)——共享同一套“识人眼光”(Embedding),用“既进门又买单”这个能数清的事件,巧妙地反推出“进门之后会买单”的概率。本文就用阿东的奶茶店,把 ESMM 从直觉到公式、从论文到工程一次讲透。

岗位关联

掌握 ESMM,你就掌握了推荐系统“曝光→点击→转化”全漏斗建模的核心范式——这是大厂推荐与广告算法岗的高频考点,也是多任务学习落地的经典范例。

目录
  1. 可视化演示
  2. 问题背景:奶茶店的“两本账”
  3. 全空间建模:把计数器装到门口
  4. 网络结构:共享地基的双层小楼
  5. 数学原理:一笔账串起两个概率
  6. 训练优化:老师傅带新徒弟
  7. 工程实践:从单店到连锁经营
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

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

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

问题背景:奶茶店的“两本账”

阿东发现,自己手里有两本完全不同的账:一本记“今天有多少人从橱窗前走过”,另一本记“进店的人里谁买了什么”。他本能地想——想知道谁会买单,那就盯着进店的人研究呗。于是他每天站在吧台后面,把进店客人的口味特征记录在案,用这些笔记训练自己的“直觉”。可这套直觉上了街就失灵:街上大部分路人根本没进过店,阿东的笔记里根本没有他们的影子。

在推荐系统里,阿东的“两本账”就是曝光日志与点击日志。推荐系统向用户展示商品叫曝光,用户点击商品叫点击,点击后完成购买(或安装、注册等)叫转化。CTR 模型预估“用户看到后会点击的概率”,CVR 模型预估“用户点击后会转化的概率”。传统 CVR 模型的做法,恰好就是阿东的旧习惯——只用点击样本训练,于是埋下两个隐患。

第一个隐患叫样本选择偏差:模型只在“已经点击”的人群上学习,却要拿去预测“所有曝光”的人群,相当于拿进店客人的口味去猜街上路人的口味,系统性跑偏。第二个隐患叫数据稀疏:转化本身是漏斗最底部的极端事件,一天下来真正的买单一双手数得过来,训练样本少得可怜,模型很难学稳。更麻烦的是,CTR 和 CVR 若各训各的,就像门口店员和柜台收银员互不交流,白白浪费了二者共享的“行人特征”。

阿东拿着那本只有进店客人笔记的小册子,站在街口发了愁。他知道,想要当好老板,必须换一种记账方式:把整条街都纳入视野。

p(z=1∣y=1,x)≠p(z=1∣x)\displaystyle p(z=1 \mid y=1, \boldsymbol{x}) \neq p(z=1 \mid \boldsymbol{x})
  1. 点击变量 $y$$y$ 表示用户是否点击,$y=1$ 代表这次曝光发生了点击,它是推荐系统里最容易观测的事件。
    y∈{0,1}y \in \{0, 1\}
  2. 转化变量 $z$$z$ 表示用户是否转化(购买),$z=1$ 代表发生了转化;转化通常只在点击之后才可能发生。
    z∈{0,1}z \in \{0, 1\}
  3. 特征向量 $\boldsymbol{x}$$\boldsymbol{x}$ 是用户、商品、场景三类特征拼接而成的向量,用来描述“谁在什么场景下看到了什么”。
    x=[xuser, xitem, xcontext]\boldsymbol{x} = [\boldsymbol{x}_{user},\ \boldsymbol{x}_{item},\ \boldsymbol{x}_{context}]
  4. 左边点击条件下的转化概率:$p(z=1 \mid y=1, \boldsymbol{x})$ 是“点击了的人”中转化比例,这正是传统 CVR 模型在点击样本上拟合的目标。
  5. 右边全空间转化概率:$p(z=1 \mid \boldsymbol{x})$ 是所有曝光样本上的转化比例,这才是推荐系统真正想要预估的量。
  6. 两者不相等点击人群是曝光人群的“筛选子集”,分布不一致导致两边概率天然不同——这就是样本选择偏差的数学刻画。

全空间建模:把计数器装到门口

阿东决定改革。他在橱窗旁装了一个感应计数器,能数清每一个经过的行人;又在收银台接上联动设备,记录“谁进门、谁买单”。这样一来,他的视野不再局限于店内,而是覆盖了整条街——这正是 ESMM 的核心思想:全空间建模,把 CVR 的训练从“点击子空间”搬到“曝光全空间”。

直接在全空间上训练 CVR 有一个绕不开的难题:没点击的人根本不可能产生转化,你无法给每个曝光样本打上“转化/未转化”的标签。阿东也明白,他没法知道街上每个没进店的人会不会买。但聪明的老板立刻发现:有一个事件是全空间可观测的——“这个人既进了店、又买了单”。于是他把目标从“进店后买单率”悄悄换成了“又进店又买单率”(pCTCVR),再借助概率论的乘法法则,把这个联合事件拆成 “进店率 × 进店后买单率”。

这就是 ESMM 的关键一步:它并不直接预测 CVR,而是先在全空间上学习“又点击又转化”的联合概率,再通过除法关系反推 CVR。数学上,只要两个子模型——pCTR 模型与 pCVR 模型——相乘能还原 pCTCVR,那么对全空间样本的监督就会同时约束两个子模型,CVR 模型便间接地“看到了”所有曝光样本,样本选择偏差从根上被消除,数据稀疏也因为全空间样本的注入而缓解。

阿东的感应计数器第一次让他看清了整条街的全貌。现在的问题只剩一个:门口店员和柜台收银员,如何分工协作,才能把这两项概率同时估准?

p(z=1, y=1∣x)=p(y=1∣x)×p(z=1∣y=1, x)\displaystyle p(z=1,\ y=1 \mid \boldsymbol{x}) = p(y=1 \mid \boldsymbol{x}) \times p(z=1 \mid y=1,\ \boldsymbol{x})
  1. 联合事件“点击且转化”$p(z=1, y=1 \mid \boldsymbol{x})$ 表示在特征 $\boldsymbol{x}$ 下,用户“既点击又转化”的概率,记为 pCTCVR,它在曝光日志里可以直接统计。
    p(z=1, y=1∣x)p(z=1,\ y=1 \mid \boldsymbol{x})
  2. $p(y=1 \mid \boldsymbol{x})$ 是 pCTR曝光后点击的概率,由 CTR 塔负责估计,训练标签就是“是否点击”。
    p(y=1∣x)p(y=1 \mid \boldsymbol{x})
  3. $p(z=1 \mid y=1, \boldsymbol{x})$ 是 pCVR点击后转化的概率,由 CVR 塔负责估计,这正是我们想要的转化率。
    p(z=1∣y=1, x)p(z=1 \mid y=1,\ \boldsymbol{x})
  4. 乘法法则联合概率等于边缘概率乘以条件概率,这是概率论的链式法则,也是 ESMM 乘积结构的来源。
    p(z,y∣x)=p(y∣x)⋅p(z∣y,x)p(z, y \mid \boldsymbol{x}) = p(y \mid \boldsymbol{x}) \cdot p(z \mid y, \boldsymbol{x})
  5. 关键洞察等式左边全空间可观测,右边第一项也有全空间标签(是否点击),于是右边第二项 pCVR 就能被间接约束——不需要每个曝光样本都有转化标签。

网络结构:共享地基的双层小楼

阿东给店里设计了一套“双层小楼”:一楼是共享地基,二楼是两个各司其职的塔。地基里存放着他对每位客人的基础认知——年龄、穿着、步速、逛街时段——这些信息对“会不会进店”和“进店后买不买”都有用。在 ESMM 中,这个地基就是共享 Embedding 层:用户、商品、场景特征被映射成稠密向量,CTR 塔和 CVR 塔共用同一份向量作为输入。

二楼的两座塔结构相似,但分工不同。CTR 塔像门口的迎宾员,拿着地基里的特征向量,判断“这位路人有多大概率进店”;CVR 塔像柜台的收银员,拿着同样的特征向量,判断“这位进店的客人有多大概率买单”。两座塔都是多层全连接网络(MLP),内部通过非线性激活函数完成特征交互——把用户特征、商品特征、场景特征的组合关系充分交叉,最终各自输出一个 0 到 1 之间的概率。

为什么必须共享 Embedding 而不是各学各的?因为转化样本太少,CVR 塔单独学 Embedding 几乎学不动;而点击样本丰富,CTR 塔能把地基“磨”得很精细。共享之后,CTR 塔磨好的地基直接供 CVR 塔使用,CVR 塔等于站在巨人的肩膀上——这正是多任务学习“任务互帮”的精髓。

地基共享、双塔分立的格局定下来了。但阿东还有一笔账没算清楚:收银员的“买单概率”到底怎么从“进店又买单”这个联合事件里反推出来?

p^iCVR=σ(W2⋅tanh⁡(W1⋅ei+b1)+b2)\displaystyle \hat{p}_i^{CVR} = \sigma\Big( \boldsymbol{W}_2 \cdot \tanh\big( \boldsymbol{W}_1 \cdot \boldsymbol{e}_i + \boldsymbol{b}_1 \big) + b_2 \Big)
  1. 共享嵌入 $\boldsymbol{e}_i$第 $i$ 个样本的用户、商品、场景特征经过 Embedding 层后得到稠密向量 $\boldsymbol{e}_i$,CTR 塔与 CVR 塔共用。
    ei=Embedding(xi)\boldsymbol{e}_i = \text{Embedding}(\boldsymbol{x}_i)
  2. 第一层全连接 $\boldsymbol{W}_1, \boldsymbol{b}_1$把嵌入向量投影到隐层空间,提取高阶交互特征。
    hi=tanh⁡(W1ei+b1)\boldsymbol{h}_i = \tanh(\boldsymbol{W}_1 \boldsymbol{e}_i + \boldsymbol{b}_1)
  3. 第二层全连接 $\boldsymbol{W}_2, b_2$把隐层向量压缩成一个实数,作为“原始打分”。
    oi=W2⋅hi+b2o_i = \boldsymbol{W}_2 \cdot \boldsymbol{h}_i + b_2
  4. Sigmoid 激活 $\sigma$把任意实数映射到 $(0,1)$ 区间,得到概率含义的输出。
    σ(o)=11+e−o\sigma(o) = \frac{1}{1 + e^{-o}}
  5. CTR 塔同构CTR 塔结构完全相同、参数独立,只是输入同样的共享嵌入、输出各自任务对应的概率。

数学原理:一笔账串起两个概率

阿东在收银台贴了一张纸条:“进店且买单的概率 = 进店率 × 进店后买单率。”这句话看起来像废话,却是 ESMM 的灵魂。它意味着:模型不需要直接输出“进店后买单率”(这个数在没点击的样本上无标签、没法直接学),而是让两个塔的输出相乘,去拟合“进店且买单”这个全空间可观测的事件。

训练时,ESMM 同时优化两个损失。第一个是 CTR 损失:把 CTR 塔的输出 $\hat{p}^{CTR}$ 与全空间的点击标签 $y$ 做交叉熵或平方损失,监督“进店率”估得准不准。第二个是 CTCVR 损失:把两个塔输出的乘积 $\hat{p}^{CTR} \cdot \hat{p}^{CVR}$ 与全空间的联合标签 $y \cdot z$(点击且转化才是 1)做损失,监督“进店且买单率”估得准不准。两个损失相加,就是 ESMM 的总损失。

为什么这样设计就能反推 CVR?因为 CTCVR 损失同时牵动了两个塔:当乘积偏离真实联合概率时,梯度会同时流向 CTR 塔和 CVR 塔。经过充分训练后,只要 CTR 塔估得足够准,CVR 塔就必须“凑”出正确的 CVR,才能让乘积对得上全空间的联合标签。于是,CVR 塔虽然从没直接见过“曝光未点击”样本的转化标签,却通过乘积关系得到了全空间的无偏监督。

阿东把这张纸条贴在收银台后,两个店员的工作一下子有了交集:迎宾员的判断影响收银员的学习,收银员的偏差也会反馈给迎宾员。那么,这笔账在训练时具体是怎么“算”的?

L=∑i∈Dl(yi, p^iCTR)+∑i∈Dl(yi⋅zi, p^iCTR⋅p^iCVR)\displaystyle L = \sum_{i \in \mathcal{D}} l\big(y_i,\ \hat{p}^{CTR}_i\big) + \sum_{i \in \mathcal{D}} l\big(y_i \cdot z_i,\ \hat{p}^{CTR}_i \cdot \hat{p}^{CVR}_i\big)
  1. 全空间样本集 $\mathcal{D}$所有曝光样本的集合,既包含点击的样本,也包含大量未点击的样本——这是 ESMM 与旧方法最大的区别。
    D={全部曝光样本}\mathcal{D} = \{\text{全部曝光样本}\}
  2. 第一项 CTR 损失$\hat{p}^{CTR}_i$ 与点击标签 $y_i$ 计算损失,让 CTR 塔在全空间上学会预估点击率。
    l(yi, p^iCTR)l\big(y_i,\ \hat{p}^{CTR}_i\big)
  3. 联合标签 $y_i \cdot z_i$只有“既点击又转化”的样本标签为 1,其余全为 0;这个标签在曝光日志中可以直接标注。
    yi⋅zi∈{0,1}y_i \cdot z_i \in \{0, 1\}
  4. 第二项 CTCVR 损失$\hat{p}^{CTR}_i \cdot \hat{p}^{CVR}_i$ 与联合标签计算损失,监督两个塔的乘积逼近真实的联合概率。
    l(yi⋅zi, p^iCTR⋅p^iCVR)l\big(y_i \cdot z_i,\ \hat{p}^{CTR}_i \cdot \hat{p}^{CVR}_i\big)
  5. 两项相加联合训练总损失由 CTR 损失与 CTCVR 损失相加构成,两个任务在共享 Embedding 上互相传递信息。
    L=LCTR+LCTCVRL = L_{CTR} + L_{CTCVR}

训练优化:老师傅带新徒弟

阿东把 CTR 塔比作老师傅,CVR 塔比作新徒弟。老师傅阅人无数,眼光老辣;新徒弟刚上手,看谁都像会买单的。徒弟怎么学?不是只站在柜台里等进店客人,而是每看到一个路人从街口走过,就要根据老师傅的判断(pCTR)来校正自己的猜测:这个人大概率不会进店,但他若真进了店,买不买?这样,每一个曝光样本——哪怕那人根本没进店——都在给徒弟提供“这人不买”的反馈,徒弟的练习量瞬间从“一天几十个进店客人”扩大到“每天上千个路人”。

机制上,这是通过梯度传播实现的。CTCVR 损失对 CVR 塔参数的偏导,中间会乘上一个 $\hat{p}^{CTR}$ 因子——可以理解为每个样本的“反馈强度”由 CTR 塔来加权:CTR 塔越有把握的样本,对 CVR 塔的修正力度越大。反过来,CTCVR 损失也会通过 $\hat{p}^{CVR}$ 因子把梯度传回 CTR 塔,让迎宾员也兼顾“最终买单”这个更远的目标。两个塔在训练中互相带教,共享 Embedding 层则同时收到两份梯度,像地基同时承受两层楼的承重一样。

实操层面还有几个收敛技巧。一是防止过拟合:CVR 塔参数多但有效样本少,通常给 CVR 塔加 Dropout,或用较小的学习率;二是损失权重:CTR 损失和 CTCVR 损失量级不同,可加权重系数平衡;三是 Embedding 维度:太大容易过拟合、太小表达不足,常见经验是 8~32 维起步;四是联合训练初期可先让 CTR 塔“预热”几个 epoch,再放开双塔联合优化,像老师傅先示范几天再放手让徒弟实操。

阿东看着徒弟一天天进步,心里踏实了:训练方法理顺了,接下来就是把这套“双层小楼”真正开成连锁店——从单店经营走向规模化部署。

∂LCTCVR∂θCVR=∑i∈Dp^iCTR⋅∂li∂p^iCTCVR⋅∂p^iCVR∂θCVR\displaystyle \frac{\partial L_{CTCVR}}{\partial \theta^{CVR}} = \sum_{i \in \mathcal{D}} \hat{p}^{CTR}_i \cdot \frac{\partial l_i}{\partial \hat{p}^{CTCVR}_i} \cdot \frac{\partial \hat{p}^{CVR}_i}{\partial \theta^{CVR}}
  1. 链式法则CTCVR 损失对 CVR 塔参数的梯度,必须穿过“乘积”这个中间环节,按链式法则逐层展开。
    ∂LCTCVR∂θCVR\frac{\partial L_{CTCVR}}{\partial \theta^{CVR}}
  2. 乘积因子 $\hat{p}^{CTR}_i$因为 pCTCVR 对 pCVR 求偏导等于 pCTR,所以每个样本的梯度都乘上 CTR 塔的输出,作为权重。
    ∂(p^CTRp^CVR)∂p^CVR=p^CTR\frac{\partial (\hat{p}^{CTR} \hat{p}^{CVR})}{\partial \hat{p}^{CVR}} = \hat{p}^{CTR}
  3. 全空间求和求和遍历所有曝光样本 $\mathcal{D}$,未点击样本的联合标签为 0,同样贡献梯度——这正是全空间训练的数学体现。
    ∑i∈D\sum_{i \in \mathcal{D}}
  4. 无偏性所有曝光样本按 pCTR 加权后参与 CVR 塔更新,训练分布就是推理分布,选择偏差随之消失。
  5. 双向带教同理,CTCVR 损失对 CTR 塔的梯度也会乘上 $\hat{p}^{CVR}$ 因子,两个塔在联合损失下互为对方的“老师”。

工程实践:从单店到连锁经营

阿东要开分店了。他总结了一套巡店手册,第一条是特征工程。地基里放什么特征,直接决定双塔的上限:用户画像特征(年龄、性别、历史消费水平)、商品特征(品类、价格、销量)、场景特征(时段、位置、设备),以及统计特征(商品近 7 天点击率、转化率等)。这些特征经过 Embedding 进入共享地基,CTR 塔和 CVR 塔都在其上做特征交互。

第二条是调参经验。除了上一节提到的 Dropout、学习率与 Embedding 维度,实际工程中还要关注样本拼接方式:曝光日志、点击日志、转化日志需要按时间窗口对齐,转化标签通常延迟到达,要设置合理的“转化等待窗口”(如 24 小时)。训练时建议先离线复现基线(单独训练的 CTR 与 CVR),再切换到 ESMM 联合训练,观察两个任务的 AUC 是否同步提升。

第三条是线上部署。模型训练完成后需要导出为线上服务格式,推荐系统中的排序链路通常先召回、再粗排、最后精排,ESMM 一般担任精排角色:线上同时计算 $\hat{p}^{CTR}$ 与 $\hat{p}^{CVR}$,再用乘积或加权组合得到最终排序分。由于两个塔共享 Embedding,一次前向计算即可同时得到两个概率,推理成本只比单任务略高,性价比很高。

第四条是离线评估。单任务常用 AUC,但推荐场景用户偏好差异巨大,业界更常用 GAUC——按用户分组算 AUC 再加权平均,能更公平地反映排序质量。ESMM 上线前要同时盯 CTR 任务的 AUC、CVR 任务的 AUC,以及 pCTCVR 的校准度(预测均值与真实转化率是否一致),避免“CTR 提升、CVR 反而崩坏”的跷跷板问题。

阿东的连锁店开到了第 20 家,每家都有一套标准化的“整条街统计 + 双塔协作”体系。他站在新店门口,看着街上川流不息的行人,终于明白:真正的好生意,从来不是只盯着进店的人,而是看清整条街。

GAUC=∑u∈Uwu⋅AUCu∑u∈Uwu\displaystyle GAUC = \frac{\sum_{u \in \mathcal{U}} w_u \cdot AUC_u}{\sum_{u \in \mathcal{U}} w_u}
  1. 用户集合 $\mathcal{U}$评估数据中的全部用户,GAUC 的核心思想是“按人分别算账”。
    U={u1,u2,…,um}\mathcal{U} = \{u_1, u_2, \dots, u_m\}
  2. 单用户 AUC $AUC_u$对每个用户 $u$,只看他自己的曝光样本,计算模型排序能力的 AUC。
    AUCu=用户 u 内部的正负样本排序指标AUC_u = \text{用户 } u \text{ 内部的正负样本排序指标}
  3. 权重 $w_u$通常取该用户的曝光次数,让活跃用户的排序质量在总指标中占更大权重。
    wu=用户 u 的曝光次数w_u = \text{用户 } u \text{ 的曝光次数}
  4. 加权平均所有用户的 AUC 按曝光量加权求和再归一化,得到全局 GAUC。
    GAUC=∑wuAUCu∑wuGAUC = \frac{\sum w_u AUC_u}{\sum w_u}
  5. 为什么要分组不同用户点击习惯差异大,全局 AUC 容易被“高活跃用户”主导,GAUC 能更公平地评估真实推荐效果。

AI 应用场景

电商平台“猜你喜欢”的成交率优化

阶段:精排模型设计

解决的问题:传统 CVR 模型只用点击样本训练,导致预测值对未点击商品系统性偏高;同时,转化样本稀疏让模型学不稳。ESMM 在全部曝光商品上联合建模,把“曝光→点击→成交”作为整体优化目标,直接服务于成交率(GMV)提升。

具体案例:某头部电商的首页信息流,每天有数十亿次商品曝光。引入 ESMM 后,精排模型以 pCTCVR 作为排序分数,未点击的商品也能通过 CTCVR 损失间接参与 CVR 塔的更新。实验显示,相比“CTR 与 CVR 分别训练再相乘”的基线,成交转化率提升 2%~4%,且 CVR 预测的校准度明显改善。

广告竞价中的转化出价

阶段:出价与拍卖策略

解决的问题:广告主按转化付费(oCPM/CPA)时,平台需要精确预估每次曝光的转化期望值。ESMM 提供的 $\hat{p}^{CVR}$ 比单纯在点击域训练的模型更接近真实转化概率,直接决定出价的合理性与广告主 ROI。

具体案例:某广告平台在信息流广告中部署 ESMM,线上同时输出 pCTR 与 pCVR,出价公式采用 pCTCVR 与广告主出价的乘积。由于 pCVR 在全空间上被校正,广告系统对“高展示、低点击但高转化”的流量识别更准,广告主 ROI 提升 15%,平台填充率同步上升。

内容平台的多目标增长

阶段:多任务学习框架

解决的问题:内容平台关注“曝光→点击→完播/关注/点赞”等多层转化目标,各目标样本量差异巨大。ESMM 的“共享 Embedding + 多塔 + 乘积关联”范式可推广为多目标模型,用主干任务(点击)的特征表达带动稀疏目标(关注、分享)的学习。

具体案例:某短视频 App 在推荐精排中采用类 ESMM 结构:主塔预估点击率,辅塔预估“点击后关注”率,二者乘积拟合“点击且关注”的联合事件。上线后关注转化率提升 5%,且冷门创作者的视频由于共享特征表达,也能获得更合理的关注率预估。

Python 演示

python73 行
"""ESMM-Lite: 用 numpy 实现 ESMM 核心思想(共享嵌入+双塔+全空间联合损失)"""import numpy as np  def sigmoid(x):    x = np.clip(x, -30, 30)    return 1.0 / (1.0 + np.exp(-x))  class ESMMLite(object):    """迷你ESMM: 共享Embedding, CTR塔与CVR塔输出相乘得到pCTCVR"""     def __init__(self, n_feat, dim=8, hid=16, seed=42):        rng = np.random.RandomState(seed)        self.e = rng.randn(n_feat, dim) * 0.1      # 共享底层 Embedding        self.wc = rng.randn(dim, hid) * 0.1        # CTR 塔        self.bc = np.zeros(hid)        self.vc = rng.randn(hid) * 0.1        self.cc = 0.0        self.wv = rng.randn(dim, hid) * 0.1        # CVR 塔        self.bv = np.zeros(hid)        self.vv = rng.randn(hid) * 0.1        self.cv = 0.0     def _tower(self, x, W, b, v, c):        h = np.tanh(self.e[x] @ W + b)        return sigmoid(h @ v + c), h     def train(self, X, yc, yb, lr=0.2, epochs=30):        for ep in range(epochs):            loss = 0.0            for x, c0, z in zip(X, yc, yb):                pc, hc = self._tower(x, self.wc, self.bc, self.vc, self.cc)                pv, hv = self._tower(x, self.wv, self.bv, self.vv, self.cv)                pt = pc * pv                              # pCTCVR = pCTR * pCVR                seg = (pc - c0) + (pt - c0 * z) * pv      # CTR塔梯度(点击损失+pCTCVR损失)                dc = seg * pc * (1 - pc)                self.vc -= lr * dc * hc                self.cc -= lr * dc                self.wc -= lr * np.outer(self.e[x], dc * (1 - hc * hc))                self.bc -= lr * dc * (1 - hc * hc)                dg = (pt - c0 * z) * pc * pv * (1 - pv)   # CVR塔梯度(经pCTR加权)                self.vv -= lr * dg * hv                self.cv -= lr * dg                self.wv -= lr * np.outer(self.e[x], dg * (1 - hv * hv))                self.bv -= lr * dg * (1 - hv * hv)                loss += abs(pt - c0 * z)            if ep % 10 == 0:                print('epoch %d: 平均pCTCVR误差=%.4f' % (ep, loss / len(X)))     def predict(self, x):        pc, _ = self._tower(x, self.wc, self.bc, self.vc, self.cc)        pv, _ = self._tower(x, self.wv, self.bv, self.vv, self.cv)        return pc, pv  def make_data(n=2000, seed=1):    """构造曝光全空间样本: X为特征, yc为点击标签, yb为转化标签(仅点击后可发生)"""    rng = np.random.RandomState(seed)    X = rng.randint(0, 50, n)    yc = (rng.rand(n) < sigmoid((X - 25) / 8.0)).astype(int)    yb = ((yc == 1) & (rng.rand(n) < sigmoid((X - 30) / 10.0))).astype(int)    return X, yc, yb  if __name__ == '__main__':    X, yc, yb = make_data()    print('曝光=%d, 点击=%d, 转化=%d' % (len(X), yc.sum(), yb.sum()))    m = ESMMLite(n_feat=50)    m.train(X, yc, yb, epochs=30)    for x in [10, 25, 40]:        pc, pv = m.predict(x)        print('样本%d: pCTR=%.2f, pCVR=%.2f, pCTCVR=%.3f' % (x, pc, pv, pc * pv))
目的:用 numpy 从零实现 ESMM 的共享嵌入、双塔结构与全空间联合损失,演示如何在不依赖深度学习框架的情况下训练一个迷你多任务模型。
  1. self.e = rng.randn(n_feat, dim) * 0.1初始化共享底层 Embedding,CTR 塔与 CVR 塔都从它取特征;
  2. pc, hc = self._tower(x, self.wc, self.bc, self.vc, self.cc)CTR 塔前向传播,输出点击概率;
  3. pt = pc * pvESMM 核心一行:pCTCVR 等于 pCTR 乘 pCVR;
  4. seg = (pc - c0) + (pt - c0 * z) * pvCTR 塔同时收到点击误差与 pCTCVR 误差,体现了联合训练的双向带教;
  5. dg = (pt - c0 * z) * pc * pv * (1 - pv)CVR 塔的梯度被 pCTR 加权,所有曝光样本(包括未点击)都在更新 CVR 塔;
  6. yb = ((yc == 1) & (rng.rand(n) < sigmoid((X - 30) / 10.0))).astype(int)转化只在点击后发生,人为构造漏斗结构,模拟真实场景的稀疏性;
  7. if ep % 10 == 0: print(...)每 10 轮打印一次平均 pCTCVR 误差,观察全空间联合损失是否在下降。

常见误区

误区转化本来就是“点击之后”才发生的事,所以训练 CVR 模型当然要用点击样本,不用点击样本用什么?

正解训练样本的分布必须与推理时的分布一致。CVR 模型上线后要对所有曝光商品打分,可训练时只看点击样本,相当于奶茶店老板只研究进店客人的口味,却要预测街上所有路人的口味——这就是样本选择偏差。正确做法是像 ESMM 一样把训练搬到曝光全空间,用 pCTCVR 反推 CVR。

为何常见:人们容易把“转化的定义依赖点击”误解为“训练的数据也依赖点击”,忽略了训练与推理分布一致性这条机器学习的基本纪律。

误区先训练一个 CTR 模型,再训练一个 CVR 模型,线上把两个概率相乘,这不就是 ESMM 吗?

正解ESMM 的灵魂是联合训练:两个塔共享 Embedding,CTCVR 损失同时反向传播到两个塔,CVR 塔因此能利用全空间样本。而“分别训练再相乘”的 CVR 模型仍然只在点击样本上训练,偏差依旧存在,共享信息也没有被利用——只是换了层皮,没换内核。

为何常见:因为 ESMM 的线上推理形式就是“两个概率相乘”,初学者容易把“推理时的乘积”误当成“训练时的联合”,忽略了模型结构层面的差异。

误区既然 pCTCVR 在全空间可观测,那直接训练一个模型去拟合“点击且转化”这个标签,不也能用到全空间数据吗?何必拆成两个塔?

正解直接建模 pCTCVR 确实用到了全空间,但它只能给出“又点击又转化”的概率,无法分解出我们要的 pCVR。推荐系统排序需要的是 pCTR、pCVR 各自的估计,用于不同的业务目标(如 pCVR 用于广告出价)。ESMM 通过乘积结构在拟合 pCTCVR 的同时分离出两个子概率,一举两得。

为何常见:人们容易把“建模目标”与“业务需要”混为一谈,以为只要损失能下降就够了,忽略了模型输出必须服务于具体的业务决策。

误区ESMM 证明两个任务互相帮助效果很好,那我加上关注、收藏、分享、加购等一堆任务,模型一定更强。

正解多任务学习存在“跷跷板效应”:任务之间相关性弱时,共享参数反而互相干扰,导致部分任务指标下降。ESMM 的成功建立在 CTR 与 CVR 天然强相关(同一漏斗上下游)之上。实际工程中需要先做任务相关性分析,必要时用任务特定网络或动态权重来隔离冲突。

为何常见:看到“多任务”就以为“多多益善”,忽略了任务相关性这个前提条件,属于把个例成功泛化成了普遍规律。

面试题

Q1. ESMM 中 CVR 任务在哪个样本空间上训练?

单选题 · easy

  • A.点击样本空间
  • B.曝光全空间← 正确答案
  • C.转化样本空间
  • D.购买用户空间

答案:B。ESMM 的核心是全空间建模,CVR 任务通过 pCTCVR 的联合损失在曝光全空间上间接学习。若只在点击样本空间训练,就会重蹈样本选择偏差的覆辙;转化样本空间与购买用户空间样本更少,都不符合 ESMM 的设计。

Q2. 传统 CVR 模型的主要问题是什么?

单选题 · easy

  • A.模型参数量大且不易训练
  • B.样本选择偏差与数据稀疏← 正确答案
  • C.线上推理延迟高且难部署
  • D.特征工程复杂且维护成本高

答案:B。传统 CVR 模型只在点击样本上训练,推理时却要对全部曝光样本打分,训练与推理分布不一致,这是样本选择偏差;同时转化事件占比极低,训练样本稀疏。这两个问题才是 ESMM 要解决的核心痛点。

Q3. pCTCVR、pCTR、pCVR 三者满足什么关系?

单选题 · easy

  • A.pCTCVR = pCTR + pCVR
  • B.pCTCVR = pCTR × pCVR← 正确答案
  • C.pCTCVR = pCTR − pCVR
  • D.pCTCVR = pCVR ÷ pCTR

答案:B。根据概率论的乘法法则,“点击且转化”的联合概率等于“点击”概率乘以“点击后转化”的条件概率,即 pCTCVR = pCTR × pCVR。ESMM 正是利用这个乘积结构,用全空间可观测的 pCTCVR 反推点击域内的 pCVR。

Q4. ESMM 中 CTR 塔与 CVR 塔的结构关系是什么?

单选题 · mid

  • A.完全独立无共享参数联系
  • B.共享底层嵌入且输出相乘关联← 正确答案
  • C.共享全部网络层与输出参数
  • D.共用同一份损失与训练标签

答案:B。两个塔各自是独立的 MLP,但它们共享底层 Embedding,且输出通过乘法组成 pCTCVR 参与联合损失。完全独立会丢失多任务共享信息,共享全部参数则无法输出两个不同的概率,共用同一损失更无从谈起。

Q5. ESMM 如何解决样本选择偏差问题?

单选题 · mid

  • A.对点击样本做随机欠采样
  • B.在全空间建模并反推转化率← 正确答案
  • C.仅扩展点击样本的特征维度
  • D.对转化样本重复采样增广

答案:B。ESMM 把训练目标从“点击域内的 CVR”改为“曝光全空间上的 pCTCVR”,让 CVR 塔通过全空间样本的联合损失间接获得无偏监督。欠采样、特征扩展、过采样都只是操作数据,没有改变训练分布与推理分布不一致的根源。

Q6. ESMM 的总损失函数由哪些部分构成?

单选题 · mid

  • A.联合使用CTR损失与CTCVR损失← 正确答案
  • B.仅使用CVR损失单独训练模型
  • C.仅使用CTR损失单独训练模型
  • D.直接使用曝光次数作为损失函数

答案:A。ESMM 的损失是两项相加:CTR 塔在全空间点击标签上的损失,加上两个塔乘积与“点击且转化”联合标签的 CTCVR 损失。只用 CTR 或只用 CVR 损失都无法实现双塔联合优化,曝光次数根本不是监督信号。

Q7. 在 ESMM 中,CVR 塔是如何在曝光全空间上被训练的?

单选题 · hard

  • A.通过 pCTCVR 损失的梯度传播← 正确答案
  • B.直接使用转化标签做全空间监督
  • C.仅在点击样本上做反向传播更新
  • D.由 CTR 塔的输出直接复制得到

答案:A。未点击样本没有转化标签,无法直接监督 CVR 塔。ESMM 的巧妙之处是让 pCTCVR 损失对 CVR 塔参数求梯度,经过乘积链式法则后,每个曝光样本(包括未点击)都按 pCTR 加权贡献一份梯度,从而在无转化标签的情况下实现全空间训练。

Q8. 以下哪些属于 ESMM 的关键设计?

多选题 · mid

  • A.共享底层 Embedding 参数← 正确答案
  • B.损失定义在曝光全空间之上← 正确答案
  • C.用乘积关系分解 pCTCVR← 正确答案
  • D.特征工程与模型结构无关

答案:ABC。共享 Embedding、全空间损失、乘积分解是 ESMM 三大支柱。特征工程当然重要,但“与模型结构无关”的说法错误——ESMM 恰恰通过结构设计放大了特征表达的价值。

Q9. 关于 ESMM 的梯度传播,下列说法正确的有哪些?

多选题 · hard

  • A.CTCVR 损失经 pCTR 加权回传 CVR 塔← 正确答案
  • B.CTR 塔只受点击标签损失监督
  • C.共享 Embedding 接收双任务梯度← 正确答案
  • D.CVR 塔只在点击样本上更新

答案:AC。CTCVR 损失对 CVR 塔的梯度要乘以 pCTR 因子,A 正确;CTR 塔同时收到点击损失与 CTCVR 损失(经 pCVR 加权)的梯度,B 错误;共享 Embedding 汇聚两个塔的梯度,C 正确;CVR 塔通过全空间上的 pCTCVR 损失更新,D 错误。

Q10. 推荐系统中“曝光→点击→转化”漏斗的环节包括哪些?

多选题 · easy

  • A.曝光环节展示给用户← 正确答案
  • B.点击环节用户点击商品← 正确答案
  • C.转化环节用户完成购买← 正确答案
  • D.退款环节用户申请退款

答案:ABC。漏斗的三个核心环节是曝光、点击、转化,CVR 模型关注的就是“点击后转化”这一跳。退款发生在转化之后,不属于漏斗前置环节,也不是 CVR 建模的目标。