HashGNN

简介

HashGNN 是一种节点嵌入算法,它类似于图神经网络 (GNN),但不需要模型或训练过程。它使用随机哈希函数(类似于 min-hash 局部敏感哈希)取代了 GNN 中的神经网络。因此,HashGNN 结合了 GNN 的思想与快速随机化算法的优势。

适用于 Snowflake 的 Neo4j 图分析中实现的 HashGNN 基于论文《Hashing-Accelerated Graph Neural Networks for Link Prediction》(哈希加速的图神经网络用于链路预测),并引入了一些改进和泛化。这些泛化包括对异构图嵌入的支持;不同类型的关系关联不同的哈希函数,从而能够保留关系类型的图拓扑结构。此外,可以通过 neighborInfluence(邻居影响)来配置嵌入更新中邻居节点特征与自身节点特征的权重比例。

该算法的运行时间通常远低于 GNN,但如原论文所示,对于某些图,它仍能提供相当的嵌入质量。此外,与《Graph Transformer Networks》(图 Transformer 网络)论文相比,该异构泛化版本在相同数据集上的基准测试结果也相当。

执行过程不需要像通常 GNN 那样使用 GPU,并且可以在多个 CPU 核心上良好地进行并行处理。

算法原理

为了阐明 HashGNN 的工作方式,对于那些对特征选择细节感到好奇并更喜欢通过示例学习的读者,我们将在下文中通过一个三节点图的虚拟示例进行演示。

HashGNN 算法仅适用于二进制特征。因此,算法中包含一个可选的首要步骤,即根据需要将(可能非二进制的)输入特征转换为二进制特征。

通过多次迭代,使用前一次迭代的嵌入为每个节点计算新的二进制嵌入。在第一次迭代中,之前的嵌入即为输入特征向量或二值化后的输入向量。

在一次迭代中,每个节点的嵌入向量是通过 K 次随机采样构建的。随机采样是通过依次选择 min-hash 值最低的特征来完成的。节点自身的特征及其邻居的特征都会被考虑在内。

涉及三种类型的哈希函数:1) 应用于节点自身特征的函数,2) 应用于邻居节点特征子集的函数,3) 应用于所有邻居特征以选择函数 2 所需子集的函数。对于每次迭代和采样轮次 k<K,都会使用新的哈希函数,并且第三种函数还会根据与邻居连接的关系类型而变化。

这种采样是一致的,即如果节点 (a) 和 (b) 具有相同或相似的局部图,则 (a) 和 (b) 的采样结果也是相同或相似的。此处“局部图”指的是包含所有在 iterations 跳数距离内的节点及其特征和关系类型的子图。

数值 K 在算法配置中被称为 embeddingDensity(嵌入密度)。

算法最后有一个可选步骤,将二进制嵌入映射为稠密向量。

特征

原始 HashGNN 算法假设节点具有二进制输入特征,并生成二进制嵌入向量作为输出(除非选择输出稠密化)。由于现实世界中的图并非总是如此,我们的算法也提供了对节点属性进行二值化,或从头生成二进制特征的选项。

使用二进制节点属性作为特征

如果您的节点属性仅包含 0 或 1 值(或此类值的数组),则可以直接将其作为 HashGNN 算法的输入。为此,需要在计算配置中将它们提供为 featureProperties

特征生成

要使用特征生成,请在 generateFeatures 计算配置参数中指定包含 dimension(维度)和 densityLevel(密度水平)的映射。这将生成指定数量的特征,其中每个节点大约有 densityLevel 数量的特征被激活。每个节点的活动特征是随机且均匀选择的(带有放回)。虽然活动特征是随机的,但节点的特征向量充当了该节点的近似唯一签名。这类似于节点 ID 的独热编码(one-hot encoding),但它是近似的,因为它比图的节点数具有低得多的维度。请注意,在使用特征生成时,不支持提供任何 featureProperties,而该属性在其他情况下是必需的。

特征二值化

特征二值化使用超平面舍入(hyperplane rounding),通过 featureProperties 以及包含 threshold(阈值)和 dimension(维度)的 binarizeFeatures 映射参数进行配置。超平面舍入使用由填充了高斯随机值的向量定义的超平面。dimension 参数决定了输入特征被转换成的生成二进制特征的数量。对于每个超平面(每个 dimension 一个)和每个节点,我们计算节点的输入特征向量与超平面法向量的点积。如果该点积大于给定的 threshold,则该节点将获得对应于该超平面的特征。

尽管超平面舍入可以应用于二进制输入,但通常直接使用已有的二进制输入效果更好。然而,有时使用与输入特征数量不同的 dimension 进行二值化,对于执行降维或引入可被 HashGNN 利用的冗余是有用的。

如果输入特征的数量级不同,超平面舍入的效果可能不佳,因为量级较大的特征会对生成的二进制特征产生更大的影响。如果这不是您应用程序预期的行为,建议在运行 HashGNN 之前对节点属性(按特征维度)进行归一化。

邻居影响

参数 neighborInfluence 决定了算法选择邻居特征而非节点自身特征的倾向。neighborInfluence 的默认值为 1.0,在该值下,平均而言,特征有 50% 的概率来自邻居。增加该值会导致邻居被选中的频率更高。作为特征源于邻居的概率函数,neighborInfluence 呈现出类似曲棍球杆的形状,类似于 y=log(x)y=C - 1/x 的形状。这意味着该概率对较低的 neighborInfluence 值更为敏感。

异构支持

适用于 Snowflake 的 Neo4j 图分析中的 HashGNN 实现为异构图提供了一种新的泛化,即它能够区分不同的关系类型。要启用异构支持,请将 heterogeneous 设置为 true。该泛化过程与原始 HashGNN 算法一致,但每当对邻居节点的特征应用哈希函数时,算法使用的哈希函数不仅取决于迭代次数和 k < embeddingDensity,还取决于连接到邻居的关系类型。考虑一个例子,HashGNN 运行一次迭代,且有 (a)-[:R]→(x), (b)-[:R]→(x)(c)-[:S]→(x)。假设 (x) 的特征 f 被选用于 (a),且哈希值非常小。这将使该特征也非常有可能被选用于 (b)。然而,在考虑关系 (c)-[:S]→(x) 时,特征 f 是否被选用于 (c) 将不存在相关性,因为 S 使用了不同的哈希函数。我们可以得出结论,具有相似邻域(包括节点属性和关系类型)的节点会获得相似的嵌入,而具有不那么相似邻域的节点则获得不那么相似的嵌入。

运行异构 HashGNN 相比运行如同 FastRP 等同构嵌入算法的优势在于,无需在对这些多个图运行 FastRP 之前手动选择多个投影或创建元路径(meta-path)图。利用异构算法,可以在单次执行中使用完整的异构图。

异构图的节点属性模式

异构图通常对不同的节点标签具有不同的节点属性。HashGNN 假设所有节点具有相同的允许特征集。因此,请在每个图投影中使用默认值 0。这在二进制输入情况下以及应用二值化时均适用,因为值为 0 的二进制特征的行为就像没有该特征一样。这些 0 值以稀疏格式表示,因此存储许多节点的 0 值的内存开销较低。

方向

创建图时选择正确的方向可能会产生重大影响。HashGNN 适用于任何方向,方向的选择取决于具体问题。给定一个有向关系类型,您可以选择一个方向,或者使用 NATURALREVERSE 两个投影。用 GNN 的类比来说,对反向关系使用不同的关系类型,会导致在考虑关系与反向关系时使用不同的权重集。对于 HashGNN,这意味着对这两种关系使用不同的 min-hash 函数。例如,在引文网络中,一篇论文引用另一篇论文与这篇论文被引用是非常不同的。

输出稠密化

由于二进制嵌入需要比稠密浮点嵌入更高的维度才能编码相同数量的信息,因此二进制嵌入需要更多的内存,且下游模型的训练时间更长。通过使用随机投影(类似于初始化带有节点属性的 FastRP 的做法),输出嵌入可以进行可选的稠密化。通过指定 outputDimension 可以激活此行为。输出稠密化可以改善下游任务的运行时间和内存占用,代价是由于投影的随机性引入了近似误差。outputDimension 越大,近似误差越小,性能节省也越少。

算法参数调优

为了提高 HashGNN 在您的图上的嵌入质量,可以对算法参数进行调优。为您的特定用例和图找到最佳参数的过程通常被称为超参数调优。我们将逐一介绍每个计算配置参数,并解释它们的行为。

迭代次数

影响节点嵌入的节点间最大跳数等于 HashGNN 的迭代次数,该次数通过 iterations 配置。这类似于 GNN 中的层数或 FastRP 中的迭代次数。通常 24 的值已足够,但有时更多的迭代是有用的。

嵌入密度

embeddingDensity 参数即原论文中的 k。在 HashGNN 的每次迭代中,会从相同节点及其邻居的前一次迭代嵌入中选择 k 个特征。所选特征以集合形式表示,因此不同所选特征的数量可能小于 k。该参数设置得越高,运行算法所需的时间就越长,运行时间呈线性增长。在很大程度上,较高的值会产生更好的嵌入。作为粗略指南,可以尝试将 embeddingDensity 设置为 128、256、512,或约为嵌入维度(即二进制特征的数量)的 25%-50%。

特征生成

dimension 参数决定了应用特征生成时的二进制特征数量。高维度增加了表达能力,但需要更多数据才能有效,并可能导致下游机器学习任务的维度灾难。此外,需要更多的计算资源。然而,二进制嵌入在每个维度上仅具有 1 位信息。相比之下,稠密的 Float 嵌入在每个维度上具有 64 位信息。因此,为了获得与产生稠密嵌入的算法(例如 FastRPGraphSAGE)相当的 HashGNN 嵌入,通常需要显著更高的维度。densityLevel 可以尝试的值有非常小的值,如 12,并根据需要增加。

特征二值化

dimension 参数决定了应用二值化时的二进制特征数量。高维度增加了表达能力,但也增加了特征的稀疏性。因此,更高的维度也应与更高的 embeddingDensity 和/或更低的 threshold 结合使用。更高的维度也会导致下游模型的训练时间更长,内存占用更高。增加阈值会导致更稀疏的特征向量。

然而,二进制嵌入在每个维度上仅具有 1 位信息。相比之下,稠密的 Float 嵌入在每个维度上具有 64 位信息。因此,为了获得与产生稠密嵌入的算法(例如 FastRPGraphSAGE)相当的 HashGNN 嵌入,通常需要显著更高的维度。

默认的 0 阈值会导致每个节点有相当多的活动特征。通常稀疏特征向量效果更好,因此增加阈值超过默认值可能是有用的。选择良好阈值的一种启发式方法是基于节点特征向量与超平面点积的平均值和标准差。例如,可以将阈值设置为平均值加上两倍的标准差。要获取这些值,请运行 HashGNN 并查看数据库日志。然后,您可以利用这些值相应地重新配置阈值。

邻居影响

如上所述,默认值是一个合理的起点。如果使用超参数调优库,该参数可能最好通过导数递增的函数进行转换,例如指数函数或 a/(b - x) 类型的函数。从不同节点选择(并在迭代中保留)特征的概率取决于 neighborInfluence 和到该节点的跳数。因此,当改变 iterations 时,应重新调优 neighborInfluence

异构

通常,在异构图中存储有关包含多种关系类型的路径的信息量很大,因此在多次迭代和多种关系类型的情况下,可能需要非常高的嵌入维度。对于诸如 HashGNN 这样的无监督嵌入算法尤其如此。因此,在异构模式下使用多次迭代时应谨慎。

随机种子

随机种子在此算法中具有特殊作用。除了使算法的所有步骤确定性之外,randomSeed 参数还在一定程度上决定了算法内部使用的哈希函数。这一点很重要,因为它极大地影响了每次迭代中采样的特征。哈希的作用类似于图神经网络每一层中的(通常是神经)变换,这告诉我们哈希函数有多重要。事实上,当计算配置中仅 randomSeed 不同时,通常可以看出算法输出的节点嵌入质量有显著差异。

基于这些原因,调优随机种子参数确实是有意义的。请注意,它应作为分类(即非序数)数值进行调优,这意味着值 1 和 2 的相似或不同程度可能与 1 和 100 一样。开始进行此操作的一个好方法是选择 5 - 10 个任意整数(例如值 1、2、3、4 和 5)作为随机种子的候选项。

randomSeed 与多个计算配置参数共同依赖,特别是与 neighborInfluence 参数直接相关,后者也会直接影响使用的哈希函数。因此,如果改变了 neighborInfluence,很可能需要重新调优 randomSeed 参数。

语法

本节涵盖了执行 HashGNN 算法所使用的语法。

运行 HashGNN。
CALL Neo4j_Graph_Analytics.graph.hashgnn(
  'CPU_X64_XS',                    (1)
  {
    ['defaultTablePrefix': '...',] (2)
    'project': {...},              (3)
    'compute': {...},              (4)
    'write':   {...}               (5)
  }
);
1 计算池选择器。
2 表引用的可选前缀。
3 项目配置。
4 计算配置。
5 写入配置。
表 1. 参数
名称 类型 默认 可选 描述

computePoolSelector

字符串

不适用

运行 HashGNN 作业的计算池选择器。

配置

Map

{}

用于图项目、算法计算和结果回写的配置。

配置映射由以下三个条目组成。

有关以下项目配置的更多详细信息,请参阅 项目文档
表 2. 项目配置
名称 类型

nodeTables

节点表列表。

relationshipTables

关系类型到关系表的映射。

表 3. 计算配置
名称 类型 默认 可选 描述

featureProperties

字符串列表

[]

应作为输入特征使用的节点属性名称。所有属性名称必须存在于投影图中,且类型为 Float 或 List of Float。

iterations (迭代次数)

整数

不适用

运行 HashGNN 的迭代次数。必须至少为 1。

embeddingDensity

整数

不适用

每次迭代中每个节点采样的特征数量。在原论文中称为 K。必须至少为 1。

heterogeneous

布尔值

false

是否应对不同的关系类型进行区分处理。

neighborInfluence

浮点数

1.0

控制每次迭代中采样邻居特征相对于采样节点自身特征的频率。必须为非负数。

binarizeFeatures

Map

不适用

包含 dimensionthreshold 键的映射。如果提供,特征将通过超平面舍入转换为 dimension 个二进制特征。增加 threshold 会使输出更稀疏,默认值为 0dimension 的值必须至少为 1。

generateFeatures

Map

不适用

包含 dimensiondensityLevel 键的映射。当且仅当 featureProperties 为空时才应提供。如果提供,将生成 dimension 个二进制特征,每个节点大约有 densityLevel 个激活特征。两者都必须至少为 1,且 densityLevel 最多为 dimension

outputDimension

整数

不适用

如果提供,嵌入将被随机投影到 outputDimension 个稠密特征中。必须至少为 1。

randomSeed

整数

不适用

用于计算嵌入中所有随机性的随机种子。

关于下文写入配置的更多详细信息,请参考 写入文档
表 4. 写入配置
名称 类型 默认 可选 描述

nodeLabel

字符串

不适用

内存中图中用于写入节点属性的节点标签。

nodeProperty

字符串

'hashgnn'

将回写到 Snowflake 数据库的节点属性。

outputTable

字符串

不适用

Snowflake 数据库中写入节点属性的表。

示例

在本节中,我们将展示在具体图上运行 HashGNN 算法的示例。目的是为了说明结果看起来是什么样子的,并为如何在实际场景中使用该算法提供指南。我们将在一个由少数节点按特定模式连接而成的小型社交网络图上进行演示。

以下 SQL 语句将在 Snowflake 中创建示例图表
CREATE OR REPLACE TABLE EXAMPLE_DB.DATA_SCHEMA.PERSONS (NODEID VARCHAR, AGE INT, EXPERIENCE FLOAT, HIPSTER INT);
ALTER TABLE EXAMPLE_DB.DATA_SCHEMA.PERSONS ADD COLUMN SOURNESS FLOAT DEFAULT 0.0;
ALTER TABLE EXAMPLE_DB.DATA_SCHEMA.PERSONS ADD COLUMN SWEETNESS FLOAT DEFAULT 0.0;
ALTER TABLE EXAMPLE_DB.DATA_SCHEMA.PERSONS ADD COLUMN TROPICAL INT DEFAULT 0;
INSERT INTO EXAMPLE_DB.DATA_SCHEMA.PERSONS (NODEID, AGE, EXPERIENCE, HIPSTER) VALUES
  ('Dan', 18, 0.63, 0),
  ('Annie', 12, 0.05, 0),
  ('Matt', 22, 0.42, 0),
  ('Jeff', 51, 0.12, 0),
  ('Brie', 31, 0.06, 0),
  ('John', 65, 0.23, 1),
  ('Brie', 4, 1.0, 0);

CREATE OR REPLACE TABLE EXAMPLE_DB.DATA_SCHEMA.FRUITS (NODEID VARCHAR, TROPICAL INT, SOURNESS FLOAT, SWEETNESS FLOAT);
ALTER TABLE EXAMPLE_DB.DATA_SCHEMA.FRUITS ADD COLUMN EXPERIENCE FLOAT DEFAULT 0.0;
INSERT INTO EXAMPLE_DB.DATA_SCHEMA.FRUITS (NODEID, TROPICAL, SOURNESS, SWEETNESS) VALUES
  ('Apple', 0, 0.3, 0.6),
  ('Banana', 1, 0.1, 0.9),
  ('Mango', 1, 0.3, 1.0),
  ('Plum', 0, 0.5, 0.8);

CREATE OR REPLACE TABLE EXAMPLE_DB.DATA_SCHEMA.LIKES (SOURCENODEID VARCHAR, TARGETNODEID VARCHAR);
INSERT INTO EXAMPLE_DB.DATA_SCHEMA.LIKES VALUES
  ('Dan', 'Apple'),
  ('Annie', 'Banan'),
  ('Matt', 'Mango'),
  ('Jeff', 'Mango'),
  ('Brie', 'Banana'),
  ('Elsa', 'Plum'),
  ('John',  'Plum');

CREATE OR REPLACE TABLE EXAMPLE_DB.DATA_SCHEMA.KNOWS (SOURCENODEID VARCHAR, TARGETNODEID VARCHAR);
INSERT INTO EXAMPLE_DB.DATA_SCHEMA.KNOWS VALUES
  ('Dan', 'Annie'),
  ('Dan', 'Matt'),
  ('Annie', 'Matt'),
  ('Annie', 'Jeff'),
  ('Annie', 'Brie'),
  ('Matt', 'Brie'),
  ('Brie',  'Elsa'),
  ('Brie', 'Jeff'),
  ('John', 'Jeff');

此图有两个节点表:person 节点和 fruit 节点。这两个节点集通过 LIKES 关系连接,person 节点之间还有 KNOWS 关系。

请注意,我们添加了一些仅具有默认值的节点表列。原因是 HashGNN 要求所有节点具有相同的特征集,而我们希望将这些节点列作为特征使用。

运行带二值化的 HashGNN

首先,我们将仅在 person 节点上运行算法,使用 AGEEXPERIENCE 列作为特征。由于这些属性不是二进制的,我们将使用 HashGNN 的二值化功能将它们转换为二进制特征。

要运行查询,需要为应用程序、您的消费者角色和您的环境设置必要的权限。请参阅 入门 页面以了解更多信息。

我们还假设应用程序名称为默认的 Neo4j_Graph_Analytics。如果您在安装过程中选择了不同的应用程序名称,请将其替换为该名称。

以下将在带二值化的情况下在 person 节点上运行该算法
CALL Neo4j_Graph_Analytics.graph.hashgnn('CPU_X64_XS', {
    'defaultTablePrefix': 'EXAMPLE_DB.DATA_SCHEMA',
    'project': {
        'nodeTables': [ 'PERSONS' ],
        'relationshipTables': {
            'KNOWS': {
                'sourceTable': 'PERSONS',
                'targetTable': 'PERSONS',
                'orientation': 'UNDIRECTED'
            }
        }
    },
    'compute': {
        'iterations': 1,
        'embeddingDensity': 2,
        'binarizeFeatures': {'dimension': 4, 'threshold': 32},
        'featureProperties': ['AGE', 'EXPERIENCE'],
        'randomSeed': 42
    },
    'write': [{
        'nodeLabel': 'PERSONS',
        'outputTable': 'PERSON_EMBEDDINGS'
    }]
});
表 5. 结果
JOB_ID JOB_STATUS JOB_START JOB_END JOB_RESULT

job_fe099995c12b431cbf5fa46d4a88a30f

SUCCESS

2025-08-06 07:33:11.282

2025-08-06 07:33:17.678

 {
  "hashgnn_1": {
    "computeMillis": 32,
    "configuration": {
      "binarizeFeatures": {
        "dimension": 4,
        "threshold": 32
      },
      "concurrency": 6,
      "embeddingDensity": 2,
      "featureProperties": [
        "AGE",
        "EXPERIENCE"
      ],
      "heterogeneous": false,
      "iterations": 1,
      "resultProperty": "hashgnn",
      "neighborInfluence": 1,
      "nodeLabels": [
        "*"
      ],
      "randomSeed": 42,
      "relationshipTypes": [
        "*"
      ]
    }
  },
  "project_1": {
    "graphName": "snowgraph",
    "nodeCount": 6,
    "nodeLabels": ...,
    "nodeMillis": 218,
    "relationshipCount": 16,
    "relationshipMillis": 462,
    "relationshipTypes": ...,
    "totalMillis": 680
  },
  "write_node_property_1": {
    "copyIntoTableMillis": 1510,
    "nodeLabel": "PERSONS",
    "nodeProperty": "hashgnn",
    "outputTable": "EXAMPLE_DB.DATA_SCHEMA.PERSON_EMBEDDINGS",
    "rowsWritten": 6,
    "stageUploadMillis": 658,
    "writeMillis": 2411
  }
}

返回的结果包含有关作业执行和结果分布的信息。此外,每个节点的嵌入已写回 Snowflake。我们可以这样查询它

SELECT * FROM EXAMPLE_DB.DATA_SCHEMA.PERSON_EMBEDDINGS;
表 6. 结果
NODEID HASHGNN

Dan

[ 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00 ]

Annie

[ 1.000000000000000e+00, 0.000000000000000e+00, 1.000000000000000e+00, 0.000000000000000e+00 ]

Matt

[ 1.000000000000000e+00, 1.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00 ]

Jeff

[ 1.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00 ]

Brie

[ 1.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00 ]

John

[ 1.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00 ]

算法的结果不是非常直观地可解释,因为节点嵌入格式是节点在其邻域内的数学抽象,旨在用于机器学习。我们可以看到的是,嵌入有四个元素(正如使用 binarizeFeatures.dimension 所配置的那样)。

由于算法的随机性质,除非指定了 randomSeed,否则结果会在多次运行之间有所不同。

运行不带二值化的 HashGNN

接下来,我们同样仅在 person 节点上运行算法,使用 HIPSTER 列作为特征。由于这些属性是二进制的,我们将不使用 HashGNN 的二值化功能。

要运行查询,需要为应用程序、您的消费者角色和您的环境设置必要的权限。请参阅 入门 页面以了解更多信息。

我们还假设应用程序名称为默认的 Neo4j_Graph_Analytics。如果您在安装过程中选择了不同的应用程序名称,请将其替换为该名称。

以下将在不带二值化的情况下在 person 节点上运行该算法
CALL Neo4j_Graph_Analytics.graph.hashgnn('CPU_X64_XS', {
    'defaultTablePrefix': 'EXAMPLE_DB.DATA_SCHEMA',
    'project': {
        'nodeTables': [ 'PERSONS' ],
        'relationshipTables': {
            'KNOWS': {
                'sourceTable': 'PERSONS',
                'targetTable': 'PERSONS',
                'orientation': 'UNDIRECTED'
            }
        }
    },
    'compute': {
        'iterations': 1,
        'embeddingDensity': 2,
        'featureProperties': ['HIPSTER'],
        'randomSeed': 123
    },
    'write': [{
        'nodeLabel': 'PERSONS',
        'outputTable': 'PERSON_EMBEDDINGS'
    }]
});
表 7. 结果
JOB_ID JOB_STATUS JOB_START JOB_END JOB_RESULT

job_92acd5e9bc374455bdd1a5a3361168c9

SUCCESS

2025-08-06 07:39:46.436

2025-08-06 07:39:52.459

 {
  "hashgnn_1": {
    "computeMillis": 34,
    "configuration": {
      "concurrency": 6,
      "embeddingDensity": 2,
      "featureProperties": [
        "HIPSTER"
      ],
      "heterogeneous": false,
      "iterations": 1,
      "resultProperty": "hashgnn",
      "neighborInfluence": 1,
      "nodeLabels": [
        "*"
      ],
      "randomSeed": 123,
      "relationshipTypes": [
        "*"
      ]
    }
  },
  "project_1": {
    "graphName": "snowgraph",
    "nodeCount": 6,
    "nodeLabels": ...,
    "nodeMillis": 139,
    "relationshipCount": 16,
    "relationshipMillis": 300,
    "relationshipTypes": ...,
    "totalMillis": 439
  },
  "write_node_property_1": {
    "copyIntoTableMillis": 1002,
    "nodeLabel": "PERSONS",
    "nodeProperty": "hashgnn",
    "outputTable": "EXAMPLE_DB.DATA_SCHEMA.PERSON_EMBEDDINGS",
    "rowsWritten": 6,
    "stageUploadMillis": 562,
    "writeMillis": 1865
  }
}

返回的结果包含有关作业执行和结果分布的信息。此外,每个节点的嵌入已写回 Snowflake。我们可以这样查询它

SELECT * FROM EXAMPLE_DB.DATA_SCHEMA.PERSON_EMBEDDINGS;
表 8. 结果
NODEID HASHGNN

Dan

[ 0.000000000000000e+00 ]

Annie

[ 0.000000000000000e+00 ]

Matt

[ 0.000000000000000e+00 ]

Jeff

[ 1.000000000000000e+00 ]

Brie

[ 0.000000000000000e+00 ]

John

[ 1.000000000000000e+00 ]

在这个示例中,嵌入维度变为 1,因为在没有二值化的情况下,它是给定特征的数量,由于只有单个 'HIPSTER' 列,所以是 1

运行带特征生成的 HashGNN

接下来,我们将再次仅在 person 节点上运行算法,不使用来自节点表的任何特征,而是生成随机二进制特征。当节点没有任何特征,或者现有特征没用时,这很有用。

要运行查询,需要为应用程序、您的消费者角色和您的环境设置必要的权限。请参阅 入门 页面以了解更多信息。

我们还假设应用程序名称为默认的 Neo4j_Graph_Analytics。如果您在安装过程中选择了不同的应用程序名称,请将其替换为该名称。

以下将在带特征生成的情况下在 person 节点上运行该算法
CALL Neo4j_Graph_Analytics.graph.hashgnn('CPU_X64_XS', {
    'defaultTablePrefix': 'EXAMPLE_DB.DATA_SCHEMA',
    'project': {
        'nodeTables': [ 'PERSONS' ],
        'relationshipTables': {
            'KNOWS': {
                'sourceTable': 'PERSONS',
                'targetTable': 'PERSONS',
                'orientation': 'UNDIRECTED'
            }
        }
    },
    'compute': {
        'iterations': 1,
        'embeddingDensity': 2,
        'generateFeatures': {'dimension': 6, 'densityLevel': 1},
        'randomSeed': 42
    },
    'write': [{
        'nodeLabel': 'PERSONS',
        'outputTable': 'PERSON_EMBEDDINGS'
    }]
});
表 9. 结果
JOB_ID JOB_STATUS JOB_START JOB_END JOB_RESULT

job_65555fca32dd4088a4d18b2b888a1b96

SUCCESS

2025-08-06 07:43:16.691

2025-08-06 07:43:23.157

 {
  "hashgnn_1": {
    "computeMillis": 25,
    "configuration": {
      "concurrency": 6,
      "embeddingDensity": 2,
      "featureProperties": [],
      "generateFeatures": {
        "densityLevel": 1,
        "dimension": 6
      },
      "heterogeneous": false,
      "iterations": 1,
      "resultProperty": "hashgnn",
      "neighborInfluence": 1,
      "nodeLabels": [
        "*"
      ],
      "randomSeed": 42,
      "relationshipTypes": [
        "*"
      ]
    }
  },
  "project_1": {
    "graphName": "snowgraph",
    "nodeCount": 6,
    "nodeLabels": ...,
    "nodeMillis": 182,
    "relationshipCount": 16,
    "relationshipMillis": 413,
    "relationshipTypes": ...,
    "totalMillis": 595
  },
  "write_node_property_1": {
    "copyIntoTableMillis": 1012,
    "nodeLabel": "PERSONS",
    "nodeProperty": "hashgnn",
    "outputTable": "EXAMPLE_DB.DATA_SCHEMA.PERSON_EMBEDDINGS",
    "rowsWritten": 6,
    "stageUploadMillis": 545,
    "writeMillis": 1825
  }
}

返回的结果包含有关作业执行和结果分布的信息。此外,每个节点的嵌入已写回 Snowflake。我们可以这样查询它

SELECT * FROM EXAMPLE_DB.DATA_SCHEMA.PERSON_EMBEDDINGS;
表 10. 结果
NODEID HASHGNN

Dan

[ 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 1.000000000000000e+00, 0.000000000000000e+00 ]

Annie

[ 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 1.000000000000000e+00, 0.000000000000000e+00 ]

Matt

[ 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 1.000000000000000e+00, 0.000000000000000e+00 ]

Jeff

[ 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 1.000000000000000e+00, 0.000000000000000e+00 ]

Brie

[ 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 1.000000000000000e+00, 1.000000000000000e+00 ]

John

[ 0.000000000000000e+00, 0.000000000000000e+00, 1.000000000000000e+00, 1.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00 ]

正如我们所看到的,每个节点至少有一个活动特征。密度约为 50%,没有节点有多于两个的活动特征(受 embeddingDensity 限制)。

在异构图上运行

最后,我们将在异构图上运行该算法,同时包括 fruit 节点,并使用 EXPERIENCETROPICALSOURNESSSWEETNESS 列作为特征。

要运行查询,需要为应用程序、您的消费者角色和您的环境设置必要的权限。请参阅 入门 页面以了解更多信息。

我们还假设应用程序名称为默认的 Neo4j_Graph_Analytics。如果您在安装过程中选择了不同的应用程序名称,请将其替换为该名称。

以下将在异构图上运行该算法
CALL Neo4j_Graph_Analytics.graph.hashgnn('CPU_X64_XS', {
    'defaultTablePrefix': 'EXAMPLE_DB.DATA_SCHEMA',
    'project': {
        'nodeTables': [ 'PERSONS', 'FRUITS' ],
        'relationshipTables': {
            'KNOWS': {
                'sourceTable': 'PERSONS',
                'targetTable': 'PERSONS',
                'orientation': 'UNDIRECTED'
            },
            'LIKES': {
                'sourceTable': 'PERSONS',
                'targetTable': 'FRUITS',
                'orientation': 'UNDIRECTED'
            }
        }
    },
    'compute': {
        'heterogeneous': true,
        'iterations': 2,
        'embeddingDensity': 4,
        'binarizeFeatures': {'dimension': 6, 'threshold': 0.2},
        'featureProperties': ['EXPERIENCE', 'SOURNESS', 'SWEETNESS', 'TROPICAL'],
        'randomSeed': 42
    },
    'write': [
        {
            'nodeLabel': 'PERSONS',
            'outputTable': 'PERSON_EMBEDDINGS'
        },
        {
            'nodeLabel': 'FRUITS',
            'outputTable': 'FRUIT_EMBEDDINGS'
        }
    ]
});
表 11. 结果
JOB_ID JOB_STATUS JOB_START JOB_END JOB_RESULT

job_2fbffb14f7c6402588192cbe8eee793f

SUCCESS

2025-08-06 07:47:04.670

2025-08-06 07:47:13.095

 {
  "hashgnn_1": {
    "computeMillis": 47,
    "configuration": {
      "binarizeFeatures": {
        "dimension": 6,
        "threshold": 0.2
      },
      "concurrency": 6,
      "embeddingDensity": 4,
      "featureProperties": [
        "EXPERIENCE",
        "SOURNESS",
        "SWEETNESS",
        "TROPICAL"
      ],
      "heterogeneous": true,
      "iterations": 2,
      "resultProperty": "hashgnn",
      "neighborInfluence": 1,
      "nodeLabels": [
        "*"
      ],
      "randomSeed": 42,
      "relationshipTypes": [
        "*"
      ]
    }
  },
  "project_1": {
    "graphName": "snowgraph",
    "nodeCount": 10,
    "nodeLabels": ...,
    "nodeMillis": 242,
    "relationshipCount": 26,
    "relationshipMillis": 333,
    "relationshipTypes": ...,
    "totalMillis": 575
  },
  "write_node_property_1": {
    "copyIntoTableMillis": 1015,
    "nodeLabel": "PERSONS",
    "nodeProperty": "hashgnn",
    "outputTable": "EXAMPLE_DB.DATA_SCHEMA.PERSON_EMBEDDINGS",
    "rowsWritten": 6,
    "stageUploadMillis": 550,
    "writeMillis": 1809
  },
  "write_node_property_2": {
    "copyIntoTableMillis": 936,
    "nodeLabel": "FRUITS",
    "nodeProperty": "hashgnn",
    "outputTable": "EXAMPLE_DB.DATA_SCHEMA.FRUIT_EMBEDDINGS",
    "rowsWritten": 4,
    "stageUploadMillis": 428,
    "writeMillis": 1583
  }
}

返回的结果包含有关作业执行和结果分布的信息。此外,每个节点的嵌入已写回 Snowflake。让我们检查一下 person 节点的嵌入

SELECT * FROM EXAMPLE_DB.DATA_SCHEMA.PERSON_EMBEDDINGS;
表 12. 结果
NODEID HASHGNN

Dan

[ 1.000000000000000e+00, 0.000000000000000e+00, 1.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00 ]

Annie

[ 1.000000000000000e+00, 1.000000000000000e+00, 1.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00 ]

Matt

[ 1.000000000000000e+00, 0.000000000000000e+00, 1.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00 ]

Jeff

[ 1.000000000000000e+00, 1.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00 ]

Brie

[ 1.000000000000000e+00, 1.000000000000000e+00, 1.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00 ]

John

[ 1.000000000000000e+00, 1.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00, 0.000000000000000e+00 ]

我们可以检查 FRUIT_EMBEDDINGS 并找到类似的结果,但为了简洁起见,我们不在这里演示。

虚拟示例

或许下面的例子最好准备好笔和纸来跟随。

假设我们有一个具有特征 f1 的节点 a,一个具有特征 f2 的节点 b,以及一个具有特征 f1f3 的节点 c。图结构为 a—​b—​c。我们想象运行一次迭代的 HashGNN,并设置 embeddingDensity=2。为了简单起见,我们将假设哈希函数随着我们的操作返回一些编造的数字。

在第一次迭代和 k=0 时,我们为 (a) 计算一个嵌入。特征 f1 的哈希值结果为 7。由于 (b)(a) 的邻居,我们为其特征 f2 生成一个值,结果为 11。值 7 是从我们称为“一”的哈希函数中采样的,11 是从哈希函数“二”中采样的。因此 f1 被添加到 (a) 的新特征中,因为它具有较小的哈希值。我们对 k=1 重复此步骤,这次哈希值分别是 42,因此现在 f2 作为特征被添加到 (a) 中。

现在我们考虑 (b)。使用哈希函数“一”,特征 f2 得到哈希值 8。查看邻居 (a),我们为特征 f1 采样一个哈希值,使用哈希函数“二”得到 5。由于 (c) 拥有不止一个特征,在考虑将“获胜”特征作为哈希函数“二”的输入之前,我们还必须从两个特征 f1f3 中选择一个。我们为此目的使用第三个哈希函数“三”,特征 f3 得到了较小的值 1。现在我们使用“二”计算 f3 的哈希值,它变成了 6。由于 5 小于 6f1(b) 的“获胜”邻居特征,并且由于 5 也小于 8,它是整体的“获胜”特征。因此,我们将 f1 添加到 (b) 的嵌入中。我们对 k=1 进行类似的操作,f1 再次被选中。由于嵌入由二进制特征组成,第二次添加没有任何效果。

我们省略了计算 (c) 嵌入的细节。

在 2 轮采样后,迭代完成。由于只有一次迭代,我们完成了。每个节点都有一个包含原始二进制特征子集的二进制嵌入。具体来说,(a) 具有特征 f1f2(b) 仅具有特征 f1