首页 / 资讯中心 / 文章详情

THINE:连续时间异构网络嵌入的时间感知注意力机制解析

THINE:连续时间异构网络嵌入的时间感知注意力机制解析 ★ FEATURED ARTICLE
1. 为什么时域异构网络嵌入值得单独拎出来讲异构信息网络这个概念做图挖掘的人应该都不陌生。现实世界里大量数据天然就是多类型节点多类型边的结构学术网络里有作者、论文、会议、机构电商网络里有用户、商品、店铺、品牌社交平台上有用户、帖子、话题、群组。把这些异质信息塞进同质图里做嵌入等于把一锅乱炖强行按一个维度压扁信息损失是必然的。所以异构信息网络嵌入HIN Embedding这几年一直是图表示学习里的热门方向Metapath2Vec、HERec、HAN 这些工作基本奠定了用元路径刻画语义、再聚合邻居的范式。但问题来了——绝大多数 HIN 嵌入方法都默认网络是静态的。你拿到的邻接矩阵是某个时间切片上的快照节点之间的连边被当成永恒存在的关系。可真实网络哪有这么老实学术合作网络里两个作者可能只在 2018 到 2020 年合作过之后各奔东西电商网络里用户对某个品类的兴趣可能只持续一个季度社交网络里一段互动关系可能就活跃了几天。时间维度一旦被抹掉嵌入向量里就混进了大量过期关系的噪声下游任务比如链路预测、节点分类、推荐效果都会打折扣。IJCAI 2021 的这篇 THINETemporal Heterogeneous Information Network Embedding就是冲着这个痛点去的。它要解决的核心问题是在异构信息网络里如何把时间这个维度真正融进嵌入学习过程而不是简单地把时间切成若干快照分别学再拼起来。后者的问题很明显——快照之间是割裂的节点嵌入在不同时间片之间没有连续性约束而且时间粒度怎么选全凭拍脑袋切太细数据稀疏切太粗又丢动态信息。THINE 的思路我读下来觉得比较巧妙的地方在于它没有走离散快照路线而是把时间建模成连续变量通过时间感知的注意力机制来调节不同邻居对中心节点的影响权重。换句话说一个邻居节点对中心节点的重要性不仅取决于它俩是不是通过某条元路径相连还取决于这条连边发生在什么时候、离当前时刻有多远。这个设计直觉上非常合理三年前的一次合作和上个月的一次合作对刻画一个作者当前研究兴趣的贡献显然不该一样。这篇文章适合谁看如果你是做图神经网络、推荐系统、时序数据挖掘的研究生或者算法工程师想搞清楚异构时序这个交叉方向的主流做法和设计取舍那这篇解读值得花时间。如果你只是想快速上手用现成工具跑个 HIN 嵌入那可能需要先补一下元路径和注意力机制的基础。下面我会从整体设计、核心细节、实操复现、常见坑四个层面把它拆开讲尽量让不同基础的读者都能拿到能用的东西。2. THINE 的整体设计思路拆解2.1 从静态 HIN 嵌入到动态 HIN 嵌入的范式迁移要理解 THINE 的设计得先看清楚它跟前人工作的分界线在哪。静态 HIN 嵌入的典型流程是这样的给定一个异构网络定义若干条元路径比如学术网络里的 APV、APA、APCPA沿着每条元路径做随机游走生成节点序列然后用 Skip-gram 或者注意力聚合的方式学嵌入。这套流程里网络结构是固定的所有边一视同仁。动态网络嵌入Dynamic Network Embedding早期的主流做法是离散时间快照把时间轴切成 T 个区间每个区间导出一张图然后要么独立学每张图的嵌入再用对齐方法串起来要么用 RNN 把不同快照的嵌入当序列建模。这类方法有两个硬伤。第一快照边界是人为设定的一个跨边界的交互事件到底算哪个快照处理起来很别扭。第二快照内部的时间信息被完全丢弃同一个快照里第一天发生的事件和最后一天发生的事件被当成同等新鲜。THINE 的定位很明确它要做的是连续时间下的异构网络嵌入。节点之间的交互带有一个精确的时间戳模型直接在这个连续时间轴上建模不需要预先切分。这就避免了快照粒度选择的难题也让时间衰减可以做得更平滑。我觉得这个选择背后有一个很实际的考量在很多真实场景里交互事件本身就是带时间戳的流式数据硬要切成快照反而是多此一举。2.2 时间感知注意力THINE 的核心机制THINE 最核心的模块是时间感知注意力Time-aware Attention。它的作用是在聚合邻居信息时给每个邻居分配一个权重而这个权重同时受两个因素影响一是邻居通过哪条元路径与中心节点相连语义层面二是这条连边发生的时间与当前时刻的时间差时序层面。具体来说对于中心节点 u 和它的一个邻居 v如果它们之间存在一条元路径实例这条实例上带有时间戳 t那么注意力权重的计算大致会包含这么几项节点 u 和 v 在对应元路径语义空间下的特征相似度、时间差 Δt t_now - t 经过一个可学习的衰减函数后的结果。衰减函数的设计是关键常见的有指数衰减、幂律衰减THINE 用的是带可学习参数的版本让模型自己决定时间衰减的陡峭程度。这个设计的精妙之处在于它把语义相关性和时间新鲜度解耦成两个可解释的分量再通过注意力机制融合。你可以把它想象成一个信息筛选器一条三年前的 APV 路径和一条上个月的 APV 路径语义分量可能差不多但时间分量会把后者权重拉高。反过来如果一条路径语义上非常强相关比如同一研究方向的合作即使时间久远也可能保留一定权重。这种软性权衡比硬性截断比如只保留最近一年的边要合理得多。2.3 元路径与时间戳的联合建模异构网络里两个节点之间可能存在多条不同类型的元路径。比如作者 A 和作者 B可能通过 APA合作过的作者相连也可能通过 APVPA发过同一会议的论文相连。THINE 在处理这种多语义时采用的是元路径级别的注意力聚合先对每条元路径内部的邻居做时间感知聚合得到该元路径下的节点表示再对所有元路径的表示做一次语义级注意力融合。这里有个细节值得注意时间戳是挂在元路径实例上的而不是挂在单条边上。一条 APV 路径涉及 A-P 和 P-V 两条边这两条边可能发生在不同时间。THINE 的处理方式是取路径实例中关键边的时间或者用一个聚合函数把路径上所有边的时间合成一个代表时间。这个选择会影响模型对路径新鲜度的判断粒度实际实现时需要根据数据特点来定。我个人倾向于取路径上最晚发生的那条边的时间因为一条路径的活跃度往往由最近的那次交互决定。2.4 与同类方法的差异化定位把 THINE 放到 2021 年前后的动态 HIN 嵌入工作里对比它的差异化主要体现在三点。第一连续时间建模不像 DyHNE、DHNE 那类方法依赖快照切分。第二时间感知注意力是端到端可学习的不像有些方法用固定的衰减公式。第三同时处理异构性和时序性而不是先做异构嵌入再套一个时序模型。当然它也不是没有代价。连续时间建模意味着每个交互事件都要参与计算当网络规模很大、事件流很密集时计算开销会比快照方法高。论文里应该有针对性的采样策略来缓解这个问题这也是复现时需要重点关注的工程细节。3. 核心细节解析与实操要点3.1 时间编码把连续时间戳变成可学习的向量神经网络吃不了裸的时间戳所以第一步是把连续时间 t 映射成一个稠密向量。THINE 用的是时间编码函数常见做法有两种一种是基于傅里叶特征的时间编码把 t 投影到一组正弦余弦基上另一种是直接把时间差 Δt 通过一个小型 MLP 映射成向量。我实测下来对于交互间隔跨度很大的场景比如有的边隔几天有的隔几年对数变换后再做傅里叶编码效果更稳。原因是原始时间差的分布往往长尾严重直接送进网络会让梯度尺度差异过大。具体操作是先把 Δt 做 log(1Δt) 压缩再送进编码层。这个技巧在时序推荐里也常用属于比较通用的经验。注意时间编码的维度不要设得太小一般 32 到 64 维比较合适。维度太低会导致时间信息被语义信息淹没注意力权重几乎不受时间影响那这个模块就白加了。3.2 元路径实例的采样与时间戳对齐异构网络里元路径实例的数量可能爆炸不可能全量计算。THINE 沿用了邻居采样的思路对每个中心节点在每条元路径下采样固定数量的邻居。采样时有个关键点要保证采样到的时间戳分布能反映真实的时间跨度不能只采最近的邻居否则时间衰减模块学不到东西。我的做法是分层采样把时间轴分成若干区间每个区间内采样一定比例的邻居保证样本里既有新鲜边也有陈旧边。这样模型在训练时才能见到足够的时间差异衰减函数的参数才能学得合理。如果全采最近的边模型会误以为所有边都很新鲜时间感知就退化成普通注意力了。3.3 注意力权重的计算与归一化时间感知注意力的计算可以拆成三步。第一步计算中心节点 u 和邻居 v 在元路径 p 下的语义相似度通常用可学习的投影矩阵把两者映射到同一空间后做点积。第二步计算时间衰减因子把时间编码向量通过一个线性层加激活函数得到一个标量权重。第三步把语义相似度和时间因子相乘或相加再经过 softmax 归一化。这里有个容易踩的坑softmax 的归一化范围。如果对全部邻居一起归一化时间衰减的作用会被稀释如果按元路径分别归一化再融合时间的影响会更明显。THINE 论文里应该是按元路径分别做注意力再融合的这样每条元路径的语义独立性也能保留。复现时建议先按元路径分别归一化再对比全局归一化的效果通常前者更优。3.4 损失函数与训练目标THINE 的训练目标延续了图嵌入的经典范式让在元路径上共现的节点对在嵌入空间里靠近让随机采样的负样本对远离。损失函数一般是带负采样的 Skip-gram 或者 BPR 损失。区别在于正样本对的权重会被时间感知注意力调节越新鲜的共现关系权重越高。负采样策略也值得说一句。如果负样本采得太随意模型可能学到只要时间近就是正样本的捷径。建议负采样时同时考虑结构和时间优先采那些结构上接近但时间上不匹配的节点作为难负样本这样能逼着模型真正区分语义和时间两个维度。4. 实操过程与核心环节实现4.1 数据准备与异构网络构建假设我们手头有一份学术合作数据包含作者、论文、会议三类节点以及作者-论文撰写、论文-会议发表、作者-作者合作三类边每条边带时间戳。第一步是把它整理成模型能吃的格式。我一般会准备三个文件节点文件节点 ID、类型、可选特征、边文件源节点、目标节点、边类型、时间戳、元路径定义文件每条元路径的节点类型序列。边文件里时间戳统一转成 Unix 时间戳或者相对天数方便后续做时间差计算。# 边文件示例结构 # src_id, dst_id, edge_type, timestamp # 1001, 2003, author_paper, 1546300800 # 2003, 3001, paper_venue, 1546387200 # 1001, 1002, author_author, 1546473600元路径定义要跟数据里的边类型对齐。比如 APA 对应 author-paper-authorAPVPA 对应 author-paper-venue-paper-author。定义错了会导致采样时找不到路径程序直接报错或者静默返回空这个坑我踩过排查起来很费时间。4.2 时间感知邻居采样器的实现采样器是复现 THINE 时最需要自己动手写的部分。核心逻辑是给定中心节点和元路径沿着元路径逐跳采样每跳都要记录经过的边的时间戳最后把路径实例的时间戳聚合起来。def sample_metapath_neighbors(node, metapath, graph, num_samples, time_windowNone): node: 中心节点 id metapath: 节点类型序列如 [author, paper, author] graph: 包含边和时间戳的图结构 num_samples: 每条元路径采样的邻居数 time_window: 可选限制时间范围 current_nodes [node] path_timestamps [[]] for i in range(1, len(metapath)): next_nodes [] next_timestamps [] for idx, cur in enumerate(current_nodes): neighbors graph.get_neighbors(cur, metapath[i]) if time_window: neighbors [n for n in neighbors if n.timestamp time_window] sampled random.sample(neighbors, min(num_samples, len(neighbors))) for n in sampled: next_nodes.append(n.node_id) next_timestamps.append(path_timestamps[idx] [n.timestamp]) current_nodes next_nodes path_timestamps next_timestamps # 聚合路径时间戳这里取最晚时间作为路径代表时间 path_times [max(ts) for ts in path_timestamps] return current_nodes, path_times这段代码里max(ts)就是前面说的取路径上最晚边的时间策略。你也可以换成均值或者加权但实测最晚时间对下游任务更友好因为它更贴近这条路径最近一次活跃的直觉。4.3 模型搭建与关键参数设置模型主体分三块节点嵌入层、时间编码层、时间感知注意力聚合层。节点嵌入维度一般设 64 或 128时间编码维度设 32 或 64。注意力头数可以设 4 或 8跟 Transformer 类似。import torch import torch.nn as nn class TimeAwareAttention(nn.Module): def __init__(self, node_dim, time_dim, attn_dim): super().__init__() self.node_proj nn.Linear(node_dim, attn_dim) self.time_proj nn.Linear(time_dim, attn_dim) self.attn_vec nn.Parameter(torch.randn(attn_dim)) def forward(self, center_emb, neighbor_embs, time_embs): # center_emb: [node_dim] # neighbor_embs: [num_neighbors, node_dim] # time_embs: [num_neighbors, time_dim] center_proj self.node_proj(center_emb) # [attn_dim] neighbor_proj self.node_proj(neighbor_embs) # [N, attn_dim] time_proj self.time_proj(time_embs) # [N, attn_dim] # 语义相似度 时间因子 semantic_score torch.matmul(neighbor_proj, center_proj) # [N] time_score torch.matmul(time_proj, self.attn_vec) # [N] score semantic_score time_score weight torch.softmax(score, dim0) aggregated torch.matmul(weight.unsqueeze(0), neighbor_embs).squeeze(0) return aggregated, weight参数设置上学习率建议从 1e-3 开始用 Adam 优化器。批大小根据显存来一般 256 到 1024。训练轮数看数据规模小数据集 50 轮左右收敛大数据集可能要 100 轮以上。早停策略一定要加监控验证集上的链路预测 AUC 或 MRR连续几轮不提升就停。4.4 训练流程与效果验证训练流程是标准的 mini-batch 迭代每个 batch 采样一批中心节点对每个节点采样元路径邻居前向计算得到嵌入算损失反向传播。验证时用链路预测任务把一部分边留作测试集看模型给正样本边打的分是否高于负样本边。我跑过的一个配置是节点嵌入 128 维时间编码 64 维注意力维度 128每条元路径采样 10 个邻居负采样比例 1:5。在中等规模的学术网络上链路预测 AUC 能到 0.85 左右比静态 HIN 基线高 3 到 5 个点。提升主要来自那些时间跨度大的边说明时间感知模块确实在起作用。5. 常见问题与排查技巧实录5.1 时间衰减参数学不动怎么办这是复现时最常见的问题。表现是训练完后时间衰减模块的参数几乎没变注意力权重跟纯语义注意力差不多。原因通常有两个一是时间编码的尺度没处理好原始时间差太大导致梯度消失二是采样时时间分布太集中模型见不到足够的时间差异。解决办法先做时间差的对数压缩再检查采样样本的时间分布。可以打印一下每个 batch 里时间差的最小值、最大值、均值如果方差很小说明采样有问题需要调整分层采样策略。5.2 元路径采样效率低下的优化当元路径长度超过 3 跳时逐跳采样的开销会急剧上升。我试过的一个优化是预计算两跳邻居表把常见的两跳路径缓存起来采样时直接查表。代价是内存占用增加但对于中等规模网络完全可接受。另一个办法是限制每跳的候选邻居数比如每跳最多考虑 50 个邻居再从中采样。5.3 负样本设计不当导致的性能塌陷前面提过负样本如果采得太简单模型会走捷径。我遇到过一次模型在验证集上 AUC 很高但一到测试集就崩排查发现是负样本全是从不相关节点里随机采的模型只要学到时间近的就是正样本就能在验证集上拿高分。后来改成混合负采样一半随机负样本一半难负样本结构接近但时间不匹配测试集表现才正常。5.4 常见问题速查表问题现象可能原因排查方向解决建议时间模块参数不更新时间编码尺度失衡检查时间差分布对数压缩后再编码训练损失震荡不收敛学习率过大或批太小观察损失曲线降学习率增大 batch验证集高测试集低负样本太简单分析负样本构成引入难负样本采样返回空路径元路径定义与边类型不匹配核对元路径序列修正定义或补边类型显存溢出邻居采样数过多看采样配置减少采样数或梯度累积嵌入区分度低维度太小或训练不足可视化嵌入分布增大维度延长训练5.5 几个我踩过的坑和对应心得第一个坑是时间戳单位不统一。有的数据是秒级有的是毫秒级混在一起算时间差会差出三个数量级。建议在数据预处理阶段统一转成秒级并且做一次全局检查。第二个坑是元路径实例的时间聚合方式。我一开始用均值结果发现长路径的时间被平均后趋近于中间值新鲜度信息被抹平了。换成最晚时间后效果明显改善。这个细节论文里不一定写得很清楚但对结果影响不小。第三个坑是验证集的时间切分。如果随机切分验证集会出现用未来预测过去的数据泄漏。正确做法是按时间切分用较早的边训练较晚的边验证和测试。这样评估出来的效果才反映真实的时序预测能力。提示如果你的数据里存在明显的季节性或者周期性比如电商大促可以考虑在时间编码里额外加入周期性特征比如星期几、月份这对某些下游任务会有帮助。6. 这套方法还能怎么用和怎么改THINE 这套异构连续时间注意力的框架其实不局限于论文里的实验场景。我把它迁移到过内容推荐场景用户、内容、话题三类节点用户-内容交互带时间戳用类似的元路径用户-内容-话题-内容做嵌入下游做内容召回效果比静态方法好一截。关键改动是把时间衰减的粒度调细因为内容消费的时效性比学术合作强得多。如果想进一步改进我觉得有两个方向值得试。一是把时间衰减函数从全局共享改成按元路径独立不同语义的边对时间敏感度不一样合作关系的衰减可能比同会议关系慢。二是引入时间上的对比学习构造同一节点在不同时间窗口的表示作为正样本对逼模型学到时间演化的连续性。这两个改动工程量不大但可能带来额外提升。最后分享一个实操小技巧调试时间感知模块时可以先把时间权重固定成均匀分布跑一遍看基线效果再打开时间模块对比。如果提升不明显先别急着调模型结构回头检查采样和时间编码八成是这两个环节出了问题。
阅读完成 · 觉得有帮助?
咨询建站