搜索广告排序从LR到深度学习:DeepFM实战与避坑指南

发布时间:2026/9/30 10:09:30
搜索广告排序从LR到深度学习:DeepFM实战与避坑指南
简介这份PDF文献聚焦深度学习在搜索广告排序中的落地应用面向广告算法工程师、推荐系统学习者及数据分析研究者帮助理解点击率CTR预估这一广告业务核心环节的技术演进。全文围绕卷积神经网络与LSTM的混合模型展开先由CNN提取广告数据中的高影响力特征再借助LSTM的时序建模能力完成预测与分类并对比了逻辑回归、决策树等传统方法以及百度FNN、谷歌WideDeep、PNN等业界模型。文中还完整梳理了特征提取、模型训练、测试预测的CTR预估流程并通过Kaggle Avazu开源数据集实验讨论LSTM层数、隐藏层节点数、学习率等参数对AUC值的影响。资源包为1个PDF文件大小约2.73MB内容紧凑、结构清晰适合作为深度学习排序方向的参考文献与专业指导材料。目前已有122人学习可为广告排序策略优化与模型调参提供可复用的思路。1. 搜索广告排序为什么不能只靠 LR从「基于深度学习的搜索广告排序应用.pdf」说起如果你在广告算法团队待过大概率经历过这样的场景query 进来召回几千条广告精排阶段还在用 LR 手工特征交叉离线 AUC 看着还行一上线 CTR 预估就偏长尾 query 的排序结果尤其难看。这份「基于深度学习的搜索广告排序应用.pdf」要解决的正是这个从线性模型到深度模型的迁移问题——它不是讲深度学习入门的科普而是把 embedding、特征交叉、多任务学习这些手段落到搜索广告排序的具体链路上。搜索广告排序和推荐排序最大的区别在于query 是用户主动表达的强意图信号广告候选又受预算、出价、质量分约束所以模型不只要预估点击率还要兼顾转化率和出价最终按 eCPM 排。深度学习在这里的价值是把稀疏的 query-ad 交叉特征自动学成低维稠密表示替代大量人工特征工程。适合谁看有 LR/GBDT 排序基础、想上手深度排序模型的算法工程师以及需要判断这条路值不值得投入的技术负责人。2. 搜索广告排序的深度学习建模从特征到网络结构2.1 为什么 LR 在搜索广告场景会触到天花板LR 的核心问题是它只能学线性组合特征交叉必须人工构造。搜索广告里最有价值的信号恰恰是交叉特征某个 query 词和某个广告标题词的共现、用户历史点击类目和当前广告类目的匹配度。人工交叉的维度爆炸而且泛化差——训练集里没出现过的组合权重就是零。常见做法是先用 FM 做二阶交叉再过渡到深度模型。FM 把交叉权重分解成隐向量内积参数量从 O(n²) 降到 O(nk)。但 FM 只到二阶搜索广告里三阶以上的组合用户、query、广告、上下文依然学不到。深度模型用 MLP 堆叠就能隐式捕捉高阶交叉这是它替代 LR 的根本理由。另一个现实约束是数据规模。搜索广告的曝光量级通常在亿级LR 用 FTRL 能在线更新但深度模型训练成本高。所以选型时要问自己离线特征交叉的收益是否值得付出训练和推理的延迟代价。如果候选集只有几百条、QPS 不高GBDTLR 可能更划算。2.2 特征体系稀疏 ID 特征和稠密统计特征怎么组织搜索广告排序的输入特征大致分四类落地时建议按这个结构组织特征类别典型字段处理方式用户侧用户 ID、历史点击类目、活跃度稀疏 ID 走 embedding统计值归一化query 侧query 分词、query 长度、意图类目分词后 hash 或查词表意图类目 embedding广告侧广告 ID、标题分词、出价、质量分ID embedding出价做 log 变换交叉侧query-ad 词匹配度、类目匹配显式交叉特征 模型隐式交叉稀疏 ID 特征用 embedding 层映射到低维向量维度一般取 8 到 32词表大的比如广告 ID 上千万取 16 起步。稠密特征必须做归一化出价这种长尾分布建议先取 log 再标准化否则梯度会被大值主导。注意query 分词后的词表如果直接用全量词表embedding 参数量会失控。常见做法是保留高频词低频词统一映射到 OOV 桶词表控制在百万级以内。2.3 网络结构选型WideDeep、DeepFM 还是 DIN这三个结构在搜索广告里都有落地案例选哪个取决于你的特征形态和算力预算。WideDeep 的 Wide 侧保留人工交叉特征记忆能力Deep 侧用 MLP 学泛化。它的好处是 Wide 侧可以复用已有的 LR 特征迁移成本低。缺点是 Wide 侧还是需要人工设计交叉。DeepFM 用 FM 替代 Wide 侧自动学二阶交叉省掉人工交叉。在搜索广告里query-ad 的二阶交叉用 FM 学比较自然。它的 embedding 层被 FM 和 Deep 共享参数量比 WideDeep 更省。DINDeep Interest Network引入注意力机制根据当前候选广告对用户历史行为加权。搜索广告里用户历史点击序列长度有限DIN 的注意力模块能提升相关性预估。但 DIN 的推理延迟比前两者高QPS 高的场景要谨慎。我一般会先用 DeepFM 跑 baseline确认深度模型相对 LR 有稳定提升后再试 DIN 看注意力模块是否带来增量。如果增量不明显说明用户行为序列信号弱不值得上 DIN 的复杂度。2.4 用 PyTorch 搭一个可跑通的 DeepFM 排序模型下面是一个最小可跑的 DeepFM 实现输入是稀疏特征索引和稠密特征输出 CTR 预估值。代码用 PyTorch环境配置参考常规的 miniconda pytorch 流程即可。import torch import torch.nn as nn class DeepFM(nn.Module): def __init__(self, sparse_feat_dims, dense_feat_dim, embed_dim16, hidden_dims[256, 128, 64]): super().__init__() # 每个稀疏特征的 embedding 表sparse_feat_dims 是各特征词表大小 self.embeddings nn.ModuleList([ nn.Embedding(dim, embed_dim) for dim in sparse_feat_dims ]) # 一阶线性部分稀疏特征权重 稠密特征权重 self.linear_sparse nn.ModuleList([ nn.Embedding(dim, 1) for dim in sparse_feat_dims ]) self.linear_dense nn.Linear(dense_feat_dim, 1) # Deep 部分输入维度 稀疏特征数 * embed_dim 稠密特征数 deep_input_dim len(sparse_feat_dims) * embed_dim dense_feat_dim layers [] for h in hidden_dims: layers [nn.Linear(deep_input_dim, h), nn.ReLU(), nn.Dropout(0.2)] deep_input_dim h layers.append(nn.Linear(deep_input_dim, 1)) self.deep nn.Sequential(*layers) self.sigmoid nn.Sigmoid() def forward(self, sparse_inputs, dense_inputs): # sparse_inputs: [batch, num_sparse_feat] emb_list [emb(sparse_inputs[:, i]) for i, emb in enumerate(self.embeddings)] emb_stack torch.stack(emb_list, dim1) # [batch, num_feat, embed_dim] # 一阶部分 linear_sparse_out sum(emb(sparse_inputs[:, i]).squeeze(-1) for i, emb in enumerate(self.linear_sparse)) linear_out linear_sparse_out self.linear_dense(dense_inputs).squeeze(-1) # FM 二阶交叉0.5 * ((sum e)^2 - sum e^2) sum_emb torch.sum(emb_stack, dim1) sum_square sum_emb * sum_emb square_sum torch.sum(emb_stack * emb_stack, dim1) fm_out 0.5 * torch.sum(sum_square - square_sum, dim1) # Deep 部分 deep_in torch.cat([emb_stack.flatten(start_dim1), dense_inputs], dim1) deep_out self.deep(deep_in).squeeze(-1) logit linear_out fm_out deep_out return self.sigmoid(logit)逻辑说明embedding 层为每个稀疏特征建独立词表避免不同特征共享词表导致的语义混淆。FM 二阶项用平方和减平方和的公式实现复杂度是 O(nk) 而非 O(n²k)。Deep 部分把 embedding 展平后和稠密特征拼接过 MLP。参数说明embed_dim取 16 是搜索广告的常见起点词表大的特征可以单独设更大维度。hidden_dims从 256 递减到 64层数不宜过深搜索广告样本噪声大太深容易过拟合。Dropout(0.2)是经验值如果离线 AUC 和线上差距大可以加到 0.3。训练时损失用BCEWithLogitsLoss数值更稳优化器用 Adam学习率 1e-3 起步。稀疏特征多的场景embedding 层的学习率可以单独调大常见做法是 embedding 用 1e-2其他层用 1e-3。3. 训练与线上部署样本、特征、延迟三件事3.1 样本构造和负采样曝光未点击怎么用搜索广告的样本来自曝光日志正样本是点击负样本是曝光未点击。这里有个容易翻车的点曝光未点击的样本里有一部分是广告没被用户看到比如排在底部没滚动到直接当负样本会引入噪声。常见做法是加位置偏差校正或者只取前几个位置的曝光作为负样本。如果日志里有可见性埋点优先用可见曝光。负采样比例上搜索广告一般不做额外负采样因为曝光量本身够大采样反而会改变先验。样本时间窗口建议取 7 到 14 天太短覆盖不了周期性太长会引入过时分布。训练集和验证集按时间切分不要随机切分——随机切分会让未来信息泄漏到训练集离线 AUC 虚高。3.2 特征归一化和 embedding 维度设置的实操参数稠密特征归一化用 z-score 或 min-max搜索广告里出价、质量分这类特征分布偏斜建议先做分位数截断再归一化。比如出价取 1% 和 99% 分位数截断避免极端值影响。embedding 维度按词表大小分档词表小于 10 万的用 8 维10 万到 100 万的用 16 维超过 100 万的用 32 维。维度不是越大越好过大容易过拟合且增加推理耗时。# 特征归一化示例分位数截断 z-score import numpy as np def normalize_feature(values, lower_pct1, upper_pct99): lower np.percentile(values, lower_pct) upper np.percentile(values, upper_pct) clipped np.clip(values, lower, upper) mean, std clipped.mean(), clipped.std() return (clipped - mean) / (std 1e-8)逻辑说明先按分位数截断长尾再做 z-score。1e-8防止除零。参数lower_pct和upper_pct根据特征分布调出价这种长尾特征可以用 0.5 和 99.5。3.3 推理延迟优化从模型剪枝到 embedding 查表加速搜索广告精排的延迟预算通常在 10 到 30 毫秒深度模型要在这个窗口内完成打分。几个实操手段第一embedding 查表是瓶颈之一。把 embedding 表放内存用连续内存布局避免随机访问导致的 cache miss。第二MLP 部分可以用 TensorRT 或 ONNX Runtime 加速FP16 量化通常能降 30% 到 50% 延迟精度损失在可接受范围。第三如果候选集大先用一个轻量模型粗排深度模型只精排 top 几百条。注意量化后一定要做离线 AUC 对比和线上小流量验证FP16 对 embedding 层的影响比对 MLP 层大必要时 embedding 保持 FP32。4. 避坑与排查搜索广告深度排序的 5 个血泪教训4.1 离线 AUC 涨了线上 CTR 没动现象离线 AUC 从 0.72 涨到 0.75上线后 CTR 持平甚至微降。原因最常见的是特征穿越。训练时用了曝光之后才能拿到的特征比如广告的实时点击率线上推理时这个特征还没生成导致线上线下不一致。另一个原因是样本和线上分布不一致比如训练用了全量曝光线上只排前几条。解决逐个特征检查时间戳确保特征在曝光时刻已可用。做线上线下特征一致性校验用同一批请求分别跑离线和线上对比特征值。样本上训练集和线上服务的候选集分布要对齐。4.2 embedding 维度设太大导致过拟合现象训练集 loss 持续下降验证集 loss 在几个 epoch 后反弹AUC 差距拉大。原因embedding 参数量过大低频特征的 embedding 更新次数少学到的向量接近随机。搜索广告里长尾广告占比高这个问题尤其明显。解决按词表大小分档设维度低频特征做 hash 或归并到 OOV。加 L2 正则embedding 层的正则系数可以比其他层大。早停策略按验证集 AUC 来不要按训练 loss。4.3 负采样比例改变导致预估偏差现象模型预估的 CTR 均值明显高于线上真实 CTR。原因训练时做了负采样但推理时没有做先验校正。负采样会改变正负样本比例模型学到的概率不是真实概率。解决如果做了负采样推理时要按采样比例做校正公式是p_real p_pred / (p_pred (1-p_pred)/sampling_rate)。更稳妥的做法是搜索广告不做负采样直接用全量曝光。4.4 多任务学习里转化率任务拖累点击率现象加了转化率预估作为辅助任务后CTR 预估的 AUC 下降。原因CTR 和 CVR 的样本空间不同CVR 只在点击样本上有标签两个任务的梯度尺度不一致共享底层参数时互相干扰。解决用 ESMM 这类结构CVR 任务只在点击样本上算损失CTCVR 用全样本。或者给两个任务的损失加权重CTR 损失权重调大。共享层不要设太深底层共享、上层分开。4.5 线上服务 embedding 表更新不及时现象新广告上线后长时间拿不到合理预估CTR 偏低。原因embedding 表是离线训练产出的新广告 ID 不在词表里只能落到 OOV 桶学不到个性化表示。解决建立增量更新机制新广告先用属性特征类目、标题分词的 embedding 做冷启动积累足够曝光后再更新 ID embedding。词表预留 OOV 桶并定期重建。5. 用校准和 A/B 实验验证深度排序模型的真实收益模型上线不是终点验证收益才是。搜索广告排序的最终指标是 eCPM 和广告收入但这两个指标受出价、预算影响大短期波动可能掩盖模型真实效果。我一般会分两层验证。第一层是预估校准。深度模型的输出概率往往有偏用保序回归isotonic regression或 Platt scaling 做校准让预估 CTR 的均值和真实 CTR 对齐。校准后再看按预估 CTR 分桶的实际 CTR 是否单调如果单调性被破坏说明模型排序能力有问题。from sklearn.isotonic import IsotonicRegression import numpy as np # preds: 模型预估 CTR, labels: 真实点击 0/1 iso IsotonicRegression(out_of_boundsclip) iso.fit(preds, labels) calibrated iso.predict(preds) # 检查分桶单调性 bins np.quantile(calibrated, np.linspace(0, 1, 11)) for i in range(10): mask (calibrated bins[i]) (calibrated bins[i1]) print(f桶{i}: 预估均值{calibrated[mask].mean():.4f}, 真实CTR{labels[mask].mean():.4f})逻辑说明IsotonicRegression拟合预估值和真实标签的单调映射。分桶检查时每个桶的预估均值和真实 CTR 应该接近且随桶号递增。如果某个桶偏离大说明该区间的预估不可信。第二层是 A/B 实验。实验组用深度模型对照组用原 LR 模型流量按用户 ID 哈希分桶保证同用户始终在同一组。观察指标除 CTR、eCPM 外还要看广告主侧的转化率和退货率避免模型只优化点击不顾质量。实验周期至少一周覆盖工作日和周末。注意A/B 实验期间不要同时改出价策略或召回逻辑否则归因不清。如果必须改用正交实验分层。一个具体技巧实验初期用小流量5%跑两三天确认没有延迟暴涨或崩溃再扩到 10% 到 20%。搜索广告的延迟敏感深度模型推理一旦超时会触发降级逻辑降级后的排序结果和 LR 没区别实验组等于白跑。所以上线前一定要压测确认 P99 延迟在预算内。我自己踩过的坑是离线 AUC 提升明显就急着全量结果线上延迟超了 5 毫秒降级率飙升实验组 eCPM 反而降了。后来养成习惯任何深度模型上线前先跑一周小流量盯紧延迟和降级率再谈收益。希望帮到你。本文还有配套的精品资源点击获取