设想你搬进一个新小区:楼里住着几百户人家,街边开着一家家小店。谁去过哪家店、买过什么,物业都会记进一本“社区关系簿”。传统的推荐召回,就像小区办了一场相亲大会——住户写一张“我喜欢什么”的喜好卡,店铺挂一张“我卖什么”的招牌,红娘直接拿卡片对招牌。卡片上没写、招牌上没说,红娘就只能摇头:不认识。 可现实生活不是这样交友的。你不知道巷子深处有家书店正中你下怀,但你的邻居恰好常去;你想给孩子挑玩具,同事的大学室友正好是母婴店的资深常客。这些“朋友的朋友”透露出的信号,往往比自我介绍更准。图召回(GNN 推荐)干的正是这件事:把“谁认识谁”织成一张大网,让信息顺着网线一跳一跳传开。这样一来,每个人和每家店都会因为“间接认识的人”而变得立体。 这种顺着关系网打听消息的机制,能挖出双塔模型看不见的“高阶协同信号”;对刚搬进来的新住户(冷启动用户)和藏在角落的小店(长尾物品),尤其有效。读完这篇,你会明白图召回如何把小区口碑网变成推荐系统的情报网,又怎样在大厂落地运转。
图召回是工业级推荐系统的核心召回手段。掌握它,你就真正理解了“协同信号”的内核,这是成长为资深推荐算法工程师的必修课。
可视化演示
展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。
本节配有交互动效(进入视口后自动加载,约 3KB)。
系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T
双塔模型局限
相亲角里,用户举着“爱看悬疑小说、周末爬山”的喜好牌,物品举着“某本书,评分 4.7”的招牌。红娘只看牌子像不像——牌子没写“我朋友喜欢什么”,她便永远不知道你们其实很合拍。双塔召回模型就是这位红娘,它撑起了整个推荐系统的第一道大门。
在推荐系统里,双塔召回是出场率最高的模型:用户塔把历史行为压缩成一个向量 $e_u$,物品塔把属性编码成向量 $e_i$,最后用内积算匹配分。它高效、易扩展,几十亿物品靠向量索引也能秒级检索,所以至今仍是工业标配。但结构决定能力——用户和物品只在最后一层“对一次牌”,全程没有信息往来,因此它只能建模“直接交互”。
训练时,双塔靠正负样本把用户向量和“点过的物品”拉近、和“没点过的物品”推远。问题在于:用户没点过 B,不代表他不喜欢 B——B 可能是“点了 A 的人最常买的第二件”。双塔看不到 A 与 B 之间的间接关联,两者的内积被压得很低,B 压根进不了候选集。这个公式里只有一对向量、一次内积,这就是双塔的全部“缘分”:
相亲角撮合不了“朋友介绍”的缘分,因为牌子上没写朋友的名字。要发现这种缘分,得换一张地图来看——不看牌子,看整张关系网。
- 用户向量用户塔把 ID 和行为序列映射成一个稠密向量,比如 64 维,代表这位住户的“画像”。
- 物品向量物品塔把物品 ID 和属性映射成同维向量,代表店铺的“招牌”。
- 转置与内积把用户向量转置后与物品向量做内积,数值越大,画像与招牌越合拍,候选越靠前。
交互图构建
好在物业有一本“拜访记录簿”:谁几点去了哪家店,记得清清楚楚。图召回的第一步,就是把簿子画成一张网——住户是圆点,店铺是方块,一次拜访就画一条连边。这张网,就是用户-物品交互图,也是图神经网络(GNN)眼里的“小区地图”。
数学上,这张网叫“二部图”:顶点分成用户集合和物品集合两类,边只存在于用户和物品之间,不存在“用户连用户”或“物品连物品”的边。日志里的点击、加购、购买都能变成边,还可以按行为类型给边加权——购买权重高于点击,让“真金白银”更有发言权。
有了图,就要把它“喂”进矩阵。邻接矩阵 A 的每个元素记录两个节点之间是否有边(或边权)。为了让热门节点不“霸麦”,还要做对称归一化:用两端度数的平方根倒数缩放。这一步直接对应后面传播公式里的归一化系数,是防止“爆款店淹没小众店”的关键。
拜访记录簿画出来的网越接近真实社区,小道消息传得就越准。这张网,是整个口碑传播的地基。
- 顶点集图中的全部节点,既包含用户也包含物品,合起来才是完整的“社区通讯录”。
- 两类顶点分边用户集与物品集不相交,边只跨类出现,所以称为“二部图”。
- 交互边集每条边代表一次真实交互,点击、加购、购买都能入边,共同构成图结构。
邻居采样聚合
想了解新邻居的口味,你不会敲遍全楼五百户的门,而是挑两三户最熟的人家打听。GNN 的“邻居采样”就是这个思路:从邻居里随机挑固定个数,把“打听”的成本管住。
二部图里,爆款店铺可能有几十万邻居用户,全量聚合在算力上不可行。采样让每个节点每层只保留 k 个邻居(常见 10~20 个),配合 mini-batch 训练,图模型才能扩展到亿级节点。这也是 GraphSAGE 的核心贡献之一——先采样,再聚合。
采样之后是聚合。聚合函数要把一堆邻居向量“拧成一股绳”:均值聚合做简单平均;求和聚合对度数敏感;注意力聚合给重要邻居更高的权重。聚合结果再乘一个可学习矩阵并过激活函数,得到节点的新表示,公式如下:
向两三户人家打听,综合意见后再做决定——“采样 + 聚合 + 变换”正是口碑传播最经济的样子。
- 邻居集合节点 u 的一跳邻居,即直接交互过的物品(或用户)。
- 聚合函数把一堆邻居向量合成一个向量,常用均值、求和或注意力加权。
- 变换与激活可学习矩阵 W 做线性变换,σ 引入非线性,得到第 l+1 层的表示。
多层传播机制
口碑不会只传一圈。你问邻居,邻居的话也是从朋友那里听来的;消息经过“邻居的朋友的邻居”,一圈圈传到你这里,越传越丰富,也可能越传越变味。GNN 的每一层,就是口碑传一圈。
第 0 层表示是节点自己的初始嵌入。第 1 层聚合一跳邻居;第 2 层在“邻居的邻居”上再聚合……经过 K 层传播,节点表示就“看见”了 K 跳之内的整片社区。这个跨越多跳汇总出的信号,正是论文里反复出现的“高阶协同信号”:它让“甲→B 店→乙→C 店”这样的四段关系也能参与打分。
具体传播常用归一化加权求和(LightGCN 风格):第 k+1 层表示 = 所有一跳邻居第 k 层表示的加权和,权重是两端度数乘积开根号的倒数。度数越高的节点,“发言”被稀释得越多,避免全网被爆款带偏。
社会经验告诉我们,传话超过三圈就严重失真——图上同样如此:层数一多,所有节点表示趋于一致,这叫“过平滑”。因此落地时一般只用 2~3 层传播,信息够丰富,又不至于变味。
- 邻居遍历求和对用户 u 的所有一跳邻居 v 做加权求和,把整片邻域“拧进”新表示。
- 对称归一化系数两端度数乘积开根号取倒数,热度越高的邻居发言权重越低,防止爆款带偏。
- 上一层表示邻居 v 在第 k 层的向量,传播一轮后变成 u 在第 k+1 层的素材。
图卷积变体
同一个小区,打听消息的流派五花八门:保守派只信铁哥们的意见(GraphSAGE 采样聚合);学院派把整条街都拉来开会、人人发言加权平均(GCN 全邻居卷积);还有一群极简主义者说“别整花活,直接听结论”——这就是 LightGCN。
GCN 把邻居信息做线性变换后再加非线性激活,理论优美,但训练开销大;GraphSAGE 主打“采样 + 多种聚合器”,面向大规模图;NGCF 在消息里再塞一道“特征交叉”。而 LightGCN 通过实验发现:在协同过滤场景下,非线性激活和特征变换几乎帮倒忙,去掉它们,只用纯线性传播,效果反而更好、更稳。
LightGCN 的最终表示,是把第 0 层到第 K 层的所有表示加权求和。权重可以固定为平均,也可以通过训练学习。这相当于把“每一圈听到的口碑”都记在心里,而不是只听最后一圈:
各流派都在回答同一个问题:口碑怎么传最准?在“以关系为主”的召回场景里,把简单做到底的 LightGCN,往往就是小区里人缘最好的那一个——这也是后面工业实践的主角。
- 各层表示第 0 层是初始嵌入,第 k 层是传播 k 圈后的表示。
- 层权重α_k 表示第 k 圈口碑的权重,可以固定成平均,也可以训练得到。
- 加权求和把所有圈听到的消息合起来,得到最终用于召回的表示。
冷启动与长尾
新住户刚搬来,拜访记录簿上一片空白。传统双塔只能对他“闭眼推热门”。图召回却不同:只要新住户还有哪怕一两条交互,他就已经挂在网上,邻居们的消息照样会涌向他——这就是“冷启动友好”。
长尾物品同理。巷子深处的手工皮具店,总顾客也许只有两百人,但在两百人的小圈子里口碑极好。图召回让它的向量从这两百个熟客身上“借光”,熟客再通过一跳交互,把皮具店带进更多相似用户的候选集。比起只看“买了 A 还买 B”的 ItemCF,GNN 的两三跳信号能触及更远、更精准的人群。
有了传播后的表示,召回打分就落在“向量相似度”上。两个物品的余弦相似度越接近 1,说明它们在图上共享的口碑圈越重合。对冷启动用户而言,他点过的哪怕只有一件物品,也能顺着相似物品链摸到想买的东西。
小区最珍贵的不是广场上的大超市,而是那些靠口碑活着的小店。图召回让“酒香不怕巷子深”第一次在大规模系统里成为现实。
- 向量点积两个物品向量做内积,衡量它们在嵌入空间里的接近程度。
- 模长归一化各自除以向量长度,把得分压缩到 [-1, 1],消除量纲影响。
- 余弦相似度接近 1 代表口碑圈高度重合,是召回排序的直接依据。
工业落地实践
口碑网要在几百万人的小区里真正运转,需要一整套“物业体系”:有人把拜访记录簿整理成网(离线构图),有人给每个住户画一张像(产出嵌入),有人把画像按街区索引好(向量索引),有人守在门口回答“你该去哪家店”(在线服务),最后还要统计问路的满不满意(评估)。
离线阶段从日志和特征仓库抽取交互,增量构建二部图,用“采样 + mini-batch”训练 LightGCN 等模型,产出用户与物品嵌入。这些嵌入会推送到 FAISS 或 HNSW 这类近邻索引中,支撑几十亿向量的近似检索。
在线阶段,用户请求一到,取出他的嵌入,在索引里做 ANN 搜索,一次性捞回 Top-K 个候选物品交给精排。对新用户,还可以临时把他的邻居嵌入“拉一把”做暖启动。效果评估先离线看召回指标,再上 A/B 实验验证线上指标:
从“记录簿”到“问路摊”,这套流水线让图召回真正从论文走进现实。它通常与双塔模型组成混合召回——一个管广度、一个管深度,共同守住推荐系统的大门。
- 候选集模型给用户 u 召回的 Top-K 个物品,即情报网递上来的“短名单”。
- 真实交互集测试集中用户 u 真正点击或购买的物品,即“事后诸葛亮”的答案。
- 交集与平均短名单与真实答案的交集大小除以真实集大小,再对全体用户平均。
AI 应用场景
场景 1:电商“猜你喜欢”的候选召回
解决的问题:电商用户行为稀疏,双塔召回只能建模“直接点击”,无法利用“买过 A 的人常买 B”这类间接关联;新用户刚注册、只加购一两件商品时尤其吃亏。图召回把“用户-商品-店铺/类目”织成一张多关系图,让候选集不再依赖用户积累的大量行为。
具体案例:某电商用户 D 刚收藏了一款登山杖。图召回离线构建的用户-商品图上,登山杖恰好连接着一批资深户外爱好者,这批人还收藏过冲锋衣、露营灯。在线召回时,D 的向量经传播后与冲锋衣的余弦相似度显著升高,冲锋衣进入 Top-50 候选并最终被精排选中、完成下单。
场景 2:音乐平台的长尾歌曲冷启动分发
解决的问题:头部歌曲吃掉绝大多数播放,独立乐队、小众品类长期无人问津。图召回把“用户-歌曲-歌手”构图,让一首只有几百播放的新歌,也能通过“老乐迷→同类歌曲→新乐迷”的 2~3 跳路径被精准召回。
具体案例:某独立乐队发布新单曲,上线首周仅 600 播放。接入图召回后,算法识别出图中与它共享“乐迷邻居”的同类歌曲,把单曲推进 5 万位相近品味用户的候选集,当周播放量提升 40%,收藏率比均值高 2.1 倍。
Python 演示
import numpy as np np.random.seed(42) # ---- 小区交互图: 3 个用户(甲/乙/丙), 4 家店(A/B/C/D) ----NUM_U, NUM_I = 3, 4EDGES = [(0, 0), (0, 1), (1, 1), (1, 2), (2, 2), (2, 3)]DIM, LAYERS, LR, EPOCHS = 8, 2, 0.05, 100TRIPLES = [(0, 0, 2), (0, 1, 3), (1, 1, 0), (1, 2, 3), (2, 2, 0), (2, 3, 1)] def build_adj(): n = NUM_U + NUM_I A = np.zeros((n, n)) for u, i in EDGES: A[u, NUM_U + i] = 1.0 A[NUM_U + i, u] = 1.0 deg = A.sum(axis=1) d_inv = np.where(deg > 0, np.power(deg, -0.5), 0.0) return A * np.outer(d_inv, d_inv) # 对称归一化邻接矩阵 A_norm = build_adj() def propagate(E): """LightGCN 式传播: 每层沿图扩散一跳, 最后取各层平均""" layers = [E] for _ in range(LAYERS): layers.append(A_norm @ layers[-1]) return np.mean(layers, axis=0) def sigmoid(x): return 1.0 / (1.0 + np.exp(-np.clip(x, -20, 20))) E = np.random.randn(NUM_U + NUM_I, DIM) * 0.1for epoch in range(EPOCHS): total_loss = 0.0 for u, i_pos, i_neg in TRIPLES: H = propagate(E) # 传播后的全部节点表示 a = float(H[u] @ H[NUM_U + i_pos]) # 正样本得分 b = float(H[u] @ H[NUM_U + i_neg]) # 负样本得分 total_loss += -np.log(sigmoid(a - b)) # BPR 损失 s = sigmoid(b - a) # BPR 梯度系数(教学简化版) E[u] += LR * s * (E[NUM_U + i_pos] - E[NUM_U + i_neg]) E[NUM_U + i_pos] += LR * s * E[u] E[NUM_U + i_neg] -= LR * s * E[u] if epoch % 20 == 0: print(f"epoch {epoch:3d} loss={total_loss:.4f}") # ---- 召回: 给用户甲(索引 0)输出 Top-2 店铺 ----H = propagate(E)scores = [float(H[0] @ H[NUM_U + i]) for i in range(NUM_I)]top2 = sorted(range(NUM_I), key=lambda i: scores[i], reverse=True)[:2]print("用户甲的 Top-2 推荐店铺:", top2, "得分:", [round(scores[i], 3) for i in top2])
A[u, NUM_U + i] = 1.0把“用户 u 逛过物品 i”记进邻接矩阵,这是把拜访记录簿画成图的关键一步。return A * np.outer(d_inv, d_inv)对称归一化,对应公式里的 1/sqrt(|N(u)||N(v)|),防止爆款店霸麦。layers.append(A_norm @ layers[-1])矩阵乘法就是消息沿边传播一跳,每循环一次多传一圈口碑。return np.mean(layers, axis=0)各层表示取平均,等价于 LightGCN 的 alpha_k=1/(K+1) 固定权重。total_loss += -np.log(sigmoid(a - b))BPR 损失:正样本得分要比负样本高,差得越多损失越小。s = sigmoid(b - a)梯度系数:模型越自信(a-b 很大),这次更新的力度越小。E[u] += LR * s * (E[NUM_U + i_pos] - E[NUM_U + i_neg])把用户向量往正样本方向拉、往负样本方向推。top2 = sorted(range(NUM_I), key=lambda i: scores[i], reverse=True)[:2]按相似度排序取前 2,就是召回 Top-K。
常见误区
误区我理解图召回就是把用户和物品的特征拼在一起,塞进一个更深的全连接网络,跟普通双塔没有本质区别。
正解图召回的关键是“消息沿边显式传播”:每一层都在做邻居聚合,信息按图结构流动,而不是在同一个向量上反复变换。特征拼接是“记住了邻居的名字”,GNN 是“真的跟邻居交上了朋友”——图的拓扑结构直接参与了计算。
为何常见:很多教程把 GNN 画成普通神经网络的方块图,掩盖了“边”这个核心元素,初学者自然以为只是层数变多。
误区多跳能带来更多信息,那我干脆把传播层数加到 5 层、6 层,效果肯定更好。
正解图神经网络有“过平滑”问题:层数过多,所有节点的表示会逐渐趋同,变成“千人一面”,区分度骤降;同时邻域指数级膨胀,采样和计算开销暴涨。工业界经验是 2~3 层足够,既覆盖有效高阶信号,又保持区分度。
为何常见:深度学习的直觉是“越深越强”,但图传播本质是邻域平滑,不是特征抽象,直接套用直觉就翻车了。
误区协同过滤不也是拿邻居的行为推荐吗?图召回不就是换个包装的 UserCF?
正解UserCF/ItemCF 只做“直接邻居投票”,影响力限于一跳;GNN 通过多层传播,把任意长度路径上的信号都编码进向量,而且表示是端到端学出来的,还能融入特征。一句话:UserCF 只看常客的推荐,GNN 还听得见常客的朋友怎么说。
为何常见:两者都源于“物以类聚、人以群分”的思想,表面相似;内部机制差异要拆开看才清楚。
误区图召回效果更好,那召回阶段直接全换成 GNN,双塔可以退休了。
正解双塔简单高效,支持超大规模物品全量打分,是工业地基;GNN 需要构图、采样、索引等额外工程成本,也并非所有场景都有优质图信号。成熟系统通常做“双塔为主、图召回补充”的混合召回,各取所长。
为何常见:论文对比多用离线指标,看不到工程复杂度与系统稳定性,容易高估新方案、低估老方案。
面试题
Q1. 图召回中的“交互图”通常用什么结构表示?
- A.仅用户节点的同构图
- B.仅物品节点的同构图
- C.用户-物品二部图← 正确答案
- D.带自环的树形结构
答案:C。交互图把用户和物品作为两类顶点,边只存在于用户与物品之间,构成二部图;同构图只有一类节点,树形结构则限制了边的形态,都不符合。
Q2. GNN 一层的消息传播主要完成什么动作?
- A.直接邻居信息聚合← 正确答案
- B.全图节点随机排序
- C.用户原始日志压缩
- D.物品属性特征筛选
答案:A。GNN 每一层的核心是把一跳邻居的向量汇总到中心节点,完成“打听周围邻居意见”的动作;随机排序、日志压缩、特征筛选都不是图传播的本职。
Q3. 相比双塔召回,图召回最核心的优势是什么?
- A.只建模直接交互关系
- B.挖掘多跳间接关联信号← 正确答案
- C.减少训练样本的规模
- D.加快向量检索的速度
答案:B。双塔只能做“一次内积”的直接匹配,图召回通过多层传播让“甲→B 店→乙→C 店”这类多跳关系参与打分,这正是它捕捉高阶协同信号的核心优势。
Q4. 邻居采样最主要的作用是什么?
- A.控制计算与内存开销← 正确答案
- B.提升模型的表达能力
- C.让梯度更新更加稳定
- D.防止过平滑的发生
答案:A。高热度节点可能有几十万邻居,全量聚合算力不可行,采样固定 k 个邻居正是为了控制开销;它不是用来提表达能力或防过平滑的手段。
Q5. LightGCN 与标准 GCN 的主要区别是什么?
- A.去除非线性与特征变换← 正确答案
- B.增加更深的注意力层
- C.把图换成随机游走
- D.使用卷积核做平滑
答案:A。LightGCN 发现协同过滤场景下非线性激活和特征变换收益甚微,直接去掉它们,只用线性传播加层融合;它并没有引入注意力或随机游走。
Q6. 图神经网络中的“过平滑”指的是什么?
- A.每层参数太多易过拟合
- B.节点表示趋同失去区分度← 正确答案
- C.邻居数量爆炸内存溢出
- D.训练数据重复导致欠拟合
答案:B。层数过多时,每个节点反复吸收整片邻域的信息,最终所有节点向量趋于一致,丧失个性化,这就是“千人一面”的过平滑现象。
Q7. 为什么图召回对冷启动用户效果更好?
- A.直接复制相似用户向量
- B.邻居信息沿图流入新节点← 正确答案
- C.随机初始化表示更丰富
- D.减少了对行为日志依赖
答案:B。冷启动用户交互极少,但只要他在图上,邻居的信号就会经传播流入他的表示,相当于“新住户靠邻居口碑补齐画像”;并非复制向量或随机初始化。
Q8. 工业界图召回落地通常包含哪些环节?
- A.离线训练图模型产出嵌入← 正确答案
- B.建立向量索引近似检索← 正确答案
- C.在线召回候选物品集← 正确答案
- D.训练语言模型生成文案
答案:ABC。落地闭环是“离线构图训练产出嵌入 → 推送到 FAISS/HNSW 索引 → 在线 ANN 检索回 Top-K 候选”;生成文案属于内容生产,与召回链无关。
Q9. LightGCN 的最终用户表示是怎么得到的?
- A.只取最后一层传播结果
- B.各层传播结果加权求和← 正确答案
- C.取所有邻居向量的中位数
- D.用户物品向量直接级联
答案:B。LightGCN 把第 0 层到第 K 层的表示按权重 α_k 加权求和,兼顾直接与多跳信息;只取最后一层会丢掉早期信息,中位数和级联都不是它的做法。
Q10. 关于多层传播与过平滑,下列说法正确的有哪些?
- A.传播层数越多信息越丰富
- B.层数过多节点表示会趋同← 正确答案
- C.工业界常用两到三层传播← 正确答案
- D.邻居采样可彻底消除过平滑
答案:BC。层数过多会过平滑、表示趋同,工业界因此常用 2~3 层;“越多越好”和“采样消除过平滑”都不成立——采样只控开销,不能根治过平滑。