训练管道

训练模式 gds.beta.pipeline.linkPrediction.train 负责数据拆分、特征提取、模型选择、训练以及存储模型以供将来使用。运行此模式会生成一个类型为 LinkPrediction 的预测模型,该模型与训练期间收集的指标一同存储在 模型目录 中。该模型可以 应用于 可能不同的图,从而生成预测链接的关系类型,每个链接都具有作为一个属性存储的预测概率。

Visualization of Link Prediction pipeline data flow

更准确地说,该过程将按顺序执行以下操作:

  1. 使用 sourceNodeLabeltargetNodeLabel 应用节点过滤,使用 targetRelationshipType 应用关系过滤。生成的图用作拆分的输入。

  2. 将图的关系拆分为 testtrainfeature-input 图,如 配置关系拆分 中所述。这些图在内部进行管理,仅在训练期间存在。

  3. 应用根据 添加节点属性 添加的节点属性步骤。每个步骤上的图过滤器由 contextNodeLabels + targetNodeLabel + sourceNodeLabelcontextRelationships + feature-input relationships 组成。

  4. 将根据 添加链接特征 添加的特征步骤应用于 train 图,这会为每个 train 关系生成一个实例,即特征向量和二元标签。

  5. 使用分层 k-折交叉验证拆分训练实例。折数 k 可以使用 gds.beta.pipeline.linkPrediction.configureSplit 中的 validationFolds 进行配置。

  6. 针对每个折叠,根据 参数空间 给出的每个模型候选进行训练,并在相应的验证集上评估模型。评估使用指定的 指标

  7. 将各折叠中平均指标最高的模型宣布为获胜模型。

  8. 在整个训练集上重新训练获胜模型,并在 traintest 集上进行评估。为了在 test 集上进行评估,特征流水线会像对 train 集一样再次应用。

  9. 将获胜模型注册到 模型目录 中。

以上步骤描述了该过程在逻辑上的执行方式。实际步骤及其在实现中的顺序可能有所不同。
一个步骤只能使用输入图中已存在或由之前添加的步骤生成的节点属性。
不支持在同一个图上并行执行同一个流水线。

语法

在命名图上以训练模式运行链接预测
CALL gds.beta.pipeline.linkPrediction.train(
  graphName: String,
  configuration: Map
) YIELD
  trainMillis: Integer,
  modelInfo: Map,
  modelSelectionStats: Map,
  configuration: Map
表 1. 参数
名称 类型 默认 可选 描述

graphName

字符串

不适用

存储在目录中的图的名称。

配置

Map

{}

算法特定配置和/或图过滤配置。

表 2. 配置
名称 类型 默认 可选 描述

modelName

字符串

不适用

要训练的模型的名称,不得存在于模型目录中。

pipeline

字符串

不适用

要执行的流水线名称。

targetRelationshipType

字符串

不适用

用于训练模型的关系类型名称。该关系类型必须是无向的。

sourceNodeLabel

字符串

'*'

训练集和测试集中关系应起始的节点标签名称 [1]

targetNodeLabel

字符串

'*'

训练集和测试集中关系应终止的节点标签名称 [1]

negativeClassWeight

浮点数

1.0

模型评估中负例的权重。正例的权重为 1。更多详细信息 在此处

metrics

字符串列表

[AUCPR]

用于评估模型的 指标

randomSeed

整数

不适用

训练期间所用随机数生成器的种子。

concurrency

整数

4 [2]

用于运行算法的并发线程数。

jobId

字符串

内部生成

可以提供一个 ID 以更轻松地跟踪算法的进度。

logProgress

布尔值

true

如果禁用,进度百分比将不会被记录。

storeModelToDisk

布尔值

false

训练后自动将模型存储到磁盘。

1. 这有助于训练模型以预测具有特定标签组合的链接。

2. 在 GDS 会话 中,默认值为可用处理器数量。

表 3. 结果
名称 类型 描述

trainMillis

整数

用于训练的毫秒数。

modelInfo

Map

关于训练和获胜模型的信息。

modelSelectionStats

Map

关于所有模型候选评估指标的统计信息。

配置

Map

用于训练过程的配置。

modelInfo 也可以稍后通过使用 模型列表过程 检索。modelInfo 返回字段具有以下特定于算法的子字段:

表 4. modelSelectionStats 的字段
名称 类型 描述

bestParameters

Map

根据主要指标在验证折叠上平均表现最好的模型参数。

modelCandidates

列表

映射列表,每个映射包含有关一个模型候选的信息。此信息包括候选参数、训练统计信息和验证统计信息。

bestTrial

整数

产生最佳模型的试验。第一次试验编号为 1。

表 5. modelInfo 的字段
名称 类型 描述

modelName

字符串

训练模型的名称。

modelType

字符串

已训练模型的类型。

bestParameters

Map

根据主要指标在验证折叠上平均表现最好的模型参数。

metrics

Map

从指标描述到获胜模型在数据子集上的评估指标的映射,见下文。

nodePropertySteps

映射列表

在流水线内产生节点属性的算法。

linkFeatures

映射列表

将端点节点的节点属性组合起来以产生作为流水线模型输入的链接(关系)特征的特征步骤。

modelInfo 的结构为

{
    bestParameters: Map,              (1)
    nodePropertySteps: List of Map,
    linkFeatures: List of Map,
    metrics: {                        (2)
        AUCPR: {
            test: Float,              (3)
            outerTrain: Float,        (4)
            train: {                  (5)
                avg: Float,
                max: Float,
                min: Float,
            },
            validation: {             (6)
                avg: Float,
                max: Float,
                min: Float
            }
        }
    }
}
1 得分最高的模型候选配置。
2 metrics 映射包含每个指标描述(目前仅为 AUCPR)的一个条目以及该指标的相应结果。
3 在测试集上评估最佳模型的数值。
4 在外部训练集上评估最佳模型的数值。
5 train 条目汇总了 train 集上的指标结果。
6 validation 条目汇总了 validation 集上的指标结果。

在 (3)-(5) 中,如果指标为 OUT_OF_BAG_ERROR,则不报告这些统计信息。OUT_OF_BAG_ERROR 仅在 (6) 中作为验证指标报告,且仅当模型为 RandomForest 时才报告。

除了该过程产生的数据外,在执行过程中,还有大量关于训练的信息被发送到 Neo4j 数据库的日志中。

例如,每个模型候选的表现如何会以 info 日志级别记录,并最终进入数据库的 neo4j.log 文件中。

有些信息仅以 debug 日志级别记录,并最终进入数据库的 debug.log 文件中。例如模型候选训练期间(模型选择阶段)特定于训练方法的元数据(如逻辑回归的每 epoch 损失)。请注意,此特定数据不会由过程调用返回。

示例

在这个例子中,我们将创建一个小图,并使用我们目前构建的训练流水线。该图是一个包含人与城市的小型社交网络,包括有关人们居住地、出生地以及他们认识的其他人的信息。我们将尝试训练一个模型来预测哪些人可能相互认识。示例图如下所示:

Visualization of the example graph
以下 Cypher 语句将在 Neo4j 数据库中创建示例图:
CREATE
  (alice:Person {name: 'Alice', age: 38}),
  (michael:Person {name: 'Michael', age: 67}),
  (karin:Person {name: 'Karin', age: 30}),
  (chris:Person {name: 'Chris', age: 52}),
  (will:Person {name: 'Will', age: 6}),
  (mark:Person {name: 'Mark', age: 32}),
  (greg:Person {name: 'Greg', age: 29}),
  (veselin:Person {name: 'Veselin', age: 3}),

  (london:City {name: 'London'}),
  (malmo:City {name: 'Malmo'}),

  (alice)-[:KNOWS]->(michael),
  (michael)-[:KNOWS]->(karin),
  (michael)-[:KNOWS]->(chris),
  (michael)-[:KNOWS]->(greg),
  (will)-[:KNOWS]->(michael),
  (will)-[:KNOWS]->(chris),
  (mark)-[:KNOWS]->(michael),
  (mark)-[:KNOWS]->(will),
  (greg)-[:KNOWS]->(chris),
  (veselin)-[:KNOWS]->(chris),
  (karin)-[:KNOWS]->(veselin),
  (chris)-[:KNOWS]->(karin),

  (alice)-[:LIVES]->(london),
  (michael)-[:LIVES]->(london),
  (karin)-[:LIVES]->(london),
  (chris)-[:LIVES]->(malmo),
  (will)-[:LIVES]->(malmo),

  (alice)-[:BORN]->(london),
  (michael)-[:BORN]->(london),
  (karin)-[:BORN]->(malmo),
  (chris)-[:BORN]->(london),
  (will)-[:BORN]->(malmo),
  (greg)-[:BORN]->(london),
  (veselin)-[:BORN]->(malmo)

有了 Neo4j 中的图,我们现在可以将其投影到图目录中。我们使用 Cypher 投影来针对 Person 节点和 KNOWS 关系。我们还将投影 age 属性,以便在创建链接特征时使用它。对于关系,我们必须使用 UNDIRECTED 方向。这是因为链接预测流水线仅针对无向图定义。我们忽略额外的节点和关系类型,以使我们的投影保持同质。我们将在后续示例中说明如何利用更大的图。

以下语句将使用 Cypher 投影来投影一个图,并将其以“myGraph”的名称存储在图目录中。
MATCH (source:Person)-[r:KNOWS]->(target:Person)
RETURN gds.graph.project(
  'myGraph',
  source,
  target,
  {
    sourceNodeProperties: source { .age },
    targetNodeProperties: target { .age },
    relationshipType: 'KNOWS'
  },
  { undirectedRelationshipTypes: ['KNOWS'] }
)
链接预测模型要求图是使用 UNDIRECTED 方向为关系创建的。

内存估算

首先,我们将使用 estimate 过程来估算训练流水线的成本。估算有助于了解在图上训练流水线将产生的内存影响。在实际训练流水线时,系统会执行估算,如果估算表明执行内存不足的可能性很高,则会阻止执行。要了解更多信息,请参阅 自动估算和执行阻塞

有关 estimate 的更多详细信息,请参阅 内存估算

以下将估算训练流水线的内存需求:
CALL gds.beta.pipeline.linkPrediction.train.estimate('myGraph', {
  pipeline: 'pipe',
  modelName: 'lp-pipeline-model',
  targetRelationshipType: 'KNOWS'
})
YIELD requiredMemory
表 6. 结果
requiredMemory

"[24 KiB ... 522 KiB]"

训练

现在我们可以实际训练一个 LinkPrediction 模型了。我们必须确保指定 targetRelationshipType 以指示模型仅使用该类型进行训练。对于图 myGraph,实际上没有投影其他关系类型,但情况并非总是如此。

以下将使用流水线训练一个模型:
CALL gds.beta.pipeline.linkPrediction.train('myGraph', {
  pipeline: 'pipe',
  modelName: 'lp-pipeline-model',
  metrics: ['AUCPR', 'OUT_OF_BAG_ERROR'],
  targetRelationshipType: 'KNOWS',
  randomSeed: 18
}) YIELD modelInfo, modelSelectionStats
RETURN
  modelInfo.bestParameters AS winningModel,
  modelInfo.metrics.AUCPR.train.avg AS avgTrainScore,
  modelInfo.metrics.AUCPR.outerTrain AS outerTrainScore,
  modelInfo.metrics.AUCPR.test AS testScore,
  [cand IN modelSelectionStats.modelCandidates | cand.metrics.AUCPR.validation.avg] AS validationScores
表 7. 结果
winningModel avgTrainScore outerTrainScore testScore validationScores

{batchSize=100, classWeights=[0.55, 0.45], focusWeight=0.070341817, hiddenLayerSizes=[4, 2], learningRate=0.001, maxEpochs=100, methodName="MultilayerPerceptron", minEpochs=1, patience=2, penalty=0.5, tolerance=0.001}

0.7579365079

0.7

0.6666666667

[0.4305555556, 0.5833333333, 0.4305555556, 0.75]

我们可以看到 MLP 模型配置胜出,在测试集上的得分为 0.67。该分数计算为 AUCPR 指标,范围在 [0, 1] 之间。对于所有链接给出的分数都高于非链接的模型得分将为 1.0,而分配随机分数的模型平均得分为 0.5。

使用上下文过滤器进行训练

在上面的例子中,我们投影了一个 Person-KNOWS-Person 子图并将其用于训练和测试。原始图中的许多信息没有被使用。我们可能希望利用更多的节点和关系类型来生成节点属性(和链接特征),并研究它是否能改善链接预测。我们可以通过传入 contextNodeLabelscontextRelationshipTypes 来做到这一点。我们显式传入 sourceNodeLabeltargetNodeLabel 以指定用于训练和测试的更窄的一组节点。

以下语句将使用 Cypher 投影投影整个图,并将其存储在名为 'fullGraph' 的图目录中。

MATCH (source:Person)-[r:KNOWS|LIVES|BORN]->(target:Person|City)
RETURN gds.graph.project(
  'fullGraph',
  source,
  target,
  {
    sourceNodeLabels: labels(source),
    targetNodeLabels: labels(target),
    sourceNodeProperties: source { age: coalesce(source.age, 1) },
    targetNodeProperties: target { age: coalesce(target.age, 1) },
    relationshipType: type(r)
  },
  { undirectedRelationshipTypes: ['KNOWS'] }
)

完整图包含 2 个节点标签和 3 个关系类型。我们仍然训练一个 Person-KNOWS-Person 模型,但使用上下文信息 Person-LIVES-City 和 Person-BORN-City 来生成模型在训练中使用的节点属性。请注意,我们不需要上下文关系类型的 UNDIRECTED 方向,因为它们被排除在 LinkPrediction 训练之外。

首先我们创建一个新的流水线。

CALL gds.beta.pipeline.linkPrediction.create('pipe-with-context')

接下来我们添加带有上下文配置的 nodePropertyStep。

CALL gds.beta.pipeline.linkPrediction.addNodeProperty('pipe-with-context', 'fastRP', {
  mutateProperty: 'embedding',
  embeddingDimension: 256,
  randomSeed: 42,
  contextNodeLabels: ['City'],
  contextRelationshipTypes: ['LIVES', 'BORN']
})

然后我们添加链接特征。

CALL gds.beta.pipeline.linkPrediction.addFeature('pipe-with-context', 'hadamard', {
  nodeProperties: ['embedding', 'age']
})

然后类似地配置数据拆分。

CALL gds.beta.pipeline.linkPrediction.configureSplit('pipe-with-context', {
  testFraction: 0.25,
  trainFraction: 0.6,
  validationFolds: 3
})

然后我们添加一个 MLP 模型候选。

CALL gds.alpha.pipeline.linkPrediction.addMLP('pipe-with-context',
{hiddenLayerSizes: [4, 2], penalty: 1, patience: 2})
以下将使用在节点属性步骤中使用了额外上下文信息的流水线来训练另一个模型:
CALL gds.beta.pipeline.linkPrediction.train('fullGraph', {
  pipeline: 'pipe-with-context',
  modelName: 'lp-pipeline-model-filtered',
  metrics: ['AUCPR', 'OUT_OF_BAG_ERROR'],
  sourceNodeLabel: 'Person',
  targetNodeLabel: 'Person',
  targetRelationshipType: 'KNOWS',
  randomSeed: 12
}) YIELD modelInfo, modelSelectionStats
RETURN
  modelInfo.bestParameters AS winningModel,
  modelInfo.metrics.AUCPR.train.avg AS avgTrainScore,
  modelInfo.metrics.AUCPR.outerTrain AS outerTrainScore,
  modelInfo.metrics.AUCPR.test AS testScore,
  [cand IN modelSelectionStats.modelCandidates | cand.metrics.AUCPR.validation.avg] AS validationScores
表 8. 结果
winningModel avgTrainScore outerTrainScore testScore validationScores

{batchSize=100, classWeights=[], focusWeight=0.0, hiddenLayerSizes=[4, 2], learningRate=0.001, maxEpochs=100, methodName="MultilayerPerceptron", minEpochs=1, patience=2, penalty=1.0, tolerance=0.001}

0.832010582

0.6666666667

0.8611111111

[0.75]

正如我们所见,结果实际上是一样的。虽然在这个玩具示例中训练和测试得分保持不变,但上下文信息很可能在更大的数据集上产生更大的影响。