☰
GC-SAN详解:图神经网络与自注意力如何破解会话推荐难题
2026/10/15 14:24:24 网站建设 项目流程

做推荐系统的人,迟早会被一个问题逼到墙角:用户没有任何历史画像,只有当前这几次点击,你该怎么预测他下一步想点什么?这个场景就是会话推荐。今天想聊的这篇论文给出的解法,是用图神经网络把会话内物品之间的关系建模成一张有向图,再用自注意力把整个会话的上下文信息融进去。论文提出的方法叫GC-SAN,也就是Graph Contextualized Self-Attention Network,图上下文自注意力网络。它解决的核心问题是:不依赖用户长期历史,仅凭当前会话里几条点击记录,预测下一个最可能被点击的物品。如果你正在做电商的短期兴趣建模、短视频的即时反馈推荐,或者在研究图神经网络和Transformer结构怎么在序列数据上配合使用,这篇论文都值得认真读一遍。下面我会从问题背景、模型设计、实验细节到复现踩坑,把它完整拆开。

1. 会话推荐为什么难:先理解GC-SAN在解决什么问题

1.1 会话推荐的定义与两类核心难点

会话推荐的任务定义很简单:给定一个session内的点击序列,比如 [运动鞋, 鞋带, 收纳盒, 运动鞋],预测用户下一个最可能点击的物品。这里的session指一次连续的浏览过程,通常持续几分钟到几十分钟。

这个任务和传统推荐最大的区别在于:传统协同过滤、矩阵分解都默认用户是一个稳定的实体,你看了什么、买过什么,都会沉淀成长期画像。但真实业务里大量流量来自游客、未登录用户,账号体系失效,画像根本不可靠。会话推荐退回到最小可用信息,只用当前这段行为序列本身做出预测。我见过不少产品把这个场景叫做“无身份推荐”或者“短会话推荐”,本质是同一件事。

任务难在两点。第一,数据极度稀疏。一个session往往只有3到15次点击,这点信息连一个像样的用户向量都拼不出来。第二,意图漂移非常常见。用户可能先看球鞋,又点进鞋带,再跳到收纳盒,最后回头又点了一次运动鞋。模型既要抓住他当下的真实意图,又不能被中间的历史噪声带偏。就好比逛超市,你从货架一头走到另一头,购物车里的东西反映的是当下的采买计划,跟你上周买了什么没有直接关系。会话推荐要学的,正是这个“当下计划”的规律。

1.2 从GRU到SR-GNN:前人在会话建模上走过的路

会话推荐不是这两年才有的方向。早期主流是统计方法:S-POP统计当前session里出现次数最多的物品直接推荐,Item-KNN计算物品之间的共现相似度。这些方法简单、可解释,但对“用户从A到B”的转移顺序不敏感,遇到冷门物品直接失效,泛化能力很弱。

之后深度学习登场。GRU4REC用GRU把点击序列编码成一个隐状态,NARM在GRU基础上加注意力,把序列里每个位置的重要程度区分出来。这两类模型有一个共同假设:用户行为是一段线性序列,按时间顺序读完就能得到整体表征。但实际问题里,用户从铅笔跳到橡皮,再跳回铅笔的包装盒,物品之间的关系是非线性的多跳关系。线性RNN对这种跳转结构编码起来非常吃力。

SR-GNN把建模思路改成了图。它把一个会话内的物品看作节点,点击顺序构成有向边,然后用门控图神经网络在图结构上做信息传播。这个思路比RNN高明的地方在于:物品之间的共现和顺序关系都被压缩进图的边里,哪怕两个物品在点击序列里隔了好几步,模型也能通过图路径看到它们之间的间接关联。但SR-GNN也有明显短板,它把所有注意力都放在局部图传播上,整个会话的全局意图并没有被显式建模。

GC-SAN就是站在SR-GNN的肩膀上,把全局上下文补了回来。我在读这篇论文前专门把这条技术脉络捋了一遍,否则很难理解它每一步设计为什么要这么做。给一张简单的对照表,方便你建立坐标系:

方法核心思路关键局限
S-POP统计session内物品频率完全没有顺序信息
Item-KNN物品共现相似度对冷门物品不友好,泛化差
GRU4REC循环网络编码点击序列线性假设,忽略多跳关系
NARMRNN + 注意力仍是线性序列建模
SR-GNN图神经网络聚合局部转移缺少全局上下文表达
GC-SAN图传播 + 上下文自注意力把局部结构与全局意图结合

2. GC-SAN架构拆解:从会话图到上下文自注意力

2.1 会话图:每个session一张局部有向图

进入模型前,每个物品会被映射成一个低维embedding向量。接着,把一个会话内的物品按点击顺序构成有向图。比如会话是v1→v2→v3→v2,那么图上有边v1→v2、v2→v3、v3→v2,并且v2出现两次,它作为中间节点连接了前后两个物品。

这里有一个特别需要注意的设计选择:图是每个session单独构建的局部图,不是把所有session拼在一起的大图。如果搞成全局大图,不同用户、不同会话之间的噪声边会混在一起,信息传播直接被污染。我在复现时试过一次错误做法,用全部会话共用一个图,P@20掉了好几个点,后来改成按session建图才恢复正常。

邻接矩阵还需要区分出边和入边。用户从A点到B点,和从B点回到A点,语义是完全不同的。在消息传播时,出边和入边用不同的权重矩阵做变换,模型才能学到方向信息。这个细节和知识图谱里关系方向的建模思路一样,方向错了,语义就反了。

2.2 GGNN传播:为什么用门控而不是普通图卷积

拿到图和初始embedding后,GC-SAN用门控图神经网络做信息传播。每一轮传播时,节点先聚合来自邻居的信息。聚合结果送入一个GRU风格的更新单元:更新门决定保留多少旧状态,重置门决定遗忘多少历史信息,再混合邻居信息生成候选状态。

不需要死记公式,理解成节点在“看图说话”:看看我的邻居是谁,把值得吸收的信息吸收进来,把不重要的旧信息忘掉。这个机制和普通GCN的邻域平均有本质区别,GCN的聚合是静态的加权平均,信息一进来就会和原向量混合,缺少显式的保留/遗忘控制。而会话图很短、噪声很大,GRU门控这种动态控制信息流量的做法,对短序列更友好。

论文里有个容易被忽略的配置:GGNN传播步数其实只用了1轮。我第一次看的时候以为会像普通GNN一样按层叠加,结果论文实验表明1轮就是最优。原因也不复杂,一条点击转移链的有效信息大多藏在直接邻居里,多传几轮会把不同子图混到一起稀释信号,反而伤害效果。这个反直觉的配置,直接帮你省了一笔训练开销,我认为是这篇论文里最有价值的实操经验之一。

2.3 上下文自注意力:全局游标如何参与打分

GGNN之后,每个物品的表征已经带上了局部图结构,但这时候整个session的“主线意图”还没有被显式表达。作者用了一个很朴素但有效的办法:把所有物品的向量做平均池化,得到会话上下文向量c,然后用这个c去改造自注意力。

普通自注意力里,每个位置自己做query去和所有位置的key匹配,注意力分布只看两两之间的关联,没有一个显式的全局主题。但用户的点击session往往是有主题的,比如你连续点了几支笔,那“文具”这个主题就应该主导后续的注意力。GC-SAN的上下文自注意力,把c当成一个所有位置共享的辅助信号。

具体流程是这样的:每个位置更新后的向量和c拼接,经过线性变换得到带上下文的新表示。打分公式和标准缩放点积注意力类似,但query和key里都带有c的贡献。最终聚合value时再把上下文信号融合回来。这样做的效果是:如果某个物品和全局上下文更契合,哪怕它在局部图上离得很远,也能拿到更高的注意力权重。

用一句话总结设计动机:在局部图传播之外,给模型一个显式的全局游标。没有这个游标,注意力盯着相邻物品转;有了游标,模型才真正理解这一整段行为是围绕什么意图展开的。我给非技术背景的同事讲这段时,常用一个类比:同一句话“苹果”,放在水果主题和数码主题里,讨论的重点完全不同。c就是这段话的主题背景。

2.4 预测头与损失函数的设计取舍

编码完成后,GC-SAN取最后一个真实物品的向量作为局部兴趣,同时把整个session的向量做平均池化作为全局兴趣,两者拼接后得到会话最终表征。然后把这个表征和候选物品embedding做内积打分,经过softmax得到概率分布。

损失函数是最常规的交叉熵,配合L2正则。预测端没有做太复杂的事情,说明论文把创新全部押在表征编码上,预测头保持简单可控。这个设计思路在工程上很友好,模型主体替换成本低,头部改动少,上线验证时不用动推荐漏斗的整体结构。

3. 实验数据说了什么:SOTA指标背后的三个细节

3.1 数据集、评估指标与预处理套路

实验的公开数据集是业界经常用的两个电商会话数据集,一个叫Yoochoose,一个叫Diginetica。数据预处理延续了前面工作的惯例:过滤长度太短的session,过滤出现次数过少的低频物品,然后按时间顺序划分训练集和测试集。Yoochoose还会按比例切出1/64和1/4两个数据规模变体,用来观察数据量对模型效果的影响。

评估指标是Recall@20和MRR@20。前者看目标物品是否出现在前20个推荐结果里,后者看目标物品在排序里的位置有多靠前。会话推荐场景里,用户往往只点击下一个物品,这两个指标一个关心召回,一个关心排序位置,配合起来能比较完整地衡量模型质量。只看其中一个容易有偏差,比如模型总把热门物品排在前面,Recall可能不高,但MRR也不会太好。

3.2 主实验结果:从统计方法到深度模型的跨越

和GC-SAN同场竞技的,有S-POP、Item-KNN这样的传统方法,也有GRU4REC、NARM、SR-GNN这些深度模型。主实验里,GC-SAN在两个数据集上全面超过所有基线,在Yoochoose 1/64上Recall@20接近70%的量级,比SR-GNN又有一个稳定提升,MRR的提升同样存在。说明在会话级短期预测上,把图结构和全局上下文结合,确实比单用其中任何一种更能打。

读实验时我会特别关注深模型和浅模型之间的差距。从Item-KNN到GRU4REC是一次大的跨越,但SR-GNN和GC-SAN之间的差距相对温和。这其实在提醒我:当数据规模有限、session又很短时,模型结构再复杂,收益也是边际递减的。工程上做技术选型时,别一上来就上最复杂的模型,先跑通简单的基线和业务指标对齐,再决定要不要为了最后那一个点的提升付出额外的部署成本。

3.3 消融实验拆解:GNN和上下文各自的贡献

论文做了很充分的消融实验。去掉GNN层,只剩上下文自注意力,效果明显下滑,说明图结构的局部转移信息不可替代。把上下文自注意力换成普通自注意力,保留GNN,效果也有下降,说明那个简单的平均池化上下文向量不是白加的。两个组件组合在一起,才能同时捕获局部结构和全局意图。

我特别注意到一个现象:对比去掉GNN和去掉上下文这两组掉点的幅度,前者通常更大。这说明图结构是这个模型的骨架,上下文自注意力是补全血肉。如果你资源有限只能保一个模块,优先保GNN;如果你想让模型再涨点,再去优化上下文注意力这块。这个结论对做后续改进方向很有指导意义。

除了消融,训练时还有个实现层面的技巧:对embedding做dropout,随机把部分物品的嵌入置零。session数据量通常不大,这种操作相当于构造了带噪声的样本,能有效压低过拟合。我自己的复现里,去掉embedding dropout,验证集指标大概会掉一个多点,这个操作值得保留。

3.4 容易被忽略的几个训练技巧

论文里的训练超参不花哨:embedding维度100左右,batch size 100,学习率0.001,权重衰减1e-5。关键反而不在超参数值上,而在几个容易被忽略的小操作。第一,会话图在batch内按session独立构建,千万不能全局共用一张图。第二,GGNN传播轮数固定为1,不要贪深。第三,所有padding位置在注意力阶段必须被mask掉,这是复现是否成功最直接的分水岭。

另外,数据规模对效果影响也很有意思。对比Yoochoose 1/64和1/4两个变体,数据量增大后,GC-SAN相对基线的领先优势更稳定。这说明图结构加上下文注意力不只是在小数据上拟合出来的结果,它确实学到了可泛化的转移模式。对做业务的人来说,这算一个正向信号:数据量上来时,这类模型是能持续吃数据的。

4. 复现GC-SAN:图构建、掩码与训练中的实操记录

4.1 构建邻接矩阵:出边、入边与自环缺一不可

复现的第一步是把会话图构建的代码写对。我先给一个反映核心逻辑的示意:

import numpy as np def build_session_graph(seq): nodes = {} for node in seq: if node not in nodes: nodes[node] = len(nodes) n = len(nodes) A_out = np.zeros((n, n)) A_in = np.zeros((n, n)) for i in range(len(seq) - 1): src = nodes[seq[i]] dst = nodes[seq[i + 1]] A_out[src][dst] += 1.0 A_in[dst][src] += 1.0 for idx in range(n): A_out[idx][idx] += 1.0 A_in[idx][idx] += 1.0 A_out = A_out / (A_out.sum(axis=1, keepdims=True) + 1e-9) A_in = A_in / (A_in.sum(axis=1, keepdims=True) + 1e-9) return A_out, A_in

这个写法省略了batch对齐,但核心逻辑足够了。出边矩阵用于正向传播,入边矩阵用于反向传播,同时加上自环保证节点自身信息不被冲掉。我第一次实现时忘了加自环,结果GNN第一轮传播把自己原本的特征全洗掉了,指标断崖下跌,查了半天才发现是这个原因。归一化需要放在自环之后,否则权重分配会被自环带偏。

4.2 Padding和Mask:一次复现中最容易翻车的两处

一个batch里的session长度不同,通常要padding到最大长度。GNN阶段要根据padding后的图计算邻接矩阵,padding位置没有实际物品,对应的邻接矩阵行是零向量,这不影响传播。但注意力阶段就麻烦了,计算每个位置对整条序列的注意力分数时,padding位置必须用mask把分数置为负无穷,否则softmax会把注意力分给一堆空位置。

另一个容易踩的坑是最后一个真实物品的定位。session经过padding后,最后一个位置已经不是原来最后点击的物品。你得把每个session的真实长度存下来,在预测阶段用真实长度减1对应的位置取最后的向量。如果错取成padding向量,模型看起来还在收敛,实际上学的全是mask噪声,验证集指标会非常不稳定。这两个坑我都在复现时踩过,每次排查到最后都发现是基本功出了问题。

还有损失函数里的负样本处理。全物品softmax在item量大的时候算不动,实践中常用batch内负采样:把同一批次其他session里的真实物品当作负样本。这个策略实现简单,效果稳定,我在两个数据集上都验证过。如果追求精细,可以再按频次做负采样加权,但对GC-SAN来说收益不大。

4.3 过拟合控制:embedding dropout比想象中更重要

这类模型很容易过拟合。session数据量通常只有几十万级别,embedding表却有几十万甚至上百万参数。我的建议是:embedding维度不要贪高,100维足够;训练必须开embedding dropout,否则训练集和验证集指标的差距会大到离谱。

学习率从0.001开始是个稳妥选择,如果loss徘徊不降,降一半再试。有人会给注意力层配warmup,前几百步把学习率从0线性升上来,让参数稳定一些。我实测下来,warmup对GC-SAN的帮助没有对纯Transformer那么大,用固定学习率也能跑出好结果。学习率之外,权重衰减保持1e-5级别即可,过大的权重衰减会把这些稀疏特征压得太死。

最后一个建议:GNN传播层数和注意力层数都不要堆太多。论文里GNN一层、自注意力一层就已经拿到SOTA,强行加深只会增加过拟合风险。我把注意力层数从1加到2试过一次,指标不但没涨,反而微跌。短会话场景里,模型复杂度收益非常有限,与其加深,不如去调数据预处理和embedding dropout。

4.4 落地场景与工程限制

说点落地的观察。GC-SAN适合会话内行为相对丰富、用户身份不明的场景:游客浏览电商页面,短视频App里一次连续刷新,酒店机票预订流程里的本轮点击。这些场景的session本身承载了大部分决策信息,图结构和上下文的组合能发挥价值。

相反,如果session只有一两条行为,图几乎退化成孤立节点,GNN的信息传播无从谈起。这种场景不如直接用热门榜或者物品向量检索。另外,新物品在训练时没有积累边关系,图传播给不了它任何信息,落地时必须在召回侧给新物品留一条兜底路径,比如内容相似度匹配。

线上部署方面,GNN和自注意力在batch推理时都是矩阵运算,一张普通GPU卡延迟没问题。更值得关注的是邻接矩阵的构建开销,数据量大时,图构建要在数据读取阶段并行预处理,不要塞进模型forward里现算,否则瓶颈一定出在这里。

5. 读完论文后我还在琢磨的几个问题

5.1 全局上下文向量用平均池化合理吗

作者选择把所有节点向量平均池化成上下文向量c,简单、稳定、可解释。但平均池化默认每个物品对会话意图的贡献相同,这并不完全符合直觉,用户最后几次点击的信息量通常大于开头。后续不少工作沿着这个方向改进,比如用注意力池化替代平均池化,或者把时间衰减嵌进上下文计算。

我自己在复现时试着把平均池化换成了带位置权重的加权池化,P@20有一定提升,但幅度不大。这也说明GC-SAN对上下文表达形式不是特别敏感,真正的收益来自“把全局信号注入注意力”这件事本身。如果你想在此基础上做改进,从上下文向量的计算方式入手,比直接改注意力结构更值得试。

5.2 这个框架和更长的序列模型怎么衔接

GC-SAN的框架本质上是两段式编码:先GNN做局部结构编码,再注意力做全局上下文编码。这个思路相当通用,完全可以展开成图加Transformer的统一模型,或者用session图替代传统位置编码,都是自然扩展方向。

如果业务里已经有一个大规模序列模型做排序,GC-SAN可以作为session侧的特征编码器,输出的向量和其他特征拼接,不会有排异反应。我在一个内部排序实验里用GC-SAN的session向量作为辅助特征,离线的AUC涨了一点,线上点击率也有正向反馈。这种接入方式很轻,不用改动推荐漏斗的主体。

5.3 复杂度现实:精排阶段的定位

任何论文方法都活在它当时的数据规模和算力条件下。GC-SAN的图传播是在batch内的稠密矩阵上做的,item量几百万级时,全量softmax不可行,工业落地必须配召回加粗排加精排的漏斗结构。

更合理的路线是把GC-SAN用在精排阶段,让召回层用双塔或者倒排索引先砍到几百个候选,再让这个模型输出精确分数。这样既享受了图结构建模的优势,又不会让算力成为瓶颈。如果你正在设计新系统,不要试图把会话图铺满全量物品,那是工程灾难,不是模型能力问题。

我自己把这篇论文从读到复现完整走了一遍,最大的体会是:一个看起来复杂的模型,往往只是两个成熟组件的有机关联。GC-SAN不是第一个把GNN用在推荐上的,也不是第一个用注意力做序列建模的,但它给了一个非常干净的示范,怎么用一句话讲清楚模型创新在哪里。如果你最近也在折腾会话推荐,建议动手实现一次这个模型,别只看公式。跑通之后再回头看那些后续工作,会发现大多都是在它这个骨架上做局部替换和叠加,理解起来会轻松很多。

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

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

立即咨询