本章内容包括:
- 从非图数据创建图数据模型
- 从原始数据源进行提取、转换、加载和预处理
- 使用 PyTorch Geometric 创建数据集和数据加载器
在本章中,我们将描述与图数据相关的实际操作,以及如何将非图数据转换为图格式。我们将解释从原始数据到预处理格式的转换过程中需要考虑的一些因素。这包括将表格数据或其他非图数据转换为图形,并对其进行预处理,以便用于基于图的机器学习包。在我们的思维模型中,如图 8.1 所示,我们处于图的左半部分。
我们将按以下方式进行。 在第 8.1 节中,我们介绍了一个可能需要图神经网络(GNN)的示例问题,并讨论了如何着手解决该项目。第 8.2 节详细讲解了如何在图模型中使用非图数据。然后,我们在第 8.3 节中将这些思想付诸实践,通过将数据集从原始文件转换为预处理数据,准备好进行训练。最后,第 8.4 节提供了寻找更多图数据集的思路。
在本章中,我们将考虑如何将 GNN 应用于招聘公司创建的社交图。在我们的示例中,节点是求职者,边表示求职者之间的关系。我们从原始数据生成图数据,形式为边列表和邻接列表。然后,我们将这些数据用于图处理框架(NetworkX)和 GNN 库(PyTorch Geometric [PyG])。这些数据中的节点包括求职者的 ID、职位类型(会计、工程师等)和行业(银行、零售、技术等)。
我们将求职者的目标框架化为基于图的挑战,详细说明将其数据转换为图学习格式的步骤。我们的目标是绘制数据工作流的蓝图,从原始数据开始,将其转换为图格式,然后为后续章节中使用的 GNN 训练做好准备。
注意:本章的代码可以在 GitHub 仓库中的笔记本形式找到(mng.bz/Xxn1)。Colab 链接和本章的数据可以在相同位置访问。
8.1 数据准备与项目规划
考虑一个假设的招聘公司,名为 Whole Staffing。Whole Staffing 为多个行业猎头,并维护着一个候选人档案数据库,包括候选人与公司以及其他候选人之间的互动历史。有些候选人是通过其他候选人推荐给公司的。
8.1.1 项目定义
Whole Staffing 希望最大化其数据库的价值。他们有一些关于招聘候选人数据集的初步问题:
- 一些档案有缺失数据。是否可以在不打扰候选人的情况下填补这些缺失数据?
- 历史数据表明,有过类似项目经验的候选人可以在未来的工作中合作愉快。是否可以找出哪些候选人能够顺利合作?
Whole Staffing 已经委托你分析数据并回答这些问题。在其他分析和机器学习方法中,你认为将数据表示为图形,并利用 GNN 来回答客户的问题,可能是一个很好的机会。你的想法是将推荐集合转化为一个社交网络,其中候选人是节点,候选人之间的推荐是边。为了简化,你可以忽略推荐的方向,使得图是无向的。你还忽略重复的推荐,以确保候选人之间的关系保持无权重。
我们将逐步走过准备数据并建立一个管道以将数据传递给 GNN 模型所需的步骤。首先,让我们考虑项目规划阶段。
8.1.2 项目目标与范围
对于任何问题,明确的目标、要求和范围将作为指南针,引导所有后续的行动和决策。从规划、架构创建到工具选择,每个方面都应遵循核心目标和范围。让我们考虑一下这些目标和范围对于我们的问题的意义。
项目目标
Whole Staffing 希望优化其候选人数据库的使用。首先,项目应该通过填补候选人档案中的缺失信息来提高数据质量,从而减少与候选人的直接互动需求。其次,未来的工作应该促进信息化的候选人建议,利用候选人的历史成功记录预测哪些团队能顺利合作。
项目要求与范围
几个关键要求将直接影响你的项目。让我们逐一讨论,并指出这些要求对客户所在行业的重要性。然后,我们将对当前项目得出一些结论。要求包括以下几点:
-
数据大小与速度:数据的大小是多少(项数、字节大小或节点数)?新信息添加的速度如何?数据是预计从实时流中上传,还是来自每天更新的数据湖?
- 计划中的图可能会随着数据量的增长而增长,这会影响计算资源需求和算法效率。准确评估数据的大小和速度,确保系统能够处理预期的负载,提供实时的见解,并具备可扩展性以适应未来增长。
-
推理速度:应用程序和基础机器学习模型的响应速度要求是多少?一些应用可能要求亚秒级响应,而其他应用可能没有时间约束。
- 响应时间在提供及时的建议和见解时尤其重要。对于招聘公司来说,匹配候选人和合适职位是时间敏感的,机会可能很快消失。
-
数据隐私:关于个人可识别信息(PII)的政策和规定是什么?这将如何影响数据转换和预处理?
- 处理敏感信息(如候选人档案、联系信息和工作历史)时,数据隐私成为一个重要问题。在图和 GNN 设置中,确保节点和边不泄露个人身份信息至关重要。必须遵守如《通用数据保护条例》(GDPR)或《加利福尼亚消费者隐私法案》(CCPA)等法规,以避免法律问题。图数据应以尊重隐私规范的方式进行处理、存储和处理。可能需要匿名化和加密技术来保护个人隐私,同时仍允许有效的数据分析。早期了解这些要求可以确保系统架构和数据处理管道设计时考虑到隐私保护。
-
可解释性:应对响应结果的可解释性要求有多高?是否仅提供直接答案就足够,还是应该提供其他数据来解释为什么做出某个建议或预测?
- 在招聘行业中,可解释性和透明度至关重要。它们通过确保人才选拔过程的公平性和清晰性,建立候选人和雇主之间的信任。道德标准得到了维护,不应产生无意的偏见。这些因素不仅是伦理要求,通常也是法律义务。
根据目标和范围,Whole Staffing 的交付物可能是一个系统,能够:
- 每两周扫描候选人数据,查找缺失项。可以推测或建议填补缺失项。
- 通过使用链接预测和/或节点分类来预测哪些候选人能顺利合作。与第一个交付物不同,响应时间应当较快。
以下是一些针对上述要求的规格:
- 数据大小:保守估计,足以容纳 100,000 名候选人及其属性,估计为 1 GB 数据。
- 推理速度:应用程序每两周运行一次,可以在一夜之间完成,因此我们没有显著的速度限制。
- 数据隐私:不能使用直接识别候选人的个人数据。然而,招聘公司已知的数据,如员工是否已成功被安排在同一雇主那里,可以用来改善公司的运营,前提是这些数据不会被分享。
- 可解释性:结果必须具有一定的可解释性。
目标和要求将指导系统设计、数据模型和 GNN 架构的决策。以上给出了开始或定义基于图的项目时所需考虑的类型。
8.2 设计图模型
在确定了适当的工作范围后,下一步是构建图模型。对于大多数机器学习问题,数据将以标准方式进行组织。例如,在处理表格数据时,行被视为观测值,列被视为特征。我们可以通过使用索引和键来连接这些表格数据。这个框架是灵活的,且相对明确。我们可能会对包含哪些观测值和特征存在争议,但我们知道如何将它们放置。
当我们想要用图来表示数据时,在除最简单的情况外,我们会有多种选择来决定使用什么样的结构。使用图时,如何放置感兴趣的实体并不总是直观的。正是这种模糊性驱动了使用图数据时需要系统方法的需求,但如果能够早期正确处理这一点,将为后续的机器学习任务奠定基础[1]。
在本节中,我们将开始将 Whole Staffing 的招聘数据转换为基于图的数据,以支持我们的后续管道。我们从考虑领域和用例开始,这是理解数据的关键步骤。接下来,我们创建并完善一个架构,这是组织和解释复杂数据集的关键。通过对架构的严格测试,我们可以确保其稳健性和可靠性。任何必要的改进都应当进行,以优化性能和准确性。这种方法确保了我们未来的分析系统能够以精准和可靠的方式处理基于图的数据,并解答关于候选人的复杂查询。以下是需要遵循的过程,图 8.2 提供了可视化展示:
- 理解数据和用例。
- 创建数据模型、架构和实例模型。
- 使用架构和实例模型测试你的模型。
- 如有必要,进行重构。
8.2.1 熟悉领域和用例
与大多数数据项目一样,为了有效进行工作,我们必须理解数据集及其背景。为了实现我们创建模型的目标,理解我们原始格式的推荐数据,并深入挖掘招聘行业的细节,可以提供关键的洞察。这些知识还为我们在部署期间设计模型的测试提供了基础。例如,对原始数据的初步分析为我们提供了表格 8.1 中的信息。
表 8.1 数据集特征
| 候选人数量 | 1,933 |
|---|---|
| 推荐数量 | 12,239 |
从原始数据来看,显然存在许多关系,这为候选人推荐提供了潜在的洞察。与候选人数量相比,推荐数量庞大,暗示了一个互联的网络。我们的模型需要足够大,以便将这种结构转化为招聘问题空间中的结果。
转向领域知识,除了客户的直接需求外,我们还应该提出一些问题,以巩固我们对行业的理解。在设置数据模型的要求时,我们应考虑行业中的关键问题和挑战。对于招聘问题,我们可能会问,如何优化推荐过程,或者是什么样的基本结构和模式支配候选人的推荐。通过解决这些问题,我们可以使模型与领域专业知识对齐,从而提高其相关性和有效性。
8.2.2 构建图数据集和架构
接下来,我们将讨论如何设计我们的数据库。图数据集一词指的是使用图的元素和结构来描述数据的通用努力:节点、边、节点特征和边特征。为了实现这一点,我们需要一个架构和实例。这些明确地指定了我们图的结构和规则,并允许我们测试和完善图数据集。本节内容来自多篇参考文献,详细信息可在本书末尾查阅。
通过提前解决图数据集的细节,我们可以避免技术债务,并更容易地测试数据的完整性。我们还可以更系统地实验不同的数据结构。此外,当图的结构和规则被明确设计时,它增加了我们能够轻松地对这些规则进行参数化,并在 GNN 管道中对其进行实验的能力。
图数据集可以很简单,由一种类型的节点和一种类型的边组成。或者它们也可以很复杂,涉及多种类型的节点和边、元数据,并且在知识图谱的情况下,还包括本体论。
关键术语
以下是本节中使用的关键术语(有关图数据模型和图类型的更多详细信息,请参见附录 A):
- 双图(或二部图) :一种图,其中有两个节点集。不同节点集之间没有边。
- 实体-关系图(ER 图) :显示图的实体、关系和约束的图示。
- 图数据集:节点、边及其关系的表示。
- 异构/同质图:同质图只有一种类型的节点或边。异构图可以有多种类型的节点或边。
- 实例模型:基于架构的模型,保存实际数据的子集。
- 本体论:描述特定知识领域中的概念和关系的方式,例如,在语义网中描述不同实体(作家)之间的连接。本体论是定义这些作家及其文学作品的角色、属性和相互关系的结构框架。
- 属性图:使用元数据(标签、标识符、属性/特性)来定义图元素的模型。
- 资源描述框架图(RDF 图,简称三元组存储) :遵循主语-谓词-宾语模式的模型,其中节点是主语和宾语,边是谓词。
- 架构:定义图元素如何组织以及这些元素将使用哪些特定规则和约束的蓝图。
- 概念架构:与任何特定数据库或处理系统无关的架构。
- 系统架构:针对特定图数据库或处理系统设计的架构。
- 技术债务:优先快速交付而非高质量代码的后果,后者需要重构。
图数据集非常适合提供图的概念描述,便于他人快速理解。例如,对于理解属性图或 RDF 图的人来说,告诉他们图是基于属性图实现的二部图,可以揭示关于数据设计的很多信息(属性图和 RDF 图的解释请见附录 A)。
架构是定义数据如何在数据存储系统中组织的蓝图,如数据库。图架构是图数据集的具体实现,详细解释了特定用例中数据如何在真实系统中表示。架构可以包括图示和书面文档。架构可以通过查询语言在图数据库中实现,或者通过编程语言在处理系统中实现。架构应当回答以下问题:
- 元素(节点、边、属性)是什么?它们代表了哪些现实世界中的实体和关系?
- 图是否包含多种类型的节点和边?
- 表示为节点的约束是什么?
- 关系的约束是什么?某些节点是否有关于邻接和关联的限制?某些关系是否有计数限制?
- 描述符和元数据如何处理?这些数据的约束是什么?
根据数据的复杂性和使用的系统,可能会使用多个但一致的架构。概念架构列出了图的元素、规则和约束,但与任何特定系统无关。系统架构反映了概念架构的规则,但仅针对特定的系统,如选择的数据库。系统架构还可以省略概念架构中不需要的元素。以下是创建架构的步骤:
- 确定主要实体和关系。例如,在我们的社交网络示例中,实体可以是候选人、招聘人员、推荐、招聘事件和关系。
- 定义节点和边标签。这些标签作为实体类型及其相互关系的标识符。
- 指定属性和约束。每个节点和边标签都与特定的属性和约束相关联,分别用于存储和限制信息。
- 定义索引(可选,用于面向数据库的架构)。基于属性或它们的组合的索引,提高图数据查询的速度。
- 将图架构应用到数据库(可选,用于面向数据库的架构)。根据特定图数据库的要求使用命令或代码创建图架构,并指定其静态或动态性质。
根据图数据集的复杂性和用例,可能会需要一个或多个架构。如果有多个架构,必须包括通过映射确保架构之间的兼容性。
对于元素较少的数据集,简单的图示和书面说明即可传达足够的信息,以便其他开发人员能够使用查询语言或代码实现。对于更复杂的网络设计,ER 图和相关的语法在以可视化和人类可读的方式展示网络架构时非常有用。
实体-关系图(ER 图)
ER 图包含了图的节点、边和属性,以及 governing 图的规则和约束的元素[2, 3]。以下图示(左侧)展示了一些可以用来说明边和关系约束的连接符号。图示(右侧)展示了一个架构图,传达了我们招聘示例中可能表示的两种节点类型(招聘人员和候选人),以及两种边类型(认识和招聘/被招聘)。该图示传达了隐性和显性约束。
一些显性约束包括:一个员工可以推荐许多其他员工,一个被推荐人可以被许多员工推荐。另一个显性约束是,一个人只能被一家企业全职雇佣,但一家企业可能拥有多个员工。一个隐性约束是,对于这个图模型,企业与推荐人之间不能存在关系。
回到我们的示例,为了为我们的示例数据集设计概念和系统架构,我们应该考虑以下几个方面:
- 数据中的实体和关系
- 可能的规则和约束
- 操作性约束,例如可用的数据库和库
- 我们希望从应用程序中得到的输出
我们的数据将包括候选人及其个人资料数据(例如,行业、职位类型、公司等),以及招聘人员。属性也可以视为实体;例如,医疗行业可以作为一个节点来处理。关系可以是候选人认识候选人、候选人推荐候选人,或招聘人员招聘候选人。如前所述,图数据在如何表示实体方面具有极大的灵活性。
考虑到这些选择,我们展示了几个概念架构的选项。选项 A 如图 8.3 所示。
如您所见,示例 A 包含一个节点类型(候选人),通过一个无向边类型(认识)连接。节点属性包括候选人的行业和职位类型。关系没有限制,因为任何候选人可以认识 0 到 n-1 个其他候选人,其中 n 是候选人的数量。第二个概念架构如图 8.4 所示。
示例 B 包含两种节点类型(候选人和招聘人员),通过一种无向边类型(认识)连接。候选人之间的边没有限制。候选人与招聘人员之间的边有一个约束:候选人只能与一个招聘人员建立联系,而招聘人员可以与多个候选人建立联系。
第三个架构如图 8.5 所示。它具有多种节点和关系类型。在示例 C 中,节点类型包括候选人、招聘人员和行业。关系类型包括候选人认识候选人、招聘人员招聘候选人、候选人是行业成员。请注意,我们将行业作为一个独立的实体,而不是候选人的一个属性。这种类型的图被称为异构图,因为它们包含多种不同类型的节点和边。从某种程度上,我们可以将这些看作是叠加在一起的多个图。当我们只有一种类型的节点和边时,这种图被称为同质图。
示例 C 的一些约束包括:
- 候选人只能有一个招聘人员和一个行业。
- 招聘人员不与行业建立联系。
根据查询和机器学习模型的目标,我们可以选择一个架构,或者在开发应用程序的过程中尝试所有三个架构。我们暂时选用第一个架构,它可以作为我们探索和实验的简单结构。
8.2.3 创建实例模型
实例模型通过提供根据架构创建的具体数据示例,来与图数据集的抽象性质形成对比。这样的示例有助于验证和测试架构。以下是创建实例模型的步骤:
- 识别架构。首先,确定你的实例将基于的通用模型或架构。确保类定义、属性和方法已经明确。
- 选择数据子集。选择一个具体的数据子集进行表示,遵循已建立的图架构。
- 创建节点。为数据子集中的每个实体创建节点,确保每个节点都有标签、唯一标识符和相关属性。
- 创建边。为每个关系创建连接,分配标签和属性,并指定边的方向和多重性。
- 遵循架构的规则和约束。在构建实例模型时,确保遵循架构的规则和约束。
- 可视化。使用可视化工具以图形方式表示实例模型。
- 实例化。通过图数据库或图处理系统实现实例模型,这将允许通过查询对其进行测试和验证。
图 8.6 展示了从前面讨论的架构派生的实例模型示例。节点和边的特征已经被候选人的真实数据填充,而不是占位符。
8.2.4 测试与重构
技术债务可能在我们需要更改和发展数据或代码时出现,但在我们的模型中尚未计划好向后或向前兼容性时也可能发生。当我们的建模选择与数据库和软件选择不匹配时,也可能会出现技术债务,这可能需要昂贵(无论是在时间还是金钱上的)解决方法或替代方案。
对数据和模型有明确的规则和约束为我们提供了测试管道的明确方式。例如,如果我们知道节点最多只能有两个度数,我们可以设计简单的函数或查询来处理并测试每个节点是否符合这一标准。
测试和重构是一个迭代过程,并且在扩展优化的图架构和实例模型时至关重要[4, 5]。这将涉及执行查询、分析结果、进行必要的调整并根据度量标准进行验证。在 Whole Staffing 的招聘数据的背景下,这一实践将确保模型能够捕捉到现实世界中的关系,并且能够处理强大的新数据流。以下是一些测试和重构的示例:
- 将实例模型应用到系统中。将模型存储在你选择的图数据库或处理系统中。
- 创建测试并运行查询。根据具体要求,草拟查询来测试模型的完整性。使用 Cypher 或 SPARQL 等查询语言在图数据库上执行查询。编程语言(例如 Python)也可以用来查询图处理系统(如 NetworkX)中的图。
对于我们示例的简单架构,以下是一些可能的测试:
- 节点属性验证:每个节点应进行检查,确认其具备所需的属性,特别是候选人的行业和职位类型,并确保这些属性具有非空值。
- 边类型验证:所有候选人之间的连接应验证确认它们是“认识”类型,确保关系标签的一致性。
- 关系验证:检查存在的平均关系数量,以确保它与推荐的平均数量一致。
- 唯一 ID:每个候选人节点应检查其唯一标识符,以防数据重复,并确保数据完整性。
- 属性数据类型:验证行业和职位类型属性的数据类型,确保所有候选人节点的数据类型一致。
- 网络结构:应验证网络结构,确保它是无向的,确认候选人节点之间“认识”关系的双向特性。
- 边界情况:确定边界情况并查询这些情况。在我们的示例中,未连接的节点可能会带来问题。使用查询了解未连接节点的范围及其对分析的影响,将推动重构的决策。另一个边界情况可能是候选人形成环路的孤立小组。确保数据模型和分析工具能够处理这种复杂或不寻常的数据模式,并仍然生成有效的结果非常重要。
- 验证并评估性能:根据测试结果,确定模型和用例是否存在逻辑问题,或者数据和属性是否存在问题。
- 重构:根据需要调整标签、属性、关系或约束,以最小化错误。
- 重复:迭代前述步骤,根据评估结果细化模型,并确保与项目需求和约束的一致性。
- 最终评估:根据标准和最佳实践评估最终模型,以确保其能够支持复杂查询和机器学习应用的准备。
通过这种测试和重构的迭代过程,我们可以为 Whole Staffing 的招聘数据和用例精炼数据集。对细节的关注确保模型能够支持对招聘数据中隐藏的复杂、细微关系的评估。
随着我们进入下一节,焦点将转向这些概念的一些实际实施。我们将讨论如何在 PyG 中创建数据管道,展示如何将数据从初始原始形式转换为预处理状态,以便输入到其他下游模型训练和测试流程中。
8.3 数据管道示例
在架构确定后,让我们通过一个数据管道的示例进行讲解。在本节中,我们假设我们的目标是创建一个简单的数据工作流,将数据从原始状态处理到最终的预处理数据集,这些数据可以传递给 GNN。以下是这些步骤的总结,如图 8.7 所示。
需要注意的是,虽然所示的整体步骤在不同问题之间可能是一致的,但每个步骤的实现细节可能会根据问题、数据以及选择的数据存储、处理和模型训练选项而有所不同。
关键术语
以下是本节中使用的关键术语(有关图数据模型和图类型的更多详细信息,请参见附录 A):
- 邻接列表:图数据的基本表示形式。在这种格式中,每个条目包含一个节点及其相邻节点的列表。
- 邻接矩阵:图数据的基本表示形式。在矩阵中,每一行和每一列对应一个节点。行和列交叉的单元格表示节点之间存在边。具有非零值的单元格表示节点之间有边,而零值表示没有连接。
- 度数:节点的度数是其相邻节点的数量。
- 边列表:图的基本表示形式。它是图中所有边的数组;数组中的每一项包含一对唯一的连接节点。
- 掩码:一个布尔数组(在 PyTorch 的情况下是张量),用于选择数据的特定子集。掩码通常用于将数据集拆分为不同的部分,如训练集、验证集和测试集。
- 排名:在我们的语境中,排名指的是每个节点的度数在排序列表中的位置。因此,度数最高的节点排名第 1,第二高的排名第 2,依此类推。
- 原始数据:数据以最未处理的形式存在。
- 序列化:将数据转换为易于存储或导出的格式。
- 子图:子图是一个较大图的节点和边的子集。
8.3.1 原始数据
原始数据指的是数据处于最未处理的状态;这些数据是我们管道的起点。数据可以存储在各种数据库中、以某种方式序列化,或者是生成的。
在应用程序的开发阶段,了解原始数据与生产中使用的实时数据之间的匹配程度非常重要。一个方法是从数据档案中抽样。
如第 8.1 节所提到的,我们的示例问题至少有两个数据源:包含推荐日志和候选人档案的关系数据库表。为了保持示例的简洁性,我们假设有一位工程师已经查询了日志数据并将其转化为 JSON 格式,其中键是推荐的候选人,而值是被推荐的候选人。根据我们的档案数据,我们有另外两个字段:行业和职位类型。对于这两个数据源,我们的工程师使用哈希保护了个人可识别信息(PII),我们可以将哈希视为候选人的唯一标识符。在本节中,我们将使用 JSON 数据,其中一个示例片段如图 8.8 所示。数据以两种方式展示:一种是带有哈希,另一种是没有哈希。
数据编码与序列化
在构建数据管道时,一个关键的考虑因素是选择何种数据格式来导入和导出数据,从一个系统到另一个系统。为了将图数据转移到另一个系统或通过互联网发送,通常使用编码或序列化。这些术语指的是将数据转换为一种易于传输的形式的过程[6, 7]。在选择编码格式之前,你必须决定以下内容:
- 数据模型——简单模型、属性图或其他?
- 架构——数据中的哪些实体是节点、边和属性?
- 数据结构——数据如何存储:在邻接矩阵、邻接列表还是边列表中?
- 接收系统——接收系统(在我们的案例中是 GNN 库和图处理系统)如何接受数据?偏好哪些编码和数据结构?导入的数据是否会自动识别,还是需要自定义编程来读取数据?
以下是您可能会遇到的一些编码选择:
语言和系统无关的编码格式
这些格式最为流行,因为它们非常灵活,能够在许多系统和语言中使用。然而,数据的安排仍可能因系统而异。因此,一个包含特定标题集的 CSV 文件中的边列表,可能不会在两个不同系统之间以相同的方式被接受或解释。以下是此格式的一些示例:
- JSON:当从 API 读取数据或将数据输入到 JavaScript 应用程序中时,JSON 格式具有优势。图形可视化库 Cytoscape.js 接受 JSON 格式的数据。
- CSV:被许多处理系统和数据库接受。然而,数据的排列和标签要求因系统而异。
- XML:图交换 XML(GEXF)格式显然是一种 XML 格式。
语言特定的编码格式
- Pickle:Python 的格式。一些系统接受 Pickle 编码的文件。尽管如此,除非您的数据管道或工作流广泛由 Python 管理,否则应谨慎使用 Pickle。其他语言特定的编码也应遵循相同的原则。
系统驱动的编码格式
特定的软件、系统和库有自己独特的编码格式。尽管这些格式在不同系统之间的可用性可能有限,但它们的一个优势是架构的一致性。具有自己编码格式的软件和系统包括 Stanford Network Analysis Platform(SNAP)、NetworkX 和 Gephi。
大数据
除了上述语言无关的格式外,还有一些编码格式用于处理更大规模的数据。
Avro:这种编码格式在 Hadoop 工作流中被广泛使用。
基于矩阵的格式:因为图可以表示为矩阵,所以有一些格式基于这种数据结构。对于稀疏图,以下格式在内存节省和计算优势(用于查找和矩阵/向量乘法)方面提供了显著的优势:
- 稀疏列矩阵(.csc 文件类型)
- 稀疏行矩阵(.csr 文件类型)
- 矩阵市场格式(.mtx 文件类型)
8.3.2 ETL 步骤
在选择了架构并确定了数据源之后,ETL(提取、转换、加载)步骤的目的是从数据源中提取原始数据,并生成符合架构并准备好进行预处理或训练的数据。对于我们的数据,这包括编写一系列操作,首先从各种数据库中提取数据,然后根据需要将它们进行合并。
我们需要的数据最终应该是一个特定的格式,便于输入到预处理步骤中。这可以是 JSON 格式或边列表格式。无论是 JSON 示例还是边列表示例,我们的架构都得到了满足;我们将拥有节点(每个人)和边(这些人之间的关系)。
对于我们的招聘示例,我们希望将原始数据转换为图数据结构,并将其编码为 CSV 格式。选择这个格式是因为它方便与 Python 一起处理。然后,这个文件可以加载到我们的图处理系统 NetworkX 中,或者加载到如 PyG 这样的 GNN 包中。接下来的步骤可以总结如下:
- 将原始数据文件转换为图格式,遵循你选择的图数据模型。在我们的案例中,我们将原始数据转换为边列表和邻接列表,然后保存为 CSV 文件。
- 将 CSV 文件加载到 NetworkX 中进行探索性数据分析(EDA)和可视化。
- 加载到 PyG 中并进行预处理。
从原始数据到邻接列表和边列表
从 CSV 和 JSON 文件开始,我们接下来将数据转换为两个关键数据模型:边列表和邻接列表,我们在附录 A 中对其进行了定义。邻接列表和边列表都是用于图的两种基本数据表示。边列表是一种列表,其中每个条目包含一个节点和一个相邻节点的列表。图 8.9 展示了这些表示方式。
首先,使用 json 模块,我们将数据从 JSON 文件加载到 Python 字典中。Python 字典的结构与 JSON 相同,成员的哈希值作为键,它们的关系作为值。
创建邻接列表
接下来,我们从这个字典创建一个邻接列表。这个列表将被存储为文本文件。文件的每一行将包含成员的哈希值,后面跟着该成员的关系哈希值。创建邻接列表的过程如图 8.10 所示。
这个函数将原始数据转换为邻接列表,我们将在我们的招聘示例中应用这个列表。我们的输入包括以下内容:
- 一个候选人推荐的字典,其中键是推荐其他候选人的成员,值是被推荐的人的列表。
- 要附加到文件名的后缀。
我们将得到以下输出:
- 一个编码后的邻接列表,存储在一个 txt 文件中。
- 一个包含找到的节点 ID 的列表。
以下是相应的代码示例。
示例 8.1 从关系字典创建邻接列表
python
复制
def create_adjacency_list(data_dict, suffix=''):
list_of_nodes = []
for source_node in list(data_dict.keys()): #1
if source_node not in list_of_nodes:
list_of_nodes.append(source_node)
for y in data_dict[source_node]: #2
if y not in list_of_nodes: #2
list_of_nodes.append(y) #2
if y not in data_dict.keys(): #2
data_dict[y]=[source_node] #2
Else: #2
if source_node not in data_dict[y]: #2
data_dict[y].append(source_node) #2
else: continue #2
g= open("adjacency_list_{}.txt".format(suffix),"w+") #3
for source_node in list(data_dict.keys()): #4
dt = ' '.join(data_dict[source_node]) #5
print("{} {}".format(source_node, dt)) #6
g.write("{} {} \n".format(source_node, dt)) #7
g.close
return list_of_nodes
- #1 遍历输入数据字典中的每个节点。
- #2 由于这是一个无向图,节点之间的关系必须是对称的;也就是说,键中的每个值必须在其对应的条目中包含该键。例如,在条目 F 中,如果 G 是一个值,那么在条目 G 中,F 必须是一个值。这些代码行检查并修正字典,如果这些条件不存在的话。
- #3 创建一个文本文件来存储邻接列表。
- #4 遍历字典中的每个键。
- #5 从字典值的列表中创建一个字符串,这个值是由空格分隔的成员 ID 字符串。
- #6 可选的打印操作。
- #7 向文本文件写入一行,这一行包含成员哈希值,后面跟着关系哈希值的字符串。
创建边列表
接下来,我们展示创建边列表的过程。与邻接列表一样,我们会转换数据以确保节点对称性。请注意,任何一种格式都可以在这个项目中使用。对于你自己的项目,可能还需要其他格式。图 8.11 展示了这一过程。
与邻接列表函数一样,边列表函数演示了如何将原始数据转换为边列表,并且具有与前一个函数相同的输入。输出包括以下内容:
- 一个
.txt文件中的边列表。 - 找到的节点 ID 列表和生成的边。
根据定义,边列表的每个条目必须是唯一的,因此我们必须确保生成的边列表是正确的。以下是从关系字典创建边列表的代码。
示例 8.2 从关系字典创建边列表
def create_edge_list(data_dict, suffix=''):
edge_list_file = open("edge_list_{}.txt".format(suffix),"w+")
edges = []
nodes_all = []
for source in list(data_dict.keys()):
if source not in list_of_nodes_all:
nodes_all.append(source)
connections = data_dict[source]
for destination in connections: #1
if destination not in nodes_all:
nodes_all.append(destination)
if {source, destination} not in edges: #2
print(f"{source} {destination}")
out_string = f"{source} {destination}\n”
edge_list_file.write(out_string) #3
edges.append({source, destination })
else: continue
edge_list_file.close
return list_of_edges, list_of_nodes_all
- #1 每个成员字典的值是一个关系列表。对于每个键,我们遍历每个值。
- #2 由于这是一个无向图,我们不想创建重复的边。例如,因为 {F,G} 与 {G,F} 是相同的,我们只需要其中之一。这一行检查节点对是否已经存在。我们使用集合对象,因为节点的顺序不重要。
- #3 将该行写入文本文件。此行将包含节点对。
在接下来的章节中,我们将使用邻接列表将图加载到 NetworkX 中。需要注意的是,加载图时使用邻接列表与边列表的区别在于,边列表无法处理单个未连接的节点。事实上,Whole Staffing 中有很多候选人没有推荐任何人,因此没有与他们相关的边。这些节点在边列表表示的数据中将是不可见的。
8.3.3 数据探索与可视化
接下来,我们想将我们的网络数据加载到图处理框架中。我们选择了 NetworkX,但根据任务和语言偏好,也有许多其他选择可供使用。我们选择 NetworkX 是因为我们的图较小,并且我们还希望进行一些轻量级的探索性数据分析(EDA)和可视化。
使用我们新创建的邻接列表,我们可以通过调用 read_edgelist 或 read_adjlist 方法来创建一个 NetworkX 图对象。接下来,我们可以加载行业和职位类型等属性。在本例中,这些属性作为字典加载,其中节点 ID 作为键。
加载完图后,我们可以探索和检查数据,以确保它符合我们的假设。首先,节点和边的数量应该分别与我们的成员数量和在边列表中创建的边的数量匹配,如下所示。
示例 8.3 从关系字典创建边列表
social_graph = nx.read_adjlist('adjacency_list_candidates.txt')
nx.set_node_attributes(social_graph, attribute_dict)
print(social_graph.number_of_nodes(), social_graph.number_of_edges())
>> 1933 12239
我们还想检查图中有多少个连通分量:
len(list((c for c in nx.connected_components(social_graph))))
>>> 219
connected_components 方法生成图的连通分量;图 8.12 展示了一个可视化结果,该结果是使用 NetworkX 生成的。图中有数百个分量,但当我们检查这些数据时,发现有一个包含 1,698 个节点的大分量,其余的则由少于 4 个节点组成。大多数未连接的分量是单节点(从未推荐过任何候选人的候选人)。关于图的分量的更多信息,我们在附录 A 中给出了定义和详细说明。
我们对这个大的连通分量感兴趣,接下来我们将继续处理它。subgraph 方法可以帮助我们隔离出这个大分量。
最后,我们使用 NetworkX 来可视化我们的图。为此,我们将使用一个标准的图分析方法,这个方法也可以在 NetworkX 的文档中找到。
让我们逐步进行(每个步骤的完整代码示例也可以在仓库中找到,标签为“可视化社交图并显示度统计的函数”):
- 创建图对象。生成一个独特的图对象,选择给定图中的最大连通分量。在只有一个连通分量的情况下,这一步可能不必要,但它确保选择了主要的分量。
connected_component = nx.connected_components(social_graph)
Gcc = social_graph.subgraph(sorted(connected), key=len, reverse=True)[0]
2. 确定布局。决定节点和边的可视化位置。选择一个适当的布局算法;例如,Spring Layout 将边建模为弹簧,节点为相互排斥的质量:
pos = nx.spring_layout(Gcc, seed=10396953)
3. 绘制节点和边。使用选定的布局来绘制节点。调整可视化的视觉参数,例如节点大小,以增强图形的清晰度。根据选择的布局绘制边。修改外观设置,例如透明度,以实现所需的视觉效果。
nx.draw_networkx_nodes(Gcc, pos, ax=ax0, node_size=20)
nx.draw_networkx_edges(Gcc, pos, ax=ax0, alpha=0.4)
ax0.set_title("Connected component of Social Graph")
ax0.set_axis_off()
4. 生成并绘制节点度数。使用图对象的 degree 方法创建一个节点的可迭代对象及其相应的度数,并从高到低排序。将排序后的节点度数在图上可视化,以分析分布和不同节点的突出程度。使用 NumPy 的 unique 方法与 return_counts 参数绘制一个直方图,显示节点的度数及其计数,从而提供图结构和复杂性的洞察:
degree_sequence = sorted([d for n, d in social_graph.degree()], reverse=True)
ax1 = fig.add_subplot(axgrid[3:, :2])
ax1.plot(degree_sequence, "b-", marker="o")
ax1.set_title("Degree Rank Plot")
ax1.set_ylabel("Degree")
ax1.set_xlabel("Rank")
ax2 = fig.add_subplot(axgrid[3:, 2:])
ax2.bar(*np.unique(degree_sequence, return_counts=True))
ax2.set_title("Degree histogram")
ax2.set_xlabel("Degree")
ax2.set_ylabel("# of Nodes")
这些图表展示在图 8.13 中。
最后,我们可以使用以下命令可视化我们的图的邻接矩阵,如图 8.14 所示:
plt.imshow(nx.to_numpy_matrix(social_graph), aspect='equal', cmap='twilight')
与数值邻接矩阵一样,对于我们的无向图,这个可视化的邻接矩阵沿对角线具有对称性。所有无向图的邻接矩阵都会是对称的。而对于有向图,虽然有时也会有这种对称性,但并不总是如此。
8.3.4 数据预处理与加载到 PyG
在本书中,数据预处理包括将我们的数据(包括其属性、标签或其他元数据)转换为适合下游机器学习模型的格式。特征工程也可以是这个过程中的一个步骤。在特征工程中,我们通常会使用图算法来计算节点、边或子图的属性。
一个节点特征的例子是中介中心性。如果我们的架构允许,我们可以计算并将这些属性附加到数据的节点实体上。为了执行此操作,我们将 ETL 步骤的输出(例如边列表)导入图处理框架,计算每个节点的中介中心性。计算出这个值后,我们可以使用一个字典存储它,以节点 ID 作为键,之后可以将其作为节点特征使用。
中介中心性
中介中心性是一个关键的节点重要性度量,量化了一个节点位于源节点到目标节点的最短路径中的倾向。在一个有 n 个节点的图中,我们可以确定图中每一对唯一节点之间的最短路径。我们可以通过查找这些最短路径来查看某个特定节点的存在。如果该节点出现在这些路径中的大部分或所有路径中,则它的中介中心性较高,被认为是具有较高影响力的节点。相反,如果该节点在最短路径集中出现的次数很少(或仅出现一次),则它的中介中心性较低,影响力较小。
现在我们已经拥有数据,我们希望将其准备好以便在选择的 GNN 框架中使用。在本书中,我们使用 PyG,因为它具有强大的工具套件,并且在处理复杂图数据方面具有灵活性。然而,大多数标准 GNN 包都有机制可以将自定义数据导入到它们的框架中。本节我们将专注于 PyG 中的三个模块:
- Data 模块(torch_geometric.data):允许检查、操作和创建 PyG 环境使用的数据对象。
- Utils 模块(torch_geometric.utils):提供许多有用的方法。本节中的方法有助于快速导入和导出图数据。
- Datasets 模块(torch_geometric.datasets):预加载的数据集,包括基准数据集和来自领域影响力论文的数据集。
让我们从 Datasets 模块 开始。这个模块包含已经预处理好的数据集,可以直接通过 PyG 的方法使用。对于初学者来说,使用这些数据集可以轻松进行实验,而不必担心创建数据管道。同样,通过研究这些数据集背后的代码库,我们还可以学习如何创建自己的自定义数据集。
在上一节的末尾,我们将原始数据转换为标准格式,并将我们的新图加载到图处理框架中。现在,我们希望将数据加载到 PyG 环境中。PyG 中的预处理有几个目标:
- 从节点、边到子图和图级别创建具有多个属性的数据对象。
- 将不同的数据源组合成一个对象或一组相关对象。
- 将数据转换为可以使用 GPU 处理的对象。
- 允许数据集的训练/测试/验证拆分。
- 支持数据的批处理以进行训练。
这些目标通过 Data 模块 中的类层次结构来实现:
- Data 类:创建图对象。这些对象可以具有内置和自定义的属性。
- Dataset 和 InMemoryDataset 类:创建可重复的数据预处理管道。你可以从原始数据文件开始,添加自定义过滤器和转换,以实现预处理的数据对象。Dataset 对象大于内存,而 InMemoryDataset 对象可以适合内存。
- Dataloader 类:对数据对象进行批处理以进行模型训练。
如图 8.15 所示,展示了不同的数据和数据集类如何连接到 Dataloader。
有两种路径可以进行数据预处理,一种使用数据集类,另一种则不使用。使用数据集类的优点在于它允许我们保存生成的数据集,并且能够保留过滤和转换的细节。数据集对象非常灵活,可以修改以输出数据集的不同变体。另一方面,如果你的自定义数据集较简单,或者是即时生成的,并且不需要长期保存数据或处理,那么跳过数据集对象可能会更合适。总而言之,我们有以下几种不同的数据相关类:
- Datasets 对象——用于基准测试或测试算法或架构的预处理数据集(不要与 Dataset 对象混淆,后者没有结尾的 "s")。
- Data 对象到迭代器——图对象是即时生成的,或者不需要保存的。
- Dataset 对象——用于应该保留的图对象,包括数据管道、过滤和转换、输入原始数据文件以及输出预处理数据文件。不要与 Datasets 对象(结尾带 "s")混淆。
了解这些基础后,让我们开始预处理我们的社交图数据。我们将覆盖以下几种情况:
- 使用 NetworkX 转换为数据实例。这对于从 NetworkX 到 PyG 的快速转换非常有用,适合临时处理或当使用 NetworkX 功能时。
- 使用输入文件转换为数据实例。提供对数据导入过程的控制,适用于原始数据和自定义预处理需求。
- 转换为数据集实例。对于系统化、可扩展和可重复的数据预处理和管理,特别适合复杂或可重用的数据集。
- 不使用数据集类的情况下,转换数据对象以供 Dataloader 使用。适用于在简单性和速度优先于系统化数据管理和预处理,或者对于即时和合成数据的场景。
首先,我们将在下面的代码示例中导入 PyG 所需的模块。
示例 8.4 所需的导入,涵盖数据对象创建
import torch
from torch_geometric.data import Data
from torch_geometric.data import InMemoryDataset
from torch_geometric import utils
情况 A:使用 NetworkX 对象创建 PyG 数据对象
在前面的章节中,我们已经探讨了一个表示为 NetworkX 图对象的图。PyG 的 utils 模块有一个方法,可以直接从 NetworkX 图对象创建 PyG 数据对象:
data = utils.from_networkx(social_graph)
from_networkx 方法保留了节点、边及其属性,但需要检查确保从一个模块到另一个模块的转换顺利进行。
情况 B:使用原始文件创建 PyG 数据对象
为了更好地控制数据导入 PyG 的过程,我们可以从原始文件或 ETL 过程中的任何阶段开始。在我们的社交图案例中,我们可以从之前创建的边列表文件开始。
现在,让我们回顾一个例子,使用代码将社交图从边列表文本文件处理并转换为适合在 PyG 中训练 GNN 模型的格式。我们为 PyG 环境准备节点特征、标签、边以及训练/测试集。
第 1 部分:导入并准备图数据
这一部分包括从文件读取边列表以创建 NetworkX 图,提取节点列表,创建从节点名称到索引的映射,反之亦然:
social_graph = nx.read_edgelist('edge_list2.txt') #1
list_of_nodes = list(set(list(social_graph))) #2
indices_of_nodes = [list_of_nodes.index(x) for x in list_of_nodes] #3
node_to_index = dict(zip(list_of_nodes, indices_of_nodes)) #4
index_to_node = dict(zip(indices_of_nodes, list_of_nodes))
- #1 从文本文件读取边列表并用它创建一个 NetworkX 图。
- #2 提取图中的所有唯一节点并列出。
- #3 生成每个节点的索引。
- #4 创建两个字典,允许在节点名称和相应索引之间进行轻松转换,从而方便图数据的处理和操作。
第 2 部分:处理边和节点特征
这一部分重点是将边和节点属性转换为可以方便地用于 PyTorch 进行机器学习任务的格式:
list_edges = nx.convert.to_edgelist(social_graph) #1
list_edges = list(list_edges)
named_edge_list_0 = [x[0] for x in list_edges] #2
named_edge_list_1 = [x[1] for x in list_edges]
indexed_edge_list_0 = [node_to_index[x] for x in named_edge_list_0] #3
indexed_edge_list_1 = [node_to_index[x] for x in named_edge_list_1]
x = torch.FloatTensor([[1] for x in range(len(list_of_nodes))]) #4
y = torch.FloatTensor([1]*974 + [0]*973) #5
y = y.long()
- #1 创建一个 NetworkX 边列表对象。
- #2 将其转换为表示每条边的源节点和目标节点的两个单独的列表。
- #3 使用之前创建的节点到索引的映射对这些列表进行索引。
- #4 使用 PyTorch 张量对象准备节点特征和标签,假设一个简单的场景,其中所有节点具有相同的单一特征。
- #5 使用 PyTorch 张量对象准备节点特征和标签,假设一个简单的场景,其中所有节点具有相同的单一特征。
第 3 部分:为训练和测试准备数据
在这一部分中,数据集被准备好进行训练和测试,创建用于数据拆分的掩码,并将所有处理过的数据合并到一个 PyTorch 数据对象中:
edge_index = torch.tensor([indexed_edge_list_0, indexed_edge_list_1]) #1
train_mask = torch.zeros(len(list_of_nodes), dtype=torch.uint8) #2
train_mask[:int(0.8 * len(list_of_nodes))] = 1 #train only on the 80% nodes
test_mask = torch.zeros(len(list_of_nodes), dtype=torch.uint8) #test on 20% nodes
test_mask[- int(0.2 * len(list_of_nodes)):] = 1
train_mask = train_mask.bool()
test_mask = test_mask.bool()
data = Data(x=x, y=y, edge_index=edge_index, train_mask=train_mask, test_mask=test_mask) #3
- #1 将第 2 部分创建的边索引转换为 PyTorch 张量。
- #2 通过将节点分为两组来创建训练和测试数据集的掩码,确保数据的特定部分用于训练和测试。
- #3 将所有处理过的组件(包括节点特征、标签、边索引和数据掩码)合并到一个 PyTorch Data 对象中,为后续的机器学习任务做准备。
我们已经从边列表文件创建了一个数据对象。可以使用 PyG 命令检查此对象,尽管与图处理库相比,命令集有所限制。这样的数据对象还可以进一步准备,以便由 Dataloader 访问,接下来我们将讨论这一点。
情况 C:使用自定义类和输入文件创建 PyG 数据集对象
如果前面的代码适用于我们的目的,并且我们希望重复使用它,那么更好的选择是创建一个永久类,将其包含到我们的数据管道中。这正是数据集类的作用。
接下来,我们将创建一个数据集对象,如示例 8.5 所示。在这个例子中,我们将数据集命名为 MyOwnDataset,并让它继承自 InMemoryDataset,因为我们的社交图足够小,可以放入内存中。如前所述,对于较大的图,可以通过让数据集对象继承自 Dataset 而不是 InMemoryDataset 来从磁盘访问数据。
代码的第一部分初始化了自定义数据集类,继承自 InMemoryDataset 类的属性。构造函数初始化数据集,加载处理过的数据,并定义原始文件和处理后文件的属性。原始文件保持为空,因为这个例子不需要它们,处理后的数据从指定路径获取。
示例 8.5 创建数据集对象的类(第 1 部分)
class MyOwnDataset(InMemoryDataset):
def __init__(self, root, transform=None, pre_transform=None): #1
super(MyOwnDataset, self).__init__(root, transform, pre_transform)
self.data, self.slices = torch.load(self.processed_paths[0])
def raw_file_names(self): #2
return []
@property
def processed_file_names(self): #3
return ['../test.dataset']
- #1 初始化数据集类。该类继承自
InMemoryDataset类。init方法创建data和slices对象,以便在process方法中更新。 - #2 一个可选方法,指定处理所需的原始文件的位置。对于我们这个简单的例子,未使用此方法,但为了完整性将其包含在内。在后续章节中,当我们的数据集变得稍复杂时,我们将使用该方法。
- #3 该方法将我们生成的数据集保存到磁盘。
这部分代码用于数据下载和处理。它从文本文件中读取边列表并将其转换为 NetworkX 图。然后,图的节点和边被索引并转换为适合机器学习任务的张量。download 方法保持为空,作为占位符,以防未来需要下载原始数据。
示例 8.6 创建数据集对象的类(第 2 部分)
def download(self): #1
# Download to `self.raw_dir`.
pass
def process(self): #2
# Read data into `Data` list.
data_list = []
eg = nx.read_edgelist('edge_list2.txt')
list_of_nodes = list(set(list(eg)))
indices_of_nodes = [list_of_nodes.index(x) for x in list_of_nodes]
node_to_index = dict(zip(list_of_nodes, indices_of_nodes))
index_to_node = dict(zip(indices_of_nodes, list_of_nodes))
list_edges = nx.convert.to_edgelist(eg)
list_edges = list(list_edges)
named_edge_list_0 = [x[0] for x in list_edges]
named_edge_list_1 = [x[1] for x in list_edges]
indexed_edge_list_0 = [node_to_index[x] for x in named_edge_list_0]
indexed_edge_list_1 = [node_to_index[x] for x in named_edge_list_1]
- #1 允许将原始数据下载到本地磁盘。
- #2
process方法包含处理步骤,用于创建数据对象,并进行额外步骤将数据拆分为训练和测试集。
代码的最后部分集中在为机器学习模型准备和保存数据。它创建了特征和标签张量,准备了边索引,并生成了训练和测试掩码来拆分数据集。然后,数据被整理并保存到处理路径中,以便在模型训练期间轻松获取。
示例 8.7 创建数据集对象的类(第 3 部分)
x = torch.FloatTensor([[1] for x in range(len(list_of_nodes))]) #
y = torch.FloatTensor([1]*974 + [0]*973)
y = y.long()
edge_index = torch.tensor([indexed_edge_list_0, indexed_edge_list_1])
train_mask = torch.zeros(len(list_of_nodes), dtype=torch.uint8)
train_mask[:int(0.8 * len(list_of_nodes))] = 1 #train only on the 80% nodes
test_mask = torch.zeros(len(list_of_nodes), dtype=torch.uint8) #test on 20% nodes
test_mask[- int(0.2 * len(list_of_nodes)):] = 1
train_mask = train_mask.bool()
test_mask = test_mask.bool()
data_example = Data(x=x, y=y, edge_index=edge_index, train_mask=train_mask, test_mask=test_mask)
data_list.append(data_example) #1
data, slices = self.collate(data_list)
torch.save((data, slices), self.processed_paths[0]) #2
- #1 在这个简单的数据集类应用中,我们使用一个小数据集。实际应用中,我们会处理更大的数据集,且不会一次性处理完所有数据。我们会创建数据的多个示例,然后将它们添加到列表中。对于我们的目的(在这些数据上训练),从列表对象中获取数据会很慢,因此我们将这个可迭代对象使用
collate方法组合成一个数据对象。collate方法还会创建一个名为slices的字典,用于从该数据对象中提取单个样本。 - #2 将预处理后的数据保存到磁盘。
情况 D:创建 PyG 数据对象供 Dataloader 使用,而不使用数据集对象
最后,我们解释如何绕过数据集对象的创建,并让 Dataloader 直接使用你的数据对象,如图 8.15 所示。在 PyG 文档中有一部分介绍了如何做到这一点。
与常规 PyTorch 相似,当你希望即时生成合成数据而不明确保存到磁盘时,你不必使用数据集对象。在这种情况下,只需传递一个普通的 Python 列表,包含 torch_geometric.data.Data 对象,并将其传递给 torch_geometric.data.DataLoader:
from torch_geometric.data import Data, DataLoader
data_list = [Data(...), ..., Data(...)]
loader = DataLoader(data_list, batch_size=32)
在本章中,我们覆盖了从项目概述到将原始数据转换为适合 GNN 使用的格式的步骤。在结束本节时,值得注意的是,每个数据集都是不同的。本讨论中概述的流程提供了一个结构框架,作为起点,而不是一种通用的解决方案。在最后一节中,我们将讨论如何为数据项目提供数据来源。
8.4 从哪里找到图数据
为了避免在为您的问题开发图数据模型和架构时从零开始,有几个已发布的模型和架构来源可供参考。这些来源包括行业标准数据模型、已发布的数据集、已发布的语义模型(包括知识图谱)和学术论文。表 8.2 提供了一些示例来源。
获取图数据
图数据的不同来源,可以用于 GNN 项目的详细信息。
-
来自非图数据——在本章中,我们假设数据来自非图数据源,并且必须使用 ETL 和预处理将其转换为图格式。拥有一个架构可以帮助指导这种转换,并确保它为进一步分析做好准备。
-
现有的图数据集——免费的图数据集数量正在增长。本书中使用的两个 GNN 库,Deep Graph Library(DGL)和 PyG,都带有多个已安装的基准数据集。这些数据集许多来自具有影响力的学术论文。然而,这些数据集的规模较小,限制了结果的可重现性,并且它们的性能未必适用于大规模数据集。
一个旨在缓解此领域早期基准数据集问题的数据来源是 Open Graph Benchmark (OGB) 。该项目提供访问各种规模的真实世界数据集的机会。OGB 还通过学习任务发布性能基准。表 8.2 列出了几个图数据集的仓库。
-
来自生成——许多图处理框架和图数据库允许使用多种算法生成随机图。虽然是随机的,但根据生成算法的不同,生成的图将具有可预测的特征。
表 8.2 图数据集和语义模型
| 来源 | 类型 | 问题领域 | URL |
|---|---|---|---|
| Open Graph Benchmark (OGB) | 图数据集和基准测试 | 社交网络、药物发现 | ogb.stanford.edu/ |
| GraphChallenge Datasets | 图数据集 | 网络科学、生物学 | graphchallenge.mit.edu/data-sets |
| Network Repository | 图数据集 | 网络科学、生物信息学、机器学习、数据挖掘、物理学和社会科学 | networkrepository.com/ |
| SNAP Datasets | 图数据集 | 社交网络、网络科学、道路网络、商业网络、金融 | snap.stanford.edu/data/ |
| Schema.org | 语义数据模型 | 互联网网页 | schema.org/ |
| Wikidata | 语义数据模型 | Wikipedia 页面 | www.wikidata.org/ |
| Financial Industry Business Ontology | 语义数据模型 | 金融 | github.com/edmcouncil/… |
| Bioportal | 医学语义模型列表 | 医学 | bioportal.bioontology.org/ontologies/ |
公共图数据集也存在于多个地方。发布的数据集具有可访问的数据和摘要统计。然而,通常它们缺乏明确的架构、概念或其他内容。为了推导出数据集的实体、关系、规则和约束,需要对数据进行查询。
对于基于属性、RDF 和其他数据模型的语义模型,有一些通用数据集,其他的则针对特定行业和领域。这些参考资料很少使用以图为中心的术语(例如,节点、顶点和边),而是使用与语义和本体相关的术语(例如,实体、关系、链接)。与图数据集不同,语义模型提供的是数据框架,而不是数据本身。
参考论文和已发布的架构可以为开发您的架构提供想法和模板。有一些案例针对行业领域,既使用图来表示一个情况,又使用图算法(包括 GNN)来解决相关问题。例如,金融机构的交易欺诈、化学工程中的分子指纹、社交网络中的网页排名等都是这样的案例。浏览这些现有的工作可以为开发工作提供动力。另一方面,这些已发布的工作通常是为了学术目标,而不是行业目标。为证明学术观点或进行经验观察而开发的网络,可能不具备适用于必须维护并在肮脏和动态数据上使用的企业系统的特性。
总结
-
图学习项目的规划涉及比传统机器学习项目更多的步骤。项目的目标和需求将影响系统设计、数据模型和 GNN 架构的构建。该项目包括创建稳健的图数据模型、理解和转换原始数据,以及确保模型能够有效地表示招聘领域中复杂的关系。
-
一个重要的步骤是为您的数据创建数据模型和架构。这些过程对于避免技术债务至关重要。该过程包括设计元素、关系和约束;执行查询;分析结果;进行调整;并根据标准进行验证,以确保模型可以进行复杂查询和机器学习应用。图数据模型将通过迭代测试和重构来不断完善,以确保它能够有效地支持招聘数据中复杂关系的分析。
-
有许多编码和序列化选项用于将数据保存在内存中或原始文件中,包括语言和系统无关的格式,如 JSON、CSV 和 XML。还提到了语言特定的格式,如 Python 的 Pickle,以及来自特定软件和库(如 SNAP、NetworkX 和 Gephi)的系统驱动格式。对于大数据,Avro 和基于矩阵的格式(稀疏列矩阵、稀疏行矩阵和矩阵市场格式)被突出作为处理大数据集的高效选项。
-
数据管道可以从原始数据开始,通过探索性分析和预处理将其转换为可由 GNN 库(如 PyG)使用的格式。原始数据被转换为标准格式,如边列表或邻接矩阵,确保一致性和可用性,适用于不同的问题。
-
图处理框架,如 NetworkX,用于轻量级的探索性数据分析(EDA)和可视化。图对象,如邻接列表和边列表,被加载到 NetworkX 中。通过可视化表示和统计分析(如节点数、边数和连通分量数)来理解图的结构和复杂性。
-
PyG 库用于预处理,涉及将数据转换为可以轻松操作和训练的格式。数据对象以多种属性在不同层次上创建,支持 GPU 处理并促进训练、测试和验证数据的拆分。是否使用数据集对象或绕过它们的选择取决于是否需要保存数据以及数据集的复杂性。
-
有许多现成的图数据集和语义模型库,涵盖了各种领域,如社交网络和药物发现。然而,尽管这些数据集对于学习和基准测试非常有用,但它们通常规模较小,可能不适用于大型的现实问题。
-
虽然公开的图数据集和语义模型提供了一个起点,但它们通常缺乏明确的架构,需要额外的工作来推导出实体、关系和约束。此外,虽然学术论文提供了开发架构的模板,但它们通常是为学术目的设计的,可能无法直接转移到具有动态和脏数据的现实世界行业应用中。