ToB企服应用市场:ToB评测及商务社交产业平台

标题: 【机器学习】训练GNN图神经网络模型进行节点分类 [打印本页]

作者: 拉不拉稀肚拉稀    时间: 2024-6-28 16:47
标题: 【机器学习】训练GNN图神经网络模型进行节点分类
1. 引言

1.1 图神经网络GNN概述

图神经网络(Graph Neural Network,GNN)是一种专门用于处置惩罚图结构数据的神经网络方法。它起源于2005年,当时Gori等人首次提出了GNN的概念,用于学习图中的节点特性以及它们之间的关系。随后,随着深度学习技术的快速发展,GNN得到了广泛的关注和研究。
1.1.1 GNN的核心算法思想

GNN的核心思想是通过迭代地聚合节点的邻居信息来更新节点的表示,从而捕获图的结构信息。这种聚合过程可以看作是一种特殊的图卷积操作,使得GNN可以或许有效地处置惩罚图数据,并提取出节点和边之间的关系特性。主流的GNN算法包罗图卷积神经网络(GCN)、图自编码器(Graph Autoencoder)、图生成网络(Graph Generative Network)等。
1.1.2 GNN的应用场景

GNN的应用场景非常广泛,包罗交际网络分析、推荐体系、生物信息学、交通流预测等。比方,在交际网络中,GNN可以用于分析用户之间的关系,预测用户的爱好和举动;在推荐体系中,GNN可以使用用户-物品交互图来提供个性化的推荐;在生物信息学中,GNN可以用于分析蛋白质-蛋白质相互作用网络,预测蛋白质的功能和性质。
1.1.3 GNN图神经网络面对的挑衅

尽管GNN在处置惩罚图数据方面取得了明显的希望,但仍面对一些挑衅。起首,大规模图数据的处置惩罚是一个难题,必要计划高效的GNN架构和训练算法来应对。其次,动态图数据的处置惩罚也是一个挑衅,由于图的结构和节点属性可能会随时间发生变革。别的,GNN在处置惩罚异构图(即节点和边具有差别类型和属性的图)时也必要进一步的研究。
未来,GNN的研究将继承关注提高模型的性能、扩展应用范围以及办理上述挑衅。随着深度学习技术的不停进步和计算本事的提升,GNN有望在更多领域发挥重要作用,并推动图数据分析和应用的进一步发展。
1.2 节点分类

节点分类(Node Classification)是图数据分析中的一个重要任务,其目标是根据图的结构信息和节点的属性特性来预测图中节点的类别标签。在图数据中,节点通常表示实体,而边则表示实体之间的关系。节点分类在很多领域都有广泛的应用,如交际网络分析、生物信息学、推荐体系等。
1.2.1 节点分类任务的步调

在图神经网络(Graph Neural Networks, GNNs)的框架下,节点分类通常通过以下步调实现:

节点分类任务中,GNNs的优势在于它们可以或许捕获图数据的复杂依靠关系,并使用这些信息来提高分类性能。与传统的机器学习方法相比,GNNs可以或许更好地处置惩罚图数据的非欧几里得结构,并考虑节点之间的连接关系。
1.2.2 节点分类面对的挑衅

然而,节点分类也面对一些挑衅。起首,当图的规模非常大时,GNNs的训练和推理过程可能会变得非常耗时。其次,对于动态图数据,GNNs必要可以或许处置惩罚节点和边的添加、删除以及属性变革等情况。别的,当图中的节点和边具有差别的类型和属性时,GNNs也必要具备处置惩罚异构图的本事。
预测未来,节点分类的研究将继承关注提高GNNs的性能、扩展应用范围以及办理上述挑衅。随着深度学习技术的不停进步和计算本事的提升,GNNs有望在更多领域发挥重要作用,并推动图数据分析和应用的进一步发展。
1.3 GNN模型用于节点分类

图神经网络(GNN)在节点分类任务中发挥着关键作用。节点分类的目标是根据图的结构信息和节点的属性特性来预测图中每个节点的类别标签。GNN模型通过迭代地更新节点的表示来捕获节点之间的相互作用和依靠关系,进而实现高效的节点分类。
1.3.1 GNN进行节点分类的原理

GNN模型的工作原理在于通过聚合节点的邻居信息来更新节点的表示。起首,每个节点的表示向量被初始化为其初始特性向量。然后,在信息传递阶段,GNN通过图卷积操作或图注意力机制来聚合节点的邻居节点的信息,并更新节点的表示。这一过程中,邻居节点的信息被加权求和或聚合,以反映它们对目标节点的影响。通过多次迭代信息传递和聚合邻居信息的过程,GNN可以或许渐渐更新节点的表示,使其包罗更丰富的图结构信息。
1.3.2 GNN的优势及应用

GNN模型在节点分类任务中的优势在于其可以或许捕获图数据中的复杂依靠关系和结构信息,这对于节点分类任务至关重要。同时,GNN模型具有灵活性和可扩展性,可以或许处置惩罚具有差别巨细和结构的图数据,并适应大规模图数据的处置惩罚需求。
GNN在节点分类任务中的应用广泛,涵盖了交际网络分析、生物信息学和推荐体系等领域。在交际网络中,GNN可以辨认用户所属的潜在角色或群体,并根据爱好对用户进行分类。在生物信息学中,GNN可以应用于分子结构的分类,预测分子的功能和性质。在推荐体系中,GNN可以处置惩罚用户-项目交互的图结构数据,预测用户对未知物品的喜好程度,并向用户推荐合适的项目。
随着深度学习技术的不停进步和计算本事的提升,GNN有望在更多领域发挥重要作用,并推动图数据分析和应用的进一步发展。
2. GNN模型实现节点分类的过程

很多在各种机器学习(ML)应用中的数据集,实在体之间存在结构关系,这些关系可以表示为图。这类应用包罗交际和通信网络分析、交通预测以及欺诈检测。图表示学习旨在构建和训练用于图数据集的模型,以便用于各种机器学习任务。
本文的例子展示了一个图神经网络(GNN)模型的简单实现。该模型用于Cora数据集上的节点预测任务,以根据论文的词汇和引用网络来预测论文的主题。
本文我们重新开始实现了一个图卷积层,以便更好地理解它们是怎样工作的。然而,也有很多基于TensorFlow的专用库提供了丰富的GNN API,比方Spectral、StellarGraph和GraphNets等。
2.1 设置

  1. import os
  2. import pandas as pd
  3. import numpy as np
  4. import networkx as nx
  5. import matplotlib.pyplot as plt
  6. import tensorflow as tf
  7. from tensorflow import keras
  8. from tensorflow.keras import layers
复制代码
2.2 准备下载

以下的段代码是用于下载息争压缩Cora数据集的Python脚本:
代码的作用是确保Cora数据集被下载并解压缩到步伐可以访问的目录中。Cora数据集通常用于图神经网络的节点分类任务,包罗了论文的引用关系和内容信息。在机器学习或深度学习项目中,这样的数据准备步调是常见的,以便于后续的数据加载和处置惩罚。
  1. zip_file = keras.utils.get_file(
  2.     fname="cora.tgz",
  3.     origin="https://linqs-data.soe.ucsc.edu/public/lbc/cora.tgz",
  4.     extract=True,
  5. )
  6. data_dir = os.path.join(os.path.dirname(zip_file), "cora")
复制代码
2.3 数据预处置惩罚

2.3.1 加载并可视化数据集

处置惩罚并可视化数据集代码是用来加载和处置惩罚Cora数据集中的引用信息:
引用数据集citations通常包罗了两列,一列是被引用论文的ID(target),另一列是引用它的论文的ID(source)。在图神经网络中,这些引用可以表示为图中的边,此中每条边连接了两个节点(论文),用于捕捉论文之间的相互关系。
  1. citations = pd.read_csv(
  2.     os.path.join(data_dir, "cora.cites"),
  3.     sep="\t",
  4.     header=None,
  5.     names=["target", "source"],
  6. )
  7. print("Citations shape:", citations.shape)
复制代码
2.3.2 展示数据框

下面的代码将展示引文(citations)数据框的一个样本。目标列包罗了由源列中的论文ID所引用的论文ID。
在Pandas中,citations.sample(frac=1).head()
这行代码执行了以下操作:
综合来看,citations.sample(frac=1).head()
这行代码的作用是:从citations DataFrame中随机抽取全部纪录,然后返回这些纪录的前5行。这通常用于获取数据集的一个随机样本,以便进行快速查抄或展示数据的多样性。
请注意,每次执行这个操作时,由于是随机采样,返回的5行可能会差别。如果你渴望每次采样都得到雷同的效果,可以在调用sample方法时指定随机种子,比方citations.sample(frac=1, random_state=1).head()。
  1. citations.sample(frac=1).head()
复制代码
2.3.3 加载数据到Pandas DataFrame

现在,让我们将论文数据加载到Pandas DataFrame中。
起首创建了一个名为column_names的列表,它包罗了Cora数据集中论文内容文件的全部列名。第一列是paper_id,接下来的1433列用term_0到term_1432命名,代表论文中术语的存在与否,最后一列是subject,表示论文的主题。
然后使用pd.read_csv函数读取Cora数据集的cora.content文件,这个文件包罗了论文的内容信息。通过os.path.join函数构建文件的完整路径,sep参数指定了制表符作为字段的分隔符,header参数设置为None表示文件中没有提供列名的头部行,names参数用来指定前面创建的列名列表。
最后,使用print函数打印出加载后的papers DataFrame的形状,即它包罗的行数和列数,这有助于了解数据集的规模。
  1. column_names = ["paper_id"] + [f"term_{idx}" for idx in range(1433)] + ["subject"]
  2. papers = pd.read_csv(
  3.     os.path.join(data_dir, "cora.content"), sep="\t", header=None, names=column_names,
  4. )
  5. print("Papers shape:", papers.shape)
复制代码
  1. Papers shape: (2708, 1435)
复制代码
2.3.4 展示Pandas DataFrame

展示论文DataFrame的一个样本。该DataFrame包罗paper_id和subject列,以及1,433个二进制列,这些列表示相应术语是否存在于论文中。
  1. print(papers.sample(5).T)
复制代码
显示每个主题的论文数量
  1. print(papers.subject.value_counts())
复制代码
  1. Neural_Networks           818
  2. Probabilistic_Methods     426
  3. Genetic_Algorithms        418
  4. Theory                    351
  5. Case_Based                298
  6. Reinforcement_Learning    217
  7. Rule_Learning             180
  8. Name: subject, dtype: int64
复制代码
2.3.5 创建数据索引和转换

索引和转换的目标是对Cora数据集中的论文和引用信息进行预处置惩罚,使其适合用于图神经网络模型的训练。

通过这些步调,原始数据集中的文本标识符被转换为模型易于处置惩罚的数值形式,为后续的图神经网络模型训练做好了准备。这种转换有助于模型更高效地学习论文之间的引用关系以及它们对应的主题分类。
  1. class_values = sorted(papers["subject"].unique())
  2. class_idx = {name: id for id, name in enumerate(class_values)}
  3. paper_idx = {name: idx for idx, name in enumerate(sorted(papers["paper_id"].unique()))}
  4. papers["paper_id"] = papers["paper_id"].apply(lambda name: paper_idx[name])
  5. citations["source"] = citations["source"].apply(lambda name: paper_idx[name])
  6. citations["target"] = citations["target"].apply(lambda name: paper_idx[name])
  7. papers["subject"] = papers["subject"].apply(lambda value: class_idx[value])
复制代码
2.3.6 可视化引用图

代码用于可视化Cora数据集的引用网络,具体步调如下:

通过这种方式,代码生成了一个视觉化的网络图,此中节点代表论文,节点之间的连线代表引用关系,节点的颜色表示论文的主题。这种可视化有助于直观地理解数据集中论文之间的相互关系及其主题分布。
  1. plt.figure(figsize=(10, 10))
  2. colors = papers["subject"].tolist()
  3. cora_graph = nx.from_pandas_edgelist(citations.sample(n=1500))
  4. subjects = list(papers[papers["paper_id"].isin(list(cora_graph.nodes))]["subject"])
  5. nx.draw_spring(cora_graph, node_size=15, node_color=subjects)
复制代码
图中的每个节点代表一篇论文,节点的颜色对应其主题。请注意,我们仅显示数据集中论文的一个样本。

2.3.7 数据分割

将数据集划分为分层的训练集和测试集
代码的目标是将Cora数据集分割为训练集和测试集,具体步调如下:

通过这种方式,代码实现了数据集的分层抽样,确保了每个主题在训练集和测试集中都有代表性,同时也包管了数据的随机性,这对于训练和评估机器学习模型是非常重要的。
  1. train_data, test_data = [], []
  2. for _, group_data in papers.groupby("subject"):
  3.     # Select around 50% of the dataset for training.
  4.     random_selection = np.random.rand(len(group_data.index)) <= 0.5
  5.     train_data.append(group_data[random_selection])
  6.     test_data.append(group_data[~random_selection])
  7. train_data = pd.concat(train_data).sample(frac=1)
  8. test_data = pd.concat(test_data).sample(frac=1)
  9. print("Train data shape:", train_data.shape)
  10. print("Test data shape:", test_data.shape)
复制代码
2.4 训练和评估实验

2.4.1 定义超参数

设置用于训练图神经网络模型的超参数:

这些超参数的选择对模型的性能和训练过程有重要影响。通常,这些值必要根据具体问题和数据集进行调整,以获得最佳的模型表现。
  1. hidden_units = [32, 32]
  2. learning_rate = 0.01
  3. dropout_rate = 0.5
  4. num_epochs = 300
  5. batch_size = 256
复制代码
2.4.2 编译和训练输入模型

定义了一个名为 run_experiment 的函数,用于编译和训练给定的图神经网络模型,并返回训练过程中的历史纪录。
这个函数封装了模型训练的整个过程,使得可以方便地对差别的模型设置进行实验,并通过早停法来优化训练过程。通太过析返回的history对象,可以了解模型在训练过程中的性能变革。
  1. def run_experiment(model, x_train, y_train):
  2.     # Compile the model.
  3.     model.compile(
  4.         optimizer=keras.optimizers.Adam(learning_rate),
  5.         loss=keras.losses.SparseCategoricalCrossentropy(from_logits=True),
  6.         metrics=[keras.metrics.SparseCategoricalAccuracy(name="acc")],
  7.     )
  8.     # Create an early stopping callback.
  9.     early_stopping = keras.callbacks.EarlyStopping(
  10.         monitor="val_acc", patience=50, restore_best_weights=True
  11.     )
  12.     # Fit the model.
  13.     history = model.fit(
  14.         x=x_train,
  15.         y=y_train,
  16.         epochs=num_epochs,
  17.         batch_size=batch_size,
  18.         validation_split=0.15,
  19.         callbacks=[early_stopping],
  20.     )
  21.     return history
复制代码
定义了一个名为 display_learning_curves 的函数,它的作用是将模型训练过程中的损失和准确率绘制成图表,以便可视化训练进度和性能。
通过这个函数,可以直观地观察模型在各个周期(epochs)上的训练损失和准确率,以及它们在验证集上的表现。这有助于分析模型的训练动态,比如是否存在过拟合(训练损失持续下降而验证损失上升)或欠拟合(训练和验证损失都较高且没有明显下降)。同时,准确率曲线可以资助我们了解模型在训练和验证集上的预测性能。
  1. def display_learning_curves(history):
  2.     fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 5))
  3.     ax1.plot(history.history["loss"])
  4.     ax1.plot(history.history["val_loss"])
  5.     ax1.legend(["train", "test"], loc="upper right")
  6.     ax1.set_xlabel("Epochs")
  7.     ax1.set_ylabel("Loss")
  8.     ax2.plot(history.history["acc"])
  9.     ax2.plot(history.history["val_acc"])
  10.     ax2.legend(["train", "test"], loc="upper right")
  11.     ax2.set_xlabel("Epochs")
  12.     ax2.set_ylabel("Accuracy")
  13.     plt.show()
复制代码
2.5 定义前馈神经网络(FFN)模块

定义了一个函数 create_ffn,用于创建一个前馈神经网络(Feed-Forward Neural Network,FFN)模块。这个模块是一个序列化的模型,可以作为图神经网络中的一部分,用于节点特性的转换和处置惩罚。
这个 create_ffn 函数提供了一个灵活的方式来构建FFN模块,可以根据差别的需求调整隐藏单元的数量和dropout率,以适应差别的图神经网络架构。通过这种方式,可以轻松地在GNN模型中实现复杂的特性转换和处置惩罚逻辑。
  1. def create_ffn(hidden_units, dropout_rate, name=None):
  2.     fnn_layers = []
  3.     for units in hidden_units:
  4.         fnn_layers.append(layers.BatchNormalization())
  5.         fnn_layers.append(layers.Dropout(dropout_rate))
  6.         fnn_layers.append(layers.Dense(units, activation=tf.nn.gelu))
  7.     return keras.Sequential(fnn_layers, name=name)
复制代码
2.6 构建基准神经网络模型

代码是数据预处置惩罚的一部分,主要负责从Cora数据集中提取特性和目标,并将它们转换为适合模型训练和测试的格式。
通过这些步调,代码准备好了用于训练和测试图神经网络模型的数据。x_train和x_test包罗了输入特性,而y_train和y_test包罗了对应的目标标签。这种数据格式是大多数机器学习模型训练所必须的。
  1. feature_names = list(set(papers.columns) - {"paper_id", "subject"})
  2. num_features = len(feature_names)
  3. num_classes = len(class_idx)
  4. # Create train and test features as a numpy array.
  5. x_train = train_data[feature_names].to_numpy()
  6. x_test = test_data[feature_names].to_numpy()
  7. # Create train and test targets as a numpy array.
  8. y_train = train_data["subject"]
  9. y_test = test_data["subject"]
复制代码
2.6.1 定义基准分类器

我们添加了五个带有跳跃连接的前馈网络(FFN)块,以便生成一个基准模型,该模型大抵具有与稍后要构建的图神经网络(GNN)模型雷同数量的参数。
定义了一个函数 create_baseline_model 用于创建一个基线模型,这个模型是一个多层前馈神经网络(FFN),用于作为图神经网络(GNN)模型的对比基准。然后,代码实例化了这个基线模型并打印了其结构摘要。以下是详细步调:
这个基线模型通过堆叠多个FFN块并使用跳跃连接来学习数据的表示,它将作为后续图神经网络模型性能的对比基准。通过比较基线模型和GNN模型的性能,可以评估GNN模型使用图结构信息的本事。
  1. def create_baseline_model(hidden_units, num_classes, dropout_rate=0.2):
  2.     inputs = layers.Input(shape=(num_features,), name="input_features")
  3.     x = create_ffn(hidden_units, dropout_rate, name=f"ffn_block1")(inputs)
  4.     for block_idx in range(4):
  5.         # Create an FFN block.
  6.         x1 = create_ffn(hidden_units, dropout_rate, name=f"ffn_block{block_idx + 2}")(x)
  7.         # Add skip connection.
  8.         x = layers.Add(name=f"skip_connection{block_idx + 2}")([x, x1])
  9.     # Compute logits.
  10.     logits = layers.Dense(num_classes, name="logits")(x)
  11.     # Create the model.
  12.     return keras.Model(inputs=inputs, outputs=logits, name="baseline")
  13. baseline_model = create_baseline_model(hidden_units, num_classes, dropout_rate)
  14. baseline_model.summary()
复制代码
2.6.2 训练基准分类器

代码执行了基线模型的训练实验,并纪录了训练过程中的详细统计信息。
这个步调是机器学习流程中模型训练的关键环节,它为后续的模型评估和调优提供了必要的信息和数据。通太过析history对象,可以对模型的训练效果有一个直观的了解。
  1. history = run_experiment(baseline_model, x_train, y_train)
  2. display_learning_curves(history)
复制代码

2.6.3 评估基准模型

现在我们使用测试数据拆分来评估基准模型。
通过这段代码,我们可以得到基线模型在未见过的数据上的表现,这是一个重要的指标,用来评估模型的泛化本事。如果准确率较高,说明模型在新数据上也能做出准确的预测;如果准确率较低,则可能必要进一步调整模型或使用更多的训练数据。
  1. test_accuracy = baseline_model.evaluate(x=x_test, y=y_test, verbose=0)
  2. print(f"Test accuracy: {round(test_accuracy * 100, 2)}%")
复制代码
2.7 查抄基准模型的预测本事

我们通过随机生成关于单词出现概率的二进制词向量来创建新的数据实例。
2.7.1 定义函数

定义了两个函数,用于生成随机实例并展示模型对这些实例的分类概率预测。
这两个函数通常用于模型的解释性分析,资助理解模型怎样对输入实例进行分类预测。通过生成随机实例,可以模拟模型在面对未知或随机数据时的举动。展示分类概率可以资助分析模型预测的置信度和多样性。
  1. def generate_random_instances(num_instances):
  2.     token_probability = x_train.mean(axis=0)
  3.     instances = []
  4.     for _ in range(num_instances):
  5.         probabilities = np.random.uniform(size=len(token_probability))
  6.         instance = (probabilities <= token_probability).astype(int)
  7.         instances.append(instance)
  8.     return np.array(instances)
  9. def display_class_probabilities(probabilities):
  10.     for instance_idx, probs in enumerate(probabilities):
  11.         print(f"Instance {instance_idx + 1}:")
  12.         for class_idx, prob in enumerate(probs):
  13.             print(f"- {class_values[class_idx]}: {round(prob * 100, 2)}%")
复制代码
2.7.2 实行预测

调用2.7.1 定义的函数和模型来生成随机实例,获取模型对这些实例的预测,并展示预测效果的分类概率。以下是详细步调:
通过这个过程,可以获得模型对于随机数据的响应,这有助于理解模型的泛化本事和预测举动。由于这些实例是随机生成的,它们并不代表实际的论文内容,但可以作为模型输出的一个参考。
  1. new_instances = generate_random_instances(num_classes)
  2. logits = baseline_model.predict(new_instances)
  3. probabilities = keras.activations.softmax(tf.convert_to_tensor(logits)).numpy()
  4. display_class_probabilities(probabilities)
复制代码
  1. Instance 1:
  2. - Case_Based: 13.02%
  3. - Genetic_Algorithms: 6.89%
  4. - Neural_Networks: 23.32%
  5. - Probabilistic_Methods: 47.89%
  6. - Reinforcement_Learning: 2.66%
  7. - Rule_Learning: 1.18%
  8. - Theory: 5.03%
  9. Instance 2:
  10. - Case_Based: 1.64%
  11. - Genetic_Algorithms: 59.74%
  12. - Neural_Networks: 27.13%
  13. - Probabilistic_Methods: 9.02%
  14. - Reinforcement_Learning: 1.05%
  15. - Rule_Learning: 0.12%
  16. - Theory: 1.31%
  17. Instance 3:
  18. - Case_Based: 1.35%
  19. - Genetic_Algorithms: 77.41%
  20. - Neural_Networks: 9.56%
  21. - Probabilistic_Methods: 7.89%
  22. - Reinforcement_Learning: 0.42%
  23. - Rule_Learning: 0.46%
  24. - Theory: 2.92%
  25. Instance 4:
  26. - Case_Based: 0.43%
  27. - Genetic_Algorithms: 3.87%
  28. - Neural_Networks: 92.88%
  29. - Probabilistic_Methods: 0.97%
  30. - Reinforcement_Learning: 0.56%
  31. - Rule_Learning: 0.09%
  32. - Theory: 1.2%
  33. Instance 5:
  34. - Case_Based: 0.11%
  35. - Genetic_Algorithms: 0.17%
  36. - Neural_Networks: 10.26%
  37. - Probabilistic_Methods: 0.5%
  38. - Reinforcement_Learning: 0.35%
  39. - Rule_Learning: 0.63%
  40. - Theory: 87.97%
  41. Instance 6:
  42. - Case_Based: 0.98%
  43. - Genetic_Algorithms: 23.37%
  44. - Neural_Networks: 70.76%
  45. - Probabilistic_Methods: 1.12%
  46. - Reinforcement_Learning: 2.23%
  47. - Rule_Learning: 0.21%
  48. - Theory: 1.33%
  49. Instance 7:
  50. - Case_Based: 0.64%
  51. - Genetic_Algorithms: 2.42%
  52. - Neural_Networks: 27.19%
  53. - Probabilistic_Methods: 14.07%
  54. - Reinforcement_Learning: 1.62%
  55. - Rule_Learning: 9.35%
  56. - Theory: 44.7%
复制代码
2.8 构建图神经网络模型

2.8.1 准备数据

将图数据准备并加载到模型中进行训练是图神经网络(GNN)模型中最具挑衅性的部分,这通常由专业库以差别的方式办理。在这个例子中,我们展示了一个简单的方法来准备和使用图数据,这种方法实用于你的数据集由单个完全可以在内存中加载的图组成的情况。
图数据由graph_info元组表示,它包罗以下三个元素:
以下代码实现图神经网络模型中图数据的准备工作:
通过这些步调,代码准备好了图神经网络模型所需的图数据结构,包罗节点特性、边的连接信息以及边的权重,这些信息将被用于后续的图神经网络模型训练和推理。
  1. # Create an edges array (sparse adjacency matrix) of shape [2, num_edges].
  2. edges = citations[["source", "target"]].to_numpy().T
  3. # Create an edge weights array of ones.
  4. edge_weights = tf.ones(shape=edges.shape[1])
  5. # Create a node features array of shape [num_nodes, num_features].
  6. node_features = tf.cast(
  7.     papers.sort_values("paper_id")[feature_names].to_numpy(), dtype=tf.dtypes.float32
  8. )
  9. # Create graph info tuple with node_features, edges, and edge_weights.
  10. graph_info = (node_features, edges, edge_weights)
  11. print("Edges shape:", edges.shape)
  12. print("Nodes shape:", node_features.shape)
复制代码
2.8.2 实现图卷积层

我们将图卷积模块实现为一个Keras层。我们的GraphConvLayer执行以下步调:
准备:输入节点表示通过全连接网络(FFN)进行处置惩罚以生成消息。你可以通过仅对表示应用线性变换来简化处置惩罚过程。
聚合:使用与edge_weights相关的排列稳定池化操作(如求和、均值和最大值)来聚合每个节点的邻居的消息,为每个节点准备一个单一的聚合消息。比方,可以使用tf.math.unsorted_segment_sum API 来聚合邻居消息。
更新:node_representations和aggregated_messages(两者都是[num_nodes, representation_dim]的形状)被组合并处置惩罚以生成节点表示的新状态(节点嵌入)。如果combination_type是gru,那么node_repesentations和aggregated_messages会被堆叠成一个序列,然后通过一个GRU层进行处置惩罚。否则,node_repesentations和aggregated_messages会被相加或连接,然后通过一个FFN进行处置惩罚。
所实现的技术借鉴了图卷积网络(Graph Convolutional Networks)、GraphSage、图同构网络(Graph Isomorphism Network)、简单图网络(Simple Graph Networks)和门控图序列神经网络(Gated Graph Sequence Neural Networks)的思想。另外两种未涵盖的关键技术是图注意力网络(Graph Attention Networks)和消息传递神经网络(Message Passing Neural Networks)。
图卷积层GraphConvLayer的实现包罗以下几个关键部分:
这个GraphConvLayer的计划结合了前馈网络和GRU单元来更新节点表示,使其可以或许捕捉图中的局部结构和节点间的关系。通过这种方式,图神经网络可以学习到节点的嵌入表示,这些表示可以用于各种下游任务,比方节点分类或图分类。
  1. def create_gru(hidden_units, dropout_rate):
  2.     inputs = keras.layers.Input(shape=(2, hidden_units[0]))
  3.     x = inputs
  4.     for units in hidden_units:
  5.       x = layers.GRU(
  6.           units=units,
  7.           activation="tanh",
  8.           recurrent_activation="sigmoid",
  9.           return_sequences=True,
  10.           dropout=dropout_rate,
  11.           return_state=False,
  12.           recurrent_dropout=dropout_rate,
  13.       )(x)
  14.     return keras.Model(inputs=inputs, outputs=x)
  15. class GraphConvLayer(layers.Layer):
  16.     def __init__(
  17.         self,
  18.         hidden_units,
  19.         dropout_rate=0.2,
  20.         aggregation_type="mean",
  21.         combination_type="concat",
  22.         normalize=False,
  23.         *args,
  24.         **kwargs,
  25.     ):
  26.         super().__init__(*args, **kwargs)
  27.         self.aggregation_type = aggregation_type
  28.         self.combination_type = combination_type
  29.         self.normalize = normalize
  30.         self.ffn_prepare = create_ffn(hidden_units, dropout_rate)
  31.         if self.combination_type == "gru":
  32.             self.update_fn = create_gru(hidden_units, dropout_rate)
  33.     else:
  34.             self.update_fn = create_ffn(hidden_units, dropout_rate)
  35.     def prepare(self, node_repesentations, weights=None):
  36.         # node_repesentations shape is [num_edges, embedding_dim].
  37.         messages = self.ffn_prepare(node_repesentations)
  38.         if weights is not None:
  39.             messages = messages * tf.expand_dims(weights, -1)
  40.         return messages
  41.     def aggregate(self, node_indices, neighbour_messages, node_repesentations):
  42.         # node_indices shape is [num_edges].
  43.         # neighbour_messages shape: [num_edges, representation_dim].
  44.         # node_repesentations shape is [num_nodes, representation_dim]
  45.         num_nodes = node_repesentations.shape[0]
  46.         if self.aggregation_type == "sum":
  47.             aggregated_message = tf.math.unsorted_segment_sum(
  48.                 neighbour_messages, node_indices, num_segments=num_nodes
  49.             )
  50.         elif self.aggregation_type == "mean":
  51.             aggregated_message = tf.math.unsorted_segment_mean(
  52.                 neighbour_messages, node_indices, num_segments=num_nodes
  53.             )
  54.         elif self.aggregation_type == "max":
  55.             aggregated_message = tf.math.unsorted_segment_max(
  56.                 neighbour_messages, node_indices, num_segments=num_nodes
  57.             )
  58.         else:
  59.             raise ValueError(f"Invalid aggregation type: {self.aggregation_type}.")
  60.         return aggregated_message
  61.     def update(self, node_repesentations, aggregated_messages):
  62.         # node_repesentations shape is [num_nodes, representation_dim].
  63.         # aggregated_messages shape is [num_nodes, representation_dim].
  64.         if self.combination_type == "gru":
  65.             # Create a sequence of two elements for the GRU layer.
  66.             h = tf.stack([node_repesentations, aggregated_messages], axis=1)
  67.         elif self.combination_type == "concat":
  68.             # Concatenate the node_repesentations and aggregated_messages.
  69.             h = tf.concat([node_repesentations, aggregated_messages], axis=1)
  70.         elif self.combination_type == "add":
  71.             # Add node_repesentations and aggregated_messages.
  72.             h = node_repesentations + aggregated_messages
  73.         else:
  74.             raise ValueError(f"Invalid combination type: {self.combination_type}.")
  75.         # Apply the processing function.
  76.         node_embeddings = self.update_fn(h)
  77.         if self.combination_type == "gru":
  78.             node_embeddings = tf.unstack(node_embeddings, axis=1)[-1]
  79.         if self.normalize:
  80.             node_embeddings = tf.nn.l2_normalize(node_embeddings, axis=-1)
  81.         return node_embeddings
  82.     def call(self, inputs):
  83.         """Process the inputs to produce the node_embeddings.
  84.         inputs: a tuple of three elements: node_repesentations, edges, edge_weights.
  85.         Returns: node_embeddings of shape [num_nodes, representation_dim].
  86.         """
  87.         node_repesentations, edges, edge_weights = inputs
  88.         # Get node_indices (source) and neighbour_indices (target) from edges.
  89.         node_indices, neighbour_indices = edges[0], edges[1]
  90.         # neighbour_repesentations shape is [num_edges, representation_dim].
  91.         neighbour_repesentations = tf.gather(node_repesentations, neighbour_indices)
  92.         # Prepare the messages of the neighbours.
  93.         neighbour_messages = self.prepare(neighbour_repesentations, edge_weights)
  94.         # Aggregate the neighbour messages.
  95.         aggregated_messages = self.aggregate(
  96.             node_indices, neighbour_messages, node_repesentations
  97.         )
  98.         # Update the node embedding with the neighbour messages.
  99.         return self.update(node_repesentations, aggregated_messages)
复制代码
2.8.3 实现图神经网络节点分类器

图神经网络(GNN)分类模型遵循图神经网络计划空间的方法,如下所述:
每个添加的图卷积层都会捕获来自更高级别邻居的信息。然而,添加过多的图卷积层可能会导致过平滑,即模型为全部节点生成相似的嵌入。
请注意,传递给Keras模型构造函数的graph_info,并作为Keras模型对象的属性使用,而不是训练或预测的输入数据。模型将接受一批节点索引(node_indices),这些索引用于从graph_info中查找节点特性和邻居。
起首义一个名为 GNNNodeClassifier 的类,它是一个基于图神经网络(GNN)的节点分类器,使用 TensorFlow 框架。
这个模型通过图卷积层来学习节点的表示,并通过残差连接来资助梯度流动,防止深层网络中的梯度消散问题。最终,模型通过一个密集层来输出每个节点属于各个类别的对数几率。
  1. class GNNNodeClassifier(tf.keras.Model):
  2.     def __init__(
  3.         self,
  4.         graph_info,
  5.         num_classes,
  6.         hidden_units,
  7.         aggregation_type="sum",
  8.         combination_type="concat",
  9.         dropout_rate=0.2,
  10.         normalize=True,
  11.         *args,
  12.         **kwargs,
  13.     ):
  14.         super().__init__(*args, **kwargs)
  15.         # Unpack graph_info to three elements: node_features, edges, and edge_weight.
  16.         node_features, edges, edge_weights = graph_info
  17.         self.node_features = node_features
  18.         self.edges = edges
  19.         self.edge_weights = edge_weights
  20.         # Set edge_weights to ones if not provided.
  21.         if self.edge_weights is None:
  22.             self.edge_weights = tf.ones(shape=edges.shape[1])
  23.         # Scale edge_weights to sum to 1.
  24.         self.edge_weights = self.edge_weights / tf.math.reduce_sum(self.edge_weights)
  25.         # Create a process layer.
  26.         self.preprocess = create_ffn(hidden_units, dropout_rate, name="preprocess")
  27.         # Create the first GraphConv layer.
  28.         self.conv1 = GraphConvLayer(
  29.             hidden_units,
  30.             dropout_rate,
  31.             aggregation_type,
  32.             combination_type,
  33.             normalize,
  34.             name="graph_conv1",
  35.         )
  36.         # Create the second GraphConv layer.
  37.         self.conv2 = GraphConvLayer(
  38.             hidden_units,
  39.             dropout_rate,
  40.             aggregation_type,
  41.             combination_type,
  42.             normalize,
  43.             name="graph_conv2",
  44.         )
  45.         # Create a postprocess layer.
  46.         self.postprocess = create_ffn(hidden_units, dropout_rate, name="postprocess")
  47.         # Create a compute logits layer.
  48.         self.compute_logits = layers.Dense(units=num_classes, name="logits")
  49.     def call(self, input_node_indices):
  50.         # Preprocess the node_features to produce node representations.
  51.         x = self.preprocess(self.node_features)
  52.         # Apply the first graph conv layer.
  53.         x1 = self.conv1((x, self.edges, self.edge_weights))
  54.         # Skip connection.
  55.         x = x1 + x
  56.         # Apply the second graph conv layer.
  57.         x2 = self.conv2((x, self.edges, self.edge_weights))
  58.         # Skip connection.
  59.         x = x2 + x
  60.         # Postprocess node embedding.
  61.         x = self.postprocess(x)
  62.         # Fetch node embeddings for the input node_indices.
  63.         node_embeddings = tf.gather(x, input_node_indices)
  64.         # Compute logits
  65.         return self.compute_logits(node_embeddings)
复制代码
2.8.4 测试GNN

测试一下GNN(图神经网络)模型的实例化和调用。请注意,如果你提供了N个节点索引作为输入,那么输出将是一个形状为[N, num_classes]的张量,这与图的巨细无关。
起首创建了一个 GNNNodeClassifier 类的实例,然后打印了模型的输出形状,并显示了模型的摘要。
  1. gnn_model = GNNNodeClassifier(
  2.     graph_info=graph_info,
  3.     num_classes=num_classes,
  4.     hidden_units=hidden_units,
  5.     dropout_rate=dropout_rate,
  6.     name="gnn_model",
  7. )
  8. print("GNN output shape:", gnn_model([1, 10, 100]))
  9. gnn_model.summary()
复制代码
2.8.5 训练GNN模型

训练GNN模型时,我们使用了标准的监督交织熵损失来训练模型。但是,我们可以为生成的节点嵌入添加一个自监督损失项,以确保图中相邻的节点具有相似的表示,而远离的节点具有不相似的表示。
调用定义的 GNNNodeClassifier 模型来进行训练,并纪录训练过程:
run_experiment 函数执行以下操作:
- 使用 x_train 和 y_train 来训练 gnn_model。
- 纪录训练过程中的关键指标,如损失值和准确率,并将这些信息存储在 history 对象中。
- 可能还包罗验证过程,使用验证集来评估模型在未见过的数据上的表现。
history 对象通常包罗了模型训练过程中的详细统计信息,可以用于后续的分析,比如绘制训练和验证损失曲线,评估模型性能等。
  1. x_train = train_data.paper_id.to_numpy()
  2. history = run_experiment(gnn_model, x_train, y_train)
  3. display_learning_curves(history)
复制代码

在测试数据集上评估GNN模型。效果可能会因训练样本的差别而有所变革,但GNN模型在测试准确率方面总是优于基线模型。
  1. x_test = test_data.paper_id.to_numpy()
  2. _, test_accuracy = gnn_model.evaluate(x=x_test, y=y_test, verbose=0)
  3. print(f"Test accuracy: {round(test_accuracy * 100, 2)}%")
复制代码
  1. Test accuracy: 80.19%
复制代码
2.9 查抄GNN模型的预测本事

让我们将新实例作为节点添加到node_features中,并为它们生成与现有节点之间的链接(引用)。
2.9.1 添加节点

这个过程模拟了新论文的引用举动,通过将新论文作为节点添加到图中,并将它们与现有论文通过边连接起来,来更新图的结构。这种更新对于图神经网络模型来说是重要的,由于它允许模型学习新论文的特性,并根据其与现有论文的关系进行分类。
  1. # First we add the N new_instances as nodes to the graph
  2. # by appending the new_instance to node_features.
  3. num_nodes = node_features.shape[0]
  4. new_node_features = np.concatenate([node_features, new_instances])
  5. # Second we add the M edges (citations) from each new node to a set
  6. # of existing nodes in a particular subject
  7. new_node_indices = [i + num_nodes for i in range(num_classes)]
  8. new_citations = []
  9. for subject_idx, group in papers.groupby("subject"):
  10.     subject_papers = list(group.paper_id)
  11.     # Select random x papers specific subject.
  12.     selected_paper_indices1 = np.random.choice(subject_papers, 5)
  13.     # Select random y papers from any subject (where y < x).
  14.     selected_paper_indices2 = np.random.choice(list(papers.paper_id), 2)
  15.     # Merge the selected paper indices.
  16.     selected_paper_indices = np.concatenate(
  17.         [selected_paper_indices1, selected_paper_indices2], axis=0
  18.     )
  19.     # Create edges between a citing paper idx and the selected cited papers.
  20.     citing_paper_indx = new_node_indices[subject_idx]
  21.     for cited_paper_idx in selected_paper_indices:
  22.         new_citations.append([citing_paper_indx, cited_paper_idx])
  23. new_citations = np.array(new_citations).T
  24. new_edges = np.concatenate([edges, new_citations], axis=1)
复制代码
2.9.2 更新节点特性

现在让我们更新GNN模型中的node_features和边。
更新图神经网络模型 gnn_model 的内部状态,并使用更新后的模型来预测新节点的类别概率:
  1. print("Original node_features shape:", gnn_model.node_features.shape)
  2. print("Original edges shape:", gnn_model.edges.shape)
  3. gnn_model.node_features = new_node_features
  4. gnn_model.edges = new_edges
  5. gnn_model.edge_weights = tf.ones(shape=new_edges.shape[1])
  6. print("New node_features shape:", gnn_model.node_features.shape)
  7. print("New edges shape:", gnn_model.edges.shape)
  8. logits = gnn_model.predict(tf.convert_to_tensor(new_node_indices))
  9. probabilities = keras.activations.softmax(tf.convert_to_tensor(logits)).numpy()
  10. display_class_probabilities(probabilities)
复制代码
3. 总结

本文气密深入探索了怎样使用Keras和图神经网络(GNN)库(如Spektral)来构建和训练一个模型,以处置惩罚引文网络数据。这种网络结构的数据常见于学术文献领域,此中节点代表论文,而边则代表论文之间的引用关系。
3.1 数据集与预处置惩罚

起首,我们使用了Cora数据集,这是一个广泛使用的引文网络数据集,包罗了2708篇科学论文,每篇论文都被标记了所属的研究领域。为了将这些数据转化为模型可以处置惩罚的形式,我们进行了数据预处置惩罚。具体来说,我们为每个文档提取了特性向量(如使用TF-IDF或Word2Vec等方法),这些特性向量构成了图中的节点特性。同时,我们构建了毗邻矩阵,此中每个元素表示两个文档之间是否存在引用关系,从而形成了图的边。
3.2 模型构建

接下来,我们构建了一个图神经网络(GNN)模型来处置惩罚这些数据。模型的核心是图神经网络层(如GraphConv层),这些层可以或许聚合节点的邻居信息。在每一次迭代中,图神经网络层都会将节点的特性与其邻居的特性进行聚合,从而生成新的节点表示。通过堆叠多个图神经网络层,我们可以捕获图中更广泛的上下文信息。
在聚合了足够的节点信息后,我们使用全连接层将节点表示映射到最终的输出类别。具体来说,我们为每个节点分配了一个类别标签(即论文所属的研究领域),并通过全连接层将这些节点表示转换为类别概率分布。
3.3 训练与评估

在训练过程中,我们采用了适当的优化器(如Adam)和损失函数(如交织熵损失)。我们将数据集划分为训练集、验证集和测试集,以便在训练过程中评估模型的性能。在每个训练迭代中,我们使用训练集来更新模型的参数,并使用验证集来评估模型的性能。如果模型在验证集上的性能有所提高,则保存当前的模型参数作为最佳参数。
训练完成后,我们在测试集上评估了模型的性能。我们使用准确率、准确率、召回率和F1分数等指标来全面评估模型的性能。这些指标可以资助我们了解模型在未见过的数据上的表现,并与其他模型进行比较。
3.4 预测

本文详细展示了怎样使用Keras和图神经网络库来处置惩罚引文网络数据。这个例子不仅让我们深入了解了GNN的工作原理和应用场景,还为我们进一步研究和应用GNN提供了宝贵的履历。在未来,我们可以实验使用更复杂的GNN结构、更先辈的优化算法和更丰富的特性表示来提高模型的性能,并探索GNN在其他领域的应用潜力。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4