训练管道
此功能处于 Beta 测试阶段。有关功能分级的更多信息,请参阅 API 分级。
训练模式 gds.beta.pipeline.nodeClassification.train 负责执行数据拆分、特征提取、模型选择、模型训练以及存储模型以供后续使用。运行此模式会生成一个类型为 NodeClassification 的分类模型,该模型随后会被存储在 模型目录 (model catalog) 中。该分类模型可以 应用于 可能不同的图,以对其中的节点进行分类。
更具体地说,训练过程如下:
-
在图上应用根据 添加节点属性 所添加的节点属性步骤。每个步骤上的图过滤器由
contextNodeLabels + targetNodeLabels和contextRelationships + relationshipTypes组成。 -
将
targetNodeLabels过滤器应用于图。 -
选择要用作特征的节点属性,具体如 添加特征 中所述。
-
将输入图拆分为两部分:训练图和测试图。这在 配置节点拆分 中有描述。这些图在内部进行管理,且仅在训练期间存在。
-
使用分层 k 折交叉验证 (stratified k-fold cross-validation) 对训练图中的节点进行拆分。折数
k的配置如 配置节点拆分 中所述。 -
在 参数空间 中定义的每个模型候选者都会在每个训练集上进行训练,并针对每一折在相应的验证集上进行评估。评估使用指定的首要 指标 (metric)。
-
根据首要指标的平均得分最高,选择表现最佳的模型。
-
在整个训练图上重新训练获胜模型。
-
在整个训练图以及测试图上评估获胜模型的性能。
-
在整个原始图上重新训练获胜模型。
-
将获胜模型注册到 模型目录 中。
| 以上步骤描述了该过程在逻辑上的执行方式。实际步骤及其在实现中的顺序可能有所不同。 |
| 一个步骤只能使用输入图中已存在或由之前添加的步骤生成的节点属性。 |
| 不支持在同一个图上并行执行同一个流水线。 |
指标 (Metrics)
Neo4j GDS 库中的节点分类模型支持以下评估指标
-
全局指标
-
F1_WEIGHTED -
F1_MACRO -
ACCURACY -
OUT_OF_BAG_ERROR(仅适用于 RandomForest,且仅提供验证和测试得分)
-
-
类内指标 (Per-class metrics)
-
F1(class=<number>)或F1(class=*) -
PRECISION(class=<number>)或PRECISION(class=*) -
RECALL(class=<number>)或RECALL(class=*) -
ACCURACY(class=<number>)或ACCURACY(class=*)
-
* 是用于报告图中每个类指标的语法糖。使用类内指标时,报告的指标包含诸如 ACCURACY_class_1 之类的键。
训练期间可以指定多个指标,但只有第一个指定的指标(即 primary 指标)用于评估,所有结果都会出现在训练结果中。首要指标不能是 * 扩展,因为不明确扩展后的哪个指标应该作为 primary 指标。
OUT_OF_BAG_ERROR 仅针对 RandomForest 模型计算,其评估方式为多数投票的准确性,即对于每个示例,仅考虑在训练期间未使用过该示例的树。每棵树使用的训练集比例由配置参数 numberOfSamplesRatio 控制。在交叉验证阶段评估时,OUT_OF_BAG_ERROR 作为验证得分报告。如果随机森林模型获胜,则根据在整个训练集上重新训练的模型报告测试得分。
语法
CALL gds.beta.pipeline.nodeClassification.train(
graphName: String,
configuration: Map
) YIELD
trainMillis: Integer,
modelInfo: Map,
modelSelectionStats: Map,
configuration: Map
| 名称 | 类型 | 默认 | 可选 | 描述 |
|---|---|---|---|---|
graphName |
字符串 |
|
否 |
存储在目录中的图的名称。 |
配置 |
Map |
|
是 |
算法特定配置和/或图过滤配置。 |
| 名称 | 类型 | 默认 | 可选 | 描述 |
|---|---|---|---|---|
pipeline |
字符串 |
|
否 |
要执行的流水线名称。 |
targetNodeLabels(目标节点标签) |
字符串列表 |
|
是 |
使用给定的节点标签过滤命名图,以获得用于训练和评估的节点。 |
字符串列表 |
|
是 |
使用给定的关系类型过滤命名图。 |
|
整数 |
|
是 |
用于运行算法的并发线程数。 |
|
字符串 |
|
是 |
可以提供一个 ID 以更轻松地跟踪算法的进度。 |
|
布尔值 |
|
是 |
如果禁用,进度百分比将不会被记录。 |
|
targetProperty |
字符串 |
|
否 |
节点的类。必须是整数类型。 |
metrics |
字符串列表 |
|
否 |
用于评估模型的 指标。 |
randomSeed |
整数 |
|
是 |
训练期间所用随机数生成器的种子。 |
modelName |
字符串 |
|
否 |
要训练的模型的名称,不得存在于模型目录中。 |
storeModelToDisk |
布尔值 |
|
是 |
训练后自动将模型存储到磁盘。 |
| 名称 | 类型 | 描述 |
|---|---|---|
trainMillis |
整数 |
用于训练的毫秒数。 |
modelInfo |
Map |
关于训练和获胜模型的信息。 |
modelSelectionStats |
Map |
关于所有模型候选评估指标的统计信息。 |
配置 |
Map |
用于训练过程的配置。 |
modelInfo 也可以通过使用 模型列表过程 在以后检索。modelInfo 返回字段具有以下算法特定的子字段
| 名称 | 类型 | 描述 |
|---|---|---|
bestParameters |
Map |
根据主要指标在验证折叠上平均表现最好的模型参数。 |
modelCandidates |
列表 |
映射列表,其中每个映射包含有关一个模型候选者的信息。此信息包括候选者的参数、训练统计信息和验证统计信息。 |
bestTrial |
整数 |
生成最佳模型的试验。第一次试验编号为 1。 |
| 名称 | 类型 | 描述 |
|---|---|---|
modelName |
字符串 |
训练模型的名称。 |
modelType |
字符串 |
已训练模型的类型。 |
classes |
整数列表 |
排序后的类 ID 列表,即 |
bestParameters |
Map |
根据主要指标在验证折叠上平均表现最好的模型参数。 |
metrics |
Map |
从指标描述到获胜模型在数据子集上的评估指标的映射,见下文。 |
nodePropertySteps |
映射列表 |
在流水线内产生节点属性的算法。 |
featureProperties |
字符串列表 |
被选为管道模型输入特征的节点属性。 |
modelInfo 的结构为
{
bestParameters: Map, (1)
nodePropertySteps: List of Map,
featureProperties: List of String,
classes: List of Integer, (2)
metrics: { (3)
<METRIC_NAME>: { (4)
test: Float, (5)
outerTrain: Float, (6)
train: { (7)
avg: Float,
max: Float,
min: Float,
},
validation: { (8)
avg: Float,
max: Float,
min: Float,
params: Map
}
}
}
}
| 1 | 得分最高的模型候选配置。 |
| 2 | 排序后的类 ID 列表,即 targetProperty 在整个图中的不同值。 |
| 3 | metrics 映射包含每个指标描述的条目,以及该指标的相应结果。 |
| 4 | 过程中配置中指定的指标名称,例如 F1_MACRO 或 RECALL(class=4)。 |
| 5 | 获胜模型在测试集上评估的数值。 |
| 6 | 获胜模型在外部训练集上评估的数值。 |
| 7 | train 条目汇总了 train 集上的指标结果。 |
| 8 | validation 条目汇总了 validation 集上的指标结果。 |
|
在 (5)-(7) 中,如果指标是 |
|
除了该过程产生的数据外,在执行过程中,还有大量关于训练的信息被发送到 Neo4j 数据库的日志中。 例如,每个模型候选的表现如何会以 有些信息仅以 |
示例
|
以下所有示例应在空数据库中运行。 这些示例通常使用 Cypher 投影。原生投影将在未来版本中弃用。 |
在本节中,我们将展示在具体图上运行节点分类训练管道的示例。目的是说明结果的样子,并提供如何在实际设置中使用模型的指南。我们将使用一个由少量代表房屋的节点组成的小图来演示。这是一个多类分类的示例,class 节点属性的不同值决定了类的数量,本例中为三个(0、1 和 2)。示例图如下所示
CREATE
(gold:House {color: 'Gold', sizePerStory: [15.5, 23.6, 33.1], class: 0}),
(red:House {color: 'Red', sizePerStory: [15.5, 23.6, 100.0], class: 0}),
(blue:House {color: 'Blue', sizePerStory: [11.3, 35.1, 22.0], class: 0}),
(green:House {color: 'Green', sizePerStory: [23.2, 55.1, 0.0], class: 1}),
(gray:House {color: 'Gray', sizePerStory: [34.3, 24.0, 0.0], class: 1}),
(black:House {color: 'Black', sizePerStory: [71.66, 55.0, 0.0], class: 1}),
(white:House {color: 'White', sizePerStory: [11.1, 111.0, 0.0], class: 1}),
(teal:House {color: 'Teal', sizePerStory: [80.8, 0.0, 0.0], class: 2}),
(beige:House {color: 'Beige', sizePerStory: [106.2, 0.0, 0.0], class: 2}),
(magenta:House {color: 'Magenta', sizePerStory: [99.9, 0.0, 0.0], class: 2}),
(purple:House {color: 'Purple', sizePerStory: [56.5, 0.0, 0.0], class: 2}),
(pink:UnknownHouse {color: 'Pink', sizePerStory: [23.2, 55.1, 56.1]}),
(tan:UnknownHouse {color: 'Tan', sizePerStory: [22.32, 102.0, 0.0]}),
(yellow:UnknownHouse {color: 'Yellow', sizePerStory: [39.0, 0.0, 0.0]}),
// richer context
(schiele:Painter {name: 'Schiele'}),
(picasso:Painter {name: 'Picasso'}),
(kahlo:Painter {name: 'Kahlo'}),
(schiele)-[:PAINTED]->(gold),
(schiele)-[:PAINTED]->(red),
(schiele)-[:PAINTED]->(blue),
(picasso)-[:PAINTED]->(green),
(picasso)-[:PAINTED]->(gray),
(picasso)-[:PAINTED]->(black),
(picasso)-[:PAINTED]->(white),
(kahlo)-[:PAINTED]->(teal),
(kahlo)-[:PAINTED]->(beige),
(kahlo)-[:PAINTED]->(magenta),
(kahlo)-[:PAINTED]->(purple),
(schiele)-[:PAINTED]->(pink),
(schiele)-[:PAINTED]->(tan),
(kahlo)-[:PAINTED]->(yellow);
在 Neo4j 中有了图之后,我们现在可以将其投影到图目录中,为管道执行做准备。我们使用针对 House 和 UnknownHouse 标签的 Cypher 投影来完成此操作。我们还将投影 sizeOfStory 属性以用作模型特征,并将 class 属性用作目标特征。
MATCH (house:House|UnknownHouse)
RETURN gds.graph.project(
'myGraph',
house,
null,
{
sourceNodeLabels: labels(house),
targetNodeLabels: [],
sourceNodeProperties: house { .sizePerStory, .class },
targetNodeProperties: {}
}
)
内存估算
首先,我们将使用 estimate 过程来估算运行算法的成本。这可以在任何执行模式下完成。在此示例中,我们将使用 train 模式。估算算法有助于了解在图上运行算法产生的内存影响。当您稍后在其中一种执行模式下实际运行算法时,系统将执行一次估算。如果估算显示执行超出内存限制的可能性非常高,则会禁止执行。要了解更多信息,请参阅 自动估算和执行阻止。
有关 estimate 的更多详细信息,请参阅 内存估算。
CALL gds.beta.pipeline.nodeClassification.train.estimate('myGraph', {
pipeline: 'pipe',
targetNodeLabels: ['House'],
modelName: 'nc-model',
targetProperty: 'class',
randomSeed: 2,
metrics: [ 'ACCURACY' ]
})
YIELD requiredMemory
| requiredMemory |
|---|
"[1264 KiB ... 1337 KiB]" |
| 如果某个节点属性步骤没有实现估算,该步骤将在估算中被忽略。 |
训练
在以下示例中,我们将演示在此图上运行节点分类训练管道。我们将训练一个模型来预测房屋所属的类别,基于其 sizePerStory 属性。
CALL gds.beta.pipeline.nodeClassification.train('myGraph', {
pipeline: 'pipe',
targetNodeLabels: ['House'],
modelName: 'nc-pipeline-model',
targetProperty: 'class',
randomSeed: 1337,
metrics: ['ACCURACY', 'OUT_OF_BAG_ERROR']
}) YIELD modelInfo, modelSelectionStats
RETURN
modelInfo.bestParameters AS winningModel,
modelInfo.metrics.ACCURACY.train.avg AS avgTrainScore,
modelInfo.metrics.ACCURACY.outerTrain AS outerTrainScore,
modelInfo.metrics.ACCURACY.test AS testScore,
[cand IN modelSelectionStats.modelCandidates | cand.metrics.ACCURACY.validation.avg] AS validationScores
| winningModel | avgTrainScore | outerTrainScore | testScore | validationScores |
|---|---|---|---|---|
{batchSize=100, classWeights=[], focusWeight=0.0, learningRate=0.001, maxEpochs=500, methodName="LogisticRegression", minEpochs=1, patience=1, penalty=5.881039654, tolerance=0.001} |
1.0 |
1.0 |
1.0 |
[0.8, 0.0, 0.5, 0.9, 0.8] |
在这里我们可以观察到,惩罚项为 5.881 的模型候选者在训练阶段表现最好,在训练图和测试图上的 ACCURACY 得分均为 1。这是自动调优找到的模型之一。这表明该模型对训练图反应良好,并且能够很好地泛化到未见过的数据。请注意,这只是一个小图上的玩具示例。为了获得更高的测试分数,我们可能需要使用更好的特征、更大的图或不同的模型配置。
为节点属性步骤提供更丰富的上下文
在上面的示例中,我们投影了一个没有关系的 House 子图,并将其用于训练和测试。原始图中的许多信息没有被使用。我们可能希望利用更多的节点和关系类型来生成节点属性(和链接特征),并调查它是否能改善节点分类。我们可以通过在添加节点属性步骤时传入 contextNodeLabels 和 contextRelationshipTypes 来做到这一点。
以下语句将使用 Cypher 投影投影一个包含关于房屋及其画家信息的图,并将其存储在名为 'paintingGraph' 的图目录中。
MATCH (house:House)
OPTIONAL MATCH (painter:Painter)-[r:PAINTED]->(house:House)
RETURN gds.graph.project(
'paintingGraph',
painter,
house,
{
sourceNodeLabels: ['Painter'],
targetNodeLabels: ['House'],
sourceNodeProperties: {},
targetNodeProperties: house { .class },
relationshipType: 'PAINTED'
},
{ undirectedRelationshipTypes: ['PAINTED'] }
)
我们仍然训练一个模型来预测每个房屋的类别,但除了 House 之外,还使用 Painter 和 PAINTED 作为上下文,以生成利用完整图结构的特征。然而,在特征生成之后,只有 House 节点被视为训练和评估实例,因此只有 House 节点需要具有目标属性 class。
首先,我们创建一个新管道。
CALL gds.beta.pipeline.nodeClassification.create('pipe-with-context')
其次,我们添加一个节点属性步骤(在本例中为节点嵌入),并将 Painter 作为 contextNodeLabels。
CALL gds.beta.pipeline.nodeClassification.addNodeProperty('pipe-with-context', 'fastRP', {
embeddingDimension: 64,
iterationWeights: [0, 1],
mutateProperty:'embedding',
contextNodeLabels: ['Painter']
})
我们将嵌入添加为模型的特征
CALL gds.beta.pipeline.nodeClassification.selectFeatures('pipe-with-context', ['embedding'])
然后,我们通过添加逻辑回归模型候选者来完成管道设置
CALL gds.beta.pipeline.nodeClassification.addLogisticRegression('pipe-with-context')
我们现在准备调用新创建管道的训练。
CALL gds.beta.pipeline.nodeClassification.train('paintingGraph', {
pipeline: 'pipe-with-context',
targetNodeLabels: ['House'],
modelName: 'nc-pipeline-model-contextual',
targetProperty: 'class',
randomSeed: 1337,
metrics: ['ACCURACY']
}) YIELD modelInfo, modelSelectionStats
RETURN
modelInfo.bestParameters AS winningModel,
modelInfo.metrics.ACCURACY.train.avg AS avgTrainScore,
modelInfo.metrics.ACCURACY.outerTrain AS outerTrainScore,
modelInfo.metrics.ACCURACY.test AS testScore,
[cand IN modelSelectionStats.modelCandidates | cand.metrics.ACCURACY.validation.avg] AS validationScores
| winningModel | avgTrainScore | outerTrainScore | testScore | validationScores |
|---|---|---|---|---|
{batchSize=100, classWeights=[], focusWeight=0.0, learningRate=0.001, maxEpochs=100, methodName="LogisticRegression", minEpochs=1, patience=1, penalty=0.0, tolerance=0.001} |
1.0 |
1.0 |
1.0 |
[1.0] |
正如我们所见,结果表明画家信息足以完美地对房屋进行分类。这种变化归因于嵌入考虑了更多的上下文信息。虽然这是一个玩具示例,但额外的上下文有时可以为管道步骤提供有价值的信息,从而产生更好的性能。