基于 SciPy.sparse 的稀疏矩阵运算及其在网络安全关联分析中的应用

陈文嘉      202528018670048

⬇ 下载报告 (PDF) ⬇ 下载报告 (ipynb)

1. 引言:为什么需要稀疏矩阵?

1.1 从实际问题说起

在科学计算与工程应用中,许多数学模型最终都归结为线性代数问题,而这些问题中的矩阵往往具有一个共同特征——绝大多数元素为零。例如:

  • 社交网络分析:一个拥有 10 万节点的好友关系图中,每个用户平均仅有几百个好友,邻接矩阵的密度不足 0.1%;
  • 有限元方法:结构力学中网格节点的关联矩阵,每个节点只与周围少数节点相连;
  • 推荐系统:用户-物品评分矩阵中,用户只对极少数物品有过评分行为;
  • 网络安全领域:企业访问控制系统中的用户-权限授权矩阵。一家拥有 10,000 名员工的企业,其 IT 系统可能定义了 5,000 项细粒度权限,但平均每位员工仅持有数十项权限,矩阵密度通常低于 0.5%。

这类矩阵被称为稀疏矩阵(Sparse Matrix)。与稠密矩阵(Dense Matrix)不同,稀疏矩阵的存储与运算应当只关注非零元素,否则将造成巨大的资源浪费。

1.2 稠密存储的"内存灾难"

为了直观感受稀疏矩阵带来的存储优势,考虑一个具体的数值实验。假设我们有一个 10,000×5,00010,000 \times 5,000 的 0-1 授权矩阵,非零元素比例(密度)仅为 0.05%。

import numpy as np
from scipy.sparse import csr_matrix

# 模拟 10000 员工 × 5000 权限的授权矩阵
m, n = 10000, 5000
density = 0.0005  # 0.05% 的非零元
nnz = int(m * n * density)

rows = np.random.randint(0, m, nnz)
cols = np.random.randint(0, n, nnz)
data = np.ones(nnz, dtype=np.int8)

# 稀疏存储
A_sparse = csr_matrix((data, (rows, cols)), shape=(m, n))

# 内存对比计算
sparse_bytes = A_sparse.data.nbytes + A_sparse.indices.nbytes + A_sparse.indptr.nbytes
dense_bytes = A_sparse.toarray().nbytes

print(f"矩阵维度: {m} × {n}")
print(f"非零元素: {nnz} (密度 {density*100:.3f}%)")
print(f"稠密存储内存: {dense_bytes / 1024**2:.2f} MB")
print(f"稀疏存储内存: {sparse_bytes / 1024**2:.2f} MB")
print(f"压缩倍数: {dense_bytes / sparse_bytes:.1f}×")

运行结果分析

当密度仅为 0.05% 时,稠密存储需要约 47.68 MB,而稀疏存储仅需约 0.29 MB,压缩比高达 164 倍。如果矩阵维度进一步增大到 105×10510^5 \times 10^5 级别,稠密矩阵将占用数十 GB 内存,远超普通计算机的物理容量;而稀疏存储仅需几十 MB,这使得在个人笔记本上处理大规模稀疏数据成为可能。

1.3 SciPy.sparse 模块概览

scipy.sparse 是 SciPy 库中专为稀疏矩阵设计的子模块,提供了多种高效的稀疏存储格式与线性代数运算工具。其核心功能可分为四大类:

功能类别 主要子模块/函数 说明
存储格式 csr_matrix, csc_matrix, coo_matrix, lil_matrix, dia_matrix 不同稀疏存储结构,适用于不同场景
构造工具 eye, diags, rand, kron, block_diag 快速构造特殊稀疏矩阵
线性代数 scipy.sparse.linalg 稀疏特征值、SVD、线性方程组求解器
图算法 scipy.sparse.csgraph 基于稀疏矩阵的图最短路径、连通分量等

本报告将围绕上述功能,从存储格式的基础原理出发,逐步深入到稀疏矩阵运算、线性代数求解,最终结合网络安全场景给出一个完整的实战算例。

2. 稀疏矩阵的存储格式

稀疏矩阵的核心思想是不存储零元。但"只存非零元"有多种组织方式,不同的组织方式在空间效率和运算效率之间存在显著差异。scipy.sparse 提供了多种存储格式,理解它们的内部结构是正确使用稀疏矩阵的前提。

2.1 COO (Coordinate Format) — 坐标格式

核心思想:使用三个等长数组分别存储每个非零元素的行索引列索引,即所谓的"三元组"表示。

调用格式

coo_matrix((data, (i, j)), shape=(M, N), dtype=None, copy=False)

参数说明

  • data:一维数组,存储所有非零元素的值,长度为 nnz(非零元个数);
  • (i, j):元组,其中 ij 分别为一维整数数组,存储非零元素的行索引和列索引;
  • shape:二元组 (M, N),指定目标矩阵的形状;
  • dtype:数据类型,默认自动推断;
  • copy:是否复制数据,默认为 False

特点与适用场景

  • 优点:构造最为直接,支持重复索引(相同位置的多个值会自动求和),适合从原始数据(如数据库查询结果、日志记录)直接构建稀疏矩阵;
  • 缺点:不支持高效的行切片、列切片和算术运算;
  • 最佳实践:常用于增量构建矩阵,构建完成后转换为 CSR 或 CSC 格式进行后续运算。

2.2 CSR (Compressed Sparse Row) — 压缩稀疏行

核心思想:对每一行的非零元素进行压缩存储,使用三个数组:

  • data:非零元素值,长度为 nnz
  • indices:每行中非零元素对应的列索引,长度为 nnz
  • indptr:行指针,长度为 M+1indptr[i] 表示第 i 行在 data 数组中的起始位置,indptr[i+1] - indptr[i] 即为第 i 行的非零元个数。
csr_matrix((data, indices, indptr), shape=(M, N))
# 或从其他格式转换
csr_matrix(coo_obj)

特点与适用场景

  • 优点行切片矩阵-向量乘法矩阵-矩阵乘法极为高效,是稀疏线性代数运算的首选格式;
  • 缺点:修改单个元素(如 A[i,j] = x)的代价很高,因为可能涉及整行数据的移动;
  • 最佳实践:矩阵构建完成后,转换为 CSR 进行运算和求解。

2.3 CSC (Compressed Sparse Column) — 压缩稀疏列

CSC 与 CSR 完全对称,只是按列而非按行进行压缩存储。其内部同样使用 dataindicesindptr 三个数组,但 indptr 指向的是列的起始位置。

适用场景

  • 列切片列乘法高效;
  • 某些直接求解器(如稀疏 LU 分解)内部使用 CSC 格式。

2.4 LIL (List of Lists) — 列表格式

核心思想:每行用一个 Python 字典(或列表)记录该行非零元素的列索引与对应值。

特点与适用场景

  • 优点逐元素修改增量构建最为方便,支持 A[i,j] = x 的高效赋值;
  • 缺点:算术运算极慢,内存开销也相对较大;
  • 最佳实践:在需要频繁修改矩阵元素时使用 LIL 构建,完成后转换为 CSR。

2.5 四种格式的内部结构对比

下图以同一个 4×44 \times 4 稀疏矩阵为例,直观展示了四种格式的内部存储差异:

alt text

格式转换的最佳实践路径

原始数据/日志 → COO / LIL(构造与修改阶段) → CSR / CSC(运算与求解阶段)

以下代码演示了从原始三元组到各种格式的完整构造与转换流程:

import numpy as np
from scipy.sparse import coo_matrix, csr_matrix, csc_matrix, lil_matrix

# 原始非零元数据(三元组)
data = np.array([1, 2, 3, 4, 5, 6])
row  = np.array([0, 1, 1, 2, 3, 3])
col  = np.array([0, 1, 3, 2, 0, 3])

# 步骤1:COO 构造(最直接)
A_coo = coo_matrix((data, (row, col)), shape=(4, 4))
print("COO 格式矩阵:")
print(A_coo.toarray())

# 步骤2:转换为 CSR(运算最快)
A_csr = A_coo.tocsr()
print("\nCSR 内部结构:")
print(f"  data    = {A_csr.data}")      # 非零元值
print(f"  indices = {A_csr.indices}")  # 列索引
print(f"  indptr  = {A_csr.indptr}")   # 行指针:[0, 1, 3, 4, 6]

# 步骤3:转换为 CSC
A_csc = A_coo.tocsc()
print("\nCSC 内部结构:")
print(f"  data    = {A_csc.data}")
print(f"  indices = {A_csc.indices}")  # 行索引
print(f"  indptr  = {A_csc.indptr}")   # 列指针

# 步骤4:LIL 逐元素修改
A_lil = lil_matrix((4, 4))
A_lil[0, 0] = 10           # 单个元素赋值
A_lil[1, [1, 3]] = [20, 30]  # 整行批量赋值
print("\nLIL 修改后:")
print(A_lil.toarray())

输出结果解读

CSR 的 indptr = [0, 1, 3, 4, 6] 表示:第 0 行从索引 0 开始(有 1 个非零元),第 1 行从索引 1 开始(有 2 个非零元),第 2 行从索引 3 开始(有 1 个非零元),第 3 行从索引 4 开始(有 2 个非零元)。这种压缩方式使得行访问的时间复杂度为 O(1)O(1),而遍历某行非零元的时间复杂度为 O(该行nnz)O(\text{该行nnz})

3. 稀疏矩阵的基本运算

3.1 算术运算

CSR 格式支持常见的算术运算,且语法与 NumPy 高度一致。稀疏矩阵可以与标量、稠密向量/矩阵、以及另一个稀疏矩阵进行运算。

from scipy.sparse import csr_matrix

A = csr_matrix([[1, 0, 2], [0, 3, 0], [4, 0, 5]])
B = csr_matrix([[0, 6, 0], [7, 0, 8], [0, 9, 0]])

# 稀疏 + 稀疏(结果仍为稀疏)
C = A + B
print("A + B =")
print(C.toarray())

# 稀疏 * 标量
D = 2.5 * A
print("\n2.5 * A =")
print(D.toarray())

3.2 矩阵-向量与矩阵-矩阵乘法

稀疏矩阵与稠密向量的乘法使用 @ 运算符(Python 3.5+),内部会自动调用优化的稀疏矩阵-向量乘法算法,时间复杂度为 O(nnz)O(\text{nnz}),而非稠密乘法的 O(M×N)O(M \times N)

v = np.array([1, 2, 3])

# 稀疏矩阵 × 稠密向量
print("A @ v =", A @ v)

# 稀疏矩阵 × 稀疏矩阵(结果仍为稀疏)
E = A @ B
print("\nA @ B =")
print(E.toarray())

3.3 切片与索引

CSR 格式支持高效的行切片,但不支持高效的单元素随机修改

# 行切片(CSR 高效)
row_0 = A[0, :]          # 第 0 行
rows_01 = A[[0, 2], :]   # 第 0、2 行

# 列切片(CSR 支持,但效率不如 CSC)
col_1 = A[:, 1]

# 单元素访问
print(f"A[0, 2] = {A[0, 2]}")

重要提示:若需在循环中频繁修改稀疏矩阵的单个元素,应先将矩阵转换为 LIL 格式,修改完成后再转回 CSR。直接在 CSR 上修改单元素会导致严重的性能下降。

4. 稀疏线性代数

对于大规模稀疏矩阵,直接求逆矩阵或进行完整的特征值分解是不现实的——求逆会严重破坏稀疏性(逆矩阵往往是稠密的),而完整的特征值分解时间复杂度高达 O(N3)O(N^3)scipy.sparse.linalg 模块提供了专门针对稀疏矩阵的线性代数工具,其核心思想是只计算需要的部分结果

4.1 常用函数概览

函数 功能 关键参数 典型应用场景
eigs(A, k) 计算前 k 个特征值/特征向量 k:个数;which:'LM'(最大)/'SM'(最小) 图聚类、谱分析、异常检测
svds(A, k) 截断奇异值分解 k:奇异值个数 推荐系统、降维、潜在语义分析
spsolve(A, b) 解线性方程组 Ax=b\mathbf{A}\mathbf{x}=\mathbf{b} A:方阵稀疏矩阵 有限元、电路网络分析
cg(A, b) 共轭梯度迭代求解 适用于对称正定矩阵 大规模偏微分方程
gmres(A, b) 广义最小残差迭代求解 适用于非对称矩阵 非对称线性系统

4.2 稀疏特征值分解详解

eigs 函数基于隐式重启 Arnoldi 方法(IRAM),适用于一般方阵;对于对称矩阵则自动使用 Lanczos 方法。其调用格式如下:

eigs(A, k=6, M=None, sigma=None, which='LM', v0=None, ncv=None, 
     maxiter=None, tol=0, return_eigenvectors=True, Minv=None, OPinv=None, OPpart=None)

核心参数说明

  • A:输入方阵,通常为 CSR 或 CSC 格式;
  • k:需要求解的特征值个数,必须满足 k < N-1N 为矩阵维度);
  • which:选择策略,'LM' 表示模最大(Largest Magnitude),'SM' 表示模最小(Smallest Magnitude),'LR' 表示实部最大;
  • return_eigenvectors:是否同时返回特征向量,默认为 True
  • tol:收敛容差,控制计算精度。

以下代码演示如何利用 eigs 计算稀疏矩阵的前几个特征值:

from scipy.sparse.linalg import eigs
from scipy.sparse import diags

# 构造 1000×1000 三对角稀疏矩阵(有限差分常见形式)
n = 1000
main_diag = 2 * np.ones(n)
off_diag = -1 * np.ones(n - 1)

A_tri = diags([off_diag, main_diag, off_diag], offsets=[-1, 0, 1], format='csr')

# 计算前 5 个最小特征值
evals, evecs = eigs(A_tri, k=5, which='SM')

print("前 5 个最小特征值:")
print(np.real(evals))

结果分析:对于三对角矩阵,其特征值具有解析解 λj=22cos(jπ/(n+1))\lambda_j = 2 - 2\cos(j\pi/(n+1)),数值结果与理论值高度吻合。这验证了 eigs 在只计算部分特征值时的正确性与高效性——我们无需对 1000×10001000 \times 1000 矩阵做完整的 O(N3)O(N^3) 分解,而只需 O(kN2)O(k \cdot N^2) 甚至更少的计算量。

5. 实战算例:网络安全中的异常权限关联分析

5.1 问题背景与建模思路

零信任安全架构(Zero Trust Architecture)最小权限原则(Principle of Least Privilege)的指导下,企业安全审计需要定期审查访问控制策略的有效性。传统的人工审计在面对数千名员工与数千项权限时几乎不可行,而稀疏矩阵恰好为这一问题提供了数学工具。

我们将企业授权系统抽象为一个二分图(Bipartite Graph):左侧节点为用户,右侧节点为权限,边表示授权关系。该二分图的邻接矩阵即为我们关注的用户-权限授权矩阵 MRn×m\mathbf{M} \in \mathbb{R}^{n \times m}

基于该矩阵,我们可以回答三个关键安全问题:

  1. 存储问题:授权矩阵极度稀疏,如何高效存储?→ 使用 scipy.sparse 的 CSR 格式;
  2. 共现问题:哪些权限经常同时授予同一用户?→ 计算 C=MM\mathbf{C} = \mathbf{M}^\top \mathbf{M}(权限共现矩阵);
  3. 异常问题:是否存在一小撮用户持有异常密集的特权组合?→ 对 C\mathbf{C} 进行稀疏特征值分解,通过特征值谱的"长尾"检测异常社群。

以下将分步骤演示完整流程。

5.2 步骤一:构造用户-权限授权矩阵

在真实环境中,授权数据通常来自 IAM(Identity and Access Management)系统的数据库导出,格式为三元组 (user_id, perm_id, perm_level)。这里我们模拟一个中型企业环境:2000 名员工,800 项权限,授权密度约 0.3%。

import numpy as np
from scipy.sparse import coo_matrix, csr_matrix

# 设置随机种子以保证结果可复现
np.random.seed(2026)

# 模拟企业规模
n_users = 2000   # 员工数
n_perms = 800    # 权限项数
density = 0.003  # 授权密度(真实系统通常 &lt; 0.5%)

# 计算非零元数量
nnz = int(n_users * n_perms * density)

# 生成随机的用户-权限授权记录
rows = np.random.randint(0, n_users, nnz)  # 用户ID
cols = np.random.randint(0, n_perms, nnz)  # 权限ID

# 权限等级:1=读,2=写,3=执行,5=管理
# 管理权限最少,读权限最多,符合实际分布
vals = np.random.choice([1, 2, 3, 5], nnz, p=[0.5, 0.3, 0.15, 0.05])

# 先用 COO 格式构造(适合从原始三元组直接转换)
UP_coo = coo_matrix((vals, (rows, cols)), shape=(n_users, n_perms))

# 转换为 CSR 格式(适合后续运算)
UP = UP_coo.tocsr()

# 输出矩阵基本信息
print("=" * 50)
print("【步骤一】用户-权限授权矩阵构造完成")
print("=" * 50)
print(f"矩阵维度: {UP.shape}")
print(f"非零元素: {UP.nnz}")
print(f"实际密度: {UP.nnz / (n_users * n_perms) * 100:.3f}%")
print(f"稀疏存储内存: {(UP.data.nbytes + UP.indices.nbytes + UP.indptr.nbytes) / 1024**2:.3f} MB")
print(f"稠密等价内存: {UP.toarray().nbytes / 1024**2:.2f} MB")
print(f"压缩比: {UP.toarray().nbytes / (UP.data.nbytes + UP.indices.nbytes + UP.indptr.nbytes):.0f}×")

结果分析:在 2000×800 的矩阵中,仅有约 4800 个非零元,密度不足 0.3%。稀疏存储仅需 0.062 MB,而稠密存储需要 12.21 MB,压缩比达到 195 倍。当企业规模扩大到数万人时,这种差距将达到数百甚至数千倍,这正是稀疏矩阵在工程实践中的首要价值。

5.3 步骤二:观察 CSR 内部结构

为了深入理解 CSR 格式的工作原理,我们提取矩阵的前 4 行、前 4 列作为微观样本,观察其内部数组。

# 提取前 4×4 子矩阵观察
micro = UP[:4, :4]

# 转回 COO 以便观察三元组
micro_coo = micro.tocoo()

print("【步骤二】CSR 内部结构微观观察(前 4×4 子矩阵)")
print("-" * 50)
print(f"非零元值 (data):    {micro_coo.data}")
print(f"行索引 (row):       {micro_coo.row}")
print(f"列索引 (col):       {micro_coo.col}")
print("-" * 50)

# 观察 CSR 压缩后的结构
micro_csr = micro.tocsr()
print(f"CSR.data:    {micro_csr.data}")
print(f"CSR.indices: {micro_csr.indices}")
print(f"CSR.indptr:  {micro_csr.indptr}")
print("-" * 50)
print("indptr 解读:")
for i in range(4):
    start = micro_csr.indptr[i]
    end = micro_csr.indptr[i+1]
    if start == end:
        print(f"  第 {i} 行: 无非零元")
    else:
        cols = micro_csr.indices[start:end]
        vals = micro_csr.data[start:end]
        print(f"  第 {i} 行: 列索引 {cols.tolist()}, 值 {vals.tolist()}")

结构解读indptr 数组是 CSR 格式的核心。以本例为例,indptr = [0, 0, 2, 3, ...] 表示第 0 行没有非零元(00),第 1 行有 2 个非零元(02),第 2 行有 1 个非零元(23)。这种压缩使得"访问第 i 行所有非零元"的操作时间复杂度为 O(1)O(1) 定位 + O(nnzi)O(\text{nnz}_i) 遍历,与该行总列数无关。

5.4 步骤三:权限共现分析(稀疏矩阵乘法)

安全审计中一个重要的任务是发现权限共现(Permission Co-occurrence):如果大量用户同时拥有权限 A 和权限 B,那么这两项权限可能存在业务关联,或者存在"过度授权"的风险。

从线性代数角度,权限共现矩阵 C\mathbf{C} 可以通过矩阵乘法得到:

C=MM\mathbf{C} = \mathbf{M}^\top \mathbf{M}

其中 M\mathbf{M} 是用户-权限矩阵,CijC_{ij} 表示权限 ii 与权限 jj 被同时授予同一用户的加权次数。由于 M\mathbf{M} 是稀疏的,C\mathbf{C} 通常也是稀疏的,但比 M\mathbf{M} 更稠密一些(权限之间的关联比用户-权限关系更密集)。

from scipy.sparse import csr_matrix

# 计算权限共现矩阵 C = M^T × M
# 结果自动保持为 CSR 格式
PP = (UP.T @ UP).astype(float)

# 去除自环(我们不关心权限与自身的共现)
PP.setdiag(0)

print("【步骤三】权限共现矩阵计算完成")
print("-" * 50)
print(f"共现矩阵维度: {PP.shape}")
print(f"非零元素: {PP.nnz}")
print(f"密度: {PP.nnz / (n_perms * n_perms) * 100:.3f}%")

# 找出共现度最高的 5 对权限
PP_coo = PP.tocoo()
top5_idx = np.argsort(PP_coo.data)[-5:][::-1]

print(f"\nTop 5 共现权限对(权限A, 权限B, 共现次数):")
for idx in top5_idx:
    perm_a = PP_coo.row[idx]
    perm_b = PP_coo.col[idx]
    count = PP_coo.data[idx]
    print(f"  权限 {perm_a:3d} ↔ 权限 {perm_b:3d} : 共现 {count:.0f} 次")

结果解读:Top 5 共现权限对揭示了系统中经常"打包"授予的权限组合。如果审计发现某些高敏感权限(如"管理"级权限)与大量普通权限高度共现,可能意味着存在角色定义过宽的问题,需要收紧授权策略。

5.5 步骤四:异常社群检测(稀疏特征值分解)

权限共现矩阵 C\mathbf{C} 的特征值谱蕴含了权限系统的社群结构信息。在图论中,这类似于图的拉普拉斯矩阵或邻接矩阵的谱分析:如果存在若干个紧密耦合的权限簇(例如管理员小圈子、开发团队内部权限组),会在特征值谱中表现为前几个特征值显著偏大。

我们使用 eigs 计算 C\mathbf{C} 的前 10 个最大特征值,并观察其分布。

from scipy.sparse.linalg import eigs

# 对权限共现矩阵进行稀疏特征值分解
# k=10: 计算前10个最大特征值
# which='LM': Largest Magnitude(模最大)
evals, evecs = eigs(PP, k=10, which='LM')

# eigs 返回的可能是复数(数值误差),取实部
evals = np.real(evals)
evals_sorted = np.sort(evals)[::-1]  # 从大到小排序

print("【步骤四】稀疏特征值分解 —— 异常社群检测")
print("-" * 50)
print(f"前 10 个最大特征值:")
for i, val in enumerate(evals_sorted, 1):
    print(f"  λ_{i:2d} = {val:8.2f}")

print("-" * 50)
print(f"λ_1 / λ_3 比值 = {evals_sorted[0] / evals_sorted[2]:.2f}")
print("\n审计提示:")
print("  • 若前几个特征值显著大于其余,说明存在紧密权限簇")
print("  • 若 λ_1/λ_3 &gt; 2.0,建议提取对应特征向量,定位具体权限ID进行重点审查")

原理解释:特征值的大小反映了对应特征向量所指向的"方向"上数据的方差。在权限共现矩阵中,一个大的特征值意味着存在一组权限,它们之间的共现关系远强于与系统内其他权限的关联,形成了一个相对独立的"权限社群"。如果某个社群包含了过多高敏感权限,或者该社群的规模与业务需求不匹配,就可能暗示内部威胁或账号泄露风险。

5.6 步骤五:间接权限提升路径检测(矩阵幂运算)

在高级持续性威胁(APT)中,攻击者往往不直接获取最高权限,而是通过权限组合实现间接提权。例如:拥有"数据库读权限"和"脚本执行权限"的攻击者,可能通过读取配置文件并执行提权脚本,间接获得管理员权限。

在矩阵视角下,这等价于在权限转移图中寻找长度大于 1 的路径。设 A\mathbf{A} 为权限转移的邻接矩阵(有向),则 A2\mathbf{A}^2 的非零元 (i,j)(i, j) 表示存在一条从权限 ii 到权限 jj 的两步路径。

以下构造一个简化的 8 节点权限转移图进行演示:

from scipy.sparse import csr_matrix

# 构造 8 节点的权限转移图(有向)
# 节点含义示例:0=普通读, 1=普通写, 2=配置读, 3=日志读, 4=备份读, 5=脚本执行, 6=网络配置, 7=管理员
edges = [
    (0, 2),   # 普通读 → 配置读
    (1, 2),   # 普通写 → 配置读
    (2, 5),   # 配置读 → 脚本执行
    (3, 5),   # 日志读 → 脚本执行
    (4, 5),   # 备份读 → 脚本执行
    (5, 7),   # 脚本执行 → 管理员
    (6, 7),   # 网络配置 → 管理员
]

# 构造稀疏邻接矩阵
row_idx = [e[0] for e in edges]
col_idx = [e[1] for e in edges]
Adj = csr_matrix(([1]*len(edges), (row_idx, col_idx)), shape=(8, 8))

# 计算两步转移矩阵 Adj^2
Adj2 = Adj @ Adj

print("【步骤五】间接权限提升路径检测")
print("-" * 50)
print(f"一步转移矩阵非零元: {Adj.nnz}")
print(f"两步转移矩阵非零元: {Adj2.nnz}")
print(f"新增间接路径数: {Adj2.nnz} 条")
print("-" * 50)
print("一步转移路径(直接):")
for i, j in zip(Adj.nonzero()[0], Adj.nonzero()[1]):
    print(f"  权限 {i} → 权限 {j}")
print("-" * 50)
print("两步转移路径(间接提权):")
for i, j in zip(Adj2.nonzero()[0], Adj2.nonzero()[1]):
    count = int(Adj2[i, j])
    print(f"  权限 {i} → ... → 权限 {j} (共 {count} 条路径)")

安全意义Adj² 揭示了所有"两步可达"的间接权限提升路径。在实际审计中,如果某个普通用户账户同时拥有路径起点(如"配置读")和路径中间节点(如"脚本执行")的权限,即使他没有直接的管理员权限,也存在被提权的风险。这种基于矩阵幂运算的可达性分析,是传统规则引擎难以发现的深层关联。

5.7 步骤六:性能对比与结果可视化

为了全面展示稀疏矩阵的优势,我们将上述分析结果进行可视化汇总,并补充一个跨规模的内存对比实验。

import matplotlib.pyplot as plt

# 创建一个综合可视化图
fig = plt.figure(figsize=(16, 10))

# (a) 用户-权限矩阵 spy 图
ax1 = plt.subplot(2, 3, 1)
ax1.spy(UP[:500, :200], markersize=1.5, color='#1B4965', aspect='auto')
ax1.set_title('(a) User-Permission Matrix (500x200)', fontsize=11)
ax1.set_xlabel('Permission ID')
ax1.set_ylabel('User ID')

# (b) 权限共现矩阵 spy 图
ax2 = plt.subplot(2, 3, 2)
ax2.spy(PP[:100, :100], markersize=2, color='#5FA8D3', aspect='auto')
ax2.set_title('(b) Permission Co-occurrence Matrix (100x100)', fontsize=11)
ax2.set_xlabel('Permission ID')
ax2.set_ylabel('Permission ID')

# (c) 特征值谱
ax3 = plt.subplot(2, 3, 3)
ax3.bar(range(1, 11), evals_sorted, color='#CA2E55', alpha=0.8, edgecolor='black', linewidth=0.5)
ax3.axhline(y=evals_sorted[2], color='#2E86AB', linestyle='--', linewidth=2, 
            label=f'阈值 λ₃={evals_sorted[2]:.1f}')
ax3.set_title('(c) Eigenvalue Spectrum (Anomaly Detection)', fontsize=11)
ax3.set_xlabel('Eigenvalue Index')
ax3.set_ylabel('Eigenvalue Magnitude')
ax3.legend(fontsize=9)

# (d) 一步权限转移
ax4 = plt.subplot(2, 3, 4)
ax4.spy(Adj, markersize=18, color='#F18F01', aspect='equal')
ax4.set_title('(d) One-Step Privilege Transfer', fontsize=11)
for i in range(8):
    for j in range(8):
        if Adj[i, j] == 1:
            ax4.text(j, i, '1', ha='center', va='center', color='white', fontsize=9, fontweight='bold')

# (e) 两步权限提升
ax5 = plt.subplot(2, 3, 5)
ax5.spy(Adj2, markersize=18, color='#C73E1D', aspect='equal')
ax5.set_title('(e) Two-Step Privilege Escalation (Adj²)', fontsize=11)
for i in range(8):
    for j in range(8):
        if Adj2[i, j] &gt; 0:
            ax5.text(j, i, str(int(Adj2[i, j])), ha='center', va='center', 
                    color='white', fontsize=9, fontweight='bold')

# (f) 跨规模内存对比
ax6 = plt.subplot(2, 3, 6)
sizes = [500, 1000, 2000, 4000, 8000]
sparse_mems = []
dense_mems = []

for n in sizes:
    nnz_t = int(n * n * 0.001)  # 0.1% 密度
    r = np.random.randint(0, n, nnz_t)
    c = np.random.randint(0, n, nnz_t)
    v = np.random.rand(nnz_t)
    S = csr_matrix((v, (r, c)), shape=(n, n))
    sparse_mems.append((S.data.nbytes + S.indices.nbytes + S.indptr.nbytes) / 1024**2)
    dense_mems.append(S.toarray().nbytes / 1024**2)

x = np.arange(len(sizes))
width = 0.35
ax6.bar(x - width/2, dense_mems, width, label='Dense', color='#E63946', alpha=0.8)
ax6.bar(x + width/2, sparse_mems, width, label='Sparse', color='#06D6A0', alpha=0.8)
ax6.set_xticks(x)
ax6.set_xticklabels([str(s) for s in sizes], fontsize=9)
ax6.set_title('(f) Memory Comparison (nxn, 0.1% Density)', fontsize=11)
ax6.set_ylabel('MB (log)')
ax6.set_yscale('log')
ax6.legend(fontsize=9)

plt.suptitle('Fig.3  Comprehensive Visualization of Cybersecurity Privilege Correlation Analysis', fontsize=14, y=0.98)
plt.tight_layout(rect=[0, 0, 1, 0.96])
plt.show()

可视化解读

  • 子图 (a):用户-权限矩阵的 spy 图直观展示了稀疏性——绝大多数位置为空白,仅有少量点分布;
  • 子图 (b):权限共现矩阵比原始矩阵稍稠密,反映了权限之间的关联模式;
  • 子图 (c):特征值谱中前几个特征值明显高于后续值,提示存在若干紧密权限簇,应进一步审查;
  • 子图 (d)(e):一步与两步权限转移图清晰展示了间接提权的路径结构;
  • 子图 (f):当矩阵规模达到 8000×8000、密度仅 0.1% 时,稀疏存储比稠密存储节省内存超过 600 倍

6. 总结

本报告围绕 SciPy.sparse 模块,系统介绍了稀疏矩阵在 Python 科学计算中的基础理论与应用方法。

核心内容回顾

  1. 存储格式:详细阐述了 COO、CSR、CSC、LIL 四种格式的内部结构、参数含义与适用场景。COO/LIL 适合构造与修改,CSR 适合运算与求解,CSC 适合列操作与某些分解算法。

  2. 基本运算:稀疏矩阵支持标量运算、矩阵加减、矩阵乘法(@)以及与稠密向量的交互,CSR 格式的行切片效率尤为突出。

  3. 稀疏线性代数scipy.sparse.linalg 提供了 eigssvdsspsolve 等工具,能够在不破坏稀疏性、不耗尽内存的前提下,求解大规模线性系统的部分关键结果。

  4. 实战应用:通过网络安全领域的异常权限关联分析算例,完整演示了从数据构造、格式转换、矩阵乘法、特征值分解到矩阵幂运算的全流程。该算例展示了稀疏矩阵如何帮助安全审计人员发现权限共现、检测异常社群、识别间接提权路径。

实际意义:稀疏矩阵不仅是数值分析领域的数学工具,更是连接理论模型与工程实现的桥梁。在网络安全、图分析、有限元、推荐系统等领域,掌握 scipy.sparse 的使用方法,意味着能够在普通计算设备上处理原本需要超级计算机才能承载的数据规模。