图神经网络实战——图嵌入

213 阅读1小时+

本章内容:

  • 探索图嵌入及其重要性
  • 使用非GNN和GNN方法创建节点嵌入
  • 在半监督问题上比较节点嵌入
  • 深入探讨嵌入方法

图嵌入是图形机器学习中的重要工具。它们将图的复杂结构——无论是整个图、单个节点还是边——转化为一个更易于处理的低维空间。我们这样做的目的是将复杂的数据集压缩成一种更易于操作的形式,同时不会丢失其中固有的模式和关系,这些信息将被我们应用到图神经网络(GNN)或其他机器学习方法中。

正如我们所学,图表示了网络中的关系和交互,无论是社交网络、生物网络,还是任何实体相互连接的系统。嵌入通过紧凑的形式捕捉这些现实世界的关系,促进可视化、聚类或预测建模等任务。

衍生这些嵌入的方法有很多,每种方法都有其独特的方式和应用:从使用网络拓扑的经典图算法,到分解表示图的矩阵的线性代数技术,再到更先进的GNN方法[1]。GNN之所以突出,是因为它们能够将嵌入过程直接融入到学习算法本身。

在传统的机器学习工作流中,嵌入通常作为一个单独的步骤生成,作为回归或分类等任务中的降维技术。然而,GNN将嵌入生成与模型的学习过程结合在一起。当网络通过其层级处理输入时,嵌入会不断被精炼和更新,这使得学习阶段和嵌入阶段不可分割。这意味着GNN在训练期间学习图数据的最有信息量的表示。

使用图嵌入可以显著提升你的数据科学和机器学习项目,特别是在处理复杂的网络数据时。通过将图的本质捕捉到低维空间,嵌入使得可以将各种其他机器学习技术应用于图数据,开启了分析和模型构建的无限可能。

在本章中,我们首先介绍图嵌入,并通过政治图书购买图的案例研究开始。我们从Node2Vec(N2V)开始,建立一个非GNN方法的基准,并引导你了解其实际应用。在第2.2节中,我们转向GNN,提供一个基于GNN的嵌入方法的实操介绍,包括设置、预处理和可视化。第2.3节提供了N2V和GNN嵌入的对比分析,重点讨论它们的应用。最后,章节通过讨论这些嵌入方法的理论方面进行总结,特别关注N2V的原理和GNN中的消息传递机制。本章的流程如图2.1所示。

image.png

注意: 本章的代码可以在GitHub仓库(mng.bz/qxnE)中以笔记本形式找到。本章的Colab链接和数据也可以在同一位置访问。

2.1 使用Node2Vec创建嵌入

理解网络中的关系是许多领域的核心任务,从社交网络分析到生物学和推荐系统。在本节中,我们将探索如何使用Node2Vec(N2V)创建节点嵌入,N2V是一种受自然语言处理(NLP)中的Word2Vec启发的技术[2]。N2V通过模拟随机游走捕捉图中节点的上下文关系,使我们能够在低维空间中理解节点之间的邻里关系。这种方法对于识别模式、聚类相似节点以及为机器学习任务准备数据非常有效。

为了让这个过程更加易于理解,我们将使用Node2Vec Python库,它适合初学者,尽管在处理较大的图时可能较慢。N2V帮助创建捕捉节点之间结构关系的嵌入,然后我们可以将这些嵌入可视化,以揭示图的结构。我们的工作流程包括几个步骤:

  1. 加载数据并设置N2V参数。我们首先加载图数据,并使用特定的参数初始化N2V,以控制随机游走的过程,例如游走长度和每个节点的游走次数。
  2. 创建嵌入。N2V通过在图上执行随机游走生成节点嵌入,有效地将每个节点的局部邻域总结为向量格式。
  3. 转换嵌入。生成的嵌入会被保存并转换为适合可视化的格式。
  4. 将嵌入可视化为二维图。我们使用UMAP,一种降维技术,将这些嵌入投影到二维空间中,便于可视化和解读结果。

我们的数据集是政治书籍数据集,其中包含在2004年美国选举期间在Amazon.com上频繁共同购买的书籍(节点)及其连接关系(边)[3]。使用这个数据集提供了一个有说服力的例子,展示了N2V如何揭示共同购买行为中的潜在模式,这些模式可能反映了书籍购买者之间的更广泛意识形态分组[4]。表2.1提供了政治书籍图的关键信息。

表2.1 政治书籍数据集概述

数据项描述
政治类书籍在Amazon.com上的共同购买
节点数量(书籍数量)105
左翼倾向节点41.0%
右翼倾向节点46.7%
中立节点12.4%
边的数量441
边表示两本书之间的共同购买频率

政治书籍数据集包含以下内容:

  • 节点 — 代表在Amazon.com上销售的关于美国政治的书籍。
  • — 表示由相同买家频繁共同购买的关系,这一点通过Amazon的“购买此书的顾客也购买了这些书籍”功能得出。

在图2.2中,书籍根据其政治倾向着色——深色代表自由派,浅色代表保守派,条纹表示中立。类别是由Mark Newman通过对Amazon上的书籍描述和评论进行定性分析而分配的。

image.png

这个数据集由Valdis Krebs编制,并可以通过GNN in Action仓库(mng.bz/qxnE)或卡内基梅隆大学网站(mng.bz/mG8M)访问,包含1…

使用N2V,我们旨在探索这组书籍的结构,揭示基于政治倾向的见解以及不同书籍类别之间可能的关联。通过可视化N2V创建的嵌入,我们可以更好地理解书籍如何分组,以及哪些书籍可能共享相同的受众,为在政治敏感时期的消费者行为提供宝贵的洞察。

从可视化中可以看到,数据已经以一种逻辑的方式进行了聚类。这要归功于Kamada-Kawai图算法,它仅利用拓扑数据而不依赖元数据,并且非常适合图的可视化。这种图可视化技术将节点定位,以反映它们的连接关系,力求将紧密连接的节点放在彼此附近,而连接较少的节点则放得更远。它通过将节点视为由弹簧连接的点,反复调整其位置,直到“弹簧”中的“张力”最小化。这样就得到了一个布局,能够自然揭示图中基于其结构的群体和关系。

对于政治书籍数据集,Kamada-Kawai算法帮助我们根据书籍在Amazon上共同购买的频率来可视化书籍(节点),而不使用任何外部信息,例如政治倾向或书籍标题。这为我们提供了关于书籍如何根据购买行为分组的初步视图。在接下来的步骤中,我们将使用如N2V等方法创建嵌入,捕捉更详细的模式,并进一步区分不同的书籍群体。

2.1.1 加载数据、设置参数并创建嵌入

我们使用Node2Vec和NetworkX库来进行第一次图嵌入的实际操作。在使用pip安装这些包后,我们通过NetworkX库加载存储在.gml格式(图建模语言,GML)中的数据集图数据,并使用Node2Vec库生成嵌入。

GML是一种简单的、易读的纯文本文件格式,用于表示图结构。它以结构化的方式存储节点、边及其属性信息,使得图数据的读取和写入变得非常方便。例如,.gml文件可能包含一个节点列表(例如我们数据集中的书籍)和边(表示共同购买的连接关系),以及附加属性,如标签或权重。这种格式广泛用于不同软件和工具之间交换图数据。通过使用NetworkX加载.gml文件,我们可以轻松地在Python中操作和分析图。

在Node2Vec库的Node2Vec函数中,我们可以使用以下参数来指定计算方法和输出嵌入的属性:

  • 嵌入的维度(Size of the embedding) — 可以理解为每个节点的详细信息,类似于你记录了多少个不同的特征。标准的细节级别为128个特征,但你可以根据需要调整每个节点的细节复杂度。
  • 每次游走的长度(Walk Length) — 这是每次随机游走的步数,通常为80步。如果你想查看节点周围更广泛的邻域,可以增加这个数字。
  • 每个节点的游走次数(Num Walks) — 这决定了我们从每个节点开始执行多少次游走。默认10次游走能提供一个较好的概览,但如果你想更全面地了解节点的周围环境,可以考虑增加游走次数。
  • 回溯控制(Return Parameter, p) — 该设置帮助决定我们的游走是否应该回到已经走过的地方。设置为1时,保持平衡,但调整它可以使游走变得更具探索性或更专注。
  • 探索深度(In-Out Parameter, q) — 这个参数决定了我们是想更广泛地探索邻域(例如,q大于1的广度优先搜索)还是深入特定路径(例如,q小于1的深度优先搜索),1表示两者的混合。

根据你希望了解的节点及其连接关系的内容来调整这些设置。想要更多深度?调整探索深度。想要更广泛的上下文?调整游走长度和游走次数。此外,请记住,嵌入的维度应该与你需要的细节级别相匹配。通常,尝试不同参数组合以查看其对嵌入的影响是一个好主意。

对于这个练习,我们将使用前四个参数。有关这些参数的更详细信息,请参见第2.4节。

Listing 2.1 生成N2V嵌入

import NetworkX as nx
from Node2Vec import Node2Vec
books_graph = nx.read_gml('PATH_TO_GML_FILE')   #1
node2vec = Node2Vec(books_graph, dimensions=64,
 walk_length=30, num_walks=200, workers=4)   #2
model = node2vec.fit(window=10, min_count=1,\
batch_words=4)   #3
embeddings = {str(node): model.wv[str(node)]\
 for node in gml_graph.nodes()}   #4
  • #1 从GML文件加载图数据到NetworkX图对象中。
  • #2 使用指定参数初始化N2V模型。
  • #3 训练N2V模型。
  • #4 提取并将生成的节点嵌入存储在字典中。

2.1.2 解密嵌入

让我们探讨一下这些嵌入是什么以及它们为何如此有价值。嵌入是一个密集的数值向量,表示节点、边或图的身份,它以捕捉其结构和关系的关键信息的方式进行表示。在我们的上下文中,N2V创建的嵌入通过使用拓扑信息,捕捉了节点在图中的位置和邻域。这意味着它总结了节点与其他节点的连接方式,有效地捕捉了节点在网络中的角色和重要性。稍后,当我们使用GNN创建嵌入时,它们还将封装节点的特征,提供一个更加丰富的表示,既包含结构信息,又包含属性信息。我们将在第2.4节深入探讨嵌入的理论方面。

这些嵌入非常强大,因为它们将复杂的高维图数据转化为一个固定大小的向量格式,能够方便地用于各种分析和机器学习任务。例如,它们允许我们通过揭示图中的模式、聚类和关系来执行探索性数据分析。除此之外,嵌入还可以直接作为机器学习模型中的特征,其中向量的每个维度表示一个独特的特征。这在一些应用中尤其有用,比如社交网络或推荐系统,在这些应用中,理解数据点之间的结构和连接关系可以显著提高模型的性能。

举个例子,考虑我们政治书籍数据集中表示书籍《Losing Bin Laden》的节点。使用命令 model.wv['Losing Bin Laden'],我们可以获取其密集的向量嵌入。这个向量,如图2.3所示,捕捉了这本书在共同购买书籍网络中的各个方面,提供了一个紧凑、信息丰富的表示,可以用于进一步的分析或作为其他模型的输入。

image.png

这些嵌入可以用于探索性数据分析,查看图中的模式和关系。然而,它们的应用远不止于此。一种常见的应用是将这些向量作为特征用于处理表格数据的机器学习问题。在这种情况下,嵌入数组中的每个元素将成为表格数据中的一个独特特征列。这可以为每个节点增加丰富的表示,以补充模型训练中的其他属性。在下一节中,我们将探讨如何可视化这些嵌入,以便更深入地洞察它们所代表的模式和关系。

2.1.3 转换和可视化嵌入

像统一流形近似和投影(UMAP)这样的可视化方法是将高维数据集转换为低维空间的强大工具[5]。UMAP特别适合识别固有的聚类并可视化那些在高维数据中难以察觉的复杂结构。与其他方法,如t-SNE相比,UMAP在保持局部和全局结构方面表现出色,使其非常适合揭示数据中不同尺度上的模式和关系。

虽然N2V通过捕捉我们数据的网络结构来生成嵌入,UMAP则将这些高维嵌入映射到低维空间(通常是二维或三维)。这个映射的目的是让相似的节点尽可能靠近,同时保留更广泛的结构关系,从而提供图拓扑的更全面的可视化。在获得我们的N2V嵌入并将其转换为数值数组后,我们初始化UMAP模型,并使用两个组件将我们的数据投影到二维平面。通过精心选择参数,如邻居数量和最小距离,UMAP可以在揭示精细的局部关系和保持聚类之间的全局距离之间找到平衡。

通过使用UMAP,我们可以获得更准确且易于解读的图嵌入可视化,如下列代码所示,这使我们能够比传统方法(如t-SNE)更有效地探索和分析模式、聚类和结构。

Listing 2.2 使用UMAP可视化嵌入

node_embeddings = [embeddings[str(node)] \
for node in gml_graph.nodes()]   #1
node_embeddings_array = np.array(node_embeddings)  

umap_model = umap.UMAP(n_neighbors=15, min_dist=0.1, n_components=2, \
random_state=42)
umap_features = umap_model.fit_transform\
(node_embeddings_array)  #2

plt.scatter(umap_features[:, 0], \
umap_features[:, 1], color=node_colors, alpha=0.7)  #3
  • #1 将嵌入转换为UMAP所需的向量列表。
  • #2 初始化并拟合UMAP。
  • #3 使用UMAP嵌入绘制节点,并根据它们的值着色。

生成的图2.4展示了通过N2V提炼并通过UMAP可视化的政治书籍图的嵌入。节点根据它们的政治倾向呈现不同的阴影。该可视化揭示了一个明显的结构,可能的聚类对应于不同的政治倾向。

image.png

你可能会想,为什么我们不直接将N2V嵌入的维度从64降到2,然后直接进行可视化,而完全绕过UMAP呢?在Listing 2.3中,我们展示了这种方法,直接对我们的books_graph对象应用2D N2V转换。(有关这些方法的更多技术细节和理论,请参见第2.4节。)

这里将dimensions参数设置为2,目标是获得一个适合立即可视化的2D表示,而无需进一步的降维。其他参数保持不变。

一旦模型使用指定的窗口和批处理设置进行拟合,我们提取2D嵌入并将它们存储在一个以每个节点的字符串表示为键的字典中。这使得节点与其嵌入向量之间的映射变得直接。

提取的2D点被汇总到一个NumPy数组中,并进行绘制。我们使用标准的Matplotlib库创建这些点的散点图,并使用准备好的颜色方案来表示每个节点的政治倾向。

Listing 2.3 不使用t-SNE的2D N2V嵌入可视化

node2vec = Node2Vec(gml_graph, dimensions=2, \
walk_length=30, num_walks=200, workers=4)   #1
model = node2vec.fit(window=10, min_count=1,\
 batch_words=4)   #2

embeddings_2d = {str(node): model.wv[str(node)] \
for node in gml_graph.nodes()}   #3

points = np.array([embeddings_2d[node] \
for node in gml_graph.nodes()])   #4

plt.scatter(points[:, 0], points[:, 1], \
color=node_colors, alpha=0.7)   #5
  • #1 使用2D嵌入初始化N2V,以便进行可视化。
  • #2 使用指定的窗口和游走设置训练N2V模型。
  • #3 将节点映射到它们的2D嵌入。
  • #4 为每个节点的嵌入创建一个2D点数组。
  • #5 使用指定的节点颜色绘制2D嵌入图。

结果显示了书籍如何根据政治倾向被分开,这与UMAP结果相似,但在这种情况下,书籍之间的距离更紧凑(见图2.5)。然后,这两种嵌入将在图2.6中展示。

image.png

image.png

显然,两种方法都能够根据政治倾向将书籍分成不同的组。N2V在分离书籍时不如UMAP表现得那么有表现力,它将书籍聚集在二维空间中。与此同时,UMAP在二维空间中更好地分散了书籍。这些维度中包含的相关信息或益处取决于具体任务的需求。

2.1.4 超越可视化:N2V嵌入的应用与考虑

虽然可视化N2V嵌入提供了对数据集结构的直观见解,但它们的应用远不止图形表示。N2V是一种专为图设计的嵌入方法,它通过模拟图中的随机游走,捕捉节点的局部和全局结构属性。这个过程使N2V能够创建密集的数值向量,总结每个节点在整体网络中的位置和上下文。

这些嵌入可以作为特征丰富的输入,应用于各种机器学习任务,例如分类、推荐或聚类。例如,在我们的政治书籍数据集中,嵌入可以帮助基于共同购买模式预测书籍的政治倾向,或者可以推荐给具有相似政治兴趣的用户的书籍。它们甚至可以用来基于书籍内容预测未来的销售情况。

然而,理解N2V的学习方法的本质是非常重要的,这种方法是传递性学习(transductive learning)。传递性学习只适用于它所训练的特定数据集,无法在不重新训练模型的情况下推广到新的、未见过的节点。这一特点使得N2V对于已知所有节点和边的静态数据集非常有效,但对于动态环境(其中新的数据点或连接经常出现)则不太适用。本质上,N2V专注于从现有图中提取详细的模式和关系,而不是开发一个能够轻松适应新数据的模型。

尽管这种传递性特性有其局限性,但它也带来了显著的优势。因为N2V在训练过程中使用了图的完整结构,它能够捕捉到可能被更通用方法忽略的复杂关系和依赖性。这使得N2V在数据的完整固定结构已知且稳定的任务中尤为强大。然而,为了有效地应用N2V,必须确保图数据的表示方式能够捕捉所有相关特征。在某些情况下,可能需要向图中添加额外的边或节点,以充分表示潜在的关系。

对于那些希望深入了解传递性模型以及N2V方法与其他方法比较的人,更多的细节将在第2.4.2节中提供。该节将更深入地探讨传递性学习和归纳学习之间的权衡[6, 7],帮助你理解何时使用每种方法最为合适。

虽然N2V对于生成捕捉固定图结构的嵌入非常有效,但现实世界中的数据通常要求采用更灵活和更具可推广性的方法。这种需求引出了我们第一个用于创建节点嵌入的GNN架构。与N2V这种传递性方法(仅限于训练数据中的特定节点和边)不同,GNN可以以归纳的方式进行学习。这意味着GNN能够在不需要对整个图重新训练的情况下,对新的、未见过的节点或边进行推广。

GNN通过不仅理解网络的复杂结构,还将节点特征和关系纳入学习过程,实现了这一点。这种方法使得GNN能够动态适应图的变化,使其非常适合用于数据持续变化的应用场景。从N2V到GNN的转变代表了一个关键过渡,从专注于静态数据集中的深度分析,转向更广泛适用的动态网络。这种适应性为处理现实世界数据动态性质的图形机器学习应用提供了更大的灵活性和可扩展性。在下一节中,我们将探讨GNN如何超越N2V和其他传递性方法的能力,允许构建更通用和强大的模型,能够处理现实世界数据的动态性。

2.2 使用GNN创建嵌入

虽然N2V通过捕捉图的局部和全局结构提供了一种强大的嵌入生成方法,但它本质上是一种传递性方法,这意味着它不能轻易地推广到未见过的节点或边,而不需要重新训练。尽管已经有扩展版的N2V可以使其在归纳设置中工作,但GNN本身就是为归纳学习设计的。这意味着它们可以从图数据中学习到一般性模式,使其能够在不需要重新训练整个模型的情况下,对新节点或边进行预测或生成嵌入。这使得GNN在需要灵活性和适应性的场景中具有显著优势。

GNN不仅像N2V一样结合了图的结构信息,还使用节点特征来创建更丰富的表示。这种双重能力使得GNN能够学习图中的复杂关系和单个节点的特定特征,从而在这些信息都很重要的任务中表现出色。

尽管如此,虽然GNN在许多应用中表现出色,但在所有情况下,它们并不总是优于像N2V这样的其他方法。例如,N2V和其他基于随机游走的方法在标注数据稀缺或噪声较大的场景中有时表现得更好,因为它们可以仅依赖图的结构而不需要额外的节点特征。

2.2.1 构建嵌入

与N2V不同,GNN在训练过程中同时学习图表示并执行诸如节点分类或链接预测等任务。图中的所有信息通过连续的GNN层进行处理,每一层都会更新节点嵌入,而不需要为创建嵌入单独进行步骤。

为了展示GNN如何从图数据中提取特征,我们将进行一次简单的传递,使用未训练的模型生成初步的嵌入。即使没有通常的训练优化,这种方法也将展示GNN如何通过消息传递(在第2.4.4节中进一步探讨)更新嵌入,捕捉图的结构和节点特征。当添加优化时,这些嵌入将针对特定任务进行定制,如节点分类或链接预测。

定义我们的GNN架构

我们通过定义一个简单的GCN架构开始,如Listing 2.4所示。我们的SimpleGNN类继承自torch.nn.Module,由两个GCNConv层组成,这些是我们GNN的构建块。该架构如图2.7所示,包含第一层消息传递层(self.conv1)、激活函数(torch.relu)、dropout层(torch.dropout)和第二层消息传递层。

Listing 2.4 我们的SimpleGNN类

class SimpleGNN_embeddings(torch.nn.Module):
    def __init__(self, num_features, hidden_channels):   #1
        super(SimpleGNN, self).__init__()
        self.conv1 = GCNConv(num_features, \
hidden_channels)   #2
        self.conv2 = GCNConv(hidden_channels,\
 hidden_channels)  #3

    def forward(self, x, edge_index):   #4
        x = self.conv1(x, edge_index)  #5
        x = torch.relu(x)   #6
        x = torch.dropout(x, p=0.5, train=self.training)   #7
        x = self.conv2(x, edge_index)    #8
        return x   #9
  • #1 使用输入和隐藏层大小初始化GNN类。
  • #2 从输入特征到隐藏层通道的第一个GCN层。
  • #3 在隐藏空间中的第二个GCN层。
  • #4 前向传播函数定义数据流。
  • #5 第一个GCN层处理。
  • #6 非线性激活函数。
  • #7 在训练期间进行正则化的dropout。
  • #8 第二个GCN层处理。
  • #9 返回最终的节点嵌入。

image.png

让我们来讨论一下与图神经网络(GNN)相关的架构方面。激活函数和丢弃法(dropout)在许多深度学习场景中是常见的。然而,GNN层与传统深度学习层有一个根本性的区别。GNN能够从图数据中学习的核心原理是信息传递(message passing)。对于每个GNN层,除了更新层的权重之外,还会从每个节点或边的邻域收集“信息”,并用这些信息来更新节点的嵌入(embedding)。本质上,每个节点会将信息发送给它的邻居,同时也接收来自邻居的信息。对于每个节点,它的新的嵌入是通过将其自身的特征与邻居节点聚合的信息结合起来计算的,经过一系列非线性变换。

在这个例子中,我们将使用图卷积网络(GCN)作为我们的信息传递GNN层。我们将在第三章中更加详细地描述GCN。现在,您只需要知道GCN作为信息传递层在构建嵌入时至关重要。

数据准备

接下来,我们准备数据。我们将从上一节中的相同图(books_gml)开始,图的格式为NetworkX。我们需要将这个NetworkX对象转换为适合与PyTorch操作一起使用的张量格式。由于PyTorch Geometric(PyG)提供了许多将图对象转换为张量的函数,我们可以通过简单的方式实现:data = from_NetworkX(gml_graph)from_NetworkX方法专门将边列表和节点/边的属性转换为PyTorch张量。

对于GNN,生成节点嵌入需要初始化节点特征。在我们的案例中,我们没有任何预定义的节点特征。当没有可用的节点特征或它们不具备信息量时,通常会将节点特征随机初始化。更有效的方法是使用Xavier初始化,它通过从某个分布中抽取值来设置初始节点特征,这个分布能够保持跨层激活的一致性。这个技术确保模型以平衡的方式开始,避免出现梯度消失或梯度爆炸等问题。

通过使用Xavier初始化来初始化data.x,我们为GNN提供了一个起点,使其能够从非信息性特征中学习有意义的节点嵌入。在训练过程中,网络会调整这些初始值以最小化损失函数。当损失函数与特定目标(如节点预测)对齐时,从初始随机特征中学习到的嵌入将根据任务的需要进行调整,从而产生更有效的表示。我们通过以下代码随机化节点特征:

data.x = torch.randn((data.num_nodes, 64), dtype=torch.float)
'nn.init.xavier_uniform_(data.x)'

我们也可以使用N2V练习中的嵌入作为节点特征。回想一下在2.1.3节中的node_embeddings对象:

node_embeddings = [embeddings[str(node)] for node in gml_graph.nodes()]

从中,我们可以将节点嵌入转换为PyTorch张量对象,并将其分配给节点特征对象data.x

node_features = torch.tensor(node_embeddings, dtype=torch.float)
data.x = node_features

将图传递给GNN

在定义好GNN模型的结构,并将我们的图数据格式化为PyG格式后,我们进入嵌入生成的步骤。我们初始化模型SimpleGNN,并指定每个节点的特征数以及网络中隐藏通道的大小:

model = SimpleGNN(num_features=data.x.shape[1], hidden_channels=64)

在这里,我们指定64个隐藏通道,因为我们想将生成的嵌入与使用node2vec方法得到的64维嵌入进行比较。由于第二个GNN层是最后一层,输出将是一个64元素的向量。

一旦初始化完成,我们使用model.eval()将模型切换到评估模式。在推理或验证阶段,我们希望进行预测或评估模型的性能时,不修改模型的参数。具体来说,model.eval()会关闭某些与训练相关的行为,例如丢弃法(dropout),它会随机停用一些神经元以防止过拟合;以及批标准化(batch normalization),它会对小批量中的输入进行标准化。通过禁用这些特性,模型能提供一致且确定性的输出,确保评估结果准确反映其在未见数据上的真实表现。

禁用梯度计算是很重要的,因为前向传播和嵌入生成过程中不需要梯度计算。因此,我们使用torch.no_grad(),确保不会构建用于反向传播的计算图,防止意外改变性能。

接下来,我们将节点特征矩阵(data.x)和边索引(data.edge_index)传递给模型。结果是gnn_embeddings,它是一个张量,每一行对应我们图中一个节点的嵌入—由GNN学习得到的数值表示,准备好用于下游任务,如可视化或分类:

model.eval()
with torch.no_grad():
    gnn_embeddings = model(data.x, data.edge_index)

生成这些嵌入后,我们使用UMAP进行可视化,正如在2.1.3节中所做的那样。由于我们一直在使用运行在GPU上的PyTorch张量数据类型,我们需要将嵌入转换为NumPy数组数据类型,以便使用PyTorch外的分析方法,这些方法是在CPU上完成的:

gnn_embeddings_np = gnn_embeddings.detach().cpu().numpy()

通过这个转换,我们可以按照在N2V案例中使用的方法进行UMAP计算和可视化。生成的散点图(图2.8)是我们图中聚类的初步观察。我们根据每个节点的标签(左倾、右倾或中立倾向)添加不同的阴影,可以看到相似倾向的书籍被相当好的分组,鉴于这些嵌入是仅根据拓扑结构构建的。

接下来,让我们讨论GNN嵌入的使用方式,以及它们与N2V生成的嵌入的不同之处。

image.png

2.2.2 GNN与N2V嵌入的比较

在本书中,我们主要使用GNN来生成嵌入,因为这一嵌入过程是GNN架构的内在组成部分。尽管嵌入在我们在本书后续探讨的方法论和应用中扮演着至关重要的角色,但它们的存在往往是隐性的,并不总是被突出展示。采用这种方式可以让我们专注于基于GNN的机器学习的更广泛概念和应用,而不会被技术细节拖慢进度。尽管如此,重要的是要承认,嵌入的底层力量和适应性是贯穿本书的高级技术和深刻见解的核心。

由GNN生成的节点嵌入尤其强大,因为它们通过其归纳性质使我们能够处理广泛的与图相关的任务。归纳学习使得这些嵌入可以推广到新的、未见过的节点,甚至是全新的图,而无需重新训练模型。相比之下,N2V嵌入仅限于它们训练时使用的特定图,并且难以适应新数据。让我们回顾一下GNN嵌入与其他嵌入方法(如N2V)的关键区别。

适应新图的能力

GNN嵌入的一个关键特性是它们的适应性。由于GNN学习的是将节点特征映射到嵌入的函数,这个函数可以应用于新图中的节点,而无需重新训练,只要这些节点具有类似的特征空间。这种归纳能力在动态图环境中尤其有价值,其中图可能随时间演变,或在应用中需要将模型应用于不同但结构相似的图。相比之下,N2V需要针对每个新图或新节点集重新应用。

强化特征整合

GNN在嵌入过程中天生会考虑节点特征,从而允许每个节点进行复杂且细致的表示。与只关注图拓扑的N2V和其他方法相比,这种特征和结构信息的整合提供了更为全面的视角。这使得GNN嵌入特别适用于节点特征包含重要附加信息的任务。

针对任务的优化

GNN嵌入是在特定任务(如节点分类、链接预测甚至图分类)中训练的。通过端到端的训练,GNN模型学习优化嵌入以适应当前任务,从而可能比使用预生成的嵌入(如N2V生成的嵌入)更具性能和效率。

尽管如此,虽然GNN嵌入在适应性和对新数据的应用方面具有明显优势,但N2V嵌入也有其独特的优点,特别是在捕捉特定图结构中的细微模式方面。在实际应用中,选择GNN嵌入或N2V嵌入可能取决于任务的具体要求、图数据的性质以及计算环境的约束。

对于图结构是静态且定义明确的任务,N2V可能提供一个更简单且计算效率更高的解决方案。相反,对于动态图、大规模应用或需要结合节点特征的场景,GNN通常是一个更强大且多功能的选择。此外,当任务本身不明确且工作是探索性的时,N2V可能更快速且易于使用。

我们现在已经成功地构建了第一个GNN嵌入。这是所有GNN模型的关键第一步,从这一点开始,后续的一切都将基于此。接下来的章节,我们将展示这些后续步骤的示例,并展示如何使用嵌入来解决机器学习问题。

2.3 使用节点嵌入

半监督学习,涉及标签数据和未标签数据的结合,为比较不同的嵌入技术提供了宝贵的机会。在本章中,我们将探讨如何使用GNN和N2V嵌入来预测标签,尤其是在大多数数据没有标签的情况下。

我们的任务涉及政治书籍数据集(books_graph),其中节点代表政治书籍,边表示共购关系。为了更清楚地阐明过程,让我们回顾到目前为止所采取的步骤,并概述我们的下一步工作,如图2.9所示。

image.png

我们从图格式的 books_graph 数据集开始,进行了轻量级的预处理,以便为嵌入做准备。对于 N2V,这涉及将数据集从 .gml 文件转换为 NetworkX 格式。对于基于 GNN 的嵌入,我们将 NetworkX 图转换为 PyTorch 张量,并使用 Xavier 初始化节点特征,以确保跨层的变异性平衡。

在准备好数据后,我们使用 N2V 和 GCN 两种方法生成嵌入。接下来,在本节中,我们将把这些嵌入应用于半监督分类问题。这需要进一步处理,以定义分类任务,其中仅保留 20% 的书籍标签,模拟具有稀疏标签数据的实际场景。

我们将使用两组嵌入(N2V 和 GCN),并配合两种不同的分类器:随机森林分类器(将嵌入用作表格特征)和 GCN 分类器(使用图结构和节点特征)。目标是预测书籍的政治倾向,其余 80% 的标签将基于给定的嵌入进行推断。

2.3.1 数据预处理

首先,我们对 books_gml 数据集进行更多的预处理(见列表 2.5)。我们必须以适合学习过程的方式格式化标签。由于所有节点都有标签,我们还需要通过随机选择一些节点来隐藏标签,从而设置半监督问题。

与属性 'c' 相关的节点被分类为 'right',而与 'l' 相关的节点被分类为 'left'。不符合这些标准的节点(包括中立或未指定属性的节点)被分类为 'neutral'。这些分类结果随后被放入一个 NumPy 数组 labels,以便进行优化计算处理。

接着,创建一个数组 indices,表示数据集中所有节点的位置信息。选取这些索引的一个子集,约占总节点数的 20%,作为我们的标签数据。

为了管理已标记和未标记的数据,初始化并填充了布尔掩码 labelled_mask 和 unlabelled_mask。labelled_mask 对于被选为已标记的索引设置为 True;这些是相应节点的真实标签。同样,unlabelled_mask 设置为 False。这些掩码将数据集划分为训练集和评估集,确保算法正确地在数据的正确子集上进行训练和验证。

列表 2.5 半监督问题的预处理
labels = []
for node, data in gml_graph.nodes(data=True):   #1
    if data['value'] == 'c':
        labels.append('right')
    elif data['value'] == 'l':
        labels.append('left')
    else:  
        labels.append('neutral')
labels = np.array(labels)

random.seed(52)   #2

indices = list(range(len(labels)))   #3

labelled_percentage = 0.2    #4

labelled_indices = random.sample(indices, \
int(labelled_percentage * len(labels)))   #5

labelled_mask = np.zeros(len(labels), dtype=bool)   #6
unlabelled_mask = np.ones(len(labels), dtype=bool)

labelled_mask[labelled_indices] = True   #7
unlabelled_mask[labelled_indices] = False

labelled_labels = labels[labelled_mask]   #8
unlabelled_labels = labels[unlabelled_mask]

label_mapping = {'left': 0, 'right': 1, 'neutral': 2}   #9
numeric_labels = np.array([label_mapping[label] for label in labels])
#1 提取标签并处理中立值
#2 设置随机种子以确保可重现性
#3 所有节点的索引
#4 保留 20% 的数据作为已标记数据
#5 选择一个子集的索引作为已标记
#6 初始化已标记和未标记数据的掩码
#7 更新掩码
#8 使用掩码分割数据集
#9 将标签转换为数值形式

现在,我们将数据转换为模型训练所需的形式,如列表 2.6 所示。对于 GNN 派生的嵌入,X_train_gnn 和 y_train_gnn 分别分配嵌入数组和通过 labelled_mask 过滤的相应数值标签。这个掩码是一个布尔数组,表示图中的哪些节点属于已标记子集,确保只有已知标签的数据点被包含在训练集中。

对于 N2V 嵌入,采用类似的方法,并增加一个预处理步骤,将嵌入与其相应的标签对齐。每个节点的嵌入将按照在 books_graph 中节点出现的顺序聚合到 NumPy 数组 X_n2v 中。这确保了嵌入与其标签之间的一致性,这是监督学习任务中的关键步骤。随后,X_train_n2v 和 y_train_n2v 被填充上 N2V 嵌入和标签,再次使用 labelled_mask 过滤出已标记的数据点。

列表 2.6 预处理:构建训练数据
X_train_gnn = gnn_embeddings[labelled_mask]   #1
Y_train_gnn = numeric_labels[labelled_mask]  

X_n2v = np.array([embeddings[str(node)] \
for node in gml_graph.nodes()])  #2
X_train_n2v = X_n2v[labelled_mask]              #3
y_train_n2v = numeric_labels[labelled_mask]     #3
#1 对于 GNN 嵌入
#2 对于 N2V 嵌入
#3 确保 N2V 嵌入与标签顺序一致

对于 N2V 嵌入的额外对齐步骤在 GNN 嵌入中并不必要,因为 GNN 模型本身就保持了节点的顺序,在处理整个图时具有结构化的顺序。因此,GNN 的输出嵌入自然与输入图的节点顺序对应。

相比之下,N2V 通过从每个节点开始的独立随机游走生成嵌入,生成的嵌入的顺序不一定与原始图数据结构中的节点顺序匹配。因此,需要一个显式的对齐步骤,确保每个 N2V 嵌入正确地与其对应的标签关联,这对于监督学习任务至关重要。在此任务中,我们使用属性 index_to_key,其中包含节点标识符,并按照处理和存储节点的顺序排列。

2.3.2 随机森林分类

在准备好数据后,我们使用第 2.1 和 2.2 节中的 GNN 和 N2V 嵌入作为输入特征来训练随机森林分类器,如列表 2.7 所示。

列表 2.7 预处理:构建训练数据
clf_gnn = RandomForestClassifier()   #1
clf_gnn.fit(X_train_gnn, y_train_gnn)

clf_n2v = RandomForestClassifier()  #2
clf_n2v.fit(X_train_n2v, y_train_n2v)
#1 用于 GNN 嵌入的分类器
#2 用于 N2V 嵌入的分类器

这种方法允许我们直接比较嵌入的预测能力,我们在表 2.2 中比较结果。

表 2.2 分类性能
嵌入类型准确率F1 分数
GNN83.33%82.01%
N2V84.52%80.72%

对于这个基本的分类任务,我们将使用两个基本指标来评估模型的表现:

  • 准确率 — 该指标衡量模型所有预测中正确预测的比例。它为我们提供了一个简单的评估,告诉我们分类器识别书籍政治倾向的准确程度。例如,84.52% 的准确率意味着模型大约每 100 次预测中有 85 次是正确的。
  • F1 分数 — 这是一个更细致的指标,平衡了精确率和召回率,对于数据不平衡的情况(即类分布不均)特别有用。它提供了精确率(真实正例预测数除以所有正例预测数)和召回率(真实正例预测数除以所有实际正例数)的调和平均值。较高的 F1 分数表示模型在正确识别各类(包括有和没有该类的情况)方面的表现更加均衡,最小化了假阳性和假阴性。

性能指标表明,当在随机森林分类器中使用时,N2V 嵌入的准确率稍高,达到了 84.52%,而 GNN 嵌入为 83.33%。然而,GNN 嵌入的 F1 分数略高,为 82.01%,相比之下 N2V 为 80.72%。这一微小的差异凸显了这两种嵌入类型之间可能的权衡:尽管 N2V 提供了稍好的整体预测准确性,但 GNN 嵌入可能在多个类的表现上更加平衡。

通常,GNN 的归纳特性为学习许多不同大小的图的节点表示提供了一个强大的框架。即使在较小的图上,GNN 也能有效地学习节点之间的基本模式和交互,正如更高的 F1 分数所显示的,这表明分类任务中精确率和召回率的平衡更好。

在这种情况下,选择 GNN 和 N2V 嵌入可能还取决于分析的具体目标和最关心的性能指标。如果优先考虑尽可能高的准确性,并且数据集不太可能显著扩展,则 N2V 可能是更合适的选择。相反,如果任务更重视精确率和召回率的平衡,并且有将学到的模型应用于类似但新的图的潜力,GNN 提供了有价值的灵活性和鲁棒性,即使是对于较小的数据集。在将 N2V 和 GNN 嵌入作为输入应用于随机森林模型后,接下来我们将研究将它们作为输入应用于完整的端到端 GNN 模型时会发生什么。

2.3.3 嵌入在端到端模型中的应用

在上一节中,我们将 GNN 和 N2V 嵌入作为静态输入应用于传统机器学习模型,即随机森林分类器。在这里,我们使用一个端到端的 GNN 模型来应用于相同的标签预测问题。所谓端到端,意味着在我们预测标签的同时,嵌入将被生成。这意味着这里的嵌入不是静态的,因为随着 GNN 的学习,节点嵌入将会更新。

为了构建这个模型,我们将使用与之前相同的工具——books_gml 数据集和 SimpleGNN 架构。我们将略微修改 GNN,添加一个 log softmax 激活函数,以便输出适用于三分类问题的结果。我们还将稍微修改 SimpleGNN 类的输出,以便观察嵌入以及预测输出。我们的过程包括以下几个步骤:

  • 数据准备
  • 模型/架构修改
  • 建立训练循环
  • 研究性能
  • 研究预训练和后训练的嵌入

数据准备

假设我们使用 books_gml 数据集,转换为 PyG 框架使用的过程保持不变。我们将训练两个版本的数据:一个节点特征随机初始化,另一个节点特征使用 N2V 嵌入。

模型修改

我们使用相同的 SimpleGNN 类,并对其进行了修改。在这个增强版本的 SimpleGNN 类中,我们扩展了其功能,以为每个节点提供预测输出。通过对第二个 GCN 层产生的嵌入应用 log softmax 激活来实现这一点。log softmax 输出提供了每个节点对于分类任务的潜在类别的归一化对数概率分布。

其次,我们引入了双输出。该方法返回两个值:来自 conv2 层的原始嵌入,表示节点表示,以及这些嵌入的 log softmax 输出。为了观察嵌入和预测,我们让 forward 方法同时返回这两者。除了这个两层模型外,我们还向该架构中添加了两层,以便进行四层模型的比较,如列表 2.8 所示。

列表 2.8 预处理:构建训练数据
python
复制
class SimpleGNN_inference(torch.nn.Module):
    def __init__(self, num_features, hidden_channels):
        super(SimpleGNN, self).__init__()
        self.conv1 = GCNConv(num_features, hidden_channels)
        self.conv2 = GCNConv(hidden_channels, hidden_channels)

    def forward(self, x, edge_index):
        # 第一层图卷积
        x = self.conv1(x, edge_index)
        x = F.relu(x)
        x = F.dropout(x, training=self.training)

        # 第二层图卷积
        x = self.conv2(x, edge_index)
        predictions = F.log_softmax(x, dim=1)    #1

        return x, predictions   #2
#1 通过将最终卷积层传递到 log softmax 来预测类别
#2 类返回最后的嵌入和预测

建立训练循环

我们为 GNN 模型在半监督学习的背景下编写了训练循环,如列表 2.9 所示。这个循环迭代指定的纪元次数,其中一个纪元表示完整地通过整个训练数据集。在每个纪元内,模型的参数都会更新,以最小化损失函数,损失函数量化了预测输出与训练集节点的实际标签之间的差异。对于那些熟悉编写深度学习训练循环的程序员来说,这应该是非常熟悉的。如果你需要一个快速的复习,下面描述了初始化和运行训练循环中的一些关键步骤:

  • 优化器初始化 — 在创建优化器时,优化器会使用特定的学习率进行初始化。例如,这里我们使用 Adam 优化器,初始学习率为 0.01。
  • 梯度归零optimizer.zero_grad() 确保在每次更新之前将梯度归零,防止它们在不同的纪元之间累积。
  • 模型前向传播 — 模型处理节点特征(data.x)和图结构(data.edge_index)以产生输出预测。在半监督设置中,并非所有节点都有标签,因此模型的输出包括对已标记节点和未标记节点的预测。
  • 应用训练掩码out_masked = out[data.train_mask] 将掩码应用于模型的输出,仅选择与已标记节点对应的预测结果。这在半监督学习中至关重要,因为只有一部分节点有已知标签。
  • 损失计算和反向传播 — 损失函数 loss_fn 将选定的预测结果(out_masked)与已标记节点的真实标签(train_labels)进行比较。loss.backward() 调用计算损失函数相对于模型参数的梯度,这些梯度随后用于通过 optimizer.step() 更新参数。
  • 日志记录 — 训练循环会定期(在本例中是每 10 个纪元)打印损失,以监控训练进度。
列表 2.9 训练循环
for epoch in range(3000):    #1
    optimizer.zero_grad()

    _, out = model(data.x, data.edge_index)   #2

    out_masked = out[data.train_mask]   #3

    loss = loss_fn(out_masked, train_labels)   #4
    loss.backward()
    optimizer.step()

    if epoch % 10 == 0:    #5
        print(f'Epoch {epoch}, Log Loss: {loss.item()}')
#1 纪元次数
#2 将节点特征和边索引传递给模型
#3 应用训练掩码,只选择已标记节点的输出
#4 使用仅包含已标记节点的输出计算损失
#5 每 10 个纪元打印一次损失

这个过程通过反复优化模型的参数,逐步改进其对训练集已标记部分的预测,以期学到一个能很好地推广到未标记节点,并且可能对新的、未见过的数据进行有效预测的模型。

GNN 结果:随机化与 N2V 节点特征的比较

接下来,我们比较使用随机化节点特征与 N2V 节点特征的 GNN 分类任务性能,如表 2.3 所示。

表 2.3 不同节点特征输入的 GNN 模型分类性能比较
模型GNN 准确率GNN F1 分数
两层,随机化特征82.27%82.14%
两层,N2V 特征87.79%88.10%
四层,随机化特征86.58%86.90%
四层,N2V 特征88.99%89.29%

表格总结了不同 GNN 模型基于准确率和 F1 分数的性能。它突出显示了使用 N2V 特征的 GNN 模型在所有模型配置中都优于使用随机化特征的模型。具体而言,使用 N2V 特征的四层 GNN 模型达到了最高的准确率和 F1 分数,表明结合从 N2V 嵌入中衍生的有意义的节点表示非常有效。如果我们有关于节点的更多具体特征,如第 3 章所示,GNN 嵌入可能进一步提高 GNN 模型的准确性。

结果:GNN 与随机森林的比较

现在,我们将本节中的 GNN 模型与上一节中的随机森林模型进行性能比较(见表 2.4)。

表 2.4 GNN 模型与随机森林模型的分类性能比较
模型数据输入准确率F1 分数
随机森林来自 GNN 的嵌入83.33%82.01%
随机森林来自 N2V 的嵌入84.52%80.72%
两层简单 GNN随机化节点特征82.27%82.14%
两层简单 GNN使用 N2V 嵌入作为节点特征87.79%88.10%
四层简单 GNN随机化节点特征86.58%86.90%
四层简单 GNN使用 N2V 嵌入作为节点特征88.99%89.29%
图 2.10 可视化表 2.4 的结果

总体而言,GNN 模型的表现优于随机森林模型。

image.png

在比较 GNN 模型与随机森林模型的性能时,我们可以做出几个观察。使用来自 GNN 传递或 N2V 嵌入的嵌入训练的随机森林,达到了与两层简单 GNN 模型相当的准确率。然而,考虑到 F1 分数时,两个 GNN 模型都优于随机森林。值得注意的是,四层简单 GNN 模型,尤其是在使用 N2V 嵌入作为特征时,表现明显优于随机森林模型,展示了更高的准确率和 F1 分数。

这表明,尽管随机森林在准确性上可能优于简单的 GNN 架构(如两层模型),更复杂的 GNN 架构在 F1 分数上展现出更优的表现,特别是当使用诸如 N2V 这样的复杂节点嵌入时。因此,在选择随机森林和 GNN 时,应考虑准确率和 F1 分数,以及模型架构的复杂性和输入特征的性质,以便为给定的任务和数据集实现最佳性能。

需要注意的是,这个简短的例子并未对 GNN 或随机森林模型进行广泛的微调。进一步优化这两种类型的模型可能会显著提升其性能。微调超参数、调整模型架构以及优化训练过程都可能有助于提升 GNN 和随机森林分类器的准确率和 F1 分数。因此,尽管这里提供的结果为在小型图数据集上的性能提供了初步见解,我们建议您尝试这些模型并实验性能。

2.4 内部机制

本节深入探讨了图表示和嵌入的理论基础,特别是在 GNN 上下文中的应用。它强调了嵌入在将复杂图数据转换为低维、可管理的形式方面的重要性,同时保留了重要信息。

我们区分了两种主要的学习类型:传导式学习和归纳式学习。传导式方法,如 N2V,专门为训练数据优化嵌入,因此在已知数据集内有效,但对新数据的适应性较差。与之相反,归纳式方法,如 GNN,通过在训练过程中整合图结构和节点特征,使得模型能够推广到新的、未见过的数据。 本节还探讨了 N2V(随机游走)和 GNN(消息传递)背后的机制。

2.4.1 表示和嵌入

理解图表示和嵌入的作用对于有效地应用 GNN 在机器学习中的作用至关重要。表示将复杂的图数据转换为更简单、可管理的形式,同时不丢失关键信息,从而促进对图中潜在结构的分析和解读。在 GNN 的背景下,表示能够以与机器学习算法兼容的方式处理图数据,确保图的丰富和复杂结构得以保留。

传统方法,如邻接矩阵和边列表,为表示图结构提供了基础方式,但它们通常无法捕捉更丰富的信息,如节点特征或微妙的拓扑细节。图嵌入正是在这个方面发挥作用。图嵌入是图、节点或边的低维向量表示,保留了重要的结构和关系信息。就像将高分辨率的图像压缩为紧凑的特征向量一样,嵌入简化了图的复杂性,同时保留了其独特的特征。

嵌入简化了数据处理,并为机器学习应用开辟了新的可能性。它们使得我们可以在二维或三维空间中可视化复杂的图,从而更直观地探索图的内在结构和关系。此外,嵌入作为多种下游任务的通用输入,例如节点分类和链接预测,正如本章前面的部分所示。通过提供原始图数据与机器学习模型之间的桥梁,嵌入是释放 GNN 潜力的关键。

节点相似性和上下文的重要性

图嵌入的一个重要应用是 encapsulate(封装)图中相似性和上下文的概念。在空间上下文中,接近性(或相似性)通常表现为点之间的可测量距离或角度。

然而,对于图来说,这些概念通过连接和路径重新定义。节点之间的相似性可以通过它们的连接性来解释,即从一个节点到另一个节点需要多少次“跳跃”或步骤,或者在图上进行随机游走时,从一个节点到另一个节点的可能性(见图 2.11)。

image.png

另一种思考接近性的方式是通过概率来表示:给定两个节点(节点 A 和节点 B),如果我从节点 A 开始跳跃,遇到节点 B 的概率是多少?在图 2.12 中,如果跳跃次数为 1,则概率为 0,因为在一次跳跃中无法从节点 A 到达节点 B。然而,如果跳跃次数为 2,那么我们需要首先计算一下有多少条不同的路径。假设在一次遍历中没有节点会被遇到两次,并且每个方向的可能性是相等的。在这些假设下,从节点 A 出发,存在三条不同的两跳路径。在这三条路径中,只有一条通向节点 B。因此,概率是三分之一,或 33%。这种通过概率衡量节点之间接近性的方法提供了对图拓扑的细致理解,意味着图结构可以在一个概率空间内进行编码。

image.png

这里解释的概念与我们接下来讨论的图嵌入中的归纳式和传导式方法密切相关。这两种方法都使用了节点接近性的概念,尽管方式不同,用以生成能够捕捉节点关系和图结构本质的嵌入。归纳式方法擅长于对新数据进行泛化,使得模型能够适应和学习超出其初始训练集的内容。相反,传导式方法专门针对训练数据本身优化嵌入,使它们在已学习的上下文中非常有效,但在遇到新数据时灵活性较差。

2.4.2 传导式和归纳式方法

嵌入的创建方式决定了其后续使用的范围。这里我们研究可以广泛归类为传导式和归纳式的嵌入方法。

传导式嵌入方法 为固定的节点集合在单一、静态的图中学习表示:

  • 这些方法直接为每个节点优化单独的嵌入。
  • 在训练过程中,整个图结构必须可用。
  • 这些方法不能自然地推广到未见过的节点或图。
  • 添加新节点需要重新训练整个模型。

示例包括 DeepWalk、N2V 和矩阵分解方法。传导式方法让我们能够缩小预测问题的范围。在传导过程中,我们只关心所提供的数据。

  • 对于大量数据,这些方法计算代价较高。

归纳式嵌入方法 学习一个函数来生成嵌入,使得能够推广到未见过的节点,甚至是全新的图:

  • 这些方法学习聚合和转换节点特征以及局部图结构。
  • 这些方法可以在不重新训练的情况下为先前未见过的节点生成嵌入。
  • 节点属性或结构特征通常被用来学习嵌入。
  • 这些方法对于动态或扩展的图更具灵活性和可扩展性。

示例包括 GraphSAGE、GCN 和图注意力网络(GAT)。让我们通过两个示例来说明这一点:

示例 1:电子邮件垃圾邮件检测 — 一个归纳式模型用于电子邮件垃圾邮件检测,训练时使用带标签的电子邮件数据集(垃圾邮件或非垃圾邮件),并学会从训练数据中泛化。训练完成后,模型可以对新的电子邮件进行垃圾邮件或非垃圾邮件分类,无需重新训练。

在这个例子中,传导式方法不会更好,因为模型在每批新的电子邮件到来时都需要重新训练,这使得它们计算成本高昂,不适用于实时垃圾邮件检测。

示例 2:社交网络中的社区检测半监督学习 — 传导式模型使用整个图来识别社交网络中的社区。通过结合已标记和未标记的节点,模型能够更好地利用网络:归纳式模型无法充分利用特定的网络结构和节点之间的连接性,因为它们只处理部分数据——即训练集。这些信息不足以进行准确的社区检测。

表 2.5 比较我们迄今为止学到的不同类型的图表示,涵盖了非嵌入方法和嵌入方法所生成的表示。
表 2.5 不同的图表示方法
表示方法描述示例
基本数据表示• 适用于涉及网络遍历的分析方法 • 对一些节点分类算法有用 • 提供的信息:节点和边邻居• 邻接表 • 边列表 • 邻接矩阵
传导式(浅层)嵌入• 对未训练过的数据无用 • 难以扩展• DeepWalk • N2V • TransE • RESCAL • 图因式分解 • 谱方法
归纳式嵌入• 模型可以推广到新的和结构不同的图 • 将数据表示为连续空间中的向量 • 学习从数据(新旧)到连续空间位置的映射 • 可以使用 GNNs 归纳式生成嵌入• Transformers • N2V 与特征连接

与传导式嵌入方法相关的术语总结

与嵌入方法相关的两个附加术语,有时与嵌入方法互换使用的是浅层方法和编码器。下面,我们将简要区分这些术语。

传导式方法,如前所述,是一个庞大的方法类,其中图嵌入是其中的一种应用。因此,在我们当前的表示学习上下文之外,传导式学习的属性保持不变。

在机器学习中,浅层通常用来指代与深度学习模型或算法相对的概念。与深度学习模型不同,这类模型不使用多层处理层来从输入数据中生成输出。在图/节点嵌入的上下文中,这个术语也指不依赖深度学习的算法,但更具体地说,它指的是模仿简单查找表的方法,而不是通过监督学习算法生成的通用模型。

任何能够重现数据的低维表示(嵌入)的方法通常被称为编码器。这个编码器简单地将给定的数据点(如节点,甚至整个图)与其在低维空间中的相应嵌入进行匹配。GNN 可以广泛地理解为编码器的一类,类似于 Transformer。然而,也有特定的 GNN 编码器,例如图自编码器(GAE),你将在第 5 章中见到它。

2.4.3 N2V:图中的随机游走

随机游走方法通过在图中进行随机游走来构建嵌入。使用这些方法,两个节点 A 和 B 之间的相似性定义为从节点 A 开始的随机图遍历中遇到节点 B 的概率(如我们在第 2.4.1 节中所描述)。这些游走是无约束的,没有任何限制阻止游走回溯或多次遇到相同的节点。

对于每个节点,我们在其邻域内执行随机游走。随着我们进行越来越多的随机游走,我们开始注意到我们遇到的节点类型之间的相似性。一种潜在的思维模型是探索一个城市或森林。例如,在一个特定的邻域中,当我们多次走同样的街道或小道时,我们开始注意到房屋有相似的风格,树木有相似的物种。

随机游走方法的结果是一个包含每次游走中访问的节点的向量,起始节点不同。在接下来的图 2.13 中,我们展示了如何在图上进行游走(或搜索)的一些示例。

DeepWalk 是一种通过对每个节点执行多个固定大小的随机游走并从每个游走中计算嵌入来创建嵌入的方法。在这里,任何路径的出现概率都是相等的,这使得游走没有偏向,也意味着所有通过边连接的节点在每一步都是同样可能被遇到的。对于图 2.13 中的 DeepWalk,输出可能是向量 [u, s1, s3] 或向量 [u, s1, s2, s4, s5]。这些向量包含了在随机游走中访问的独特节点。

N2V 在 DeepWalk 的基础上进行了改进,通过引入可调的偏向来调整这些随机游走。其思路是能够在学习节点附近邻域的知识和更远处的知识之间进行权衡。N2V 在两个参数中捕捉到这一点:

  • p — 控制路径返回到前一个节点的概率。
  • q — 控制是进行深度优先搜索(DFS,一种强调远离节点的跳跃策略)还是广度优先搜索(BFS,一种强调附近节点的策略)的概率。DFS 和 BFS 在图 2.13 中有示意,我们展示了四次跳跃时发生的情况。

为了模仿 DeepWalk 算法,p 和 q 都会设置为 0,这样搜索就不会有偏向。因此,对于图 2.13,N2V 的输出可能是 [u, s1, s2] 或 [u, s4, s5, s6],这取决于游走是 BFS 还是 DFS。

image.png

一旦我们得到了节点的向量,就可以通过使用神经网络来预测给定节点的最可能邻居节点,从而创建嵌入。通常,这个神经网络是浅层的,只有一个隐藏层。经过训练后,隐藏层就成为该节点的嵌入。

2.4.4 消息传递作为深度学习

一般来说,深度学习方法由构建块或层组成,这些层接收一些基于张量的输入,然后在通过各个层的过程中产生输出,输出会经过转换。最终,更多的转换和聚合操作会被应用,以得到预测。然而,隐藏层的输出通常会被直接用于模型架构中的其他任务,或者作为输入传递给其他模型。这就是我们在第 2.3 节的分类问题中看到的情况。我们构建了一个访问节点的向量,这些节点随后被传递到深度学习模型中。深度学习模型学会了基于起始节点预测未来的节点。但实际的嵌入是包含在网络的隐藏层中的。

提示: 若想复习深度学习的相关内容,可以阅读 François Chollet 所著的《Deep Learning with Python》(Manning,2021)。

我们在图 2.14 中展示了一个经典的深度前馈神经网络架构,特别是多层感知器(MLP)。简而言之,网络接收一个节点向量作为输入,隐藏层被训练以产生一个输出向量,用于完成某个任务,例如识别节点类别。输入向量可以是展平的图像,而输出可能是一个单一的数字,表示图像中是否有狗或猫。对于 N2V 示例,输入是一个起始节点的向量,输出是从起始节点遍历图后访问的相应其他节点的向量。在图像示例中,输出是明确的任务函数,即根据图像中是否包含狗或猫来分类。在 N2V 中,输出隐含在图的结构中。我们知道会访问哪些后续节点,但我们关心的是网络如何编码数据,也就是它如何构建节点数据的嵌入。这些嵌入包含在隐藏层中,通常我们只取最后一层。

image.png

对于 GNN,输入将是整个图的结构,输出将是嵌入。因此,模型在构建嵌入时是显式的。然而,输出不仅限于嵌入。相反,我们可以将输出设置为分类任务,例如判断图中该节点的书籍是否具有特定的政治倾向。嵌入仍然是隐式地包含在隐藏层中的。然而,整个过程被封装在一个模型中,因此我们无需提取这些数据。相反,我们通过使用嵌入来实现我们的目标,例如节点分类。

尽管 GNN 的架构与前馈神经网络非常不同,但也有一些相似之处。在我们学习的许多 GNN 中,一个张量形式的图被传入 GNN 架构,并应用一次或多次消息传递迭代。消息传递过程如图 2.15 所示。

image.png

在第 1 章中,我们首先讨论了消息传递的概念。在其最简单的形式中,消息传递反映了我们从节点或边获取信息或数据并将其发送到其他地方[1]。消息即数据,我们在图的结构中传递这些消息。每个消息可以包含来自发送方或接收方的信息,或者通常同时包含两者的信息。

现在,我们可以进一步解释为什么消息传递对 GNN(图神经网络)如此重要。消息传递步骤通过使用节点信息和节点邻域信息(包括附近节点的数据和连接它们的边数据)来更新每个节点的信息。消息传递是我们构建图表示的方式。这些是构建图嵌入的关键机制,图嵌入随后用于其他任务,如节点分类。在构建这些节点(或边)嵌入时,有两个重要的方面需要考虑。

首先,我们需要思考消息中包含的内容。在我们之前的例子中,我们有一个关于政治主题书籍的列表。这个数据集只有书籍的联合购买连接信息和它们的政治倾向标签。然而,如果我们有额外的信息,比如书籍的长度、作者名,甚至是书籍的简介,那么这些节点特征可以包含在我们的消息中。然而,重要的是要记住,消息中也可能包含边数据,比如另一本书一起购买的情况。实际上,有时消息可能同时包含节点和边的数据。

第二,我们需要思考在制作每个嵌入时,我们希望考虑多少局部信息。我们希望知道在采样邻域时需要考虑多少邻居。我们在介绍随机游走方法时已经讨论过这一点。我们需要定义在采样图时需要跳跃多少次。

数据和跳跃次数对 GNN 中的消息传递至关重要。特征,无论是节点数据还是边数据,都是消息,而跳跃次数是我们传递消息的次数。这两者都由 GNN 的层来控制。隐藏层的数量是我们传递消息的跳跃次数。每个隐藏层的输入是消息中包含的数据。对于 GNN,这几乎总是成立的,但值得注意的是,它并不总是如此。有时,像注意力机制这样的其他机制可以决定消息传递样本的深度,尤其是来自邻域的样本。我们将在第 4 章讨论图注意力网络(GATs)。在此之前,理解 GNN 中层数反映了消息传递期间的跳跃次数是一个很好的直觉。

对于像图 2.15 左侧的前馈网络,信息在神经网络的节点之间传递。在 GNN 中,这些信息就是我们在图上传递的消息。对于每个消息传递步骤,神经网络中的顶点从相距一跳的节点或边收集信息。因此,如果我们希望节点的表示考虑到来自三跳之外的节点,我们需要三个隐藏的消息传递层。三个层可能看起来不多,但随着跳跃次数的增加,我们覆盖的图的规模是指数级增长的。直观上,我们可以将其理解为一种六度分隔原理——即所有人之间最多只有六度社交分隔。这意味着你和我可以通过全球社交网络的六次短跳连接起来。

不同的消息传递方案导致了不同类型的 GNN。因此,对于我们在本书中学习的每个 GNN,我们将密切关注消息传递的数学和代码实现。一个重要的方面是我们如何聚合消息,我们将在第 3 章深入讨论 GCN 时讨论这一点。

消息传递之后,结果张量会通过前馈层,最终产生一个预测。在图 2.16 左侧的图示中,展示了一个用于节点预测的 GNN 模型,数据流通过消息传递层,然后张量经过额外的 MLP 和激活函数,输出预测。例如,我们可以使用我们的 GNN 来分类员工是否可能加入新公司,或是否有好的推荐。

image.png

然而,与之前所示的前馈神经网络一样,我们也可以仅输出隐藏层,并直接使用该输出。对于 GNN,这个输出就是图、节点或边的嵌入。

关于 GNN 中消息传递的最后一点需要注意的是,在 GNN 的每个消息传递层中,我们将从一个节点向另一个相距一跳的节点传递信息。重要的是,神经网络随后从这些一跳邻居节点获取数据,并应用非线性变换。这就是 GNN 的美妙之处;我们在每个节点和/或边的层面应用多个小的神经网络,以构建图特征的嵌入。因此,当我们说消息传递层像是神经网络的第一层时,我们实际上是在说它是许多独立神经网络的第一层,这些神经网络都在学习局部节点数据或边特定的数据。实际上,整体的构建和训练代码与单一变换相同,但我们应用独立非线性变换的直觉将在我们深入研究复杂 GNN 模型的工作原理时变得非常有用。

总结

  • 节点和图嵌入是从数据中提取洞察的强大方法,它们可以作为我们机器学习模型中的输入/特征。生成此类嵌入的方法有多种独立的方式。GNN 将嵌入内置于架构中。

  • 图嵌入,包括节点和边的嵌入,是将复杂图数据转换为适合机器学习任务的结构化格式的基础技术。

  • 我们探索了两种主要类型的图嵌入:N2V,这是一种传导式方法,和基于 GNN 的嵌入,这是一种归纳式方法,它们各自具有不同的特点和应用。

  • N2V 通过随机游走在固定数据集上建立节点上下文和相似性,但它无法推广到未见过的数据或图。

  • 另一方面,GNN 是多功能的归纳式框架,可以为新的、未见过的数据生成嵌入,使其能够适应不同的图结构。

  • 在机器学习任务中比较嵌入方法(如半监督学习)揭示了根据数据大小、复杂性和具体问题选择合适的嵌入方法的重要性。

  • 尽管随机森林分类器在处理较小图的 N2V 和 GNN 嵌入时表现有效,GNN 显示出利用图拓扑和节点特征的独特能力,特别是在较大和更复杂的图中。

  • 嵌入可以作为特征用于传统机器学习模型,也可以用于图数据可视化和洞察提取。