高德推荐算法
一、介绍Transformer中的位置编码(Positional Encoding)
在 Transformer 结构中,由于模型没有内置的序列信息(不像 RNN 那样有时间步的顺序依赖),需要通过**位置编码(Positional Encoding, PE)**来提供位置信息,使得模型能够区分不同 token 的相对位置。
1.1. 位置编码的作用
由于 Transformer 采用的是自注意力机制(Self-Attention),它对输入序列的排列顺序不敏感,因此需要显式地向输入中添加位置信息。位置编码的主要作用包括:
- 提供位置信息,使模型能够捕捉顺序关系。结合词向量(Embedding)输入到 Transformer 中,以提供语义和位置信息。
1.2. 正弦-余弦位置编码(Sinusoidal Positional Encoding)
Vaswani 等人在 "Attention is All You Need" 论文中提出了一种固定的 位置编码方法,使用不同频率的正弦(sin)和余弦(cos)函数 来编码位置:
P E ( p o s , 2 i ) = sin ( p o s 1000 0 2 i / d ) P E ( p o s , 2 i + 1 ) = cos ( p o s 1000 0 2 i / d ) PE_{(pos, 2i)} = \sin\left(\frac{pos}{10000^{2i/d}}\right) \\ \ \\ PE_{(pos, 2i+1)} = \cos\left(\frac{pos}{10000^{2i/d}}\right) PE(pos,2i)=sin(100002i/dpos) PE(pos,2i+1)=cos(100002i/dpos)
其中:
- p o s pos pos 表示序列中的位置(Position)。
- i i i 表示维度索引(每个位置编码有 d 维)。
- d d d 表示编码的总维度(等于 Embedding 维度)。
- 1000 0 2 i / d 10000^{2i/d} 100002i/d 作为不同维度的缩放因子,使得不同维度的 PE 具有不同的变化率。
1.3. 为什么使用正弦和余弦?
- 周期性 :不同位置的编码可以通过线性组合推导出新位置的编码,有利于泛化到更长的序列。
- 不同频率:较低维度的编码变化较快,较高维度的编码变化较慢,使得模型能够学习不同粒度的相对位置信息。
- 平滑变化:相邻位置的编码变化平滑,符合自然语言的顺序特性。
1.4. PyTorch 实现
以下是使用 PyTorch 实现的位置编码模块:
python
import torch
import torch.nn as nn
import math
class PositionalEncoding(nn.Module):
def __init__(self, d_model: int, max_len: int = 5000):
"""
:param d_model: 词向量维度 (Embedding 维度)
:param max_len: 序列最大长度
"""
super(PositionalEncoding, self).__init__()
# 创建位置编码矩阵(shape: [max_len, d_model])
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1) # [max_len, 1]
div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)) # [d_model/2]
# 计算 sin 和 cos 位置编码
pe[:, 0::2] = torch.sin(position * div_term) # 偶数索引
pe[:, 1::2] = torch.cos(position * div_term) # 奇数索引
# 增加 batch 维度以便广播
pe = pe.unsqueeze(0) # shape: [1, max_len, d_model]
# 注册为 buffer,使其不会被模型参数更新
self.register_buffer('pe', pe)
def forward(self, x):
"""
:param x: 输入张量,形状为 [batch_size, seq_len, d_model]
:return: 加入位置编码后的张量
"""
return x + self.pe[:, :x.size(1), :]
二、如何解决CTR预估中广告位置的bias,讲讲网络中的bias net如何做
见【搜广推校招面经二十六】
在广告投放中,**广告位置(Ad Placement)会对点击率(CTR)、转化率(CVR)等关键指标产生影响,导致模型学习到的特征偏向某些广告位,而不是用户真实的兴趣或广告的质量。这种位置偏差(Position Bias)**可能导致:
- 高曝光广告位置的 CTR 过高,导致模型高估广告效果。
- 低曝光广告位置的 CTR 过低,导致模型低估广告的潜力。
- 难以推广新的广告位,模型可能更倾向于已经有数据的广告位。
2.1. Bias Net(偏置网络)原理
Bias Net 主要用于建模广告位置对点击率的影响 ,并将其从主模型(如 CTR 预估模型)中剥离,使主模型学习到去除位置影响后的广告真实效果。
Bias Net 主要采用 "双塔网络"(Two-Tower Model) 的思想:
- 一个塔(主网络 MainNet):学习广告本身的影响,如广告内容、用户兴趣等。
- 一个塔(BiasNet) :专门学习广告位置的偏置,并将其影响去除。
最终的去偏目标 :
P ( click ∣ ad, user ) = P ( click ∣ ad, user, pos ) / P ( click ∣ pos ) P(\text{click} | \text{ad, user}) = P(\text{click} | \text{ad, user, pos}) / P(\text{click} | \text{pos}) P(click∣ad, user)=P(click∣ad, user, pos)/P(click∣pos)
其中: - P ( click ∣ ad, user, pos ) P(\text{click} | \text{ad, user, pos}) P(click∣ad, user, pos) 是普通 CTR 预估模型的预测值。
- P ( click ∣ pos ) P(\text{click} | \text{pos}) P(click∣pos) 由 BiasNet 预测,建模广告位置的影响。
2.2. Bias Net 结构
BiasNet 通过 MLP(多层感知机)或 Embedding 方式 学习广告位置的偏置。以下是 BiasNet 的常见结构:
- Embedding + MLP 方式:
- 输入:广告位
position_id
- 处理:通过
Embedding
层将广告位转换为向量,再通过MLP
学习其对点击率的影响。 - 输出:广告位的偏置得分。
- 输入:广告位
- 独立建模 :
- 训练一个单独的网络,仅依赖广告位置
position_id
预测CTR
,然后归一化,使其成为位置偏置因子。
- 训练一个单独的网络,仅依赖广告位置
2.2.1. PyTorch 实现 BiasNet
python
import torch
import torch.nn as nn
class MainNet(nn.Module):
def __init__(self, ad_feature_dim, user_feature_dim):
super(MainNet, self).__init__()
self.fc = nn.Sequential(
nn.Linear(ad_feature_dim + user_feature_dim, 128),
nn.ReLU(),
nn.Linear(128, 64),
nn.ReLU(),
nn.Linear(64, 1),
nn.Sigmoid()
)
def forward(self, ad_features, user_features):
x = torch.cat([ad_features, user_features], dim=1)
return self.fc(x)
2.3. 去除广告位置(pos)的影响,为什么使用除法?
在去除广告位置(Position, pos
)的影响时,使用除法 而不是加法的主要原因是 归一化 和 比例调整。
2.3.1. 直觉解释
假设我们要预测广告的点击率(CTR),模型的原始预测值是:
P ( click ∣ ad , user , pos ) P(\text{click} | \text{ad}, \text{user}, \text{pos}) P(click∣ad,user,pos)
但由于广告位置会影响点击率(例如,广告展示在页面顶部时点击率更高),我们需要去除 pos
的影响,使得模型的预测结果更加公正,仅反映广告内容和用户兴趣。
BiasNet 是专门用于学习 pos
对点击率影响的网络,它的输出是:
P ( click ∣ pos ) P(\text{click} | \text{pos}) P(click∣pos)
最终,我们想要的去偏点击率应该是:
P debiased ( click ∣ ad , user ) = P ( click ∣ ad , user , pos ) P ( click ∣ pos ) P_{\text{debiased}}(\text{click} | \text{ad}, \text{user}) = \frac{P(\text{click} | \text{ad}, \text{user}, \text{pos})}{P(\text{click} | \text{pos})} Pdebiased(click∣ad,user)=P(click∣pos)P(click∣ad,user,pos)
2.3.2. 为什么使用除法?
(1) 归一化(Normalization)
- P ( click ∣ pos ) P(\text{click} | \text{pos}) P(click∣pos) 代表某个广告位置的整体点击率,它可以看作是该位置的"全局 CTR"。
- 通过除法,我们可以消除广告位置对 CTR 的整体提升或降低的影响,使不同广告位置的 CTR 处于相同的基准水平。
(2) 避免负值
- 如果使用加法去除位置影响,比如 P ( click ∣ ad , user , pos ) − P ( click ∣ pos ) P(\text{click} | \text{ad}, \text{user}, \text{pos}) - P(\text{click} | \text{pos}) P(click∣ad,user,pos)−P(click∣pos),可能会导致负的点击率,这不符合实际情况。
- 而使用除法确保调整后的 CTR 始终是正数,并且仍然具有可解释性。
(3) 保持比例关系
- 广告位的影响往往是乘法关系,而非加法关系。例如:
- 广告 A 在顶部位置 的点击率可能是 广告 B 在底部位置 点击率的 3 倍。
- 如果使用除法,广告 A 和广告 B 的 CTR 仍然可以保持这个比例。
- 而如果使用加法,会导致 CTR 计算失真,失去原有的相对比例。
2.4. 也可以通过多任务学习,联合优化两个loss。
- CTR 预测的目标 : P ( click ∣ ad , user ) P(\text{click} | \text{ad}, \text{user}) P(click∣ad,user)
- 位置偏置预测的目标 : P ( click ∣ pos ) P(\text{click} | \text{pos}) P(click∣pos)
- 联合损失函数 :
L = L CTR + λ L BiasNet L = L_{\text{CTR}} + \lambda L_{\text{BiasNet}} L=LCTR+λLBiasNet
三、点击率(CTR)建模中如何保证广告位自上而下 CTR 率依次递减?
在广告点击率(CTR)建模中,广告通常是按一定策略排序展示的。通常情况下,用户的注意力随广告位(Position)下降而减少,因此 CTR 也应该遵循 "自上而下递减" 的规律。然而,在实际建模过程中,CTR 可能会受到其他因素(如广告内容、用户兴趣等)的干扰,导致某些下方广告的 CTR 反而比上方广告高。
3.1. 为什么需要保证 CTR 递减?
- 符合用户行为模式:在大多数应用场景中,用户对上方广告的关注度较高,因此 CTR 也应该较高。
- 避免模型异常学习:如果 CTR 没有递减趋势,可能意味着模型没有正确建模广告位置的影响,或者数据存在偏差。
- 提升广告排序的稳定性:确保 CTR 预测结果合理,有助于广告投放系统优化广告排序,提高收益。
如果模型未能正确学习广告位的影响,CTR 预测可能出现异常。
3.2 保证广告位 CTR 递减的方法
(1) 在模型输入中添加广告位置信息
- 显式建模广告位特征 :在模型中添加广告位
pos
作为一个重要特征,并确保其影响符合期望:
P ( click ∣ pos ) 递减 P(\text{click} | \text{pos}) \text{ 递减} P(click∣pos) 递减 - 位置编码方式 :
- Embedding 方式 :使用
pos_embedding = nn.Embedding(n_pos, embedding_dim)
,让模型学习广告位的影响。 - 数值归一化方式 :直接使用
pos / n_pos
作为一个数值特征输入。
- Embedding 方式 :使用
(2) 在模型优化中添加单调性约束
为了确保 P ( click ∣ pos ) P(\text{click} | \text{pos}) P(click∣pos) 递减,可以对模型进行约束:
-
位置影响单调递减:
- 约束 ∂ P ( click ) ∂ pos ≤ 0 \frac{\partial P(\text{click})}{\partial \text{pos}} \leq 0 ∂pos∂P(click)≤0,即广告位
pos
越大(排名越靠后),CTR 预测值不会上升。 - 在神经网络中,可通过 单调性网络(Monotonic Neural Networks) 或 权重约束 来实现。
- 约束 ∂ P ( click ) ∂ pos ≤ 0 \frac{\partial P(\text{click})}{\partial \text{pos}} \leq 0 ∂pos∂P(click)≤0,即广告位
-
使用单调递减的变换函数:
- 例如在 MLP 层中,使用 S o f t p l u s ( − x ) Softplus(-x) Softplus(−x) 或 − R e L U ( x ) -ReLU(x) −ReLU(x) 来确保输出单调递减:
python
class MonotonicLayer(nn.Module):
def __init__(self, input_dim):
super().__init__()
self.linear = nn.Linear(input_dim, 1)
def forward(self, x):
return -torch.relu(self.linear(x)) # 保证单调递减
- 使用排序损失(Ranking Loss)**:
- 设
pos_1
在pos_2
之上,则P(\text{click} | \text{pos_1})
应该大于P(\text{click} | \text{pos_2})
:
- 设
(3) 在数据处理阶段去除位置偏差
如果数据中广告位的 CTR 没有遵循递减规律,可以通过以下方法调整:
-
分层归一化(Layer-wise Normalization):
- 在训练数据中,对不同广告位的 CTR 进行归一化,避免数据不均衡带来的问题。
- 例如,对每个广告位
pos
计算均值 CTRμ_pos
,然后对y
进行调整:
y ′ = y μ pos y' = \frac{y}{\mu_{\text{pos}}} y′=μposy
-
逆倾向采样(Inverse Propensity Weighting, IPW):
- 由于广告位较高的曝光量远大于广告位较低的曝光量,可以使用
P(\text{pos})
作为权重,对样本进行重新加权:
w i = 1 P ( pos i ) w_i = \frac{1}{P(\text{pos}_i)} wi=P(posi)1 - 在损失函数中加入权重:
- 由于广告位较高的曝光量远大于广告位较低的曝光量,可以使用
python
loss = torch.mean(weight * loss_function(y_pred, y_true))
(4) 在预测阶段调整 CTR
如果模型仍然预测出了不符合递减规律的 CTR,可以在后处理时进行 排序约束调整:
- 强制后验排序修正 :
- 对每个广告列表的
CTR_pred
进行Sort
,确保P(\text{click} | \text{pos})
递减:
- 对每个广告列表的
ctr_pred_sorted, _ = torch.sort(ctr_pred, descending=True)
- 加权平均平滑 :
- 使用滑动窗口平滑方法,确保预测的 CTR 递减:
python
def smooth_ctr(ctr_pred):
for i in range(1, len(ctr_pred)):
ctr_pred[i] = min(ctr_pred[i], ctr_pred[i-1])
return ctr_pred