HashGNN 的节点分类
此 Jupyter notebook 托管在 Neo4j 图数据科学客户端 Github 仓库的此处。
本 notebook 演示了如何使用 graphdatascience
库来
-
使用便捷数据加载器将包含
Movie
、Actor
和Director
节点的 IMDB 数据集直接导入到 GDS -
配置一个使用 HashGNN 嵌入的节点分类管道,用于预测
Movie
节点的类型 -
使用自动调优训练管道并检查结果
-
对缺少指定类型的电影节点进行预测
1. 先决条件
运行此 notebook 需要一个安装了最新版本(2.5 或更高版本)Neo4j 图数据科学库 (GDS) 插件的 Neo4j 数据库服务器。我们推荐使用包含 GDS 的 Neo4j Desktop 或 AuraDS。
# Install necessary Python dependencies
%pip install "graphdatascience>=1.6"
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 import ServerVersion
assert gds.server_version() >= ServerVersion(2, 5, 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 节点嵌入算法
作为训练管道的最后一部分,将有一个机器学习训练算法。如果我们直接将 plot_keywords
作为机器学习算法的特征输入,我们将无法利用图中已有的任何关系数据。由于关系很可能会用更有价值的信息来丰富我们的特征,我们将使用一个考虑关系的节点嵌入算法,并将其输出作为机器学习训练算法的输入。
在这种情况下,我们将使用 HashGNN 节点嵌入算法,这是 GDS 2.3 中的新功能。与名称暗示的不同,HashGNN 并非监督式神经网络学习模型。它实际上是一种无监督算法。其名称来源于算法设计灵感来自图神经网络,它在每个节点上进行消息传递并与转换交织进行。但与大多数 GNN 不同,它的转换是通过局部敏感最小哈希(locality sensitive min-hashing)完成的。由于所使用的哈希函数是随机选择的,并且独立于输入数据,因此无需训练。
我们将把 plot_keywords
节点属性作为输入提供给 HashGNN,它将为每个通过关系消息传递得到丰富的新特征向量的节点输出新的特征向量。由于 plot_keywords
向量已经是二进制的,我们无需对输入进行任何二值化处理。
由于我们有多个节点标签和关系,因此通过设置 heterogeneous=True
来确保启用 HashGNN 的异构能力。值得注意的是,我们还通过明确指定 contextNodeLabels
来声明我们希望包含所有类型的节点,而不仅仅是我们将用于训练的 Movie
节点。
有关此算法的更多信息,请参阅 HashGNN 文档。
# Add a HashGNN node property step to the pipeline
_ = pipe.addNodeProperty(
"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. 设置自动调优
现在是时候为管道的训练部分设置机器学习算法了。
在此示例中,我们将添加逻辑回归和随机森林算法作为最终模型的候选。每个候选算法都将由管道进行评估,并根据我们指定的指标选择最佳算法。
很难知道需要多少正则化才能避免模型在训练数据集上过拟合,因此我们将利用 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()}")
正如我们所看到的,自动调优找到的最佳机器学习算法配置是逻辑回归,其 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
表示的 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 数据库投影之类的操作来替换这一部分,以创建更真实的生产工作流程。
10.1. 与其他方法的比较
如前所述,我们试图模仿 NeurIPS 论文 Graph Transformer Networks 中的基准设置,以便与当前最先进的方法进行比较。与该论文的一个不同之处在于,他们有一个预定义的训练-测试集划分,而我们只是在训练管道中均匀随机生成一个划分(大小相同)。然而,我们没有理由认为论文中预定义的划分不是也均匀随机生成的。此外,他们使用长度为 64 的浮点嵌入(64 * 32 = 2048 位),而我们使用 HashGNN 的长度为 1256 位的位嵌入。
他们观察到的分数如下:
算法 | 测试集 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)并且所需内存少得多