scipy.sparse.csgraph.

laplacian#

scipy.sparse.csgraph.laplacian(csgraph, normed=False, return_diag=False, use_out_degree=False, *, copy=True, form='array', dtype=None, symmetrized=False)[source]#

返回有向图的拉普拉斯算子。

参数:
csgraph类似数组或稀疏数组或矩阵,2 维

压缩稀疏图,形状为 (N, N)。

normedbool,可选

如果为 True,则计算对称归一化拉普拉斯算子。默认值:False。

return_diagbool,可选

如果为 True,则还返回与顶点度相关的数组。默认值:False。

use_out_degreebool,可选

如果为 True,则使用出度而不是入度。此区别仅在图不对称时才重要。默认值:False。

copy: bool, 可选

如果为 False,则尽可能就地更改 csgraph,避免内存使用量翻倍。默认值:True,为了向后兼容。

form: ‘array’,或 ‘function’,或 ‘lo’

确定输出拉普拉斯算子的格式

  • ‘array’ 是一个 numpy 数组;

  • ‘function’ 是指向评估拉普拉斯向量或拉普拉斯矩阵乘积的指针;

  • ‘lo’ 导致 LinearOperator 的格式。

选择 ‘function’ 或 ‘lo’ 始终避免内存使用量翻倍,忽略 copy 值。默认值:‘array’,为了向后兼容。

dtype: None 或一个数字 numpy dtype,可选

输出的 dtype。如果 dtype=None,则输出的 dtype 与输入 csgraph 的 dtype 匹配,除了 normed=True 和类整数 csgraph 的情况,其中输出 dtype 为 ‘float’,允许精确归一化,但会大大增加内存使用量。默认值:None,为了向后兼容。

symmetrized: bool, 可选

如果为 True,则输出拉普拉斯算子是对称/埃尔米特矩阵。对称化是通过 csgraph + csgraph.T.conj 完成的,而不除以 2,以便在构建拉普拉斯算子之前尽可能保留整数 dtype。除非稀疏模式是对称的或 form 是 ‘function’ 或 ‘lo’,否则对称化将增加稀疏矩阵的内存占用。默认值:False,为了向后兼容。

返回值:
lapndarray,或稀疏数组或矩阵,或 LinearOperator

csgraph 的 N x N 拉普拉斯算子。如果输入是密集的,则它将是一个 NumPy 数组(密集的),否则将是一个稀疏数组,或者如果 form 分别等于 ‘function’ 或 ‘lo’,则为函数或 LinearOperator 的格式。

diagndarray,可选

拉普拉斯矩阵的长度为 N 的主对角线。对于归一化拉普拉斯算子,这是顶点度数的平方根数组,如果度数为零,则为 1。

注释

图的拉普拉斯矩阵有时被称为“基尔霍夫矩阵”或简称为“拉普拉斯算子”,在谱图理论的许多部分中都很有用。 特别是,拉普拉斯算子的特征分解可以深入了解图的许多属性,例如,通常用于谱数据嵌入和聚类。

如果 copy=Trueform="array"(这是默认值),则构造的拉普拉斯算子会将内存使用量翻倍。 选择 copy=False 没有效果,除非 form="array" 或矩阵是 coo 格式的稀疏矩阵,或者密集数组,除了 normed=True 的整数输入会强制浮点输出。

如果 form="array"(这是默认值),则稀疏输入将重新格式化为 coo

如果输入邻接矩阵不是对称的,则除非使用 symmetrized=True,否则拉普拉斯算子也是非对称的。

输入邻接矩阵的对角线条目将被忽略,并替换为零,以便在 normed=True 的情况下进行归一化。 归一化使用输入邻接矩阵的行和的平方根倒数,因此如果行和包含负值或具有非零虚部的复数值,则可能会失败。

归一化是对称的,如果输入 csgraph 是对称的,则使归一化拉普拉斯算子也是对称的。

参考文献

示例

>>> import numpy as np
>>> from scipy.sparse import csgraph

我们的第一个图示是对称图

>>> G = np.arange(4) * np.arange(4)[:, np.newaxis]
>>> G
array([[0, 0, 0, 0],
       [0, 1, 2, 3],
       [0, 2, 4, 6],
       [0, 3, 6, 9]])

及其对称拉普拉斯矩阵

>>> csgraph.laplacian(G)
array([[ 0,  0,  0,  0],
       [ 0,  5, -2, -3],
       [ 0, -2,  8, -6],
       [ 0, -3, -6,  9]])

非对称图

>>> G = np.arange(9).reshape(3, 3)
>>> G
array([[0, 1, 2],
       [3, 4, 5],
       [6, 7, 8]])

具有不同的行和列和,导致拉普拉斯矩阵的两种变体,使用入度,这是默认值

>>> L_in_degree = csgraph.laplacian(G)
>>> L_in_degree
array([[ 9, -1, -2],
       [-3,  8, -5],
       [-6, -7,  7]])

或者使用出度

>>> L_out_degree = csgraph.laplacian(G, use_out_degree=True)
>>> L_out_degree
array([[ 3, -1, -2],
       [-3,  8, -5],
       [-6, -7, 13]])

构造一个对称拉普拉斯矩阵,可以将两个加在一起,如

>>> L_in_degree + L_out_degree.T
array([[ 12,  -4,  -8],
        [ -4,  16, -12],
        [ -8, -12,  20]])

或使用 symmetrized=True 选项

>>> csgraph.laplacian(G, symmetrized=True)
array([[ 12,  -4,  -8],
       [ -4,  16, -12],
       [ -8, -12,  20]])

这相当于对称化原始图

>>> csgraph.laplacian(G + G.T)
array([[ 12,  -4,  -8],
       [ -4,  16, -12],
       [ -8, -12,  20]])

归一化的目标是使拉普拉斯矩阵的非零对角线条目都为单位,同时相应地缩放非对角线条目。 可以手动完成归一化,例如

>>> G = np.array([[0, 1, 1], [1, 0, 1], [1, 1, 0]])
>>> L, d = csgraph.laplacian(G, return_diag=True)
>>> L
array([[ 2, -1, -1],
       [-1,  2, -1],
       [-1, -1,  2]])
>>> d
array([2, 2, 2])
>>> scaling = np.sqrt(d)
>>> scaling
array([1.41421356, 1.41421356, 1.41421356])
>>> (1/scaling)*L*(1/scaling)
array([[ 1. , -0.5, -0.5],
       [-0.5,  1. , -0.5],
       [-0.5, -0.5,  1. ]])

或者使用 normed=True 选项

>>> L, d = csgraph.laplacian(G, return_diag=True, normed=True)
>>> L
array([[ 1. , -0.5, -0.5],
       [-0.5,  1. , -0.5],
       [-0.5, -0.5,  1. ]])

现在,它返回缩放系数而不是对角线

>>> d
array([1.41421356, 1.41421356, 1.41421356])

零缩放系数被替换为 1,因此缩放没有效果,例如

>>> G = np.array([[0, 0, 0], [0, 0, 1], [0, 1, 0]])
>>> G
array([[0, 0, 0],
       [0, 0, 1],
       [0, 1, 0]])
>>> L, d = csgraph.laplacian(G, return_diag=True, normed=True)
>>> L
array([[ 0., -0., -0.],
       [-0.,  1., -1.],
       [-0., -1.,  1.]])
>>> d
array([1., 1., 1.])

仅实现了对称归一化,如果且仅当其图是对称的并且具有所有非负度数时,才导致对称拉普拉斯矩阵,如以上示例所示。

默认情况下,输出拉普拉斯矩阵是一个密集数组或稀疏数组或矩阵,从输入图矩阵推断其类、形状、格式和 dtype

>>> G = np.array([[0, 1, 1], [1, 0, 1], [1, 1, 0]]).astype(np.float32)
>>> G
array([[0., 1., 1.],
       [1., 0., 1.],
       [1., 1., 0.]], dtype=float32)
>>> csgraph.laplacian(G)
array([[ 2., -1., -1.],
       [-1.,  2., -1.],
       [-1., -1.,  2.]], dtype=float32)

但也可以作为无矩阵的 LinearOperator 生成

>>> L = csgraph.laplacian(G, form="lo")
>>> L
<3x3 _CustomLinearOperator with dtype=float32>
>>> L(np.eye(3))
array([[ 2., -1., -1.],
       [-1.,  2., -1.],
       [-1., -1.,  2.]])

或作为 lambda 函数

>>> L = csgraph.laplacian(G, form="function")
>>> L
<function _laplace.<locals>.<lambda> at 0x0000012AE6F5A598>
>>> L(np.eye(3))
array([[ 2., -1., -1.],
       [-1.,  2., -1.],
       [-1., -1.,  2.]])

拉普拉斯矩阵用于谱数据聚类和嵌入以及谱图划分。 我们的最后一个示例说明了后一种情况,用于噪声有向线性图。

>>> from scipy.sparse import diags_array, random_array
>>> from scipy.sparse.linalg import lobpcg

使用稀疏邻接矩阵 G 创建一个具有 N=35 个顶点的有向线性图

>>> N = 35
>>> G = diags_array(np.ones(N - 1), offsets=1, format="csr")

修复随机种子 rng 并向图 G 添加随机稀疏噪声

>>> rng = np.random.default_rng()
>>> G += 1e-2 * random_array((N, N), density=0.1, rng=rng)

设置特征向量的初始近似值

>>> X = rng.random((N, 2))

非归一化拉普拉斯算子的常数向量始终是一个平凡的特征向量,需要将其滤除

>>> Y = np.ones((N, 1))

交替 (1) 图权重的符号允许在单个循环中确定谱最大切割和最小切割的标签。 由于该图是无向图,因此在构造拉普拉斯算子时必须使用选项 symmetrized=True。 由于负权重,不能在 (2) 中使用选项 normed=True,因为对称归一化会评估平方根。 (2) 中的选项 form="lo" 是无矩阵的,即保证了固定的内存占用和对图的只读访问。 调用特征值求解器 lobpcg (3) 计算 Fiedler 向量,该向量将标签确定为 (5) 中其分量的符号。 由于特征向量中的符号不是确定性的,并且可以翻转,因此我们固定第一个分量的符号始终为 +1(在 (4) 中)。

>>> for cut in ["max", "min"]:
...     G = -G  # 1.
...     L = csgraph.laplacian(G, symmetrized=True, form="lo")  # 2.
...     _, eves = lobpcg(L, X, Y=Y, largest=False, tol=1e-2)  # 3.
...     eves *= np.sign(eves[0, 0])  # 4.
...     print(cut + "-cut labels:\n", 1 * (eves[:, 0]>0))  # 5.
max-cut labels:
[1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1]
min-cut labels:
[1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]

正如预期的那样,对于一个(略有噪声的)线性图,最大切割剥离了该图的所有边,并将所有奇数顶点着色为一种颜色,将所有偶数顶点着色为另一种颜色,而平衡的最小切割通过删除一条边将该图在中间进行划分。 两个确定的分区都是最优的。