{"notes":[{"id":"graph-learning/graphsage","category":"graph-learning","legacyPath":"","legacySlug":"","legacyPaths":[],"title":"GraphSAGE","subtitle":"抽样点名","author":"Z. Yuan","paperDate":"2017-06-07","publishedOn":"2023-10-21","world":"external/graphsage","paperTitle":"Inductive Representation Learning on Large Graphs","paperUrl":"https://arxiv.org/abs/1706.02216","bibliography":{"title":"Inductive Representation Learning on Large Graphs","authors":["William L. Hamilton","Rex Ying","Jure Leskovec"],"url":"https://arxiv.org/abs/1706.02216","pdfUrl":"https://arxiv.org/pdf/1706.02216v4","codeUrl":"https://github.com/williamleif/GraphSAGE","preprint":{"id":"1706.02216","submittedOn":"2017-06-07","url":"https://arxiv.org/abs/1706.02216"},"publication":{"venue":"NeurIPS","year":2017,"kind":"conference","url":"https://papers.nips.cc/paper_files/paper/2017/hash/5dd9db5e033da9c6fb5ba83c7a7ebea9-Abstract.html"},"citation":"@misc{graphlearninggraphsage2017,\n  title = {{Inductive Representation Learning on Large Graphs}},\n  author = {William L. Hamilton and Rex Ying and Jure Leskovec},\n  year = {2017},\n  eprint = {1706.02216},\n  archivePrefix = {arXiv},\n  url = {https://arxiv.org/abs/1706.02216}\n}\n","citationPath":"/content/papers/citations/graph-learning/graphsage.1a0b3fd6e8b8.bib"},"thumbnail":{"src":"/content/papers/assets/88f6d9397f20155603c7ee0daef374fb944aac5fb73ed6573a573560b5b9c6aa.png","width":1090,"height":384},"locale":"zh-hans","path":"/content/papers/zh-hans/graph-learning/graphsage.e062006971fb.md","headings":[{"id":"定义问题","text":"定义问题","depth":2},{"id":"transductive-与-inductive","text":"transductive 与 inductive","depth":3},{"id":"邻居太多","text":"邻居太多","depth":3},{"id":"解决问题","text":"解决问题","depth":2},{"id":"算法","text":"算法","depth":3},{"id":"邻居用抽的","text":"邻居用抽的","depth":3},{"id":"三种聚合器","text":"三种聚合器","depth":3},{"id":"没有标签也能学","text":"没有标签也能学","depth":3},{"id":"和-wl-测试的关系","text":"和 WL 测试的关系","depth":3},{"id":"讨论","text":"讨论","depth":2},{"id":"三个数据集","text":"三个数据集","depth":3},{"id":"主要结果","text":"主要结果","depth":3},{"id":"速度与采样的取舍","text":"速度与采样的取舍","depth":3},{"id":"后来的事","text":"后来的事","depth":3},{"id":"结论","text":"结论","depth":2}],"anchorAliases":{},"guide":null,"relatedReading":[{"id":"graph-learning/gat","relation":"citing","topic":""}]},{"id":"graph-learning/gat","category":"graph-learning","legacyPath":"","legacySlug":"","legacyPaths":[],"title":"GAT","subtitle":"邻居有轻重","author":"Z. Yuan","paperDate":"2017-10-30","publishedOn":"2023-11-10","world":"external/gat","paperTitle":"Graph Attention Networks","paperUrl":"https://arxiv.org/abs/1710.10903","bibliography":{"title":"Graph Attention Networks","authors":["Petar Veličković","Guillem Cucurull","Arantxa Casanova","Adriana Romero","Pietro Liò","Yoshua Bengio"],"url":"https://arxiv.org/abs/1710.10903","pdfUrl":"https://arxiv.org/pdf/1710.10903v3","codeUrl":"https://github.com/PetarV-/GAT","preprint":{"id":"1710.10903","submittedOn":"2017-10-30","url":"https://arxiv.org/abs/1710.10903"},"publication":{"venue":"ICLR","year":2018,"kind":"conference","url":"https://arxiv.org/abs/1710.10903"},"citation":"@misc{graphlearninggat2017,\n  title = {{Graph Attention Networks}},\n  author = {Petar Veličković and Guillem Cucurull and Arantxa Casanova and Adriana Romero and Pietro Liò and Yoshua Bengio},\n  year = {2017},\n  eprint = {1710.10903},\n  archivePrefix = {arXiv},\n  url = {https://arxiv.org/abs/1710.10903}\n}\n","citationPath":"/content/papers/citations/graph-learning/gat.402fa57986a5.bib"},"thumbnail":{"src":"/content/papers/assets/1a68144470e25ef01f2cd6ccf405483bc7960ca1cc9f7ebf5991060fb3a92010.png","width":1124,"height":540},"locale":"zh-hans","path":"/content/papers/zh-hans/graph-learning/gat.8d91c14ca422.md","headings":[{"id":"定义问题","text":"定义问题","depth":2},{"id":"解决问题","text":"解决问题","depth":2},{"id":"一个注意力头","text":"一个注意力头","depth":3},{"id":"多头","text":"多头","depth":3},{"id":"成本与特性","text":"成本与特性","depth":3},{"id":"讨论","text":"讨论","depth":2},{"id":"实验设置","text":"实验设置","depth":3},{"id":"主要结果","text":"主要结果","depth":3},{"id":"学到了什么","text":"学到了什么","depth":3},{"id":"作者自己列的限制","text":"作者自己列的限制","depth":3},{"id":"静态的注意力","text":"静态的注意力","depth":3},{"id":"结论","text":"结论","depth":2}],"anchorAliases":{},"guide":null,"relatedReading":[{"id":"graph-learning/gcn","relation":"referenced","topic":""},{"id":"graph-learning/graphsage","relation":"referenced","topic":""}]}],"text":"Reddit 每分钟都有新帖子，YouTube 每天都有新视频。推荐系统要替这些新节点算出表示，最好当下就能算，不必把整张图重新训练一次。\n\n2017 年以前，主流的节点嵌入做不到这件事。DeepWalk、node2vec 这类方法替每个节点各学一个向量，像查表一样，新节点根本不在表里。GCN 虽然吃进节点特征，但论文的设置是整张图一起训练，每一步都要把整个邻接矩阵放进内存。\n\nStanford 的 William Hamilton、Rex Ying 与 Jure Leskovec 换了一个学习目标。不学每个节点的向量，改学「怎么从邻居算出一个向量」的函数。学会之后，新节点只要有特征、有邻居，就能直接应用。\n\n## 定义问题\n\n### transductive 与 inductive\n\n论文把问题分成两类。\n\n**transductive**（直推式）是训练时整张图都看得到，只是部分节点没有标签，GCN 论文的半监督分类就是这一类。**inductive**（归纳式）是训练完之后，还会冒出没见过的节点，甚至整张新的图。\n\n作者认为 inductive 更难。模型不能靠「记住」每个节点的位置，得学会从节点的局部结构与特征，辨认它在图上扮演的角色；而且要能把新节点放到和旧节点一致的空间里，训练时也没有机会看到它们。\n\n已有的嵌入方法大多是 transductive，硬要支持新节点，就得在测试时再跑几轮优化，很慢。\n\n### 邻居太多\n\n第二个问题是规模。论文用的 Reddit 数据集有 232,965 篇帖子，同一个用户留言过的两篇帖子就连一条边，平均每篇有 492 条边。\n\n如果每个节点都要看完整的两步邻居，一个节点就可能牵动几十万个节点，批量的内存和时间都无法预测。\n\n## 解决问题\n\n### 算法\n\nGraphSAGE 的名字来自 SAmple and aggreGatE，采样与聚合。前向传递跑 $K$ 轮，每一轮对每个节点 $v$ 做三件事：\n\n$$\nh^k_{\\mathcal N(v)}=\\mathrm{AGGREGATE}_k\\left(\\left\\{h^{k-1}_u,\\ \\forall u\\in\\mathcal N(v)\\right\\}\\right)\n$$\n\n$$\nh^k_v=\\sigma\\left(W^k\\cdot\\mathrm{CONCAT}\\left(h^{k-1}_v,\\ h^k_{\\mathcal N(v)}\\right)\\right)\n$$\n\n$$\nh^k_v\\leftarrow h^k_v/\\left\\|h^k_v\\right\\|_2\n$$\n\n先把邻居的表示聚合成一个向量，再和自己上一轮的表示**串接**，乘上权重矩阵、过非线性，最后做 L2 归一化。$h^0_v$ 是输入特征，第 $K$ 轮的输出就是节点的嵌入。\n\n和 GCN 最大的差别在串接。GCN 把自己当成邻居之一，一起加权平均，所有人共用一个 $W$；GraphSAGE 把自己和邻居分开，等于自己和邻居各用一组权重。作者说这可以看成不同深度之间的一种 skip connection，在实验里带来明显的进步。\n\n[![原论文 Figure 1：三个步骤由左到右。第一步在目标节点周围采样一步与两步的邻居；第二步把邻居的特征经由聚合器传回目标节点；第三步用聚合后的表示预测图的脉络与标签。](/content/papers/assets/88f6d9397f20155603c7ee0daef374fb944aac5fb73ed6573a573560b5b9c6aa.png)](/content/papers/assets/88f6d9397f20155603c7ee0daef374fb944aac5fb73ed6573a573560b5b9c6aa.png)\n\n*原论文 Figure 1（Hamilton 等人），取自 [**PDF 第 2 页**](https://arxiv.org/pdf/1706.02216v4#page=2)。图注未收录。*\n\n### 邻居用抽的\n\n$\\mathcal N(v)$ 不是完整的邻居集合，而是**均匀抽出的固定数量**邻居，每一轮重抽一次。第 $i$ 轮抽 $S_i$ 个，一个批量的成本就固定在 $O\\left(\\prod_{i=1}^KS_i\\right)$。作者发现 $K=2$、$S_1\\cdot S_2\\le500$ 就能有很好的表现，实验用的是 $S_1=25$、$S_2=10$。\n\n拿 Reddit 的平均度数算一下差多少：\n\n```python\nS1, S2 = 25, 10                 # 第一层抽 25 个、第二层每个再抽 10 个\navg_deg = 492                   # Reddit 的平均度数\nprint(\"采样后，一个目标节点最多牵动:\", 1 + S1 + S1 * S2, \"个节点\")\nprint(\"不采样、两步以内的上限:     \", 1 + avg_deg + avg_deg ** 2, \"个节点\")\n```\n\n```text\n采样后，一个目标节点最多牵动: 276 个节点\n不采样、两步以内的上限:      242557 个节点\n```\n\n第二个数字只是粗略的上限，没有扣掉重复的邻居，但它已经比整个数据集的 232,965 个节点还多。不采样的话，算一个节点的两层表示，几乎等于把整张图读一遍。\n\n小批量训练时，作者先从目标节点往外采样出所需的邻居集合，再由外往内一层一层算回来，只算递归真正用到的节点。\n\n接着用一张六个节点的小图跑一层，每个节点抽 2 个邻居，聚合器用平均：\n\n```python\nimport numpy as np\nnp.set_printoptions(precision=2, suppress=True)\nrng = np.random.default_rng(0)\n\nneighbors = [[1, 2], [0, 2, 3], [0, 1, 3, 4, 5], [1, 2], [2], [2]]\nh = rng.random((6, 3))                       # 6 个节点、每个 3 维特征\nW = rng.normal(size=(4, 6))                  # 串接后 6 维 → 输出 4 维\n\ndef sage_layer(h, neighbors, W, S):\n    out = []\n    for v, nbrs in enumerate(neighbors):\n        picked = rng.choice(nbrs, size=S, replace=len(nbrs) < S)   # 邻居不够就重复抽\n        h_nb = h[picked].mean(0)                                    # mean 聚合\n        z = np.maximum(W @ np.concatenate([h[v], h_nb]), 0)          # 自己和邻居串接后乘 W\n        out.append(z / (np.linalg.norm(z) + 1e-12))                  # L2 归一化\n    return np.array(out)\n\nh1 = sage_layer(h, neighbors, W, S=2)\nprint(h1.shape)\nprint(h1)\n```\n\n```text\n(6, 4)\n[[0.96 0.12 0.   0.24]\n [0.74 0.   0.19 0.64]\n [0.99 0.   0.   0.16]\n [0.95 0.3  0.   0.  ]\n [0.71 0.   0.   0.7 ]\n [0.81 0.   0.13 0.57]]\n```\n\n2 号节点有五个邻居，每次只抽两个；4 号、5 号只有一个邻居，就重复抽。官方的 TensorFlow 实现也是先替每个节点建一张固定长度的邻居表，邻居不够就重复抽到满，每个批量再从表里随机取前 $S$ 个。\n\n权重 $W$ 和节点无关，所以新节点进来时，只要有特征、有邻居，同一个 `sage_layer` 就能直接算。\n\n### 三种聚合器\n\n邻居没有顺序，聚合器最好对输入的排列保持不变，同时又要可训练、够有表达力。作者试了三种：\n\n- **Mean**：逐元素取平均。作者也做了一个 GCN 式的变体，不串接自己，直接对 $\\{v\\}\\cup\\mathcal N(v)$ 取平均再乘权重，称为 convolutional 聚合器。\n- **LSTM**：表达力最强，但 LSTM 本身会看顺序。作者的折衷是每次把邻居随机打乱再喂进去。\n- **Pooling**：每个邻居先各自过一层全连接，再逐元素取最大值，$\\max\\left(\\left\\{\\sigma\\left(W_{\\text{pool}}h_{u}+b\\right)\\right\\}\\right)$。作者说把 max 换成 mean，差异不大。\n\n### 没有标签也能学\n\n如果没有任务标签，GraphSAGE 用一个以图为基础的损失：\n\n$$\nJ_{\\mathcal G}(z_u)=-\\log\\sigma\\left(z_u^\\top z_v\\right)-Q\\cdot\\mathbb{E}_{v_n\\sim P_n(v)}\\log\\sigma\\left(-z_u^\\top z_{v_n}\\right)\n$$\n\n$v$ 是在固定长度的随机游走上，出现在 $u$ 附近的节点；$v_n$ 是负样本，$Q$ 是负样本数。这和 DeepWalk 的目标很像，差别在 $z_u$ 是由邻居特征算出来的，不是查表查来的。有标签时，把这个损失换成或加上交叉熵就好。\n\n### 和 WL 测试的关系\n\n作者也指出，把 $K$ 设成节点数、权重设成单位矩阵、聚合器换成哈希函数，算法 1 就是 Weisfeiler-Lehman 图同构测试。GraphSAGE 可以看成 WL 的连续近似，只是目的是学出有用的节点表示，不是判断同构。\n\n## 讨论\n\n### 三个数据集\n\n实验都是 inductive 的设置：\n\n- **引用网络**：Web of Science 中六个生物相关领域、2000 到 2005 年的论文，共 302,424 个节点，平均度数 9.15。用 2000 到 2004 年训练，预测 2005 年论文的领域。\n- **Reddit**：2014 年 9 月、50 个大型子版的帖子。用前 20 天训练，预测之后帖子属于哪个子版。\n- **PPI**：蛋白质交互作用图，一张图对应一种人体组织。20 张图训练、2 张验证、2 张测试，预测蛋白质的 121 种功能标签。测试的图在训练时完全没见过。\n\n### 主要结果\n\n| 方法 | 引用网络 | Reddit | PPI |\n| --- | ---: | ---: | ---: |\n| 原始特征 | 0.575 | 0.585 | 0.422 |\n| DeepWalk + 特征 | 0.701 | 0.691 | — |\n| GraphSAGE-GCN | 0.772 | 0.930 | 0.500 |\n| GraphSAGE-mean | 0.820 | 0.950 | 0.598 |\n| GraphSAGE-LSTM | 0.832 | **0.954** | **0.612** |\n| GraphSAGE-pool | **0.839** | 0.948 | 0.600 |\n\n表中是有监督版本的 micro-F1。GCN 式的聚合器在三个数据集都垫底，PPI 上和其他三种差了将近 0.1；它和 mean 版本最大的差别，就是少了自己与邻居的串接。\n\n作者用 Wilcoxon 检定比较六组设置（三个数据集各有监督与非监督两种），LSTM、pool、mean 都显著胜过 GCN 式的聚合器；LSTM 和 pool 之间没有显著差异。作者也承认只有六组设置，检定力有限。\n\nLSTM 本来是给序列用的，在无序的邻居集合上也表现很好，作者自己也觉得有点意外。\n\n### 速度与采样的取舍\n\n[![原论文 Figure 2：A 图是 Reddit 上的训练与测试时间，DeepWalk 的测试时间远高于各种 GraphSAGE；B 图是引用网络上，每层采样的邻居数从个位数增加到 60 多个时，micro-F1 从约 0.78 升到约 0.83，运行时间则一路上升。](/content/papers/assets/ff62a5b1aa8349edd927fc6014fd713adf4d76672507efe1a7527ff6ecdc45ca.png)](/content/papers/assets/ff62a5b1aa8349edd927fc6014fd713adf4d76672507efe1a7527ff6ecdc45ca.png)\n\n*原论文 Figure 2（Hamilton 等人），取自 [**PDF 第 7 页**](https://arxiv.org/pdf/1706.02216v4#page=7)。图注未收录。*\n\n左图里，各方法的训练时间差不多，GraphSAGE-LSTM 最慢；但到了测试，DeepWalk 得替新节点重新跑随机游走和 SGD，比 GraphSAGE 慢了 100 到 500 倍。\n\n右图是采样数量的影响。邻居抽得越多，F1 越高，但报酬递减得很快，时间却持续增加。作者也发现，$K=2$ 比 $K=1$ 平均好 10% 到 15%；再往上加到 $K>2$，只多 0% 到 5%，运行时间却多了 10 到 100 倍。\n\n### 后来的事\n\n论文最后提到，想要更好的 PPI 成绩，可以参考 Chen 与 Zhu 的后续工作，他们专门替 PPI 调了 GraphSAGE 的超参数，又加上 dropout、layer normalization 和新的采样方法。\n\n隔年，Rex Ying、Hamilton、Leskovec 和 Pinterest 合作发表了 PinSage，在一张 30 亿个节点的图上，用 75 亿笔样本训练，部署成 Pinterest 的推荐引擎。[PinSage 摘要](https://arxiv.org/abs/1806.01973v1)\n\n## 结论\n\nGraphSAGE 让图神经网络学的是一个函数，不是一张查表。邻居用抽的，批量的成本固定下来，大图也能用小批量训练；自己和邻居分开乘权重，就比 GCN 式的平均好上一截。\n\n要在自己的大图上用，先从 $K=2$、$S_1=25$、$S_2=10$ 开始，mean 聚合器就够当基准。PyTorch Geometric 有 `SAGEConv` 和配套的 `NeighborLoader`，采样和小批量都不用自己写。\n","imageSizes":{"/content/papers/assets/88f6d9397f20155603c7ee0daef374fb944aac5fb73ed6573a573560b5b9c6aa.png":{"width":1090,"height":384},"/content/papers/assets/ff62a5b1aa8349edd927fc6014fd713adf4d76672507efe1a7527ff6ecdc45ca.png":{"width":1092,"height":336}}}
