SciPy CSGraph 怎么用?

文章导读
Previous Quiz Next CSGraph 代表 Compressed Sparse Graph,它专注于基于稀疏矩阵表示的快速图算法。
📋 目录
  1. 图表示
  2. 获取单词列表
A A

SciPy - CSGraph



Previous
Quiz
Next

CSGraph 代表 Compressed Sparse Graph,它专注于基于稀疏矩阵表示的快速图算法。

图表示

首先,让我们了解什么是稀疏图以及它如何帮助图表示。

什么是稀疏图?

图只是节点集合,这些节点之间存在连接。图可以表示几乎任何事物——社交网络连接,其中每个节点是一个人,并连接到熟人;图像,其中每个节点是一个像素,并连接到相邻像素;高维分布中的点,其中每个节点连接到其最近邻;以及你能想象的几乎任何其他事物。

表示图数据的一种非常有效的方法是使用稀疏矩阵:我们称之为 G。矩阵 G 的大小为 N x N,G[i, j] 给出了节点 i 和节点 j 之间连接的值。稀疏图主要包含零——也就是说,大多数节点只有少数连接。这一特性在大多数感兴趣的情况下都成立。

稀疏图子模块的创建受到了 scikit-learn 中使用的几种算法的启发,这些算法包括以下内容——

  • Isomap − 一种流形学习算法,需要在图中找到最短路径。

  • Hierarchical clustering − 基于最小生成树的聚类算法。

  • Spectral Decomposition − 基于稀疏图拉普拉斯算子的投影算法。

作为一个具体的例子,想象我们想表示以下无向图——

Undirected Graph

这个图有三个节点,其中节点 0 和 1 通过权重为 2 的边连接,节点 0 和 2 通过权重为 1 的边连接。我们可以构造密集、掩码和稀疏表示,如以下示例所示,请注意无向图由对称矩阵表示。

G_dense = np.array([ [0, 2, 1],
                     [2, 0, 0],
                     [1, 0, 0] ])
                     
G_masked = np.ma.masked_values(G_dense, 0)
from scipy.sparse import csr_matrix

G_sparse = csr_matrix(G_dense)
print G_sparse.data

上述程序将生成以下输出。

array([2, 1, 2, 1])

Undirected Graph Using Symmetric Matrix

这与前一个图相同,除了节点 0 和 2 通过权重为零的边连接。在这种情况下,上述密集表示会导致歧义——如果零是一个有意义的值,如何表示非边呢。在这种情况下,必须使用掩码或稀疏表示来消除歧义。

让我们考虑以下示例。

from scipy.sparse.csgraph import csgraph_from_dense
G2_data = np.array
([
   [np.inf, 2, 0 ],
   [2, np.inf, np.inf],
   [0, np.inf, np.inf]
])
G2_sparse = csgraph_from_dense(G2_data, null_value=np.inf)
print G2_sparse.data

上述程序将生成以下输出。

array([ 2., 0., 2., 0.])

使用稀疏图的单词梯子

单词梯子是由 Lewis Carroll 发明的一种游戏,其中通过每步更改一个字母来连接单词。例如——

APE → APT → AIT → BIT → BIG → BAG → MAG → MAN

在这里,我们从 "APE" 到 "MAN" 走了七步,每次更改一个字母。问题是 - 我们能否使用相同规则找到这些单词之间更短的路径?这个问题自然表达为稀疏图问题。节点将对应单个单词,我们将在最多相差一个字母的单词之间创建连接。

获取单词列表

首先,当然,我们必须获取一个有效单词列表。我使用的是 Mac,Mac 在以下代码块中给出的位置有一个单词字典。如果您使用的是不同的架构,您可能需要搜索一下以找到您的系统字典。

wordlist = open('/usr/share/dict/words').read().split()
print len(wordlist)

上述程序将生成以下输出。

235886

现在,我们想要查看长度为 3 的单词,因此让我们只选择那些正确长度的单词。我们还将消除以大写字母开头(专有名词)的单词,或包含非字母数字字符(如撇号和连字符)的单词。最后,我们将确保一切都转换为小写,以便后续比较。

word_list = [word for word in word_list if len(word) == 3]
word_list = [word for word in word_list if word[0].islower()]
word_list = [word for word in word_list if word.isalpha()]
word_list = map(str.lower, word_list)
print len(word_list)

上述程序将生成以下输出。

1135

现在,我们有了一个包含 1135 个有效三字母单词的列表(确切数量可能因使用的特定列表而异)。这些单词中的每一个都将成为我们图中的一个节点,我们将创建连接与每对仅相差一个字母的单词相关联的节点的边。

import numpy as np
word_list = np.asarray(word_list)

word_list.dtype
word_list.sort()

word_bytes = np.ndarray((word_list.size, word_list.itemsize),
   dtype = 'int8',
   buffer = word_list.data)
print word_bytes.shape

上述程序将生成以下输出。

(1135, 3)

我们将使用每个点之间的 Hamming 距离来确定哪些单词对是连接的。Hamming 距离测量两个向量之间不同条目的比例:任何两个 Hamming 距离等于 1/N 的单词,其中 N 是字母数量,在单词阶梯中是连接的。

from scipy.spatial.distance import pdist, squareform
from scipy.sparse import csr_matrix
hamming_dist = pdist(word_bytes, metric = 'hamming')
graph = csr_matrix(squareform(hamming_dist < 1.5 / word_list.itemsize))

在比较距离时,我们不使用相等性,因为这对于浮点值可能不稳定。只要单词列表中没有两个条目相同,不等式就会产生所需的结果。现在,我们的图已经设置好了,我们将使用最短路径搜索来查找图中任意两个单词之间的路径。

i1 = word_list.searchsorted('ape')
i2 = word_list.searchsorted('man')
print word_list[i1],word_list[i2]

上述程序将生成以下输出。

ape, man

我们需要检查这些是否匹配,因为如果单词不在列表中,输出将出错。现在,我们只需要在图中找到这两个索引之间的最短路径。我们将使用 dijkstras 算法,因为它允许我们仅为一个节点找到路径。

from scipy.sparse.csgraph import dijkstra
distances, predecessors = dijkstra(graph, indices = i1, return_predecessors = True)
print distances[i2]

上述程序将生成以下输出。

5.0

因此,我们看到 ape 和 man 之间的最短路径仅包含五个步骤。我们可以使用算法返回的前驱来重建此路径。

path = []
i = i2

while i != i1:
   path.append(word_list[i])
   i = predecessors[i]
   
path.append(word_list[i1])
print path[::-1]i2]

上述程序将生成以下输出。

['ape', 'ope', 'opt', 'oat', 'mat', 'man']