使用 HashGNN 进行节点分类
此 Jupyter 笔记本托管在 Neo4j 图数据科学客户端 Github 存储库中 此处。
笔记本展示了如何使用 graphdatascience
库来
-
使用便捷的数据加载器将 IMDB 数据集(包括
Movie
、Actor
和Director
节点)直接导入 GDS -
使用 HashGNN 嵌入配置一个节点分类管道,用于预测
Movie
节点的类型 -
使用自动调整训练管道并检查结果
-
对缺少指定类型的电影节点进行预测
1. 先决条件
运行此笔记本需要 Neo4j 数据库服务器,其中安装了最新版本的 Neo4j 图数据科学库 (GDS) 插件(2.3 版或更高版本)。我们建议使用安装了 GDS 的 Neo4j 桌面或 AuraDS。
此外,使用的 graphdatascience
库版本必须为 1.6 版或更高版本。
# Install necessary Python dependencies
%pip install graphdatascience
2. 设置
首先,导入依赖项并设置与数据库的 GDS 客户端连接。
# Import our dependencies
import os
from graphdatascience import GraphDataScience
# Get Neo4j DB URI, credentials and name from environment if applicable
NEO4J_URI = os.environ.get("NEO4J_URI", "bolt://localhost:7687")
NEO4J_AUTH = None
NEO4J_DB = os.environ.get("NEO4J_DB", "neo4j")
if os.environ.get("NEO4J_USER") and os.environ.get("NEO4J_PASSWORD"):
NEO4J_AUTH = (
os.environ.get("NEO4J_USER"),
os.environ.get("NEO4J_PASSWORD"),
)
gds = GraphDataScience(NEO4J_URI, auth=NEO4J_AUTH, database=NEO4J_DB)
from graphdatascience.server_version.server_version import ServerVersion
assert gds.server_version() >= ServerVersion(2, 3, 0)
3. 加载 IMDB 数据集
接下来,使用 graphdatascience
内置 IMDB 加载器 将数据导入 GDS 服务器。这应该会得到一个包含 Movie
、Actor
和 Director
节点的图,它们通过 ACTED_IN
和 DIRECTED_IN
关系连接。
请注意,在“真实世界场景”中,我们可能会将自己的数据从 Neo4j 数据库投影到 GDS 中,或者使用 gds.graph.construct
从自己的客户端数据创建图。
# Run the loading to obtain a `Graph` object representing our GDS projection
G = gds.graph.load_imdb()
检查图以查看其内容。
print(f"Overview of G: {G}")
print(f"Node labels in G: {G.node_labels()}")
print(f"Relationship types in G: {G.relationship_types()}")
看起来一切正常,但我们注意到一些节点属于 UnclassifiedMovie
标签。实际上,这些是我们希望使用节点分类模型预测类型的节点。查看各种节点标签上存在的节点属性以更清楚地了解这一点。
print(f"Node properties per label:\n{G.node_properties()}")
因此,我们看到 Movie
节点具有 genre
属性,这意味着我们可以在以后训练模型时使用这些节点。如预期的那样,UnclassifiedMovie
节点没有 genre
属性,这正是我们想要预测的内容。
此外,我们注意到所有节点都具有 plot_keywords
属性。这是一个二进制的“词袋”型特征向量,表示 1256 个情节关键字中哪些描述了某个节点。这些特征向量将在以后用作 HashGNN 节点嵌入算法的输入。
4. 配置训练管道
现在我们已经加载并理解了要分析的数据,我们可以继续研究用于实际进行上述 UnclassifiedMovie
节点类型预测的工具。
由于我们要预测节点的离散值属性,因此我们将使用 节点分类管道.
# Create an empty node classification pipeline
pipe, _ = gds.beta.pipeline.nodeClassification.create("genre-predictor-pipe")
为了能够将我们的准确率评分与该数据集上现有方法的最新水平进行比较,我们希望使用与 Graph Transformer Network 论文中相同的测试集大小 (NIPS 论文链接)。我们相应地配置管道。
# Set the test set size to be 79.6 % of the entire set of `Movie` nodes
_ = pipe.configureSplit(testFraction=0.796)
请注意,如果使用更标准的训练测试分割(例如 80/20),我们会得到更好的模型。通常在实际使用场景中会采用这种方法。
5. HashGNN 节点嵌入算法
作为训练管道的最后部分,将有一个 ML 训练算法。如果直接使用 plot_keywords
作为 ML 算法的特征输入,我们将不会利用图中任何关系数据。由于关系可能会用更有价值的信息丰富我们的特征,因此我们将使用一个考虑关系的节点嵌入算法,并将它的输出用作 ML 训练算法的输入。
在本例中,我们将使用 GDS 2.3 中新增的 HashGNN 节点嵌入算法。与名字暗示的不同,HashGNN 不是一个监督式神经学习模型。实际上,它是一个无监督算法。它的名字来源于该算法的设计灵感来自图神经网络,因为它在每个节点上进行消息传递并进行变换。但与大多数 GNN 不同,它不是执行神经变换,而是通过局部敏感最小哈希来执行变换。由于所用的哈希函数是随机选择的,独立于输入数据,因此无需训练。
我们将向 HashGNN 提供 plot_keywords
节点属性作为输入,它将输出每个节点的新特征向量,这些向量通过关系消息传递进行丰富。由于 plot_keywords
向量已经是二进制的,因此我们无需对输入进行任何 二值化 处理。
由于我们有多个节点标签和关系,因此通过设置 heterogeneous=True
确保启用 HashGNN 的异构功能。值得注意的是,我们还声明要包含所有类型的节点,而不仅仅是我们将在其上进行训练的 Movie
节点,方法是明确指定 contextNodeLabels
。
有关此算法的更多信息,请参阅 HashGNN 文档。
# Add a HashGNN node property step to the pipeline
_ = pipe.addNodeProperty(
"beta.hashgnn",
mutateProperty="embedding",
iterations=4,
heterogeneous=True,
embeddingDensity=512,
neighborInfluence=0.7,
featureProperties=["plot_keywords"],
randomSeed=41,
contextNodeLabels=G.node_labels(),
)
# Set the embeddings vectors produced by HashGNN as feature input to our ML algorithm
_ = pipe.selectFeatures("embedding")
6. 设置自动调整
现在开始设置训练管道中 ML 算法。
在本例中,我们将添加逻辑回归和随机森林算法作为最终模型的候选者。管道将评估每个候选者,并根据我们指定的指标选择最佳候选者。
很难知道我们需要多少正则化才能避免模型过度拟合训练数据集,因此我们将使用 GDS 的自动调整功能来帮助我们。自动调整算法将尝试使用正则化参数 penalty
(逻辑回归)和 minSplitSize
(随机森林)的几个值,并选择找到的最佳值。
# Add logistic regression as a candidate ML algorithm for the training
# Provide an interval for the `penalty` parameter to enable autotuning for it
_ = pipe.addLogisticRegression(penalty=(0.1, 1.0), maxEpochs=1000, patience=5, tolerance=0.0001, learningRate=0.01)
# Add random forest as a candidate ML algorithm for the training
# Provide an interval for the `minSplitSize` parameter to enable autotuning for it
_ = pipe.addRandomForest(minSplitSize=(2, 100), criterion="ENTROPY")
7. 训练管道
配置已完成,现在我们准备开始训练管道并查看结果。
在训练调用中,我们提供训练要针对的节点标签和属性,以及确定如何选择最佳模型候选者的指标。
# Call train on our pipeline object to run the entire training pipeline and produce a model
model, _ = pipe.train(
G,
modelName="genre-predictor-model",
targetNodeLabels=["Movie"],
targetProperty="genre",
metrics=["F1_MACRO"],
randomSeed=42,
)
检查训练管道创建的模型。
print(f"Accuracy scores of trained model:\n{model.metrics()['F1_MACRO']}")
print(f"Winning ML algorithm candidate config:\n{model.best_parameters()}")
如我们所见,自动调整找到的最佳 ML 算法配置是 penalty=0.159748
的逻辑回归。
此外,我们注意到测试集 F1 分数为 0.59118347,与文献中该数据集上其他算法的得分相比,这确实很好。有关更多信息,请参阅下面的 结论 部分。
8. 进行新的预测
现在我们可以使用训练管道生成的模型来预测UnclassifiedMovie
节点的类型。
# Predict `genre` for `UnclassifiedMovie` nodes and stream the results
predictions = model.predict_stream(G, targetNodeLabels=["UnclassifiedMovie"], includePredictedProbabilities=True)
print(f"First predictions of unclassified movie nodes:\n{predictions.head()}")
在本例中,我们将预测结果流回客户端应用程序,但例如,我们也可以通过调用model.predict_mutate
而不是G
来修改由G
表示的GDS图。
9. 清理
可以选择清理我们的GDS状态,以便释放内存用于其他任务。
# Drop the GDS graph represented by `G` from the GDS graph catalog
_ = G.drop()
# Drop the GDS training pipeline represented by `pipe` from the GDS pipeline catalog
_ = pipe.drop()
# Drop the GDS model represented by `model` from the GDS model catalog
_ = model.drop()
10. 结论
通过仅使用GDS库及其客户端,我们能够使用复杂的HashGNN节点嵌入算法和逻辑回归来训练节点分类模型。我们的逻辑回归配置是通过自动调整过程,从多个其他算法(如具有各种配置的随机森林)中自动选择最佳候选者。我们能够用很少的代码,以及非常好的分数来实现这一点。
虽然我们使用了graphdatascience
库的便捷方法将IMDB数据集加载到GDS中,但很容易用类似于从Neo4j数据库的投影来替换这部分,从而创建一个更现实的生产工作流程。
11. 与其他方法的比较
如前所述,我们试图模仿NeurIPS论文图Transformer网络中基准测试的设置,以便与当前最先进的方法进行比较。与这篇论文的不同之处在于,它们有一个预定义的训练集-测试集分割,而我们在训练管道中只是随机生成一个分割(具有相同大小)。但是,我们没有理由认为论文中预定义的分割不是也是随机生成的。此外,他们使用长度为64的浮点数嵌入(64 * 32 = 2048位),而我们使用长度为1256位的HashGNN嵌入。
他们观察到的分数如下
算法 | 测试集F1分数(%) |
---|---|
DeepWalk |
32.08 |
metapath2vec |
35.21 |
GCN |
56.89 |
GAT |
58.14 |
HAN |
56.77 |
GTN |
60.92 |
鉴于此,我们使用HashGNN和逻辑回归获得了59.11%的测试集F1分数,这确实令人印象深刻。特别是考虑到:- 我们使用更少的位来表示嵌入(1256 vs 2048)- 在梯度下降中使用比上面的深度学习模型少得多的训练参数- HashGNN是一种无监督算法- HashGNN运行速度更快(即使没有GPU),并且需要更少的内存