想象你是一家老剧院工作了十年的领位员老周。每晚观众按先后顺序入场,老周在票根上记下每位观众坐过的座位、看过的演出。时间久了,他发现规律:看过《罗密欧与朱丽叶》的观众,大概率会订下一场的《哈姆雷特》;连刷三场音乐剧的人,下次更想要靠近舞台的座位。这种“按时间顺序发生的观看历史”,就是推荐系统里的行为序列;而每张票根上印着的“第几排第几号”,就是位置编码。位置编码与序列位置感知,就是让 AI 学会像老周一样,既看懂“用户看过什么”,也看懂“按什么顺序看的”,从而在下一次推荐到来之前,精准预判观众最想坐的位置。
理解位置编码与序列位置感知,是你在搜推系统里做出真正“懂顺序”的推荐模型的必经之路。它直接关系 Transformer 架构落地、多任务模型收敛,以及工业推荐系统的效果上限。
可视化演示
展示正弦位置编码、RoPE相对位置编码、ALiBi线性偏置和位置感知注意力机制,含数据流背景和发光动画,交互切换各方案。
本节配有交互动效(进入视口后自动加载,约 3KB)。
序列行为建模
老周手上有一叠票根,每一张都写着“某年某月某日、第几排几座”。他从来不会只看最新一张票根,而是把整叠票根按时间排开,寻找演出的先后关联——看完悲剧的人接下来想看喜剧,连着看历史剧的人会偷偷换成轻歌剧。这叠按时间顺序摊开的票根,就是推荐系统里的“行为序列”。
在推荐系统里,用户不会一次性提交全部偏好,而是像入场检票一样,一个动作一个动作地留下脚印:点击、收藏、购买、分享。这些按时序排列的交互项 \(s_1, s_2, \dots, s_T\) 就构成了行为序列。序列推荐的核心任务,是预测用户下一步最可能交互的物品 \(s_{T+1}\)。
与“把用户交互当成一袋没有顺序的物品袋”的传统协同过滤不同,序列推荐高度依赖顺序。“先买 AirPods 再买 iPhone”和“先买 iPhone 再买 AirPods”,物品集合完全一样,含义却天差地别。整个序列模型,就是把全部历史交互作为条件,输出下一个物品的概率分布:
老周正是这样工作的:把观众一连串历史在脑中“编码”成直觉,再对明天的座位打概率分。序列建模的本质,就是把记忆按时间排序,而不是把票根揉成一团扔进口袋。
- 序列编码让编码函数 \(f_\theta\) 把从 \(s_1\) 到 \(s_T\) 的所有交互压缩成一个隐藏向量 \(h\),它融合了顺序信息。
- 线性映射用权重矩阵 \(W\) 把隐藏向量映射到物品空间,产生每个候选物品的未归一化分数。
- 概率归一化通过 Softmax 把分数转换为概率,总和为 1,从而得到下一个物品的分布。
位置编码原理
票根上最显眼的,是那个“第几排第几号”的座位号。同一张椅子、同一个座位,在不同场次价格不同;同一个物品,出现在用户历史的第 1 位还是第 50 位,语义也截然不同。位置编码,就是给序列里每个时刻发一张唯一的“座位号”。
在 Transformer 出现之前,RNN 靠逐步读取天然感知顺序,但它很难记住长距离的相对关系。于是研究者把位置信息直接写进向量,最常见的做法是正弦位置编码。它用一组固定频率的正弦和余弦函数生成位置向量,再与物品嵌入相加:
除了这种固定正弦编码,还有可学习位置编码——把位置编号当成一张查找表,靠梯度更新;也有相对位置编码——不关心“第几个”,只关心“相隔几个”。就好比老周记观众,既可以记“老张是今晚第 7 个入场的”,也可以记“老张坐在小李左边第三排”。后者往往在跨长序列时更稳健,也是现代序列推荐模型的主流选择。
- 位置计数\(pos\) 是物品在序列中的绝对位置序号,从 0 或 1 开始,对应“这是第几个座位”。
- 奇偶分工位置向量的偶数维用正弦、奇数维用余弦,让不同维度携带不同的相位信息。
- 频率衰减分母 \(10000^{2i/d}\) 随维度增大而指数增大,使低维分量变化快、高维分量变化慢。
- 向量相加将位置向量与物品嵌入逐元素相加,让模型同时读到“是什么物品”和“在哪个位置”。
位置感知机制
老周的眼睛很毒:他不仅知道观众是第几个进场的,还牢牢记住了“这位观众隔壁坐的是谁”。在推荐场景中,“刚从哪个物品过来”往往比“这是第几个点击”更重要。这种能力,对应模型里的相对位置感知机制。
自注意力机制让序列中任意两个位置直接建立联系,但它本身是“顺序无关”的:如果输入被打乱,注意力分数不会变。为了恢复顺序敏感度,需要把位置信息注入注意力计算。经典做法是在注意力分数中加入相对位置偏差矩阵:
时间间隔也是一种重要信号:用户 1 分钟前的点击和 3 天前的点击,即使相对位置相邻,含义也完全不同。实践中常把时间差映射成额外特征与位置偏差相乘,相当于在剧场里既看“座位相隔几排”,也看“两场演出隔了几天”。
- 查询与键点积计算第 \(i\) 个位置与其他所有位置 \(j\) 的原始相关度。
- 缩放稳定除以 \(\sqrt{d_k}\),防止高维度下点积值过大导致训练不稳定。
- 相对位置偏差加入可学习的 \(B^{\text{rel}}_{i-j}\),它的值只取决于位置差 \((i-j)\),体现了“相隔几步”的影响。
- 加权聚合对每行做 Softmax 归一化,再用权重对值向量加权求和。
多任务扩展
一家剧院不可能只靠售票活命:它同时做售票、退改签、座位升级、会员回访四门生意。聪明的做法是让同一个领位员团队服务所有柜台,而不是每个柜台养一批互不相识的人。推荐系统里的“多任务学习”,就是把点击率预测、下一个物品预测、观看时长预测等多个目标交给同一个共享编码器。
共享底层的好处是让不同任务互相借力:点击任务是“数据最充足”的任务,可帮助优化物品表示;下一个物品预测任务提供强顺序信号;排序任务则把顺序信号转成最终排名。多任务推荐模型在共享网络之上接出多个任务头,总损失为各任务损失加权相加:
老周带的团队在售票时积累的信息,能直接用来优化退改签服务;多任务推荐也一样——位置感知让点击率任务和下一个物品任务共享同一份“座位记忆”,互相增强,而不是各干各的。
- 点击率任务用二分类交叉熵衡量点击预测的偏差,是收集最充分的基础任务。
- 序列任务计算下一个交互物品的负对数似然,强制模型利用顺序信息。
- 排序任务用成对损失约束“正样本得分高于负样本得分”,保证最终列表顺序合理。
- 加权融合用超参数 \(\lambda\) 平衡不同任务的量级与重要程度。
深度模型演进
最早的序列推荐模型像一位只记“上一位观众”的实习生,用 RNN 一步步传递记忆,但很难捕捉长距离依赖。后来剧院升级了智能系统,领位员能一眼看清全场座次关系——这就是 Transformer 架构带来的变化。
Transformer 用自注意力让任意两个位置直接交互,配合位置编码获得顺序感知。标准 Transformer 编码层用残差连接和层归一化堆叠:
Transformer 之外,图神经网络把物品看成节点,把“共同出现”看成边,通过消息传递建模高阶关系;双塔结构则把用户序列塔和候选物品塔分开,离线算好物品塔向量,在线只算用户塔,从而在十万级候选集的召回阶段保持低延迟。老周的剧场也在演进:从靠人脑排队的旧票台,升级成“智能闸机 + 中央排座系统”,更快也更准。
- 多头自注意力在多个子空间并行计算注意力,捕捉不同类型的顺序关系,如“紧邻”、“隔三差五”、“长期相似”。
- 残差连接把子层输出与输入相加,让梯度顺畅传回浅层。
- 层归一化对每个样本的特征维度做归一化,稳定深层网络训练。
优化与训练
剧院不是每场都满座,许多观众只来一次就不再出现。推荐系统同样面临数据稀疏问题:大部分用户行为序列很短,位置编码在高频交互上学习充分,却在长尾用户上几乎失效。训练阶段必须像老周培训实习生那样,刻意制造“缺座位”的演练场景。
一种有效手段是位置丢弃增强:训练时随机把序列中部分位置“罩住”,迫使模型不依赖某一个固定位置,而学会利用剩余位置的综合信号。这种掩码增强的表达式是:
训练时还会使用动态损失加权:多任务模型早期以点击任务为主,后期逐步提高序列任务的权重,避免一上来就被“难任务”带偏。位置表征增强则通过对位置号做小幅随机扰动,让模型对“位置偏移”不再敏感。这些技巧就像剧院在淡季反复排练“缺人手”的应急流程,保证正式演出时无论上座率冷热,领位员都能稳定发挥。
- 采样掩码每个位置独立以概率 \(p\) 置 0、以概率 \(1-p\) 保留。
- 逐元素遮蔽掩码与序列逐元素相乘,被遮住的位置变成占位符。
- 多个副本训练每次迭代用不同的随机掩码生成新的训练样本,增大有效样本量。
工程评估实践
演出结束,老周要对着售票系统复盘:今晚哪些座位卖得最好、哪些观众连买三场、推荐下一场时命中了几位。推荐系统的评估也分离线与在线两套体系,离线阶段最常用的是 Recall@K 和 NDCG@K:
离线指标足够好之后,还要上线做 A/B 实验,观察点击率、转化率、人均时长等商业指标。工业部署时通常采用“双塔召回 + 位置感知精排”的架构:召回阶段用轻量双塔从百万商品中筛出几百个候选,精排阶段再用带相对位置编码的 Transformer 模型对候选精细排序,同时把序列特征预计算后缓存,把线上延迟控制在几十毫秒以内。老周的复盘会上一张张数字报表,正是推荐系统工程师每天都在看的成绩单。
- 取 Top-K 集合模型为所有候选物品打分,取分数最高的 K 个。
- 计算命中集合把真实交互物品集合和推荐集合求交集。
- 召回率归一化交集大小除以真实集合大小,得到召回率。
- 位置折扣NDCG 按排名位置对命中折扣,靠前的命中获得更高增益。
AI 应用场景
电商“买完再推荐”的链路优化
阶段:离线训练 → 在线序列召回与重排
解决的问题:传统协同过滤只看“用户买过什么”,忽略购买顺序。用户“买完奶粉再买纸尿裤”和“先买纸尿裤再买奶粉”行为路径不同,后者可能意味着新手父母刚进入育儿阶段,推荐逻辑完全不同。位置感知模型把每次购买当作带座位号的序列元素,让系统学会顺序语义。
具体案例:某母婴电商在订单完成后,用 Transformer 序列模型编码最近 20 次购物记录,结合相对位置偏差预测下一单。上线后,奶粉关联推荐点击率提升 8%,且“新客首购后二次购”转化提升明显。
短视频信息流去重与续刷体验优化
阶段:特征工程 → 排序模型
解决的问题:用户连刷三条跳舞视频后,如果第四条还是跳舞,会产生审美疲劳。传统模型只统计内容类目分布,感知不到“内容类型在时间序列上的位置”。加入位置编码后,模型知道“同类内容连续出现 3 次”这个位置信号,并主动压缩同类内容权重。
具体案例:某视频平台在排序模型的自注意力层加入相对位置偏差,使模型关注“上一个视频与当前视频的间隔距离”。实验结果显示连续同类内容占比下降 15%,用户人均观看时长提高 4%。
金融交易序列风控
阶段:序列特征建模 → 风险评分
解决的问题:单笔交易很难看出异常,但“5 分钟内 3 笔小额试探、随后一笔大额转出”这组顺序模式,是典型的盗刷特征。传统规则引擎只做单笔阈值判断,无法识别这种顺序模式。序列位置感知建模把每笔交易当作用户行为序列元素,学习危险顺序。
具体案例:某支付平台用带时间间隔感知的 Transformer 模型分析用户近 50 笔交易,把交易时间戳差值融入位置偏差矩阵。上线后盗刷拦截召回率提升 23%,误报率下降 11%。
Python 演示
import mathimport torchimport torch.nn as nn class PositionalEncoding(nn.Module): def __init__(self, d_model, max_len=200): super().__init__() pe = torch.zeros(max_len, d_model) pos = torch.arange(max_len).unsqueeze(1).float() div = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model)) pe[:, 0::2] = torch.sin(pos * div) pe[:, 1::2] = torch.cos(pos * div) self.register_buffer("pe", pe.unsqueeze(0)) def forward(self, x): return x + self.pe[:, :x.size(1)] class SeqRec(nn.Module): def __init__(self, vocab_size, d_model=64, nhead=4, layers=2): super().__init__() self.embed = nn.Embedding(vocab_size, d_model) self.pos = PositionalEncoding(d_model) self.encoder = nn.TransformerEncoder( nn.TransformerEncoderLayer(d_model, nhead, batch_first=True), layers ) self.head = nn.Linear(d_model, vocab_size) def forward(self, seq): x = self.embed(seq) x = self.pos(x) x = self.encoder(x) return self.head(x[:, -1, :]) def train(model, data, epochs=3): opt = torch.optim.Adam(model.parameters(), lr=1e-3) criterion = nn.CrossEntropyLoss() model.train() for epoch in range(epochs): total_loss = 0.0 for seq in data: opt.zero_grad() logits = model(seq[:, :-1]) label = seq[:, 1:] ls = criterion(logits.reshape(-1, logits.size(-1)), label.reshape(-1)) ls.backward() opt.step() total_loss += ls.item() print(f"epoch {epoch + 1}: loss = {total_loss / len(data):.3f}") if __name__ == "__main__": torch.manual_seed(42) vocab, batch, length = 500, 8, 20 data = torch.randint(1, vocab, (batch, length)) model = SeqRec(vocab) train(model, data, epochs=2)
pe[:, 0::2] = torch.sin(pos * div)给偶数维写入正弦位置信号,让每个位置拥有独特编码pe[:, 1::2] = torch.cos(pos * div)奇数维写入余弦信号,与正弦维度互补,组成完整位置指纹return x + self.pe[:, :x.size(1)]把位置编码加到物品嵌入上,序列长度不足时自动截断x = self.encoder(x)经过多层 Transformer 自注意力,让每个位置聚合全序列上下文return self.head(x[:, -1, :])只取序列最后一个位置的输出,用它预测下一个物品的分数logits = model(seq[:, :-1])输入去掉最后一个物品,模拟“只知道历史,不知未来”label = seq[:, 1:]目标右移一位,对应“每个位置的下一个物品”,构成序列监督信号
常见误区
误区RNN 天生按顺序读取数据,内部隐状态已经携带了顺序信息,不需要额外位置编码。加位置编码只会浪费参数。
正解RNN 的顺序感知来自“逐步读取”的机制,但它很难表达长距离的精确位置关系,比如“第 5 个物品和第 100 个物品隔了多远”。许多 RNN 序列推荐模型在加入位置感知特征后依然有显著提升,因为隐状态会随时间衰减,位置编码可以补充精确的偏移信号。位置编码不是 Transformer 的专利,而是所有序列模型都可能需要的“定位能力增强包”。
为何常见:RRN 的循环结构让人误以为它“天生就懂顺序”,忽视了隐状态记忆衰减的本质缺陷。
误区反正都是把位置信息告诉模型,绝对位置编码和相对位置编码只是写法不同,替换使用效果差不多。
正解绝对位置编码直接表达“第几个”,在固定长度序列上简单有效;但它面对比训练时更长的序列时,外推能力较弱。相对位置编码只关心“两个物品隔了几步”,这种差值语义与序列长度无关,因此更强调整体。模型在长序列和短序列混合场景下,相对位置编码通常更稳定,但它在实现上更复杂,也会增加计算量。二者各有适用边界,不能随便换。
为何常见:刚接触位置编码时,看到“都是加一个位置向量”的表层相似性,忽略了底层泛化逻辑的差异。
误区在共享网络后面接上点击率输出头、序列预测输出头、排序输出头,这个模型就算多任务学习了。
正解多任务学习的核心在于共享表示的同时进行梯度平衡。如果只堆输出头而不管任务权重,量级较大的任务会主导梯度,小任务学不好。必须配合任务权重、梯度归一化或动态损失加权,让多个任务互相促进而不是互相拖累。输出头只是外壳,平衡机制才是灵魂。
为何常见:从代码看,多个输出头确实“长得很像多任务”,容易让人忽略训练层面的微妙平衡问题。
误区位置编号已经区分了先后顺序,第 3 个和第 5 个位置不会混在一起,因此不需要再额外建模时间间隔。
正解位置编码表达的是“排序位置”,但真实推荐场景中“1 分钟前”和“3 天前”即使位置相邻,语义也可能完全不同。把时间差映射成连续特征(如对数衰减)并融入位置偏差,已成为序列推荐的标准做法。位置编号回答“第几个”,时间间隔回答“隔了多久”,二者缺一不可。
为何常见:经典 Transformer 公式中只有位置序号,没有时间戳,初学者很容易照搬算法而忽略工业场景中的时间信号价值。
误区可学习的参数比固定函数更灵活,所以可学习位置编码在效果上必然优于正弦位置编码。
正解两者各有优势。正弦编码用固定频率生成无限多的位置,天然具备超出训练长度的外推能力;可学习编码只能在训练见过的长度范围内优化,遇到更长序列会失灵。实际工程中常把可学习编码初始化成正弦编码的形状,既保留外推能力,又允许模型微调。灵活不等于全优,具体要看序列长度分布。
为何常见:看到“可学习”三个字就默认它是更先进的版本,忽略了工业场景中对长度泛化和稳定性的硬要求。
面试题
Q1. 在序列推荐中,为什么行为顺序本身很重要?
- A.顺序不会影响用户的真实兴趣
- B.顺序不同可能反映不同意图← 正确答案
- C.顺序只影响模型参数量大小
- D.顺序只影响训练时间长短
答案:B。同样的物品集合,顺序不同可能代表完全不同的用户意图,比如“先买手机再买手机壳”和“先买手机壳再买手机”传递的信息不同。选项 A 否定了顺序价值,C 和 D 把顺序的影响降级为工程参数,均不准确。
Q2. 正弦位置编码中,不同维度使用不同频率的主要目的是什么?
- A.让模型能区分不同位置← 正确答案
- B.让位置向量更节省显存
- C.让模型训练速度更快
- D.让物品向量维度变小
答案:A。正弦位置编码用不同频率让每个位置生成唯一指纹,同时保留相邻位置之间的平滑关系,模型因此能区分“第几位”。选项 B、C 与编码的作用无关,D 说反了,维度高低不是由编码方式决定的。
Q3. 相对位置编码相比绝对位置编码的主要优势是什么?
- A.参数量一定更小且更省内存
- B.更容易处理变长序列泛化← 正确答案
- C.完全不需要训练任何参数
- D.只能用于图像分类任务
答案:B。相对位置编码只依赖“两个位置之间的距离”,与序列绝对长度无关,因此面对超出训练长度的序列时泛化更好。参数量并不必然更小,它同样需要训练参数,也能用于推荐等多种任务,因此 A、C、D 都错。
Q4. 注意力公式中为什么要除以 \(\sqrt{d_k}\)?
- A.防止点积过大导致数值不稳定← 正确答案
- B.为了让 Q 和 K 的形状一致
- C.为了减少模型参数量
- D.让 V 的数值全部归一化
答案:A。当向量维度过大时,点积结果会迅速膨胀,进入 Softmax 饱和区造成梯度消失,除以 \(\sqrt{d_k}\) 是保持方差稳定的缩放操作。它与形状、参数数量、V 值的归一化都没有关系。
Q5. 多任务推荐中,任务权重 \(\lambda\) 的主要作用是什么?
- A.决定物品嵌入维度大小
- B.平衡各任务损失的量级← 正确答案
- C.控制用户序列的最大长度
- D.调节模型学习率数值
答案:B。不同任务的损失量级差异很大,权重 \(\lambda\) 让模型在梯度方向上权衡“点击率任务”和“序列预测任务”等不同目标的贡献。它不控制嵌入维度、序列长度或学习率。
Q6. 位置丢弃(position dropout)这类增强的主要目的是什么?
- A.减少模型可学习参数的规模
- B.增强位置表征对噪声的鲁棒性← 正确答案
- C.使物品嵌入完全变成随机数
- D.加速离线训练时数据读取
答案:B。位置丢弃随机遮住序列中的部分位置,迫使模型不依赖某几个固定位置,从而在数据稀疏时学到更稳健的位置表征。它不改变参数量,也不是把嵌入变随机,更与数据读取速度无关。
Q7. 评估序列推荐时,NDCG 相对 Recall 额外考虑了什么?
- A.物品的类目数是否均匀
- B.命中的物品是否排名靠前← 正确答案
- C.线上服务延迟是否稳定
- D.用户是否点击过展示广告
答案:B。Recall@K 只统计前 K 个推荐里命中多少个真实物品,不关心命中物品排在什么位置;NDCG 则用位置折扣加上对数衰减,命中越靠前得分越高。A、C、D 都是干扰项。
Q8. 下列哪些属于位置编码的实现方式?
- A.正弦余弦固定函数编码← 正确答案
- B.可学习位置嵌入查找表← 正确答案
- C.基于相对距离的偏差矩阵← 正确答案
- D.对物品向量做随机初始化
答案:ABC。正弦函数的固定频率、可学习的位置查表、相对距离偏差矩阵都是工业界常见的位置编码方案。随机初始化物品向量是嵌入层的常规做法,并不承担位置编码职责。
Q9. 序列位置感知建模通常需要哪些信息?
- A.物品交互记录的先后顺序← 正确答案
- B.交互的时间间隔信息← 正确答案
- C.每次交互的位置编码向量← 正确答案
- D.用户是否首次登录系统
答案:ABC。先后顺序是序列建模的基础,时间间隔补充了“隔多久”的语义,位置编码向量将顺序转化为模型可计算的表示。用户是否首次登录属于用户属性特征,不是序列位置感知的必要信息。
Q10. 工业序列推荐系统部署时,以下哪些做法合理?
- A.双塔召回减少在线检索开销← 正确答案
- B.序列特征预计算并缓存← 正确答案
- C.位置感知重排调整顺序← 正确答案
- D.删除全部位置编码节省成本
答案:ABC。双塔召回把物品向量离线计算好,在线只算用户侧,能大幅降低检索开销;序列特征预计算可以节省线上耗时;位置感知重排则让最终列表保留顺序语义。删除位置编码会让模型退化为无序模型,严重损失推荐效果,是不合理的做法。