当前位置: 首页 > news >正文

图机器学习(1)——图论基础

图机器学习(1)——图论基础

    • 0. 前言
    • 1. 使用 networkx 初识图结构
      • 1.1 图论基础
      • 1.2 图的基础属性
    • 2. 图的类型
      • 2.1 有向图
      • 2.2 多重图
      • 2.3 加权图
      • 2.4 二部图
    • 3. 图的表示
      • 3.1 邻接矩阵
      • 3.2 边列表
    • 小结

0. 前言

图 (Graph) 是一种描述实体间关系的数学结构,几乎在各个领域都有应用。例如,社交网络就是图,其中用户之间的连接取决于一个用户是否“关注”另一个用户;图也可以用于表示地图,其中城市通过道路相连接;生物结构、网页链接乃至神经疾病的演变过程均可由图来描述。
图论,即对图的研究,一直以来广受关注,包含了大量算法、性质分析方法和数学模型,以解析复杂系统的行为特征。
本节将介绍图数据结构的核心概念,通过理论阐述与实例演示相结合的方式,理解基础理论用以实践应用。重点介绍用于创建、操作和研究复杂网络结构与功能的 Python 库。

1. 使用 networkx 初识图结构

本节将介绍图论基础概念,并通过 Python 代码片段(基于 networkx 库)实现理论与实践的结合。

1.1 图论基础

一个简单的无向图(或简称图,Graph) GGG 定义为二元组 G=(V,E)G=(V,E)G=(V,E),其中 V={v1,v2,...,vn}V=\{v_1, v_2, ..., v_n\}V={v1,v2,...,vn} 表示节点( node,或称顶点,vertice )集合, E={(v1,v2),...,(vn,vm)}E=\{(v_1,v_2), ..., (v_n,v_m)\}E={(v1,v2),...,(vn,vm)} 表示边( edge,或称连接,link )集合,每条边由 VVV 中两个节点构成的无序对组成。
需要注意的是,由于E中的元素均为无序二元组,因此每条边之间没有顺序关系。具体来说,{v1,v2}\{v_1,v_2\}{v1,v2}{v2,v1}\{v_2,v_1\}{v2,v1} 表示同一条边。
接下来,我们介绍图和节点的基本属性定义:

  • 图的阶 (order) 是其顶点的数量 ∣V∣|V|V,图的大小是其边的数量 ∣E∣|E|E
  • 顶点的度 (degree) 是与该顶点相邻的边的数量
  • GGG 中一个顶点 vvv 的邻居 (neighbor) 是由所有与 vvv 相邻的顶点构成的子集
  • GGG 中顶点 vvv 的邻域图( neighborhood graph,也称为自我图,ego graph )是一个由与 vvv 相邻的顶点及所有连接这些顶点的边组成的子图

简单的无向图如下所示:

无向图

根据这种表示方法,由于没有方向,从 BeijingBaoding 的边与从 BaodingBeijing 的边是相等的。因此,可以在这两个方向上自由移动。分析上图中所示图结构的性质,可以看到它的阶数和大小都等于 4 (总共有四个顶点和四条边)。BeijingTangshan 顶点的度为 2Baoding 的度为 3Langfang 的度为 1。每个节点的邻居如下所示:

  • Tangshan = {Beijing, Baoding}
  • Baoding = {Beijing, Langfang, Tangshan}
  • Beijing = {Baoding, Tangshan}
  • Langfang = {Baoding}

networkx 中可通过以下代码实现该图结构:

import networkx as nx
G = nx.Graph() 
V = {'Beijing', 'Langfang', 'Tangshan', 'Baoding'} 
E = [('Beijing','Baoding'), ('Beijing','Tangshan'), ('Baoding','Langfang'), ('Baoding','Tangshan')] 
G.add_nodes_from(V) 
G.add_edges_from(E) 

默认情况下,nx.Graph() 生成无向图,因此无需为每条边指定双向关系。networkx 允许节点为任何可哈希对象,包括字符串、类对象甚至其他 networkx 图对象。接下来,计算已生成图的基础属性。

1.2 图的基础属性

(1) 获取图中所有节点和边:

print(f"V = {G.nodes}") 
print(f"E = {G.edges}") 

输出结果如下所示:

V = ['Tangshan', 'Baoding', 'Beijing', 'Langfang']
E = [('Tangshan', 'Beijing'), ('Tangshan', 'Baoding'), ('Baoding', 'Beijing'), ('Baoding', 'Langfang')]

(2) 计算图的阶数、大小,以及每个节点的度和邻域:

print(f"Graph Order: {G.number_of_nodes()}") 
print(f"Graph Size: {G.number_of_edges()}") 
print(f"Degree for nodes: { {v: G.degree(v) for v in G.nodes} }") 
print(f"Neighbors for nodes: { {v: list(G.neighbors(v)) for v in G.nodes} }") 

输出结果如下所示:

Graph Order: 4
Graph Size: 4
Degree for nodes: {'Tangshan': 2, 'Baoding': 3, 'Beijing': 2, 'Langfang': 1}
Neighbors for nodes: {'Tangshan': ['Beijing', 'Baoding'], 'Baoding': ['Beijing', 'Langfang', 'Tangshan'], 'Beijing': ['Baoding', 'Tangshan'], 'Langfang': ['Baoding']}

(3) 计算特定节点的邻域图 (ego graph):

ego_graph_milan = nx.ego_graph(G, "Baoding") 
print(f"Nodes: {ego_graph_milan.nodes}") 
print(f"Edges: {ego_graph_milan.edges}") 

输出结果如下所示:

Nodes: ['Tangshan', 'Baoding', 'Beijing', 'Langfang']
Edges: [('Tangshan', 'Beijing'), ('Tangshan', 'Baoding'), ('Baoding', 'Beijing'), ('Baoding', 'Langfang')]

(4) 通过添加新节点或边修改原始图:

new_nodes = {'Tianjin', 'Chengde'} 
new_edges = [('Tianjin','Langfang'), ('Chengde','Beijing')] 
G.add_nodes_from(new_nodes) 
G.add_edges_from(new_edges) 
print(f"V = {G.nodes}") 
print(f"E = {G.edges}") 

输出结果如下所示:

V = ['Tangshan', 'Baoding', 'Beijing', 'Langfang', 'Chengde', 'Tianjin']
E = [('Tangshan', 'Beijing'), ('Tangshan', 'Baoding'), ('Baoding', 'Beijing'), ('Baoding', 'Langfang'), ('Beijing', 'Chengde'), ('Langfang', 'Tianjin')]

(5) 删除节点(关联边会自动从边列表中删除):

node_remove = {'Tianjin', 'Chengde'} 
G.remove_nodes_from(node_remove) 
print(f"V = {G.nodes}") 
print(f"E = {G.edges}") 

输出结果如下所示:

V = ['Tangshan', 'Baoding', 'Beijing', 'Langfang']
E = [('Tangshan', 'Beijing'), ('Tangshan', 'Baoding'), ('Baoding', 'Beijing'), ('Baoding', 'Langfang')]

(6) 删除指定边:

node_edges = [('Tangshan','Baoding'), ('Baoding','Beijing')] 
G.remove_edges_from(node_edges) 
print(f"V = {G.nodes}") 
print(f"E = {G.edges}") 

输出结果如下所示:

V = ['Tangshan', 'Baoding', 'Beijing', 'Langfang']
E = [('Tangshan', 'Beijing'), ('Baoding', 'Langfang')]

networkx 库还支持单节点/边删除操作:

  • 删除单个节点:G.remove_node('Langfang')
  • 删除单条边:G.remove_edge('Baoding','Tangshan')

2. 图的类型

在上一小节中,我们介绍了如何创建和修改简单无向图。接下来,我们将介绍如何通过有向图、加权图和多重图来扩展基础数据结构,以封装更多信息。

2.1 有向图

有向图 (Digraphs) GGG 定义为二元组 G=(V,E)G = (V, E)G=(V,E),其中 V={v1,v2,...,vn}V=\{v_1, v_2, ..., v_n\}V={v1,v2,...,vn} 是节点集合, E={(v1,v2),...,(vn,vm)}E=\{(v_1,v_2), ..., (v_n,v_m)\}E={(v1,v2),...,(vn,vm)} 是表示连接 VVV 中两个节点之间关系的有序对的集合。
由于 EEE 中的每个元素(边)都是有序对,具有明确的方向区分。边 (Vw,Vk)(V_w, V_k)(Vw,Vk) 表示节点 VwV_wVw 指向 VkV_kVk 的连接关系,这与 (Vk,Vw)(V_k, V_w)(Vk,Vw) 不同——后者表示的是节点 VkV_kVk 指向 VwV_wVw 的连接。起始节点 VwV_wVw 称为头 (head),结束节点 VkV_kVk 称为尾 (tail)。由于存在边的方向,节点度的定义需要扩展。

入度和出度
对于一个节点 vvv,以 vvv 为尾节点的边数称为入度( indegree,用 deg−(v)deg^-(v)deg(v) 表示),而以 vvv 为头节点的边数称为出度( outdegree,用deg+(v)deg^+(v)deg+(v) 表示)。
如下所示是一个典型的有向图:

有向图

方向通过箭头表示——例如,Baoding-> Beijing 表示从 BaodingBeijingBeijing 的入度 deg−(v)=2deg^-(v)= 2deg(v)=2,出度 deg+(v)=0deg^+(v) = 0deg+(v)=0Tangshan 的入度 deg−(v)=0deg^-(v) = 0deg(v)=0,出度 deg+(v)=2deg^+(v)= 2deg+(v)=2Baoding 的入度 deg−(v)=1deg^-(v)= 1deg(v)=1,出度 deg+(v)=2deg^+(v)= 2deg+(v)=2Langfang 的入度 deg−(v)=1deg^-(v)= 1deg(v)=1,出度 deg+(v)=0deg^+(v)= 0deg+(v)=0
networkx 中可通过以下代码实现该有向图结构:

G = nx.DiGraph() 
V = {'Beijing', 'Tangshan', 'Baoding', 'Langfang'} 
E = [('Baoding','Beijing'), ('Tangshan','Baoding'), ('Tangshan','Beijing'), ('Baoding','Langfang')]
G.add_nodes_from(V) 
G.add_edges_from(E)

与简单无向图类似,唯一的区别在于用于实例化对象的 networkx 类。对于有向图,使用 nx.DiGraph() 类。
计算入度和出度:

print(f"Indegree for nodes: { {v: G.in_degree(v) for v in G.nodes} }") 
print(f"Outdegree for nodes: { {v: G.out_degree(v) for v in G.nodes} }") 

输出结果如下所示:

Indegree for nodes: {'Tangshan': 0, 'Baoding': 1, 'Beijing': 2, 'Langfang': 1}
Outdegree for nodes: {'Tangshan': 2, 'Baoding': 2, 'Beijing': 0, 'Langfang': 0}

对于有向图,同样可以使用 G.add_nodes_from()G.add_edges_from()G.remove_nodes_from()G.remove_edges_from() 函数来修改给定图 GGG

2.2 多重图

多重图 (multigraph) 是图定义的推广,允许同一对节点间存在多条边。多重图 GGG 定义为 G=(V,E)G = (V, E)G=(V,E),其中 VVV 是节点集,EEE 是边的多重集(允许每个元素有多个实例)。
如果 EEE 是有序对的多重集,则该多重图称为有向多重图;否则,如果 EEE 是无序二元组的多重集,则称为无向多重图。
如下所示是一个典型的有向多重图:

有向多重图

使用 networkx 创建有向和无向多重图:

directed_multi_graph = nx.MultiDiGraph() 
undirected_multi_graph = nx.MultiGraph() 
V = {'Baoding', 'Beijing', 'Langfang', 'Tangshan'} 
E = [('Langfang','Baoding'), ('Langfang','Baoding'), ('Beijing','Langfang'), ('Beijing','Baoding'), ('Langfang','Tangshan'), ('Langfang','Tangshan')] 
directed_multi_graph.add_nodes_from(V) 
undirected_multi_graph.add_nodes_from(V) 
directed_multi_graph.add_edges_from(E) 
undirected_multi_graph.add_edges_from(E) 

有向多重图和无向多重图之间的唯一区别在于实例化对象的 networkx 类不同:nx.MultiDiGraph() 用于创建有向多重图,而 nx.MultiGraph() 用于构建无向多重图。用于添加节点和边的函数对于这两个对象是相同的。

2.3 加权图

接下来我们介绍有向、无向和多重加权图。
边加权图 (edge-weighted graph,或简称为加权图) GGG 定义为 G=(V,E,w)G = (V, E, w)G=(V,E,w),其中 VVV 是节点集,EEE 是边集,w:E→Rw:E\rightarrow \mathbb Rw:ER 是加权函数,它为每条边 e∈Ee\in EeE 分配一个权重,表示为实数。
节点加权图 (node-weighted graph) GGG 定义为 G=(V,E,w)G = (V, E, w)G=(V,E,w),其中 VVV 是节点集,EEE 是边集,w:V→Rw:V\rightarrow \mathbb Rw:VR 是加权函数,它为每个节点 v∈Vv\in VvV 分配一个权重,表示为实数。
需要注意的是:

  • 如果 EEE 是有序对的集合,则称为有向加权图 (directed weighted graph)
  • 如果 EEE 是无序二元组的集合,则称为无向加权图 (undirected weighted graph)
  • 如果 EEE 是多重集,则称为加权多重图( weighted multigraph,或无向加权多重图)
  • 如果 EEE 是有序对的多重集,则称为有向加权多重图 (directed weighted multigraph)

如下所示是一个典型的有向加权图:

有向加权图

可以看到,图中加权边的存在有助于为数据结构添加有用的信息。我们可以将边的权重视为从一个节点到另一个节点的“成本”。例如,从 BaodingBeijing 的“成本”是 12,而从 TangshanBeijing 的“成本”是 4
使用 networkx 创建有向加权图:

G = nx.DiGraph() 
V = {'Beijing', 'Baoding', 'Langfang', 'Tangshan'} 
E = [('Baoding','Beijing', 12), ('Baoding','Langfang', 9), ('Tangshan','Beijing', 4), ('Tangshan','Baoding', 20)] 
G.add_nodes_from(V) 
G.add_weighted_edges_from(E) 

2.4 二部图

接下来,介绍另一种类型的图:多部图 (multipartite graph)。二部图、三部图以及更一般的 k 部图,是图的节点可以分为两个、三个或 k 个互斥节点集的图。边只允许在不同集合的节点之间连接,而不允许在属于同一集合的节点之间连接。在大多数情况下,属于不同集合的节点也具有特定的类型属性。例如,推荐系统中的用户-商品属于典型的二部图。

(1) 使用 networkx 创建二部图:

import pandas as pd 
import numpy as np 
n_nodes = 10 
n_edges = 12 
bottom_nodes = [ith for ith in range(n_nodes) if ith % 2 ==0] 
top_nodes = [ith for ith in range(n_nodes) if ith % 2 ==1] 
iter_edges = zip( np.random.choice(bottom_nodes, n_edges), np.random.choice(top_nodes, n_edges)) 
edges = pd.DataFrame([{"source": a, "target": b} for a, b in iter_edges]) 
B = nx.Graph() 
B.add_nodes_from(bottom_nodes, bipartite=0) 
B.add_nodes_from(top_nodes, bipartite=1) 
B.add_edges_from([tuple(x) for x in edges.values]) 

(2) 可以使用 networkxbipartite_layout 函数方便地绘制二部图:

from networkx.drawing.layout import bipartite_layout 
pos = bipartite_layout(B, bottom_nodes) 
nx.draw_networkx(B, pos=pos) 

输出结果如下所示:

二部图

3. 图的表示

如前所述,使用 networkx 时我们可以直接通过节点和边对象来定义和操作图结构。但在应用场景中,这种表示方式可能不够高效。在本节中,我们将介绍两种图数据结构的紧凑表示方法:邻接矩阵和边列表。

3.1 邻接矩阵

对于图 G=(V,E)G=(V,E)G=(V,E),其邻接矩阵 (adjacency matrix) MMM 是一个 ∣V∣×∣V∣|V|×|V|V×V 的方阵,其中当存在从节点 iii 指向节点 jjj 的边时,元素 MijM_{ij}Mij1;当不存在对应边时,元素 MijM_{ij}Mij0。以下展示了不同类型图结构对应的邻接矩阵示例:

邻接矩阵

可以看到,无向图的邻接矩阵是对称矩阵,因为边没有定义方向。相反,由于边的方向存在约束,有向图的邻接矩阵通常并不对称。对于多重图,矩阵中的值可以大于 1,因为多个边可以用来连接相同的一对节点。对于加权图,矩阵中元素的值等于对应边的权重值。
networkx 中,给定图的邻接矩阵可以通过以下方式计算:

nx.to_pandas_adjacency(G)

输出结果如下所示:

			Baoding	Beijing	Langfang	Tangshan
Baoding		0.0		12.0	9.0			0.0
Beijing		0.0		0.0		0.0			0.0
Langfang	0.0		0.0		0.0			0.0
Tangshan	20.0	4.0		0.0			0.0

3.2 边列表

与邻接矩阵一样,边列表是另一种表示图的紧凑方式,其核心思想是将图表示为边的列表。
G=(V,E)G=(V,E)G=(V,E) 的边列表 LLL 是一个大小为 ∣E∣|E|E 的矩阵,其元素 LiL_iLi 是一个表示边 iii 的起始节点和终止节点的元组。以下展示了不同类型图的边列表示例:

边列表

使用 networkx 中计算简单无向图 GGG 的边列表:

print(nx.to_pandas_edgelist(G)) 

输出结果如下所示:

     source    target  weight
0   Baoding   Beijing      12
1   Baoding  Langfang       9
2  Tangshan   Beijing       4
3  Tangshan   Baoding      20

除上述表示方法外,networkx 还支持其他图结构表示方式,如 nx.to_dict_of_dicts(G)nx.to_numpy_array(G) 等。

小结

本文介绍了图数据结构及其在 Python 中的实现方法,主要围绕 networkx 库展开。首先阐述了图论的基本概念,包括图的定义(节点集合 VVV 和边集合 EEE 构成的二元组 G=(V,E)G=(V,E)G=(V,E) )及其基础属性(阶、度、邻居、邻域图等),并通过城市连接示例演示了 networkx 的代码实现(如 nx.Graph() 的创建、节点/边的增删操作)。
其次,详细探讨了图的多种变体:

  • 有向图 (nx.DiGraph) 通过有序边区分方向,引入入度/出度概念
  • 多重图 (nx.MultiGraph/nx.MultiDiGraph) 支持节点间多边连接
  • 加权图通过 add_weighted_edges_from 为边赋予权重,扩展了成本建模能力
  • 二部图以互斥节点集和跨集合边为特征,适用于推荐系统等场景

最后,对比了图的两种高效表示方法:

  • 邻接矩阵 (to_pandas_adjacency) 以矩阵形式反映节点连接关系
  • 边列表 (to_pandas_edgelist) 直接存储边信息,适合稀疏图
http://www.dtcms.com/a/278371.html

相关文章:

  • android tabLayout 切换fragment fragment生命周期
  • 50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | GithubProfies(GitHub 个人资料)
  • 如何改变音乐的音质kbps和采样率hz
  • HTML面试题
  • [spring6: Resource ResourceLoader]-加载资源
  • (三)OpenCV——图像形态学
  • 【算法深练】BFS:“由近及远”的遍历艺术,广度优先算法题型全解析
  • ubuntu透网方案
  • 多客户端-服务器(select,poll)
  • 使用 keytool 在服务器上导入证书操作指南(SSL 证书验证错误处理)
  • Linux的相关学习
  • 20250714-day15
  • imx6ull-系统移植篇4——U-Boot 工程目录分析
  • ubuntu之坑(十八)——XML相关
  • 【机器学习深度学习】Ollama vs vLLM vs LMDeploy:三大本地部署框架深度对比解析
  • MIPI DSI (一) MIPI DSI 联盟概述
  • 智能Agent场景实战指南 Day 12:医疗咨询Agent设计模式
  • Python与MongoDB深度整合:异步操作与GridFS实战指南
  • Apache-web服务器环境搭建
  • 前端开发中的常见问题及解决方案
  • 前端基础之《Vue(22)—安装MongoDB》
  • Python+MongoDB高效开发组合
  • mongodb原理及其实现
  • MongoDB从入门到精通
  • HarmonyOS从入门到精通:动画设计与实现之九 - 实用动画案例详解(下)
  • 第十八篇 数据清洗:Python智能筛选与统计:从海量Excel数据中秒级挖掘,辅助决策!你的数据分析利器!
  • ResizeObserver 深入全面讲解
  • C++类与对象(上)
  • 迅为八核高算力RK3576开发板摄像头实时推理测试 ppyoloe目标检测
  • 视频动态范围技术演进:从SDR到HDR的影像革命