资讯中心

ESMM学习笔记:如何解决CVR预估中的样本选择偏差与数据稀疏难题

📅 2026/7/25 18:07:53
ESMM学习笔记:如何解决CVR预估中的样本选择偏差与数据稀疏难题
ESMM学习笔记如何解决CVR预估中的样本选择偏差与数据稀疏难题引言CVR预估的两大挑战在推荐系统和广告点击率预估领域CVRConversion Rate转化率预估是一个核心任务。与CTRClick-Through Rate点击率预估不同CVR预估面临两个特殊的难题1.样本选择偏差Sample Selection BiasSSBCVR模型通常只在点击样本上训练但实际预测时需要对所有曝光样本进行预估。点击样本和未点击样本在用户行为模式上存在显著差异导致训练集和预测集的分布不一致模型泛化能力下降。2.数据稀疏Data SparsityDS转化行为远比点击行为稀少导致CVR训练样本数量远少于CTR。在广告场景中点击率可能只有1%-5%而转化率可能只有0.1%-0.5%这意味着CVR模型可用的正样本极其有限容易过拟合。为了解决这两个问题阿里巴巴在2018年提出了ESMMEntire Space Multi-Task Model全空间多任务模型。本文将循序渐进地讲解ESMM的原理与实现。## 基础概念从CTR预估到CVR预估### CTR预估与CVR预估的关系在电商或广告系统中用户行为通常遵循一个漏斗模型曝光Impression → 点击Click → 转化ConversionCTR预估关注的是给定曝光样本用户点击的概率 P(click1|impression)。CVR预估关注的是给定点击样本用户转化的概率 P(conversion1|click1)。传统方法中CVR模型只在点击样本上训练这就导致了样本选择偏差问题——模型从未见过未点击样本的特征分布却在预测时需要对它们进行推断。### 多任务学习的思想ESMM的核心思想是将CVR预估和CTR预估作为两个相关任务联合训练。既然点击和转化是顺序发生的那么我们可以利用CTR任务中丰富的点击样本信息来辅助CVR任务。具体来说ESMM引入了一个辅助任务——CTCVRClick-Then-Conversion Rate点击后转化率即 P(click1, conversion1|impression)。通过全空间所有曝光样本上的CTCVR预估间接学习CVR。## ESMM模型架构详解ESMM的模型架构包含以下几个关键部分1.共享嵌入层Shared Embedding Layer将高维稀疏特征映射到低维稠密向量。2.两个独立的塔Two TowersCTR塔和CVR塔分别用于预估CTR和CVR。3.乘积层Multiplication Layer通过CTR和CVR的乘积得到CTCVR的预估值。关键公式- CTR预估pCTR P(click1|impression)- CVR预估pCVR P(conversion1|click1)- CTCVR预估pCTCVR P(click1, conversion1|impression) pCTR × pCVR训练时我们用全空间样本包括未点击和点击来优化CTCVR损失和CTR损失而CVR损失则通过反向传播间接优化。## 代码实现从零构建ESMM下面我们实现一个简化版的ESMM模型。首先我们使用TensorFlow 2.x来构建模型。### 示例1模型架构定义pythonimport tensorflow as tffrom tensorflow.keras.layers import Dense, Embedding, Flatten, Concatenate, Inputfrom tensorflow.keras import Modelclass ESMM(Model): def __init__(self, feature_columns, embedding_dim8): feature_columns: 字典包含每个特征的类别数 embedding_dim: 嵌入维度 super(ESMM, self).__init__() self.feature_columns feature_columns self.embedding_dim embedding_dim # 共享嵌入层 self.embeddings {} for feat_name, vocab_size in feature_columns.items(): self.embeddings[feat_name] Embedding( input_dimvocab_size, output_dimembedding_dim, namefembedding_{feat_name} ) # CTR塔简单MLP self.ctr_dense1 Dense(64, activationrelu, namectr_dense1) self.ctr_dense2 Dense(32, activationrelu, namectr_dense2) self.ctr_output Dense(1, activationsigmoid, namectr_output) # CVR塔简单MLP self.cvr_dense1 Dense(64, activationrelu, namecvr_dense1) self.cvr_dense2 Dense(32, activationrelu, namecvr_dense2) self.cvr_output Dense(1, activationsigmoid, namecvr_output) def call(self, inputs, trainingFalse): # inputs: 字典每个键对应一个特征值为整数索引 # 共享嵌入 embeddings [] for feat_name in self.feature_columns.keys(): feat_input inputs[feat_name] emb self.embeddings[feat_name](feat_input) embeddings.append(Flatten()(emb)) # 展平为向量 # 拼接所有特征嵌入 concat_emb Concatenate()(embeddings) # CTR塔 ctr_hidden self.ctr_dense1(concat_emb) ctr_hidden self.ctr_dense2(ctr_hidden) ctr_pred self.ctr_output(ctr_hidden) # CVR塔 cvr_hidden self.cvr_dense1(concat_emb) cvr_hidden self.cvr_dense2(cvr_hidden) cvr_pred self.cvr_output(cvr_hidden) # CTCVR CTR * CVR ctcvr_pred ctr_pred * cvr_pred return ctr_pred, cvr_pred, ctcvr_pred# 假设有3个特征用户ID1000个、商品ID500个、广告位ID10个feature_columns { user_id: 1000, item_id: 500, ad_pos: 10}# 创建模型实例model ESMM(feature_columns, embedding_dim8)# 查看模型结构model.build(input_shape({user_id: (None,), item_id: (None,), ad_pos: (None,)}))model.summary()### 示例2训练循环与损失函数在ESMM的训练中我们需要同时优化CTR损失和CTCVR损失。注意CVR损失是基于点击样本的但ESMM巧妙地通过共享嵌入和乘积操作来间接优化。pythonimport numpy as np# 生成模拟数据def generate_synthetic_data(num_samples10000): np.random.seed(42) # 特征用户ID、商品ID、广告位ID data { user_id: np.random.randint(0, 1000, sizenum_samples), item_id: np.random.randint(0, 500, sizenum_samples), ad_pos: np.random.randint(0, 10, sizenum_samples) } # 标签点击5%概率和转化点击后10%概率 click np.random.binomial(1, 0.05, sizenum_samples) conversion np.random.binomial(1, 0.1 * click, sizenum_samples) # 只有点击后才可能转化 return data, click, conversion# 自定义损失函数def ctr_loss(y_true, y_pred): # y_true: [click, conversion] 这里我们只使用click return tf.keras.losses.binary_crossentropy(y_true[:, 0], y_pred[:, 0])def ctcvr_loss(y_true, y_pred): # y_true: [click, conversion] 这里使用conversion因为CTCVRclick conversion return tf.keras.losses.binary_crossentropy(y_true[:, 1], y_pred[:, 0])# 准备数据X, click, conversion generate_synthetic_data(2000)# 构建输入字典input_dict {k: v for k, v in X.items()}# 构建标签第一列是click第二列是conversiony np.stack([click, conversion], axis1)# 编译模型注意TensorFlow需要自定义损失函数optimizer tf.keras.optimizers.Adam(learning_rate0.001)# 训练步骤tf.functiondef train_step(features, labels): with tf.GradientTape() as tape: # 前向传播 ctr_pred, cvr_pred, ctcvr_pred model(features, trainingTrue) # 计算损失 # CTR损失使用点击标签 loss_ctr tf.reduce_mean( tf.keras.losses.binary_crossentropy(labels[:, 0], tf.squeeze(ctr_pred)) ) # CTCVR损失使用转化标签因为转化只发生在点击后 loss_ctcvr tf.reduce_mean( tf.keras.losses.binary_crossentropy(labels[:, 1], tf.squeeze(ctcvr_pred)) ) # 总损失两个损失相加可加权重 total_loss loss_ctr loss_ctcvr # 计算梯度并更新 gradients tape.gradient(total_loss, model.trainable_variables) optimizer.apply_gradients(zip(gradients, model.trainable_variables)) return total_loss, loss_ctr, loss_ctcvr# 训练10个epochfor epoch in range(10): total_loss, loss_ctr, loss_ctcvr train_step(input_dict, y) print(fEpoch {epoch1}: Total Loss {total_loss:.4f}, CTR Loss {loss_ctr:.4f}, CTCVR Loss {loss_ctcvr:.4f})# 训练完成后预测CVR注意只在点击样本上评估更合理import randomtest_sample { user_id: np.array([random.randint(0, 999)]), item_id: np.array([random.randint(0, 499)]), ad_pos: np.array([random.randint(0, 9)])}_, cvr_pred, _ model(test_sample, trainingFalse)print(f预测CVR: {cvr_pred.numpy()[0][0]:.4f})## 高级用法与优化技巧### 多任务学习的权重平衡在ESMM中CTR损失和CTCVR损失的权重可以调整。如果CVR任务特别稀疏可以适当增加CTCVR损失的权重让模型更关注转化信号。### 特征工程与嵌入维度在实际应用中共享嵌入层的维度选择非常关键。过小的嵌入维度会导致信息丢失过大的维度则会导致过拟合。通常建议在8-64之间调整。### 其他变体ESMM的后续改进包括-ESMMAttention在CVR塔中引入注意力机制让模型更关注与转化相关的特征。-Multi-ESMM处理多步转化如加购、收藏、支付的序列任务。## 总结ESMM通过多任务学习巧妙解决了CVR预估中的两大难题1.样本选择偏差通过在全空间所有曝光样本上训练CTCVR任务避免了只在点击样本上训练导致的分布偏移。即使未点击样本的CVR不可观测模型也能通过CTR和CTCVR的乘积关系学到正确的CVR。2.数据稀疏通过共享嵌入层让CTR任务中丰富的点击数据帮助CVR任务学习更好的特征表示。CTR任务提供了大量的训练信号缓解了CVR正样本不足的问题。ESMM的核心思想可以概括为用辅助任务CTR、CTCVR来间接优化主任务CVR同时利用共享表示来迁移知识。这种范式在推荐系统、广告预估等领域具有广泛的应用价值。理解ESMM不仅有助于解决CVR预估问题也为处理其他存在样本选择偏差或数据稀疏的任务提供了思路——例如在医疗诊断中我们可以用高频率的检查结果来辅助预测罕见疾病。