☰
深度会话兴趣网络DSIN:以会话切分提升CTR预估的建模实践
2026/10/22 5:40:55 网站建设 项目流程

做推荐和广告的同学,十有八九都经历过这么一幕:离线实验跑得好好的AUC提升,一上线CTR就掉了。尤其是做用户行为序列建模的时候,各种网络结构层出不穷,但真正能在工业场景里稳定产生收益的,往往不是最花哨的那个,而是最贴合业务数据特性的那个。今天要聊的DSIN,也就是Deep Session Interest Network,深度会话兴趣网络,就是这样一个看着不复杂、但把用户兴趣的时间特性吃得很透的模型。它是我个人在CTR预估实践中反复对比后认为非常值得借鉴的一类做法。

CTR预估(点击率预测)在广告、推荐、搜索这些场景里的核心任务,是判断“用户对某个候选物品点不点”。早期大家拼的是特征工程,后来DNN统一了特征侧;再往后,大家发现用户的历史行为序列本身就是一个金矿,于是开始用序列建模去挖掘用户兴趣。DSIN做的事情,就是在这条线上往前走了一步——它把用户的历史行为按照“会话”重新组织,再在会话级别建模兴趣的提取和演化,最后在候选物品的约束下完成兴趣的加权聚合。

1. 为什么需要DSIN:用户行为序列建模的演进

1.1 从单一向量到序列建模

最早做用户兴趣的时候,大家习惯把用户过去一段时间的点击、加购、购买等行为,通过Sum Pooling或者Mean Pooling压成一个定长向量。这种方法简单粗暴,冷启动场景下也够用,但缺点很明显:用户的兴趣是动态的、多峰的,一个平均向量会把“最近在看电竞鼠标”和“上个月买过一套厨具”这两件毫不相干的事情揉成一锅粥,信息损失非常大。我见过不少业务,用户活跃度越高、行为越杂,这种固定池化带来的损失就越明显。

后来出现了基于注意力机制的DIN模型,它引入了局部激活单元,让用户的历史行为按照当前候选物品动态加权。这个思路在工业界非常管用,因为它本质上回答了“针对当前这个候选,用户过去哪次行为更重要”这个问题。再后来,DIEN又往前走了一步,把行为序列看成一条时间线上的电影,用GRU去模拟兴趣的演化过程。这两个模型在公开数据集上的收益都很明确,也成了后续很多序列建模工作的核心基线。

但这两个模型有一个共同的隐含假设:把用户所有历史行为当成一条连续的长序列来处理。这个假设在真实业务里是有瑕疵的。比如一个用户上午在刷资讯,下午去电商平台搜笔记本,晚上又回来刷资讯。这三种行为之间的时间间隔很长,兴趣切换也非常明显,把它们放在同一条序列里,用序列模型去强行捕捉演化关系,训练效率低,而且很容易被“上一个会话里的强兴趣”压制“当前会话里的真实意图”。

1.2 会话切分:给兴趣装上时间盒子

DSIN的出发点很朴素:把用户行为序列按时间间隔切分成多个会话。什么叫会话?就是用户在一段连续时间内的行为集合,会话之间被一个足够大的时间间隙分割。这个思路不是DSIN首创的,在很多传统推荐系统里就已经有会话推荐这个概念,但DSIN把会话思想移植到了深度CTR模型里,而且做得更彻底。

为什么按会话切分有效?我给你打个比方。一个人的购物行为就像一本书,书里有很多章节,每个章节讲一个相对独立的主题;你在连续半小时内浏览的母婴用品,大概率是在为宝宝囤货,但半小时之后又去看了机械键盘,那又是另一个话题。如果把这些内容全部串成一章,阅读体验就很混乱。会话切分的作用,就是把“章节”切出来,让模型在每一章内部去捕捉兴趣,而不是通篇找不到重点。

具体切分方式上,常见做法是按时间间隔阈值切分。比如设置30分钟为一个间隔阈值,如果相邻两次行为之间的时间差超过30分钟,就把它们划到不同会话。当然这个阈值在不同业务里差异很大:短视频场景可能十几分钟就算新会话,电商场景可能几个小时才算。阈值选对了,模型提升明显;选错了,效果可能还不如不切。

1.3 DSIN的整体设计思路

DSIN的整体思路可以概括成四个步骤。第一步,把用户原始行为序列切分成若干会话,每个会话内保持行为的时间顺序。第二步,对每个会话内的行为做兴趣提取,得到每个会话的兴趣向量;这里不是简单池化,而是用多个兴趣提取器加注意力机制,得到多个角度下的兴趣表征。第三步,把这些会话兴趣向量当成一条新序列,用双向LSTM建模会话之间的兴趣转移和演化。第四步,针对当前候选物品,对所有会话兴趣做局部激活加权,得到最终的用户向量,再送入全连接层做预估。

这个设计与DIEN那种单条序列建模有一个关键区别:DIEN是在行为级别建模演化,DSIN是在会话级别建模演化。会话级别向量更稠密、语义更完整,也更容易被后续全连接层利用。在实际工程中,这意味着DSIN可以在不增加太多复杂度的前提下,比DIEN等基线模型取得更稳定、更明显的离线AUC提升。有一个数据可以说明问题:在某个公开电商数据集上,DSIN的AUC比DIN高出一截,比DIEN也有明显优势,而这个增益大部分来自会话切分和会话级交互,而不是某个单独模块的魔法。

2. 核心模块拆解:四个部件背后的设计逻辑

2.1 嵌入层与输入组织

所有的行为序列模型都离不开嵌入层。在DSIN里,每个行为会涉及物品ID、物品类目等特征,这些特征通过查表转换为Embedding向量,然后拼接成行为向量。之后,按照会话结构重新组织这些行为向量,把原始序列从形状[batch_size, max_seq_len, emb_dim]重排成[batch_size, max_session_num, max_session_len, emb_dim]。

这个重排不仅仅是维度变化,它其实是给模型加了一个先验结构:告诉模型第i个会话是一个语义单元,第j个会话是另一个语义单元。这个先验非常重要,因为纯数据驱动的序列模型很难自动学会这个界限。我记得自己第一次实现时,为了偷懒,把一整个长序列直接丢给模型,只在损失函数里加入会话约束,结果训练了好几天模型也没有明显学会会话间的区分。后来老老实实加了重排逻辑,离线效果立刻有可感知的提升。

重排之后,每个会话内部的长度可能参差不齐。工程上通常按固定长度截断或者补齐,比如每个会话最多保留20个行为,超出部分截断,不足部分用全零向量做mask。会话长度选择也很关键——太短的话,一个会话里的信息量不够模型提取兴趣;太长的话,会话内部可能又混入了多个主题,失去切分意义。

2.2 会话兴趣提取层:多提取器加注意力

这一层是DSIN最核心的部分。论文里的做法是对每个会话内部做处理:先通过一个多兴趣提取器,把会话内的行为向量按兴趣分组;再用一个注意力池化得到每个会话的固定维度表示。

多兴趣提取器本质上是多个并行的“注意力+池化”分支。每个分支有自己的参数,倾向于关注会话内不同模式的行为。比如一个电商会话里,用户可能在对比三款手机的同时加购了一个手机壳,一个分支可能主要捕捉手机型号的偏好,另一个分支捕捉配件的偏好。这样得到的多个会话兴趣向量,比单一池化更能表达用户在同一会话里的多方面意图。

实现细节上,多兴趣提取器通常采用多头注意力机制或者多个独立的局部激活单元。其中,多头注意力的做法是:把会话内行为向量作为Q、K、V输入,经过多头自注意力之后,再进行一个展平或者池化,得到多个兴趣向量。这里有个容易被忽略的地方:多头注意力的Q、K、V来自同一个会话内行为,所以在提取时不会引入其他会话的信息,这有助于保持每个会话兴趣的纯粹性。在具体实践中,多头数量一般取4到8,兴趣向量维度常见128到256。

这里有个值得注意的工程点:多兴趣提取器对每个会话做的是局部建模,它不会跨会话共享信息。这样设计的好处是,每个会话的兴趣表示不会被其他会话干扰,坏处是计算量随着会话数量线性增长。因此在线推理的时候,通常会把这个模块的复杂度优先优化,比如提前把会话分成固定长度块,或者在服务端缓存会话兴趣向量,避免每次请求都重新走一遍全量会话。

2.3 会话兴趣交互层:双向LSTM的重要作用

得到每个会话的兴趣向量后,DSIN把它们的序列送入一个双向LSTM。这一步要回答的问题是:用户的会话兴趣不是孤立的,当前会话的兴趣会受到前几个会话的影响,也会反过来影响后面的兴趣。双向LSTM的Forward方向捕捉过往兴趣对当前的影响,Backward方向则是从后续兴趣往前推断当前会话的上下文。

双向LSTM在这里之所以有效,其中一个原因是会话数量通常不会太多。用户一天的会话可能就几个到几十个,这个长度对LSTM来说非常友好,既不会像长序列那样梯度消失,也不会出现计算量爆炸。实际调参中,LSTM隐层维度一般与兴趣向量维度保持一致,层数1到2层就够,再加深收益很快变平。我在几个场景里试过3层双向LSTM,离线指标几乎没有增长,训练耗时却多了将近一倍,所以不建议无脑堆深度。

不过这里我也要提醒一句:双向LSTM不是万能的。如果你的业务是类似短视频这种用户行为序列极长、会话切分后仍剩下几十上百个会话,双向LSTM的训练效率会显著下降,而且未必比简单的Transformer Encoder或者纯注意力池化效果好。这个在后面的踩坑部分会再展开。

2.4 局部激活单元与应用层

DSIN最后要做的,是把当前的候选物品当成“问题”,去用户的所有会话兴趣中找“答案”。这一步用的还是DIN里经典的局部激活单元:对每个会话兴趣向量,和候选物品向量做内积,得到注意力权重,然后对所有会话兴趣做加权求和。这样得到的最终用户向量,其实是对“当前候选物品最关心的那几个会话”的加权组合。

为什么不用简单的平均池化?因为用户当前的兴趣是有强上下文的。比如用户现在浏览的是一台游戏主机,那系统更看重的是他过去会话里对游戏设备、显示器的兴趣,而不是他上个月研究过的洗衣机。局部激活单元本质上是在扮演这种上下文路由的角色。加权之后,模型理论上还可以再做一层非线性变换,把用户向量和候选物品双向融合得更充分。

应用层其实就是常规套路:把最终用户向量、候选物品向量、上下文特征拼接,送入一个多层全连接网络,输出预估点击概率。损失函数用logloss,即二元交叉熵,必要时加采样或层次softmax提高训练效率。整个DSIN的训练端到端,所有模块一起反向传播更新,不需要像传统会话推荐那样分阶段训练。这也是深度学习CTR模型能大规模落地的重要原因之一——特征侧和数据侧的统一,远比模型本身的复杂更关键。

3. 工程落地:代码实现与训练细节

3.1 数据预处理:会话切分的实操

会话切分听起来容易,做起来有不少细节。我给一个在电商场景下验证过的方案:先用用户ID和时间戳排序,然后计算相邻行为时间差,当时间差超过阈值(电商场景通常取30分钟)时,标记为新的会话起点。切分完成后,每个会话按行为时间排序,截取固定长度(比如20),如果会话数超过预设上限(比如20),只保留最近的部分会话。

阈值选择上,有一个简单统计方法:对训练集用户做会话长度分布统计,把阈值设在“会话长度分布最平稳”的那个区间附近。我见过最省事的做法,是直接对session gap画分布曲线,寻找明显的谷底。如果曲线平滑没有谷底,说明你的用户行为连续性很强,这时候切分会话可能本身就不合适。

工程实现时要注意时区问题。有些业务凌晨会有一批机器人行为或者异常流量,这段时间的会话切分很容易把正常用户和异常流量混在一起。处理手段通常是:先对样本过滤异常行为,再做会话切分;或者把凌晨时间段单独切一个会话,不跟前后晚上、早上的行为串起来。否则你精心设计的多兴趣提取器,很可能把机器人行为当成一个正经兴趣模式去学。

3.2 模型核心层的实现参考

下面给一个基于TensorFlow的简化实现,覆盖会话切分后的矩阵重排、会话兴趣提取器和双向LSTM这几块核心逻辑。真正工程使用时,还需要加上特征接入、Embedding维度控制、在线推理优化等。这里的关键在于理解每个张量经过每一层之后的shape变化,我写代码时也是靠打印shape才把整个链路捋清楚的。

# 输入:行为序列和基本信息 # item_seq: [batch, max_seq_len] # item_emb_seq: [batch, max_seq_len, emb_dim] # 经embedding lookup后的行为向量 def build_session_input(item_emb_seq, session_ids, max_session_num, max_session_len): # session_ids: [batch, max_seq_len],把每个行为映射到会话编号 # 这一步通过tf.gather或onehot加segment,将行为向量按会话重排 # 输出: [batch, max_session_num, max_session_len, emb_dim] # 需要注意长度mask,padding位置不能在后续计算中产生梯度 pass def multi_interest_extractor(session_emb, head_num=4): # session_emb: [batch*max_session_num, max_session_len, emb_dim] # 多头注意力编码,Q、K、V均来自session_emb attn_output = tf.keras.layers.MultiHeadAttention( num_heads=head_num, key_dim=emb_dim // 2)(session_emb, session_emb) attn_output = tf.keras.layers.Flatten()(attn_output) # 输出维度 [batch*max_session_num, head_num * interest_dim] # 后续reshape成会话兴趣向量序列 return attn_output def session_interact_layer(session_interest, hidden_dim=128): # session_interest: [batch, max_session_num, interest_dim] # 双向LSTM建模会话之间的演化 bi_rnn = tf.keras.layers.Bidirectional( tf.keras.layers.LSTM(hidden_dim, return_sequences=True)) return bi_rnn(session_interest) def local_activation_unit(session_hidden, target_emb): # session_hidden: [batch, max_session_num, hidden*2] # 对目标物品做注意力加权 # score = DNN(tanh([session_hidden, target_emb, session_hidden-target_emb])) # weight = softmax(score) pass

这个代码只保留了主干结构。真实场景里,Mask处理是最大的坑,尤其多头注意力和LSTM都会把padding位置也算进去,必须提前构造attention_mask或者对LSTM输入做mask处理。我建议先在一个小批量数据上手动验证输出shape,再逐步放大训练。

3.3 训练技巧与调参次序

DSIN训练时有两个常见现象:第一,收敛比DIN慢,因为多兴趣提取器和LSTM层引入了更多参数;第二,学习率稍微大一点就容易训练不稳定。我给的建议是,先固定其他模型参数,单独调试LSTM学习率,通常在1e-3到3e-4之间收敛最稳定。如果训练曲线震荡明显,优先降低学习率,而不是加大正则。

还有一个经验:对多兴趣提取器加正则约束。比如给注意力分布加一点稀疏正则,避免在冷启动时所有兴趣权重都集中到一个方向,导致其余提取器退化。这个做法在样本量比较小的子场景效果尤其明显。我遇到过一个新频道冷启动,前两周模型只学到一种兴趣模式,其他头全是死参数,加了稀疏约束后才缓解。

另外,特征侧配合也很重要。会话切分只是一个维度,如果同时把会话内的统计特征,比如会话内平均停留时长、会话内点击次数,作为辅助特征拼进全连接层,往往能再涨一点AUC。这些统计特征虽然有信息泄漏风险,但只要冻结在样本生成期,不跨窗口泄露,在线推理时也能用最近会话实时计算,就没有问题。

4. 踩坑实录:效果不佳时的排查思路

4.1 会话切分阈值怎么选才靠谱

我在项目里踩过最典型的坑,是直接照搬论文中的30分钟阈值。公开数据集上的行为密度和真实业务场景差异非常大。到了自己的业务里,30分钟可能把正常用户的连续浏览行为切成碎块,模型反而丢失了长程依赖。

排查方法很简单:先只做会话切分替换,不做其他改动,看离线AUC趋势。如果你的切分会话后,AUC还不如不切分,优先怀疑阈值过小或过大。把阈值从10分钟到120分钟分别跑一组实验,画一条曲线,一般能看到明显拐点。如果所有阈值都不如不切分,那说明业务本身不适合会话建模,不如直接换成DIN或者纯记忆型结构。

另外还要注意样本采样比例。有时候看起来是DSIN效果不行,其实是训练数据里某些活跃用户的行数太多,会话切分后同一样本反复出现,导致过拟合。建议在数据构造阶段,对超长序列用户做随机截断。我做过一个实验,把每天行为超过1000条的用户保留最近300条,模型AUC反而涨了,原因就是去掉了大量无意义的长期噪声历史。

4.2 多头注意力的头数不是越多越好

多兴趣提取器的头数,直接影响模型的表达能力和训练难度。头数太少,每个会话内的兴趣会被过度平均化;头数太多,每个头学到的模式都很稀疏,而且容易在样本不足时退化成噪声。

我自己的经验是,先跑头数1、2、4、8四组实验。如果4头比8头好,说明数据复杂度不足以支撑8个兴趣模式;如果1头就很好,那说明会话内兴趣本来就很集中,多兴趣提取器根本没必要,直接用简单池化即可。这个判断越早做,越能避免在错误方向上浪费时间。不要因为论文里用了多头,就觉得自己的场景也必须用多头。

4.3 双向LSTM带来的收益不明显怎么办

离线实验里,经常能看到双向LSTM只比不带交互的版本高一点点AUC,甚至有时没有提升。这时候首先要检查会话数量分布:如果一个用户平均只有2到3个会话,双向LSTM实际上学到的交互就非常有限,价值不大。

另一个常见问题是,把LSTM放在多兴趣提取器之前还是之后,两个方案差异巨大。论文结构是把LSTM放在会话兴趣向量之后,也就是先提兴趣再交互;如果实现顺序反了,先对会话内行为直接做LSTM,等价于在行为级别建模,会话切分的作用就被削弱了。我在调代码时遇到过AUC异常下跌,排查半天就是顺序问题。

4.4 行为向量维度和矩阵重排的坑

矩阵重排看着简单,但出错率极高。常见坑包括:padding位置不对导致待预测行为被mask、session_ids和item_seq长度不一致导致gather偏移、多兴趣提取器输出的维度与LSTM输入维度不匹配。建议在模型forward里加断言,每个张量shape在build阶段打印一次,数据规模不大时逐层验证。

下表是我总结的几个高频问题与排查方向:

现象可能原因建议操作
离线AUC比基线低会话切分阈值不合适跑阈值扫描,对比10到120分钟
训练曲线震荡学习率过高或LSTM梯度爆炸降低学习率,增加梯度裁剪
多头兴趣基本无差异头数过多或数据量不足减少头数,检查各头权重分布
双LSTM无增益会话数太少,或顺序放错检查平均会话数,验证模块位置
上线指标波动会话统计特征跨窗口泄漏冻结特征生成窗口,重放离线校验

4.5 从离线到在线的一致性检查

DSIN的一个隐形风险是,离线训练时使用的会话切分信息和在线推理时不一致。离线时我们可能拿到了用户全天完整的日志,在线请求时却只能拿到请求时刻之前的部分历史。如果两者差异过大,离线AUC再高,上线效果都会打折扣。

解决方法是在离线样本构造时就模拟在线状态:每个训练样本只保留“预测时刻之前”的行为,并且只用同样的时间切分逻辑生成会话。这样做虽然会损失一部分训练数据,但换来的是离线在线一致性。我在一个信息流广告场景里,光是修正这个偏差,线上CTR提升就有小一个点,比折腾模型结构还值。

5. DSIN的适用边界与扩展方向

5.1 哪些场景用DSIN收益最大

DSIN最适合的是用户行为具有明显会话特征的业务。最典型的是电商:用户今天早上搜索奶粉,下午看股票课程,晚上逛潮牌鞋,会话之间主题差异大、时间间隙明显。其次是本地生活、招聘、二手交易这类低频高意图场景,用户每次使用时长短、意图明确,会话切分能显著提升意图识别质量。

反而在短视频、信息流这类高频场景里,用户行为几乎是连续流式的,会话边界不明显,DSIN的优势就打了折扣。这时候把行为按阅读时长、视频播放时长加权,可能比按时间间隔切分更有效。所以选型之前,先花半天时间做行为分布分析,是值得的。我一般会统计用户日均行为数、平均会话时长、会话内主题离散度,这三个指标基本就能判断DSIN是否值得尝试。

5.2 可以扩展的方向

DSIN的框架是开放式的,可以替换部分组件来做扩展。比如把双向LSTM换成Transformer Encoder,在会话数量较大的场景下效果可能更好;把多兴趣提取器和后续局部激活单元换成多头注意力,形成类似BST的结构;再结合用户画像做跨会话的个性化兴趣权重,也可以进一步增效。

另外,DSIN产出的会话兴趣向量本身有复用价值。在实际系统中,会话兴趣向量可以离线生成、缓存下来,作为用户画像特征提供给召回阶段使用,这样整个漏斗的形态就打通了:召回用会话兴趣向量,排序用DSIN做最终精排。我实践下来,这种两段式的落地方式,比单一模型包打天下的方案,整体效率高很多,尤其在召回侧拿用户近期会话兴趣做向量检索,能大幅提升新物品的曝光效率。

我个人实际操作中的体会是,DSIN真正的价值不在于某一个模块有多惊艳,而是它提供了一个“先切分会话、再提取兴趣、再建模演化”的完整思路。离线阅读论文时你可能觉得平平无奇,但当你把行为序列按会话重新排布后,很多业务洞察突然就变得可解释了——某个用户过去几天反复在多个会话里看同类商品,这个信号比单条长序列里的模式要清晰得多。这套思路,无论你是做广告CTR、商品排序还是信息流推荐,都值得在多套基线上认真对比验证一次。最后再分享一个小技巧:如果你暂时没有精力完整复现DSIN,先单独把“会话切分+分会话注意力池化”接到现有模型上,这个改动通常就能带来肉眼可见的收益,而且风险极低。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询