GraphSAGE
此功能处于 Beta 测试阶段。有关功能分级的更多信息,请参阅 API 分级。
术语表
- 有向
-
有向图特性。该算法在有向图上定义良好。
- 有向
-
有向图特性。该算法忽略图的方向。
- 有向
-
有向图特性。该算法不适用于有向图。
- 无向
-
无向图特性。该算法在无向图上定义良好。
- 无向
-
无向图特性。该算法忽略图的无向性。
- 异构节点
-
异构节点完全支持。该算法具有区分不同类型节点的能力。
- 异构节点
-
异构节点允许使用。无论标签如何,该算法对所有选定节点的处理方式相同。
- 异构关系
-
异构关系完全支持。该算法具有区分不同类型关系的能力。
- 异构关系
-
异构关系允许使用。无论类型如何,该算法对所有选定关系的处理方式相同。
- 加权关系
-
加权特性。该算法支持将关系属性用作权重,通过 relationshipWeightProperty 配置参数指定。
- 加权关系
-
加权特性。该算法将每种关系视为同等重要,丢弃任何关系权重值。
- 节点属性
-
节点属性特性。该算法利用节点属性。
GraphSAGE 是一种用于计算节点嵌入的归纳式算法。GraphSAGE 使用节点特征信息为未见过的节点或图生成节点嵌入。该算法不是为每个节点训练单独的嵌入,而是学习一个通过采样和聚合节点局部邻域特征来生成嵌入的函数。
| 该算法定义用于无向图。 |
有关此算法的更多信息,请参阅
注意事项
孤立节点
如果您正在为包含孤立节点的图进行嵌入,GraphSAGE 中的聚合步骤只能从节点本身提取信息。当该节点的所有属性均为 0.0 且激活函数为 ReLU 时,会导致该节点的向量全为 0。然而,由于 GraphSAGE 使用 L2 范数对节点嵌入进行归一化,而零向量无法归一化,因此在这些特殊情况下,我们为这些节点分配全零嵌入。在为孤立节点生成全零嵌入的情况下,这可能会影响近邻算法或其他相似度算法等下游任务。在运行 GraphSAGE 之前,过滤掉这些断开连接的节点可能更为合适。
图预采样以减少时间和内存消耗
由于在大型图上训练 GraphSAGE 模型可能需要大量时间和内存,因此在训练前采样一个较小的子图并在该子图上进行训练会很有帮助。由于 GraphSAGE 是归纳式的,训练好的模型仍然可以应用于预测完整图(或其他图)上的嵌入。要采样具有结构代表性的子图,请参阅 带重启的随机游走采样 (Random walk with restarts sampling)。
在机器学习流水线中的使用
将 GraphSAGE 生成的节点嵌入作为机器学习流水线(如 链接预测流水线 和 节点属性预测)中的一个节点属性步骤可能很有用。不支持在流水线内部训练 GraphSAGE 模型,必须先在流水线外部训练模型。模型训练完成后,可以使用 gds.beta.graphSage 或缩写 beta.graphSage 作为 procedureName 参数,将 GraphSAGE 添加到流水线的节点属性步骤中,并在过程配置映射中引用训练好的模型,就像在 predict mutate 模式 中所做的那样。
调优参数
通常,调优参数在很大程度上取决于具体的数据集。
聚合器
聚合器定义了如何组合节点嵌入和来自前一层的采样邻居嵌入。GDS 支持 Mean 和 Pool 聚合器。
Mean 更简单,需要的内存更少,计算速度也更快。Pool 更复杂,可以编码更丰富的邻域信息。
采样大小
每个采样大小代表一个输出大小等于嵌入维度的隐藏层。该层使用给定的聚合器和激活函数。更多的层意味着节点的嵌入会考虑更远的邻居。第 N 层使用距离 <= N 的采样邻居嵌入作为第 N -1 层的输入。层数越多,内存和计算时间消耗越大。
采样大小 n 表示我们尝试从一个节点采样最多 n 个邻居。更大的采样大小同样需要更多的内存和计算时间。
批大小 (Batch size)
此参数定义了单个批次中分组的训练示例数量。对于每个训练示例,我们还将采样一个正例和一个负例。梯度使用 concurrency 个线程在批次上并发计算。
批大小不会影响模型质量,但可用于调整训练速度。较大的批大小会增加计算的内存消耗。
轮次 (Epochs)
此参数定义训练的最大轮次。在每一轮之前,会根据 采样大小 为每一层重新采样邻居。无论模型质量如何,训练都将在达到此轮次后终止。请注意,如果损失收敛,训练也可能在轮次结束前提前停止(见 容差 (Tolerance))。
设置此参数有助于限制模型的训练时间。限制计算预算可以起到正则化作用并减轻过拟合,当轮次过多时,过拟合会成为一个风险。
因为每一轮都会重新采样邻居,所以多轮训练可以避免对特定邻域的过拟合。
批次采样率
此参数定义单次迭代中采样的批次数。
采样的批次越多,梯度计算就越准确。然而,更多的批次也会增加单次迭代的运行时间。
通常建议确保至少使用与定义的 concurrency(并发度)相同数量的批次。
语法
CALL gds.beta.graphSage.train(
graphName: String,
configuration: Map
) YIELD
modelInfo: Map,
configuration: Map,
trainMillis: Integer
| 名称 | 类型 | 默认 | 可选 | 描述 |
|---|---|---|---|---|
graphName |
字符串 |
|
否 |
存储在目录中的图的名称。 |
配置 |
Map |
|
是 |
算法特定配置和/或图过滤配置。 |
| 名称 | 类型 | 默认 | 可选 | 描述 |
|---|---|---|---|---|
modelName |
字符串 |
|
否 |
要训练的模型的名称,不得存在于模型目录中。 |
featureProperties |
字符串列表 |
|
否 |
应作为输入特征使用的节点属性名称。所有属性名称必须存在于投影图中,且类型为 Float 或 List of Float。 |
字符串列表 |
|
是 |
使用给定的节点标签过滤命名图。具有任何给定标签的节点都将被包含。 |
|
字符串列表 |
|
是 |
使用给定的关系类型过滤命名图。具有任何给定类型的关系都将被包含。 |
|
整数 |
|
是 |
用于运行算法的并发线程数。 |
|
字符串 |
|
是 |
可以提供一个 ID 以更轻松地跟踪算法的进度。 |
|
布尔值 |
|
是 |
如果禁用,进度百分比将不会被记录。 |
|
embeddingDimension |
整数 |
|
是 |
生成的节点嵌入及其隐藏层表示的维度。 |
aggregator |
字符串 |
|
是 |
各层使用的聚合器。支持的值为 "Mean" 和 "Pool"。 |
activationFunction |
字符串 |
|
是 |
模型架构中使用的激活函数。支持的值为 "Sigmoid" 和 "ReLu"。 |
sampleSizes |
整数列表 |
|
是 |
整数值列表,列表的大小决定层数,值决定各层将采样的节点数。 |
projectedFeatureDimension |
整数 |
|
是 |
投影的 |
batchSize |
整数 |
|
是 |
每个批次的节点数。 |
浮点数 |
|
是 |
用于单轮训练提前收敛的容差,在每次迭代后检查。 |
|
learningRate |
浮点数 |
|
是 |
学习率决定了每次迭代向损失函数最小值移动时的步长。 |
epochs |
整数 |
|
是 |
遍历图的次数。 |
整数 |
|
是 |
每轮的最大迭代次数。每次迭代都会更新权重。 |
|
batchSamplingRatio |
浮点数 |
|
是 |
每次权重更新要考虑的批次采样率。默认情况下,每个线程评估一个批次。 |
searchDepth |
整数 |
|
是 |
为训练采样附近节点时随机游走的最大深度。 |
negativeSampleWeight |
整数 |
|
是 |
负样本的权重。 |
字符串 |
|
是 |
用作权重的关系属性名称。如果未指定,算法将作为无权重运行。 |
|
randomSeed |
整数 |
|
是 |
用于控制计算嵌入时随机性的随机种子。 |
penaltyL2 |
浮点数 |
|
是 |
L2 惩罚项对损失函数的影响。 |
storeModelToDisk |
布尔值 |
|
是 |
训练后自动将模型存储到磁盘。 |
| 名称 | 类型 | 描述 |
|---|---|---|
|
Map |
训练模型的详细信息。 |
|
Map |
运行该过程所使用的配置。 |
|
整数 |
训练模型所需的毫秒数。 |
| 名称 | 类型 | 描述 |
|---|---|---|
|
字符串 |
训练模型的名称。 |
|
字符串 |
训练模型的类型。始终为 |
|
Map |
与运行训练相关的指标,详细信息见下表。 |
| 名称 | 类型 | 描述 |
|---|---|---|
|
整数 |
训练期间运行的轮次数。 |
|
列表 |
每轮训练后每个节点的平均损失。 |
|
浮点数列表的列表 |
每轮训练中每次迭代后每个节点的平均损失。 |
|
布尔值 |
指示训练是否已收敛。 |
CALL gds.beta.graphSage.stream(
graphName: String,
configuration: Map
) YIELD
nodeId: Integer,
embedding: List
| 名称 | 类型 | 默认 | 可选 | 描述 |
|---|---|---|---|---|
graphName |
字符串 |
|
否 |
存储在目录中的图的名称。 |
配置 |
Map |
|
是 |
算法特定配置和/或图过滤配置。 |
| 名称 | 类型 | 默认 | 可选 | 描述 |
|---|---|---|---|---|
modelName |
字符串 |
|
否 |
模型目录中 GraphSAGE 模型的名称。 |
字符串列表 |
|
是 |
使用给定的节点标签过滤命名图。具有任何给定标签的节点都将被包含。 |
|
字符串列表 |
|
是 |
使用给定的关系类型过滤命名图。具有任何给定类型的关系都将被包含。 |
|
整数 |
|
是 |
用于运行算法的并发线程数。 |
|
字符串 |
|
是 |
可以提供一个 ID 以更轻松地跟踪算法的进度。 |
|
布尔值 |
|
是 |
如果禁用,进度百分比将不会被记录。 |
|
batchSize |
整数 |
|
是 |
每个批次的节点数。 |
| 名称 | 类型 | 描述 |
|---|---|---|
|
整数 |
Neo4j 节点 ID。 |
|
浮点数列表 |
计算出的节点嵌入。 |
CALL gds.beta.graphSage.mutate(
graphName: String,
configuration: Map
)
YIELD
nodeCount: Integer,
nodePropertiesWritten: Integer,
preProcessingMillis: Integer,
computeMillis: Integer,
mutateMillis: Integer,
configuration: Map
| 名称 | 类型 | 默认 | 可选 | 描述 |
|---|---|---|---|---|
graphName |
字符串 |
|
否 |
存储在目录中的图的名称。 |
配置 |
Map |
|
是 |
算法特定配置和/或图过滤配置。 |
| 名称 | 类型 | 默认 | 可选 | 描述 |
|---|---|---|---|---|
modelName |
字符串 |
|
否 |
模型目录中 GraphSAGE 模型的名称。 |
mutateProperty |
字符串 |
|
否 |
GDS 图中写入嵌入的节点属性。 |
字符串列表 |
|
是 |
使用给定的节点标签过滤命名图。 |
|
字符串列表 |
|
是 |
使用给定的关系类型过滤命名图。 |
|
整数 |
|
是 |
用于运行算法的并发线程数。 |
|
布尔值 |
|
是 |
如果禁用,进度百分比将不会被记录。 |
|
字符串 |
|
是 |
可以提供一个 ID 以更轻松地跟踪算法的进度。 |
|
batchSize |
整数 |
|
是 |
每个批次的节点数。 |
| 名称 | 类型 | 描述 |
|---|---|---|
nodeCount |
整数 |
处理的节点数。 |
nodePropertiesWritten |
整数 |
写入的节点属性数。 |
preProcessingMillis |
整数 |
预处理数据的毫秒数。 |
computeMillis |
整数 |
运行算法的毫秒数。 |
mutateMillis |
整数 |
将结果数据写回投影图的毫秒数。 |
配置 |
Map |
用于运行算法的配置。 |
CALL gds.beta.graphSage.write(
graphName: String,
configuration: Map
)
YIELD
nodeCount: Integer,
nodePropertiesWritten: Integer,
preProcessingMillis: Integer,
computeMillis: Integer,
writeMillis: Integer,
configuration: Map
| 名称 | 类型 | 默认 | 可选 | 描述 |
|---|---|---|---|---|
graphName |
字符串 |
|
否 |
存储在目录中的图的名称。 |
配置 |
Map |
|
是 |
算法特定配置和/或图过滤配置。 |
| 名称 | 类型 | 默认 | 可选 | 描述 |
|---|---|---|---|---|
modelName |
字符串 |
|
否 |
模型目录中 GraphSAGE 模型的名称。 |
字符串列表 |
|
是 |
使用给定的节点标签过滤命名图。具有任何给定标签的节点都将被包含。 |
|
字符串列表 |
|
是 |
使用给定的关系类型过滤命名图。具有任何给定类型的关系都将被包含。 |
|
整数 |
|
是 |
用于运行算法的并发线程数。 |
|
字符串 |
|
是 |
可以提供一个 ID 以更轻松地跟踪算法的进度。 |
|
布尔值 |
|
是 |
如果禁用,进度百分比将不会被记录。 |
|
整数 |
|
是 |
用于将结果写入 Neo4j 的并发线程数。 |
|
字符串 |
|
否 |
Neo4j 数据库中写入嵌入的节点属性。 |
|
batchSize |
整数 |
|
是 |
每个批次的节点数。 |
| 名称 | 类型 | 描述 |
|---|---|---|
nodeCount |
整数 |
处理的节点数。 |
nodePropertiesWritten |
整数 |
写入的节点属性数。 |
preProcessingMillis |
整数 |
预处理数据的毫秒数。 |
computeMillis |
整数 |
运行算法的毫秒数。 |
writeMillis |
整数 |
将结果数据写回 Neo4j 的毫秒数。 |
配置 |
Map |
用于运行算法的配置。 |
示例
|
以下所有示例应在空数据库中运行。 这些示例均使用 Cypher 投影 作为标准。原生投影将在未来版本中被弃用。 |
在本节中,我们将演示在一个具体图上运行 GraphSAGE 算法的示例。目的是说明结果的样子,并提供如何在实际场景中使用该算法的指南。我们将使用一个小型朋友网络图,其中少数几个节点以特定模式连接。示例图如下所示:
CREATE
// Persons
( dan:Person {name: 'Dan', age: 20, heightAndWeight: [185, 75]}),
(annie:Person {name: 'Annie', age: 12, heightAndWeight: [124, 42]}),
( matt:Person {name: 'Matt', age: 67, heightAndWeight: [170, 80]}),
( jeff:Person {name: 'Jeff', age: 45, heightAndWeight: [192, 85]}),
( brie:Person {name: 'Brie', age: 27, heightAndWeight: [176, 57]}),
( elsa:Person {name: 'Elsa', age: 32, heightAndWeight: [158, 55]}),
( john:Person {name: 'John', age: 35, heightAndWeight: [172, 76]}),
(dan)-[:KNOWS {relWeight: 1.0}]->(annie),
(dan)-[:KNOWS {relWeight: 1.6}]->(matt),
(annie)-[:KNOWS {relWeight: 0.1}]->(matt),
(annie)-[:KNOWS {relWeight: 3.0}]->(jeff),
(annie)-[:KNOWS {relWeight: 1.2}]->(brie),
(matt)-[:KNOWS {relWeight: 10.0}]->(brie),
(brie)-[:KNOWS {relWeight: 1.0}]->(elsa),
(brie)-[:KNOWS {relWeight: 2.2}]->(jeff),
(john)-[:KNOWS {relWeight: 5.0}]->(jeff)
MATCH (source:Person)
OPTIONAL MATCH (source:Person)-[r:KNOWS]->(target:Person)
RETURN gds.graph.project(
'persons',
source,
target,
{
sourceNodeLabels: labels(source),
targetNodeLabels: labels(target),
sourceNodeProperties: source { .age, .heightAndWeight },
targetNodeProperties: target { .age, .heightAndWeight },
relationshipType: type(r),
relationshipProperties: r { .relWeight }
},
{ undirectedRelationshipTypes: ['KNOWS'] }
)
| 该算法定义用于无向图。 |
训练
在生成节点嵌入之前,我们需要训练一个模型并将其存储在模型目录中。以下是如何执行此操作的示例。
featureProperties 配置参数中指定的名称必须存在于投影图中。 |
CALL gds.beta.graphSage.train(
'persons',
{
modelName: 'exampleTrainModel',
featureProperties: ['age', 'heightAndWeight'],
aggregator: 'mean',
activationFunction: 'sigmoid',
randomSeed: 1337,
sampleSizes: [25, 10]
}
) YIELD modelInfo as info
RETURN
info.modelName as modelName,
info.metrics.didConverge as didConverge,
info.metrics.ranEpochs as ranEpochs,
info.metrics.epochLosses as epochLosses
| modelName | didConverge | ranEpochs | epochLosses |
|---|---|---|---|
"exampleTrainModel" |
true |
1 |
[26.5784954435] |
| 由于权重变量的随机初始化,结果在不同运行之间可能会有所不同。 |
观察结果,我们可以得出以下结论:训练在第一轮后收敛,损失几乎相同。调整算法参数(例如尝试不同的 sampleSizes、searchDepth、embeddingDimension 或 batchSize)可以改善损失。对于不同的数据集,GraphSAGE 可能需要不同的训练参数才能生成高质量的模型。
训练好的模型会自动注册到 模型目录 中。
使用多个节点标签进行训练
在本节中,我们将介绍如何在具有多个标签的图上进行训练。不同的标签可能具有不同的属性集。要在这样的图上运行,GraphSAGE 以 多标签模式 运行,其中特征属性被投影到一个公共特征空间中。因此,投影后所有节点都具有相同维度的特征向量。
标签的投影是线性的,由权重矩阵给出。每个标签的权重与 GraphSAGE 模型的其他权重联合学习。
在多标签模式下,在通常的聚合层之前应用以下操作:
-
将表示标签的属性添加到该标签的特征属性中
-
将每个标签的特征属性投影到共享维度的特征向量中
投影特征维度由 projectedFeatureDimension 配置,指定它即启用多标签模式。
用于标签的特征属性是 featureProperties 配置参数中存在于该标签图中的属性。在多标签模式下,不再要求所有标签都具有所有指定的属性。
示例
为了演示具有多个标签的 GraphSAGE,我们在示例图中添加了乐器以及人与乐器之间类型为 LIKE 的关系。
MATCH
(dan:Person {name: "Dan"}),
(annie:Person {name: "Annie"}),
(matt:Person {name: "Matt"}),
(brie:Person {name: "Brie"}),
(john:Person {name: "John"})
CREATE
(guitar:Instrument {name: 'Guitar', cost: 1337.0}),
(synth:Instrument {name: 'Synthesizer', cost: 1337.0}),
(bongos:Instrument {name: 'Bongos', cost: 42.0}),
(trumpet:Instrument {name: 'Trumpet', cost: 1337.0}),
(dan)-[:LIKES]->(guitar),
(dan)-[:LIKES]->(synth),
(dan)-[:LIKES]->(bongos),
(annie)-[:LIKES]->(guitar),
(annie)-[:LIKES]->(synth),
(matt)-[:LIKES]->(bongos),
(brie)-[:LIKES]->(guitar),
(brie)-[:LIKES]->(synth),
(brie)-[:LIKES]->(bongos),
(john)-[:LIKES]->(trumpet)
MATCH (source:Person)-[r:LIKES]->(target:Instrument)
RETURN gds.graph.project(
'persons_with_instruments',
source,
target,
{
sourceNodeLabels: labels(source),
sourceNodeProperties: source { .age, .heightAndWeight },
targetNodeLabels: labels(target),
targetNodeProperties: target { .cost },
relationshipType: type(r),
relationshipProperties: r { .relWeight }
},
{ undirectedRelationshipTypes: ['LIKES'] }
)
现在,我们可以通过指定 projectedFeatureDimension 参数在该图上以多标签模式运行 GraphSAGE。多标签 GraphSAGE 移除了内存图中每个节点必须具有所有 featureProperties 的要求。然而,投影是按标签独立的,即使两个标签具有相同的 featureProperty,在投影之前它们也会被视为不同的特征。projectedFeatureDimension 应该等于特征数组的最大长度。在我们的示例中,人有 age (1) 和 heightAndWeight (2),总长度为 3。乐器只有 cost,长度为 1。因此,projectedFeatureDimension 应该设置为 3。对于每个节点,其唯一的标签属性会使用标签特定的投影投影到维度为 projectedFeatureDimension 的向量空间。请注意,cost 特征仅定义用于乐器节点,而 age 和 heightAndWeight 仅定义用于人节点。
CALL gds.beta.graphSage.train(
'persons_with_instruments',
{
modelName: 'multiLabelModel',
featureProperties: ['age', 'heightAndWeight', 'cost'],
projectedFeatureDimension: 3
}
)
使用关系权重进行训练
GraphSAGE 实现支持使用关系权重进行训练。节点之间更大的关系权重意味着这些节点应具有更相似的嵌入值。
CALL gds.beta.graphSage.train(
'persons',
{
modelName: 'weightedTrainedModel',
featureProperties: ['age', 'heightAndWeight'],
relationshipWeightProperty: 'relWeight',
nodeLabels: ['Person'],
relationshipTypes: ['KNOWS']
}
)
当图中不存在节点属性时进行训练
以下示例演示了如何在 mutate 模式下调用度中心性 (Degree Centrality),然后将变异后的属性用作 GraphSAGE 训练的特征。在本例中,我们将使用 Persons 图,但不向内存图加载任何属性。
MATCH (source:Person)-[r:KNOWS]->(target:Person)
RETURN gds.graph.project(
'noPropertiesGraph',
source,
target,
{},
{ undirectedRelationshipTypes: ['*'] }
)
CALL gds.degree.mutate(
'noPropertiesGraph',
{
mutateProperty: 'degree'
}
) YIELD nodePropertiesWritten
CALL gds.beta.graphSage.train(
'noPropertiesGraph',
{
modelName: 'myModel',
featureProperties: ['degree']
}
)
YIELD trainMillis
RETURN trainMillis
gds.degree.mutate 将为内存图中的每个节点创建一个新的节点属性 degree,然后可以在 GraphSAGE.train 模式下将其用作 featureProperty。
| 使用单独的算法来生成 featureProperties 对于捕获图拓扑属性也非常有用。 |
流模式
要生成嵌入并将它们流式传输回客户端,我们可以使用流模式。我们必须首先训练一个模型,我们使用 gds.beta.graphSage.train 过程来完成。
CALL gds.beta.graphSage.train(
'persons',
{
modelName: 'graphSage',
featureProperties: ['age', 'heightAndWeight'],
embeddingDimension: 3,
randomSeed: 19
}
)
一旦我们训练好一个模型(命名为 'graphSage'),我们就可以使用它来生成并流式传输嵌入。
CALL gds.beta.graphSage.stream(
'persons',
{
modelName: 'graphSage'
}
)
YIELD nodeId, embedding
RETURN gds.util.asNode(nodeId).name AS person, embedding
ORDER BY person, embedding
| person | embedding |
|---|---|
"Annie" |
[0.5285002573, 0.4682181872, 0.7081378445] |
"Brie" |
[0.5285002574, 0.4682181872, 0.7081378445] |
"Dan" |
[0.5285002573, 0.4682181872, 0.7081378445] |
"Elsa" |
[0.5285002574, 0.4682181872, 0.7081378444] |
"Jeff" |
[0.5285002573, 0.4682181872, 0.7081378445] |
"John" |
[0.5285002573, 0.4682181872, 0.7081378445] |
"Matt" |
[0.5285002573, 0.4682181872, 0.7081378445] |
| 由于权重变量的随机初始化,结果在不同运行之间可能会有细微差异。 |
改变 (Mutate) 模式
作为 流示例的一部分训练的模型 可以通过过程的 mutate 模式重复使用,将结果写入内存图。以下是如何实现此操作的示例。
CALL gds.beta.graphSage.mutate(
'persons',
{
mutateProperty: 'inMemoryEmbedding',
modelName: 'graphSage'
}
) YIELD
nodeCount,
nodePropertiesWritten
| nodeCount | nodePropertiesWritten |
|---|---|
7 |
7 |
写入 (Write) 模式
作为 流示例的一部分训练的模型 可以重复使用,以将结果写入 Neo4j。以下是如何实现此操作的示例。
CALL gds.beta.graphSage.write(
'persons',
{
writeProperty: 'embedding',
modelName: 'graphSage'
}
) YIELD
nodeCount,
nodePropertiesWritten
| nodeCount | nodePropertiesWritten |
|---|---|
7 |
7 |