ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

推荐系统重排序:从PRM模型到工业级Transformer序列建模实践

2026/8/13 3:52:27 拓冰建站 浏览量
推荐系统重排序:从PRM模型到工业级Transformer序列建模实践 1. 项目概述从排序到重排序的认知升级在推荐系统的技术栈里排序Ranking大家都很熟悉了无论是经典的LR、FM还是后来的深度学习模型如DIN、DIEN目标都是给海量的候选物品打一个分然后按分高低排出个一二三。但不知道你有没有发现当你把排序好的列表直接推给用户时有时候会感觉“差点意思”。比如你给一个喜欢数码和体育的用户推荐排序模型可能把十个高分的手机和十个高分的篮球鞋排在最前面结果用户看到的首页全是手机和球鞋虽然每个item的预估点击率pCTR都很高但整体体验单一、缺乏惊喜感甚至可能因为同质化严重导致用户快速滑走。这就是重排序Re-ranking要解决的问题。它不关心单个item的绝对得分而是关心整个列表的排列组合是否最优。你可以把它想象成一场团体赛的排兵布阵你有一群个人能力很强的队员排序模型筛选出的Top-N候选但如何安排他们的上场顺序才能让团队整体得分最高、比赛最好看重排序就是这个“教练”的角色。阿里巴巴在2019年提出的PRMPersonalized Re-ranking Model模型就是在这个背景下将当时如日中天的Transformer架构引入推荐系统重排序领域的一个经典工作。它不再孤立地看待每个商品而是让商品之间“互相看”通过自注意力机制Self-Attention建模商品与商品之间的相互影响从而生成一个全局最优的序列。2. PRM模型的核心思想与架构拆解2.1 重排序的本质从点估计到序列建模要理解PRM首先要跳出传统排序模型的思维定式。传统排序模型Point-wise, Pair-wise, List-wise无论多么复杂其本质都是在做“点估计”给定用户U和商品I模型输出一个分数f(U, I)。List-wise虽然考虑了整个列表但其目标函数如ListNet依然是基于每个item的得分分布来优化并未显式建模item之间的相互影响关系。这种相互影响是真实存在的。比如替代性用户买了iPhone 15大概率不会再买iPhone 14。即使模型认为用户对iPhone 14的点击概率很高但把它紧挨着iPhone 15推荐实际点击的可能性会骤降。互补性推荐了手机壳紧接着推荐钢化膜后者的点击转化率可能会提升。多样性疲劳连续推荐多个高度相似的商品如不同品牌的运动鞋用户会产生审美疲劳后续item的点击率会衰减。上下文位置偏差列表顶部的item天然会获得更多曝光和点击这与item本身的质量无关。PRM模型的核心思想就是通过Transformer的Encoder部分为排序后的初始列表S[i1, i2, ..., in]中的每一个商品学习一个新的、融合了全局列表信息的上下文表征并基于这个新表征进行最终的点击率预测。其输入不再是孤立的(user, item)对而是整个item序列。2.2 PRM模型架构全景图PRM的架构非常清晰可以看作是一个专为重排序任务定制的Transformer Encoder。我们来逐层拆解输入层Input Layer 对于初始列表S中的每一个商品i我们构造其输入向量e_i。e_i由三部分拼接而成商品嵌入Item Embedding从商品特征ID、类目、品牌等学习得到的稠密向量。这是商品的基础表征。位置嵌入Position Embedding这是Transformer的标准操作用于让模型感知商品在列表中的顺序。PRM使用的是可学习的位置编码而非Transformer原版的正弦余弦函数。这是因为在推荐列表中位置信息极其重要如首位效应且位置数量相对固定如Top-50可学习编码能更灵活地捕捉位置偏差。个性化嵌入Personalized Vector这是PRM的“个性化”精髓所在。它不是简单的用户嵌入而是通过一个预训练的“个性化网络”Personalized Net基于用户特征和当前商品特征计算出一个向量v_i。公式可以简化为v_i f(user_embed, item_i_embed)其中f是一个多层感知机MLP。这意味着同一个商品在不同的用户面前其输入向量中的个性化部分是不同的从而将用户兴趣深度融入了重排序过程。所以每个商品的初始输入是E_i Concat(item_embed_i, position_embed_i, personalized_vector_i)。整个列表的输入就是一个矩阵E [E_1; E_2; ...; E_n]。Transformer编码层Transformer Encoder Layers 这一部分就是标准的Transformer Encoder堆叠。E矩阵经过多层论文中用了2层Multi-Head Self-Attention和Feed-Forward Network的处理。Self-Attention的作用这是建模商品间相互影响的关键。对于列表中的商品AAttention机制会让它“看到”列表里所有其他商品包括它自己。通过计算注意力权重模型可以学习到当商品B存在时应该如何调整商品A的表征。例如如果B是A的强替代品那么模型在更新A的表征时可能会通过Attention机制降低A的权重。多层堆叠每一层都在上一层的“上下文感知”表征基础上进一步抽象和融合信息从而捕捉更复杂的商品间高阶交互。输出层与预测Output Layer Prediction 经过L层Transformer编码后我们得到了每个商品富含上下文信息的最终表征O_i。然后通过一个简单的输出层例如一个全连接层Softmax来预测每个商品的点击率y_i Sigmoid(W * O_i b)。 这里需要注意的是PRM的损失函数是列表级的。它使用初始列表的真实点击序列作为监督信号例如用户点击了列表中的第2和第5个商品通过交叉熵损失来优化模型使得预测的点击概率分布尽可能接近真实的点击分布。最终我们根据新的预测分数y_i对初始列表进行重新排列。注意PRM并不重新生成候选集它只在排序模型给出的Top-N例如N50列表内部进行重排。它的计算复杂度是O(N^2)由于N通常不大线上服务是可行的。3. 关键实现细节与实操要点3.1 个性化向量的生成不只是用户嵌入很多人在复现PRM时容易把“个性化向量”简单理解为用户ID的嵌入。这是不够的。论文中的Personalized Net是一个轻量级的神经网络它以用户特征和当前商品特征的交互作为输入。一个更具体的实现方案可以是输入用户特征向量u_emb融合了用户历史行为、画像等当前商品特征向量i_emb。处理将两者进行拼接Concat或者按位乘Element-wise Product然后送入一个两层的MLP。输出得到一个与商品/用户嵌入同维度的个性化向量v_i。import torch.nn as nn class PersonalizedNet(nn.Module): def __init__(self, user_embed_dim, item_embed_dim, hidden_dim, output_dim): super().__init__() # 假设我们对用户和商品特征做了初步融合 self.mlp nn.Sequential( nn.Linear(user_embed_dim item_embed_dim, hidden_dim), nn.ReLU(), nn.Dropout(p0.2), nn.Linear(hidden_dim, output_dim) ) def forward(self, user_embed, item_embed): # user_embed: [batch_size, user_embed_dim] # item_embed: [batch_size, seq_len, item_embed_dim] batch_size, seq_len, _ item_embed.shape # 将用户向量扩展至序列长度维度 user_embed_expanded user_embed.unsqueeze(1).expand(-1, seq_len, -1) # [batch, seq_len, user_embed_dim] # 拼接特征 combined torch.cat([user_embed_expanded, item_embed], dim-1) personalized_vec self.mlp(combined) # [batch, seq_len, output_dim] return personalized_vec这样对于同一个用户列表里不同的商品会获得不同的个性化向量精准反映了用户对该商品的瞬时兴趣强度。3.2 位置编码的设计学习位置偏差在推荐场景中位置偏差是强烈且非线性的。榜首的位置收益可能远大于第二、第三位。因此使用可学习的位置编码比固定的三角函数编码更合适。class LearnablePositionalEncoding(nn.Module): def __init__(self, max_seq_len, d_model): super().__init__() self.pos_embedding nn.Embedding(max_seq_len, d_model) def forward(self, x): # x: [batch_size, seq_len, d_model] seq_len x.size(1) positions torch.arange(seq_len, devicex.device).expand(x.size(0), seq_len) return x self.pos_embedding(positions)在训练时这个位置嵌入参数会随着任务一起更新最终学到的可能就是一个反映“曝光点击概率随位置衰减”的向量。3.3 损失函数与训练技巧PRM使用标准的二分类交叉熵损失BCE Loss但标签是基于整个列表的真实点击反馈。假设我们有一个长度为N的列表用户点击了其中某些商品那么我们就有了一个N维的0/1标签向量。训练数据构造这是重排序模型训练的关键。你不能用全站曝点击日志因为那对应的是精排之前的海量候选。你需要记录精排模型输出Top-N列表后实际展现给用户的顺序以及用户在这个列表上的点击行为。这条日志才是重排序模型的训练样本。一个重要的实操心得列表完整性。你必须保证训练和线上服务时输入的列表是完整的、一致的Top-N。如果在训练时用了Top-50但线上因为某些原因只传了Top-20模型的行为会不可预测。因此在数据管道和模型服务中对列表长度的检查和填充Padding必须严格。4. 从PRM到工业级重排序的演进思考PRM作为一个开创性的工作指明了重排序的方向但在真实的工业级推荐系统中直接套用可能会遇到挑战也需要进行诸多演进。4.1 线上服务性能与效率Transformer的自注意力复杂度是O(N^2)。当N50时计算量尚可接受。但如果业务希望重排序的列表更长例如N100或200或者希望在精排阶段就引入序列建模即“精排即重排”计算开销就会成为瓶颈。解决方案蒸馏与剪枝训练一个大的、性能好的PRM模型作为教师模型然后蒸馏到一个结构更简单如层数更少、头数更少的学生模型上用于线上服务。高效注意力机制可以探索Linformer、Performer等线性复杂度的高效注意力变体在保证效果的同时大幅降低计算量。级联架构采用“粗排-精排-重排”的级联架构确保进入重排的列表N保持在一个较小的、可计算的范围内。4.2 多目标与业务约束PRM论文主要优化点击率CTR。但现代推荐系统往往是多目标优化的例如同时考虑点击率、转化率、观看时长、点赞、评论、关注等。此外还有强烈的业务约束如多样性约束要求列表中同类目的商品不能超过K个。新鲜度约束必须插入一定比例的新品或冷门商品。商业规则必须固定插入某些广告或运营位。如何融合一个常见的工业实践是两阶段法模型打分阶段使用像PRM这样的模型学习一个综合的“用户体验分”这个分数可以融合多目标通过MMOE、PLE等多任务学习网络输出一个初步的排序。规则调控阶段在模型打分的基础上通过启发式规则或更复杂的运筹学方法如打散算法硬性插入多样性、新鲜度等约束对列表进行微调。可以将规则设计成可学习的模块与模型进行联合优化。4.3 实时个性化与序列动态性用户的兴趣是实时变化的。一个刚看完手机评测视频的用户短期内对手机配件的兴趣会急剧升高。PRM的个性化向量虽然包含了用户信息但主要是静态或近期的画像。如何融入更实时的序列信息演进方向融合实时行为序列将用户最近几次点击、搜索的item序列也通过一个Transformer或GRU进行编码将其状态作为额外的上下文信息注入到PRM的输入层或中间层。会话感知的重排序不仅考虑当前列表内item的交互还考虑当前列表与用户当前会话历史Session的交互避免重复推荐。4.4 与RAG架构中重排序的异同当前热门的RAG检索增强生成架构中也存在“重排序”环节。其流程是用户查询 - 知识库多路召回 - 粗排 -重排序- 将Top-K文档送入LLM生成答案。 这里的重排序与推荐系统的PRM神似而形不同目标相似都是对初步筛选出的候选列表进行优化追求列表整体的最优效用推荐系统是用户体验和业务指标RAG是答案的相关性和准确性。输入不同PRM的输入是(item, user, position)RAG重排序的输入是(query, document, position)。RAG中商品间的关系较弱更多的是文档与查询的相关度以及文档之间的冗余度。模型借鉴RAG的重排序完全可以借鉴PRM的思想使用Transformer来建模(query, doc_i)对与(query, doc_j)对之间的相互影响。例如如果文档A已经很好地回答了问题的核心那么与A高度冗余的文档B的排名就应该降低。一些先进的RAG重排序模型如BGE-Reranker就采用了类似的双塔或交叉编码结构其中交叉编码器本质上就是在进行深度的“序列”交互建模。5. 实战构建一个简易的PRM模型我们使用PyTorch来搭建一个简化版的PRM模型用于理清数据流和核心代码逻辑。假设我们的商品嵌入维度是64用户嵌入维度是64位置编码和个性化向量维度也设为64最终Transformer输出的表征维度是64。import torch import torch.nn as nn import torch.nn.functional as F class SimplifiedPRM(nn.Module): def __init__(self, item_embed_dim64, user_embed_dim64, pos_embed_dim64, personalized_hidden32, num_layers2, num_heads4, max_seq_len50): super().__init__() self.max_seq_len max_seq_len # 输入层组件 self.item_embed_proj nn.Linear(item_embed_dim, 64) # 将商品嵌入投影到统一维度 self.pos_embed nn.Embedding(max_seq_len, pos_embed_dim) self.personalized_net nn.Sequential( nn.Linear(user_embed_dim item_embed_dim, personalized_hidden), nn.ReLU(), nn.Linear(personalized_hidden, 64) ) # Transformer编码层 encoder_layer nn.TransformerEncoderLayer(d_model64*3, nheadnum_heads, dim_feedforward256, dropout0.1, batch_firstTrue) self.transformer_encoder nn.TransformerEncoder(encoder_layer, num_layersnum_layers) # 输出层 self.output_layer nn.Linear(64*3, 1) def forward(self, item_embeds, user_embed): item_embeds: [batch_size, seq_len, item_embed_dim] user_embed: [batch_size, user_embed_dim] batch_size, seq_len, _ item_embeds.shape # 1. 商品基础嵌入 item_base self.item_embed_proj(item_embeds) # [batch, seq, 64] # 2. 位置嵌入 positions torch.arange(seq_len, deviceitem_embeds.device).unsqueeze(0).expand(batch_size, -1) # [batch, seq] pos_emb self.pos_embed(positions) # [batch, seq, 64] # 3. 个性化向量 user_expanded user_embed.unsqueeze(1).expand(-1, seq_len, -1) # [batch, seq, user_dim] personalized_input torch.cat([user_expanded, item_embeds], dim-1) personalized_vec self.personalized_net(personalized_input) # [batch, seq, 64] # 4. 拼接成最终输入 transformer_input torch.cat([item_base, pos_emb, personalized_vec], dim-1) # [batch, seq, 64*3] # 5. Transformer编码 (注意这里需要处理padding mask示例省略) # 假设我们不需要padding mask context_aware_repr self.transformer_encoder(transformer_input) # [batch, seq, 64*3] # 6. 预测得分 scores self.output_layer(context_aware_repr).squeeze(-1) # [batch, seq] return torch.sigmoid(scores) # 输出点击概率 # 模拟数据 batch_size 32 seq_len 20 item_dim 64 user_dim 64 model SimplifiedPRM() item_emb torch.randn(batch_size, seq_len, item_dim) user_emb torch.randn(batch_size, user_dim) output_scores model(item_emb, user_emb) print(fOutput scores shape: {output_scores.shape}) # [32, 20]这个简化版模型包含了PRM的核心要素可学习的位置编码、基于用户-商品交互的个性化网络、以及Transformer编码层。在实际应用中你需要添加更完善的特征工程、掩码处理、以及复杂的多任务损失函数。6. 常见陷阱与效果调优指南在实际部署PRM或类似重排序模型时有几个坑几乎每个人都会遇到。陷阱一数据泄露与线上不一致这是最大的坑。重排序模型训练数据的标签必须是精排后展现的列表顺序下的用户反馈。如果你错误地使用了“全局点击日志”即把用户在不同时间、不同上下文下点击的商品强行拼成一个列表作为正样本模型就学到了错误的知识。它以为这些商品会同时出现并存在某种关系但线上服务时根本不会这样组合。务必保证训练样本的“列表快照”与线上服务逻辑一致。陷阱二位置偏差过拟合由于位置偏差很强模型很容易学会“不管商品是什么只要放在前面得分就高”。这会淹没商品间真实的相互影响信号。缓解策略位置消偏在训练时可以采用“曝光点击”数据而非“展现点击”数据或者使用IPSInverse Propensity Scoring等方法对位置偏差进行纠偏。在损失函数中加入正则项惩罚模型对位置特征的过度依赖。A/B测试时核心指标要看“同一位置下的CTR提升”而不是整体CTR因为整体CTR提升可能只是模型把好商品挪到了前面。陷阱三列表长度的影响模型在训练时见到的列表长度是固定的如50。如果线上动态变化有时30有时60模型的性能会不稳定。建议在训练时就对短列表进行随机padding用零或特殊标记填充让模型适应不同长度的输入并在padding处使用正确的注意力掩码。效果调优方向个性化网络的深度尝试加深或加宽Personalized Net或者引入更复杂的用户-商品交叉特征如外积、注意力。Transformer层数与头数对于商品关系复杂的场景如服饰搭配可以适当增加层数如3-4层以捕捉更深层的关系。头数也可以调整更多的头可能让模型从不同子空间关注不同的关系模式如替代性、互补性。引入更多序列信息除了当前列表可以将用户最近的行为序列也通过一个RNN/Transformer编码将其最终状态作为全局上下文拼接到每个商品的输入中。多目标学习将损失函数改为多任务形式同时预测点击、转化、时长等多个目标。这能让模型学习到的“好列表”定义更加全面平衡短期点击和长期用户价值。重排序是推荐系统提升用户体验的最后一道也是至关重要的一道关卡。PRM模型为我们提供了一个强大的基于Transformer的解决方案框架。它的价值不在于其网络结构有多复杂而在于它明确地将“列表整体效用最大化”作为建模目标并利用自注意力机制实现了商品间的全局交互建模。在实际工作中我们很少会原封不动地使用论文中的模型而是汲取其思想内核结合自身业务的数据特点、性能约束和商业目标进行持续的迭代和定制化开发。理解商品为何相互影响比选择哪个模型结构更重要。