K-Means 聚类

词汇表

有向

有向特性。该算法在有向图上定义良好。

有向

有向特性。该算法忽略图的方向。

有向

有向特性。该算法不能在有向图上运行。

无向

无向特性。该算法在无向图上定义良好。

无向

无向特性。该算法忽略图的无向性。

异构节点

异构节点 完全支持。该算法能够区分不同类型的节点。

异构节点

异构节点 允许。该算法以相同的方式处理所有选定的节点,而不管其标签如何。

异构关系

异构关系 完全支持。该算法能够区分不同类型的关系。

异构关系

异构关系 允许。该算法以相同的方式处理所有选定的关系,而不管其类型如何。

加权关系

加权特性。该算法支持将关系属性用作权重,通过 relationshipWeightProperty 配置参数指定。

加权关系

加权特性。该算法将每个关系视为同等重要,忽略任何关系权重的值。

简介

K-Means 聚类是一种无监督学习算法,用于解决聚类问题。它遵循一个简单的过程,将给定的数据集分类到由参数 k 定义的多个聚类中。Neo4j GDS 库根据节点属性进行聚类,通过 nodeProperty 参数传递浮点数组节点属性作为输入。然后将图中的节点定位为 d 维空间中的点(其中 d 是数组属性的长度)。

然后,该算法开始选择 k 个初始聚类中心,它们是 d 维数组(有关更多详细信息,请参阅以下部分)。质心充当聚类的代表。

然后,图中的所有节点计算它们到每个聚类中心的欧几里得距离,并被分配到距离它们最近的聚类。在这些分配之后,每个聚类取分配给它的所有节点(作为点)的平均值,以形成其新的代表性中心(作为d维数组)。

该过程使用新的中心重复进行,直到结果稳定,即每次迭代只有几个节点更改聚类或达到最大迭代次数。

请注意,K-Means 实现忽略了关系,因为它只关注节点属性。

有关此算法的更多信息,请参见

初始中心点采样

该算法首先通过从可用节点集中随机采样来选择k个中心点。有两种不同的采样策略。

均匀采样

使用均匀采样,每个节点被选为k个初始中心点之一的概率相同。这是 K-Means 的默认采样器,用uniform参数表示。

K-Means++

此采样策略适用于著名的 K-means++ 初始化算法[1] 用于 K-Means。采样首先随机均匀地选择第一个中心点。然后,剩余的k-1个中心点逐一基于加权随机采样进行选择。也就是说,一个节点被选为下一个中心点的概率与其到已选中心点的最小距离成正比。因此,距离较大的节点更有可能被选为中心点。这种采样策略试图更均匀地分散初始聚类,以便获得更好的最终聚类结果。可以通过在配置中选择kmeans++作为初始采样器来启用此选项。

也可以通过seedCentroids参数显式地向算法提供初始中心点的列表。在这种情况下,即使在配置中更改,initialSampler参数的值也将被忽略。

注意事项

为了使 K-Means 正确工作,所有节点的属性数组必须具有相同数量的元素。此外,它们应该只包含数字,并且不包含任何 NaN 值。

语法

每个模式下的 K-Means 语法
在命名图上以流模式运行 K-Means。
CALL gds.kmeans.stream(
  graphName: String,
  configuration: Map
)
YIELD
  nodeId: Integer,
  communityId: Integer,
  distanceFromCentroid: Float,
  silhouette: Float
表 1. 参数
名称 类型 默认值 可选 描述

graphName

字符串

n/a

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

configuration

映射

{}

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

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

nodeLabels

字符串列表

['*']

使用给定的节点标签过滤命名图。将包含具有任何给定标签的节点。

relationshipTypes

字符串列表

['*']

使用给定的关系类型过滤命名图。将包含具有任何给定类型的关系。

concurrency

整数

4

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

jobId

字符串

内部生成

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

logProgress

布尔值

true

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

nodeProperty

字符串

n/a

对应于 K-Means 用于将节点聚类到社区中的浮点数数组的节点属性。

k

整数

10

所需聚类的数量。

maxIterations

整数

10

要运行的 K-Means 的最大迭代次数。

deltaThreshold

浮点数

0.05

作为百分比的值,用于确定何时提前停止。如果少于“deltaThreshold * |nodes|”个节点更改其聚类,则算法停止。值必须在 0(不含)和 1(含)之间。

numberOfRestarts

整数

1

使用不同的初始中心执行 K-Means 的次数。返回的社区是最小化平均节点中心距离的社区。

randomSeed

整数

n/a

用于控制初始中心点分配的种子值。

initialSampler

字符串

"uniform"

用于采样前k个中心点的方法。“uniform”和“kmeans++”(均不区分大小写)是有效的输入。

seedCentroids

浮点数列表列表

[]

显式提供初始中心点的参数。它不能与numberOfRestarts参数的非默认值一起启用。

computeSilhouette

布尔值

false

如果设置为 true,则在确定聚类后计算轮廓得分。轮廓是衡量节点聚类效果的指标。

表 3. 结果
名称 类型 描述

nodeId

整数

节点 ID。

communityId

整数

社区 ID。

distanceFromCentroid

浮点数

节点与其社区中心点的距离。

silhouette

浮点数

节点的轮廓得分。

在命名图上以统计模式运行 K-Means。
CALL gds.kmeans.stats(
  graphName: String,
  configuration: Map
)
YIELD
  preProcessingMillis: Integer,
  computeMillis: Integer,
  postProcessingMillis: Integer,
  communityDistribution: Map,
  centroids: List of List of Float,
  averageDistanceToCentroid: Float,
  averageSilhouette: Float,
  configuration: Map
表 4. 参数
名称 类型 默认值 可选 描述

graphName

字符串

n/a

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

configuration

映射

{}

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

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

nodeLabels

字符串列表

['*']

使用给定的节点标签过滤命名图。将包含具有任何给定标签的节点。

relationshipTypes

字符串列表

['*']

使用给定的关系类型过滤命名图。将包含具有任何给定类型的关系。

concurrency

整数

4

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

jobId

字符串

内部生成

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

logProgress

布尔值

true

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

nodeProperty

字符串

n/a

对应于 K-Means 用于将节点聚类到社区中的浮点数数组的节点属性。

k

整数

10

所需聚类的数量。

maxIterations

整数

10

要运行的 K-Means 的最大迭代次数。

deltaThreshold

浮点数

0.05

作为百分比的值,用于确定何时提前停止。如果少于“deltaThreshold * |nodes|”个节点更改其聚类,则算法停止。值必须在 0(不含)和 1(含)之间。

numberOfRestarts

整数

1

使用不同的初始中心执行 K-Means 的次数。返回的社区是最小化平均节点中心距离的社区。

randomSeed

整数

n/a

用于控制初始中心点分配的种子值。

initialSampler

字符串

"uniform"

用于采样前k个中心点的方法。“uniform”和“kmeans++”(均不区分大小写)是有效的输入。

seedCentroids

浮点数列表列表

[]

显式提供初始中心点的参数。它不能与numberOfRestarts参数的非默认值一起启用。

computeSilhouette

布尔值

false

如果设置为 true,则在确定聚类后计算轮廓得分。轮廓是衡量节点聚类效果的指标。

表 6. 结果
名称 类型 描述

preProcessingMillis

整数

预处理数据所用的毫秒数。

computeMillis

整数

运行算法所用的毫秒数。

postProcessingMillis

整数

计算百分位数和社区计数所用的毫秒数。

communityDistribution

映射

包含最后一级的社区大小的最小值、最大值、平均值以及 p1、p5、p10、p25、p50、p75、p90、p95、p99 和 p999 百分位数值的映射。

centroids

浮点数列表列表

中心点坐标列表。每个项目都是一个包含一个中心点坐标的列表。

averageDistanceToCentroid

浮点数

节点与中心点之间的平均距离。

averageSilhouette

浮点数

所有节点的平均轮廓得分。

configuration

映射

用于运行算法的配置。

在命名图上以变异模式运行 K-Means。
CALL gds.kmeans.mutate(
  graphName: String,
  configuration: Map
)
YIELD
  preProcessingMillis: Integer,
  computeMillis: Integer,
  mutateMillis: Integer,
  postProcessingMillis: Integer,
  nodePropertiesWritten: Integer,
  communityDistribution: Map,
  centroids: List of List of Float,
  averageDistanceToCentroid: Float,
  averageSilhouette: Float,
  configuration: Map
表 7. 参数
名称 类型 默认值 可选 描述

graphName

字符串

n/a

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

configuration

映射

{}

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

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

mutateProperty

字符串

n/a

GDS 图中将聚类写入到的节点属性。

nodeLabels

字符串列表

['*']

使用给定的节点标签过滤命名图。

relationshipTypes

字符串列表

['*']

使用给定的关系类型过滤命名图。

concurrency

整数

4

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

jobId

字符串

内部生成

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

nodeProperty

字符串

n/a

对应于 K-Means 用于将节点聚类到社区中的浮点数数组的节点属性。

k

整数

10

所需聚类的数量。

maxIterations

整数

10

要运行的 K-Means 的最大迭代次数。

deltaThreshold

浮点数

0.05

作为百分比的值,用于确定何时提前停止。如果少于“deltaThreshold * |nodes|”个节点更改其聚类,则算法停止。值必须在 0(不含)和 1(含)之间。

numberOfRestarts

整数

1

使用不同的初始中心执行 K-Means 的次数。返回的社区是最小化平均节点中心距离的社区。

randomSeed

整数

n/a

用于控制初始中心点分配的种子值。

initialSampler

字符串

"uniform"

用于采样前k个中心点的方法。“uniform”和“kmeans++”(均不区分大小写)是有效的输入。

seedCentroids

浮点数列表列表

[]

显式提供初始中心点的参数。它不能与numberOfRestarts参数的非默认值一起启用。

computeSilhouette

布尔值

false

如果设置为 true,则在确定聚类后计算轮廓得分。轮廓是衡量节点聚类效果的指标。

表 9. 结果
名称 类型 描述

preProcessingMillis

整数

预处理数据所用的毫秒数。

computeMillis

整数

运行算法所用的毫秒数。

mutateMillis

整数

向投影图添加属性所用的毫秒数。

postProcessingMillis

整数

计算百分位数和社区计数所用的毫秒数。

nodePropertiesWritten

整数

添加到投影图中的属性数量。

communityDistribution

映射

包含最后一级的社区大小的最小值、最大值、平均值以及 p1、p5、p10、p25、p50、p75、p90、p95、p99 和 p999 百分位数值的映射。

centroids

浮点数列表列表

中心点坐标列表。每个项目都是一个包含一个中心点坐标的列表。

averageDistanceToCentroid

浮点数

节点与中心点之间的平均距离。

averageSilhouette

浮点数

所有节点的平均轮廓得分。

configuration

映射

用于运行算法的配置。

在命名图上以写入模式运行 K-Means。
CALL gds.kmeans.write(
  graphName: String,
  configuration: Map
)
YIELD
  preProcessingMillis: Integer,
  computeMillis: Integer,
  writeMillis: Integer,
  postProcessingMillis: Integer,
  nodePropertiesWritten: Integer,
  communityDistribution: Map,
  centroids: List of List of Float,
  averageDistanceToCentroid: Float,
  averageSilhouette: Float,
  configuration: Map
表 10. 参数
名称 类型 默认值 可选 描述

graphName

字符串

n/a

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

configuration

映射

{}

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

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

nodeLabels

字符串列表

['*']

使用给定的节点标签过滤命名图。将包含具有任何给定标签的节点。

relationshipTypes

字符串列表

['*']

使用给定的关系类型过滤命名图。将包含具有任何给定类型的关系。

concurrency

整数

4

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

jobId

字符串

内部生成

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

logProgress

布尔值

true

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

writeConcurrency

整数

'concurrency' 的值

用于将结果写入 Neo4j 的并发线程数。

writeProperty

字符串

n/a

Neo4j 数据库中将聚类写入到的节点属性。

nodeProperty

字符串

n/a

对应于 K-Means 用于将节点聚类到社区中的浮点数数组的节点属性。

k

整数

10

所需聚类的数量。

maxIterations

整数

10

要运行的 K-Means 的最大迭代次数。

deltaThreshold

浮点数

0.05

作为百分比的值,用于确定何时提前停止。如果少于“deltaThreshold * |nodes|”个节点更改其聚类,则算法停止。值必须在 0(不含)和 1(含)之间。

numberOfRestarts

整数

1

使用不同的初始中心执行 K-Means 的次数。返回的社区是最小化平均节点中心距离的社区。

randomSeed

整数

n/a

用于控制初始中心点分配的种子值。

initialSampler

字符串

"uniform"

用于采样前k个中心点的方法。“uniform”和“kmeans++”(均不区分大小写)是有效的输入。

seedCentroids

浮点数列表列表

[]

显式提供初始中心点的参数。它不能与numberOfRestarts参数的非默认值一起启用。

computeSilhouette

布尔值

false

如果设置为 true,则在确定聚类后计算轮廓得分。轮廓是衡量节点聚类效果的指标。

表 12. 结果
名称 类型 描述

preProcessingMillis

整数

预处理数据所用的毫秒数。

computeMillis

整数

运行算法所用的毫秒数。

writeMillis

整数

向 Neo4j 数据库添加属性所用的毫秒数。

postProcessingMillis

整数

计算百分位数和社区计数所用的毫秒数。

nodePropertiesWritten

整数

添加到投影图中的属性数量。

communityDistribution

映射

包含最后一级的社区大小的最小值、最大值、平均值以及 p1、p5、p10、p25、p50、p75、p90、p95、p99 和 p999 百分位数值的映射。

centroids

浮点数列表列表

中心点坐标列表。每个项目都是一个包含一个中心点坐标的列表。

averageDistanceToCentroid

浮点数

节点与中心点之间的平均距离。

averageSilhouette

浮点数

所有节点的平均轮廓得分。

configuration

映射

用于运行算法的配置。

示例

以下所有示例都应在空数据库中运行。

这些示例使用Cypher 投影作为规范。原生投影将在将来的版本中弃用。

在本节中,我们将展示在具体图上运行 K-Means 算法的示例。目的是说明结果是什么样子,并提供有关如何在实际环境中使用该算法的指南。我们将在少量节点以特定模式连接的小型城市图上进行此操作。示例图如下所示

Visualization of the example graph
以下 Cypher 语句将在 Neo4j 数据库中创建示例图
CREATE
  (:City {name: 'Surbiton', coordinates: [51.39148, -0.29825]}),
  (:City {name: 'Liverpool', coordinates: [53.41058, -2.97794]}),
  (:City {name: 'Kingston upon Thames', coordinates: [51.41259, -0.2974]}),
  (:City {name: 'Sliven', coordinates: [42.68583, 26.32917]}),
  (:City {name: 'Solna', coordinates: [59.36004, 18.00086]}),
  (:City {name: 'Örkelljunga', coordinates: [56.28338, 13.27773]}),
  (:City {name: 'Malmö', coordinates: [55.60587, 13.00073]}),
  (:City {name: 'Xánthi', coordinates: [41.13488, 24.888]});

此图由各种城市节点组成,分布在三个全球位置——英国、瑞典和欧洲巴尔干地区。

我们现在可以投影图并将其存储在图目录中。我们加载具有coordinates节点属性的City节点标签。

以下语句将投影图并将其存储在图目录中。
MATCH (c:City)
RETURN gds.graph.project(
  'cities',
  c,
  null,
  {
    sourceNodeProperties: c { .coordinates },
    targetNodeProperties: {}
  }
)

在以下示例中,我们将演示使用此图上的 K-Means 算法来查找地理位置接近的城市社区。

内存估算

首先,我们将使用estimate过程估算运行算法的成本。这可以使用任何执行模式来完成。在本例中,我们将使用write模式。估算算法有助于了解在您的图上运行算法将产生的内存影响。当您稍后实际以其中一种执行模式运行算法时,系统将执行估算。如果估算表明执行很有可能超过其内存限制,则会禁止执行。有关此内容的更多信息,请参见自动估算和执行阻止

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

以下将估算运行算法所需的内存。
CALL gds.kmeans.write.estimate('cities', {
  writeProperty: 'kmeans',
  nodeProperty: 'coordinates'
})
YIELD nodeCount, bytesMin, bytesMax, requiredMemory
表 13. 结果
nodeCount bytesMin bytesMax requiredMemory

8

33248

54240

"[32 KiB ... 52 KiB]"

stream执行模式下,算法返回每个节点的聚类。这使我们能够直接检查结果或在 Cypher 中对其进行后处理,而不会产生任何副作用。

有关stream模式的更多详细信息,请参见

以下将运行算法并流式传输结果
CALL gds.kmeans.stream('cities', {
  nodeProperty: 'coordinates',
  k: 3,
  randomSeed: 42
})
YIELD nodeId, communityId
RETURN gds.util.asNode(nodeId).name AS name, communityId
ORDER BY communityId, name ASC
表 14. 结果
name communityId

"Kingston upon Thames"

0

"Liverpool"

0

"Surbiton"

0

"Sliven"

1

"Xánthi"

1

"Malmö"

2

"Solna"

2

"Örkelljunga"

2

在上面的示例中,我们可以看到城市在地理上聚类在一起。

统计

stats执行模式下,算法返回包含算法结果摘要的单行。此执行模式没有任何副作用。通过检查computeMillis返回值,它可以用于评估算法性能。在下面的示例中,我们将省略返回时间。可以在语法部分中找到过程的完整签名。

有关stats模式的更多详细信息,请参见统计

以下将运行算法并以统计和测量值的形式返回结果
CALL gds.kmeans.stats('cities', {
  nodeProperty: 'coordinates',
  k: 3,
  randomSeed: 42
})
YIELD communityDistribution
表 15. 结果
communityDistribution

{max=3, mean=2.6666666666666665, min=2, p1=2, p10=2, p25=2, p5=2, p50=3, p75=3, p90=3, p95=3, p99=3, p999=3}

变异

mutate 执行模式扩展了 stats 模式,并带有一个重要的副作用:使用包含节点集群的新节点属性更新命名图。新属性的名称使用强制配置参数 mutateProperty 指定。结果是一个类似于 stats 的单个摘要行,但包含一些额外的指标。mutate 模式在多个算法联合使用时特别有用。

有关 mutate 模式的更多详细信息,请参阅 变异

以下将运行算法并将结果存储在 cities 图中
CALL gds.kmeans.mutate('cities', {
  nodeProperty: 'coordinates',
  k: 3,
  randomSeed: 42,
  mutateProperty: 'kmeans'
})
YIELD communityDistribution
表 16. 结果
communityDistribution

{max=3, mean=2.6666666666666665, min=2, p1=2, p10=2, p25=2, p5=2, p50=3, p75=3, p90=3, p95=3, p99=3, p999=3}

mutate 模式下,过程只返回一行。结果写入 GDS 内存图而不是 Neo4j 数据库。

写入

write 执行模式扩展了 stats 模式,并带有一个重要的副作用:将每个节点的集群作为属性写入 Neo4j 数据库。新属性的名称使用强制配置参数 writeProperty 指定。结果是一个类似于 stats 的单个摘要行,但包含一些额外的指标。write 模式能够将结果直接持久化到数据库中。

有关 write 模式的更多详细信息,请参阅 写入

以下将运行算法并将结果写回 Neo4j
CALL gds.kmeans.write('cities', {
  nodeProperty: 'coordinates',
  k: 3,
  randomSeed: 42,
  writeProperty: 'kmeans'
})
YIELD nodePropertiesWritten
表 17. 结果
nodePropertiesWritten

8

write 模式下,过程只返回一行。结果写入 Neo4j 数据库而不是 GDS 内存图。

播种初始中心点

现在我们看到了播种中心点对 K 均值的影响。我们使用纽约、阿姆斯特丹和罗马的坐标作为初始种子运行 K 均值。

以下将运行算法并流式传输结果
CALL gds.kmeans.stream('cities', {
  nodeProperty: 'coordinates',
  k: 3,
  seedCentroids: [[40.712776,-74.005974], [52.370216,4.895168],[41.902782,12.496365]]
})
YIELD nodeId, communityId
RETURN gds.util.asNode(nodeId).name AS name, communityId
ORDER BY communityId, name ASC
表 18. 结果
name communityId

"Kingston upon Thames"

1

"Liverpool"

1

"Malmö"

1

"Solna"

1

"Surbiton"

1

"Örkelljunga"

1

"Sliven"

2

"Xánthi"

2

请注意,在这种情况下,城市已在地理上聚类为两个集群:一个包含北欧城市,另一个包含南欧城市。另一方面,以纽约为初始中心点的集群在第一阶段与任何城市都不最接近。


1. Arthur, David 和 Sergei Vassilvitskii。“k-means++:仔细播种的优势。” *ACM-SIAM 离散算法研讨会*(2007)。