目录

  1. 引言:稀疏矩阵——“空”中的智慧
    • 1.1 什么是稀疏矩阵?
    • 1.2 为什么需要稀疏矩阵?(对比密集矩阵)
    • 1.3 Python 中处理稀疏矩阵的库:scipy.sparse
  2. 核心概念:稀疏矩阵的效率原理
    • 2.1 存储非零元素
    • 2.2 内存效率
    • 2.3 计算效率
  3. 稀疏矩阵的常见存储格式详解
    • 3.1 COO (Coordinate List Format):坐标列表格式
      • 描述与结构
      • 创建方法
      • 优缺点与适用场景
    • 3.2 LIL (List of Lists Format):行列表格式
      • 描述与结构
      • 创建方法
      • 优缺点与适用场景
    • 3.3 DOK (Dictionary of Keys Format):字典键格式
      • 描述与结构
      • 创建方法
      • 优缺点与适用场景
    • 3.4 CSR (Compressed Sparse Row Format):压缩稀疏行格式
      • 描述与结构
      • 创建方法
      • 优缺点与适用场景
    • 3.5 CSC (Compressed Sparse Column Format):压缩稀疏列格式
      • 描述与结构
      • 创建方法
      • 优缺点与适用场景
    • 3.6 其他格式:BSL、DIA 等(简述)
  4. 在 Python 中创建稀疏矩阵
    • 4.1 从密集 NumPy 数组创建
    • 4.2 从数据和索引直接创建
    • 4.3 格式之间的转换
  5. 稀疏矩阵的基本操作
    • 5.1 访问元素
    • 5.2 算术运算
    • 5.3 矩阵乘法 (@ 运算符)
    • 5.4 转换为密集矩阵
  6. 稀疏矩阵的优势与劣势
    • 6.1 优势:内存、性能、处理大规模数据
    • 6.2 劣势:开销、随机访问、API复杂性
  7. 知识扩展:稀疏矩阵在实际应用中的场景
    • 7.1 机器学习:特征工程、推荐系统、自然语言处理 (NLP)
    • 7.2 图论与网络分析
    • 7.3 有限元分析与数值模拟
    • 7.4 深度学习框架中的稀疏张量 (TensorFlow, PyTorch)
  8. 代码示例
    • 8.1 密集矩阵与稀疏矩阵的内存对比
    • 8.2 不同格式稀疏矩阵的创建与转换
    • 8.3 稀疏矩阵的基本操作
  9. 最佳实践与常见误区
    • 9.1 选择合适的格式
    • 9.2 避免不必要的密集化
    • 9.3 理解性能权衡
    • 9.4 注意数据类型
  10. 总结

1. 引言:稀疏矩阵——“空”中的智慧

在处理大量数据时,我们经常会遇到这样一种矩阵:它的绝大多数元素都是零。这样的矩阵被称为稀疏矩阵(Sparse Matrix)。与此相对,如果矩阵的大多数元素是非零的,则被称为密集矩阵(Dense Matrix)

1.1 什么是稀疏矩阵?

一个稀疏矩阵是指一个矩阵,其中非零元素的数量远少于零元素的数量。通常,当非零元素的比例低于某个阈值(例如,10% 或 5%)时,矩阵就被认为是稀疏的。

1.2 为什么需要稀疏矩阵?(对比密集矩阵)

考虑一个 10000×1000010000 \times 1000010000×10000 的矩阵。如果它是一个密集矩阵,需要存储 10810^8108 个浮点数。每个浮点数(例如 8 字节)将占用 108×8=80010^8 \times 8 = 800108×8=800 MB 的内存。如果这个矩阵只有 0.1% 的非零元素,那么实际上只需要存储 10 万个非零元素。在这种情况下,使用传统的密集矩阵存储方式会造成巨大的内存浪费。

  • 内存效率:只存储非零元素的位置和值,大大节省内存。
  • 计算效率:许多线性代数运算(如矩阵乘法)可以避免对零元素的计算,从而加速运算。
  • 处理大规模数据:使得处理传统密集矩阵无法装入内存的巨大数据集成为可能。

1.3 Python 中处理稀疏矩阵的库:scipy.sparse

在 Python 中,scipy.sparse 模块提供了多种稀疏矩阵存储格式的实现,并支持它们之间的转换以及各种线性代数操作。它是处理稀疏矩阵的标准工具。

2. 核心概念:稀疏矩阵的效率原理

稀疏矩阵的核心思想是:“只存储有用的信息”。对于零元素,我们不需要存储它们的值,只需要知道它们的位置是零即可。

2.1 存储非零元素

不同的稀疏格式采用不同的数据结构来存储非零元素的:

  • 值 (Data):非零元素本身的值。
  • 行索引 (Row Indices):非零元素所在的行号。
  • 列索引 (Column Indices):非零元素所在的列号。

通过这三部分信息,可以完全重建原始矩阵。

2.2 内存效率

例如,一个 M×NM \times NM×N 的密集矩阵需要 M×NM \times NM×N 个内存单元。
一个稀疏矩阵,如果它有 KKK 个非零元素,则通常需要存储 KKK 个值、KKK 个行索引和 KKK 个列索引。这总共是 3K3K3K 个内存单元(大致)。
3K≪MN3K \ll MN3KMN 时,稀疏矩阵的内存效率就远高于密集矩阵。

2.3 计算效率

许多矩阵操作,例如矩阵向量乘法 y=Axy = Axy=Ax,在密集矩阵中需要 O(MN)O(MN)O(MN) 次乘法和加法。
如果 AAA 是一个稀疏矩阵,有 KKK 个非零元素,则只需要进行 KKK 次乘法和 KKK 次加法,从而将计算复杂度降低到 O(K)O(K)O(K)。对于非常稀疏的矩阵,这意味着巨大的性能提升。

3. 稀疏矩阵的常见存储格式详解

scipy.sparse 提供了多种稀疏矩阵格式,每种格式都有其特定的优缺点和适用场景。选择正确的格式对于性能至关重要。

3.1 COO (Coordinate List Format):坐标列表格式

  • 描述与结构:这是最简单、最直观的格式。它存储非零元素的 (row, column, value) 元组列表。可以理解为三条平行的数组:data(值)、row(行索引)、col(列索引)。
  • 创建方法scipy.sparse.coo_matrix((data, (row, col)), shape=(M, N))
  • 优缺点
    • 优点
      • 易于创建:从数据源构建稀疏矩阵最直接的方式,特别是当非零元素是无序的时候。
      • 易于转换:可以高效地转换为其他格式。
      • 支持重复坐标:如果同一 (row, col) 有多个值,它们会被求和。
    • 缺点
      • 效率低下:不支持高效的行切片、列切片、算术运算或矩阵乘法。一旦创建,通常应转换为其他格式进行计算。
      • 不支持快速索引:获取单个元素或修改元素效率低。
  • 适用场景:作为稀疏矩阵的初始构建格式

3.2 LIL (List of Lists Format):行列表格式

  • 描述与结构:矩阵的每一行都被存储为一个列表,其中包含该行中非零元素的列索引和对应的值。它是一个包含行列表的列表,每个行列表又包含一对 (column_index, value)
    • 实际上,scipy.sparse.lil_matrix 使用两个列表的列表:data 存储值,rows 存储列索引。
  • 创建方法scipy.sparse.lil_matrix((M, N)),然后逐个设置 matrix[i, j] = value
  • 优缺点
    • 优点
      • 高效的逐元素修改:特别适合增量构建稀疏矩阵或修改现有稀疏矩阵的结构。
      • 高效的行切片。
    • 缺点
      • 效率较低:不支持高效的列切片和大多数数值运算。
      • 转换成 CSR/CSC 格式较慢。
  • 适用场景增量构建稀疏矩阵(即逐个添加或修改非零元素)时,或者当矩阵的稀疏模式是动态变化时。

3.3 DOK (Dictionary of Keys Format):字典键格式

  • 描述与结构:将矩阵存储为一个 Python 字典,其中键是 (row, column) 元组,值是非零元素。
  • 创建方法scipy.sparse.dok_matrix((M, N)),然后像字典一样 matrix[i, j] = value
  • 优缺点
    • 优点
      • 高效的随机读写:通过哈希表实现,matrix[i, j] 访问速度快。
      • 易于理解和实现。
    • 缺点
      • 效率较低:不支持高效的行/列切片和大多数数值运算。
      • 内存开销较大:因为每个 (key, value) 对都是一个 Python 对象。
  • 适用场景:当矩阵需要大量随机访问和修改时,但整体计算任务不多或完成后会转换为其他格式。

3.4 CSR (Compressed Sparse Row Format):压缩稀疏行格式

  • 描述与结构:这是一种非常高效的存储和计算格式,特别适合行向量操作和矩阵-向量乘法。它通过三个一维数组来存储:
    1. data:所有非零元素的值,按行顺序排列。
    2. indicesdata 中每个元素的列索引。
    3. indptr (index pointer):一个指针数组,指示每行在 dataindices 数组中开始和结束的位置。indptr[i] 存储第 i 行第一个非零元素在 dataindices 中的索引。indptr[i+1] - indptr[i] 就是第 i 行的非零元素数量。
  • 创建方法scipy.sparse.csr_matrix(dense_array)scipy.sparse.csr_matrix((data, indices, indptr), shape=(M, N))
  • 优缺点
    • 优点
      • 高效的算术运算和矩阵乘法
      • 高效的行切片。
      • 内存效率高。
    • 缺点
      • 不适合增量构建或修改稀疏模式(添加/删除元素成本高)。
      • 列切片效率低。
  • 适用场景一旦稀疏矩阵的结构确定下来,且需要进行大量的数值计算时,尤其是矩阵乘法和行操作。 这是最常用的计算格式。

3.5 CSC (Compressed Sparse Column Format):压缩稀疏列格式

  • 描述与结构:与 CSR 格式类似,但它是按列存储和优化的。它也使用三个一维数组:
    1. data:所有非零元素的值,按列顺序排列。
    2. indicesdata 中每个元素的行索引。
    3. indptr (index pointer):一个指针数组,指示每列在 dataindices 数组中开始和结束的位置。
  • 创建方法scipy.sparse.csc_matrix(dense_array)scipy.sparse.csc_matrix((data, indices, indptr), shape=(M, N))
  • 优缺点
    • 优点
      • 高效的算术运算和矩阵乘法
      • 高效的列切片。
      • 内存效率高。
    • 缺点
      • 不适合增量构建或修改稀疏模式。
      • 行切片效率低。
  • 适用场景与 CSR 类似,但当需要进行大量的列操作时(例如,与列向量乘法)。

3.6 其他格式(简述)

  • BSR (Block Sparse Row Format):块稀疏行格式。适用于当稀疏矩阵包含许多小的密集块时,可以进一步优化存储和计算。
  • DIA (Diagonal Format):对角线格式。适用于矩阵只有少数几条非零对角线(如三对角矩阵)的情况。

4. 在 Python 中创建稀疏矩阵

使用 scipy.sparse 模块。

4.1 从密集 NumPy 数组创建

可以直接将 NumPy 数组转换为稀疏矩阵,NumPy 会自动识别零元素。

import numpy as np
from scipy import sparse

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

# 转换为 CSR 格式 (最常用)
csr_mat = sparse.csr_matrix(dense_matrix)
print("CSR 格式:\n", csr_mat)
# 转换为 COO 格式
coo_mat = sparse.coo_matrix(dense_matrix)
print("COO 格式:\n", coo_mat)

4.2 从数据和索引直接创建

这是创建稀疏矩阵最常用的方法,特别是当你的数据已经是稀疏形式时。

COO 格式:

# (data, (row_indices, col_indices))
data = np.array([1, 2, 3])
row = np.array([0, 1, 2])
col = np.array([1, 3, 0])
# 形状必须明确指定
coo_mat_from_data = sparse.coo_matrix((data, (row, col)), shape=(3, 4))
print("从数据创建的 COO 格式:\n", coo_mat_from_data)
print("转换成密集矩阵:\n", coo_mat_from_data.toarray())

CSR 格式:

# (data, indices, indptr)
data_csr = np.array([1, 2, 3])
indices_csr = np.array([1, 3, 0]) # 对应 data 的列索引
indptr_csr = np.array([0, 1, 2, 3]) # 指针:row0(idx 0->1), row1(idx 1->2), row2(idx 2->3)
# data[0] 是 row 0 的,data[1] 是 row 1 的,data[2] 是 row 2 的
# 注意:indptr 的长度是 num_rows + 1。
# indptr[i] 指向第 i 行第一个非零元素在 data 和 indices 中的索引。
# indptr[i+1] - indptr[i] 是第 i 行的非零元素数量。
# Example:
# data = [1, 2, 3]
# indices = [1, 3, 0]
# Matrix:
# row 0: value 1 at col 1
# row 1: value 2 at col 3
# row 2: value 3 at col 0
csr_mat_from_data = sparse.csr_matrix((data_csr, indices_csr, indptr_csr), shape=(3, 4))
print("从数据创建的 CSR 格式:\n", csr_mat_from_data.toarray())

注意: 上述 CSR 格式的 indptr 示例仅适用于每行一个非零元素的情况。更复杂的 indptr 例子如下:

# Matrix:
# [[1, 0, 2],
#  [0, 3, 0],
#  [4, 0, 5]]
# Non-zero elements and their columns, by row:
# Row 0: (1, col 0), (2, col 2)
# Row 1: (3, col 1)
# Row 2: (4, col 0), (5, col 2)

data_ex = np.array([1, 2, 3, 4, 5])
indices_ex = np.array([0, 2, 1, 0, 2]) # Column indices corresponding to data
indptr_ex = np.array([0, 2, 3, 5]) # Pointers to data/indices for each row
# indptr[0]=0: row 0 starts at data[0], indices[0]
# indptr[1]=2: row 1 starts at data[2], indices[2]
# indptr[2]=3: row 2 starts at data[3], indices[3]
# indptr[3]=5: end of matrix data
# Non-zeros in row 0: data[0:2] -> (1,2), indices[0:2] -> (0,2)
# Non-zeros in row 1: data[2:3] -> (3), indices[2:3] -> (1)
# Non-zeros in row 2: data[3:5] -> (4,5), indices[3:5] -> (0,2)

csr_complex = sparse.csr_matrix((data_ex, indices_ex, indptr_ex), shape=(3, 3))
print("从复杂数据创建的 CSR 格式:\n", csr_complex.toarray())

4.3 格式之间的转换

scipy.sparse 对象提供了便捷的方法来在不同格式之间进行转换。

  • .tocoo()
  • .tolil()
  • .todok()
  • .tocsr()
  • .tocsc()
  • .toarray() (转换为密集 NumPy 数组)
csr_mat = sparse.csr_matrix(np.array([[0, 1, 0, 0], [0, 0, 0, 2], [3, 0, 0, 0]]))
print("原始 CSR 矩阵:\n", csr_mat.toarray())

lil_mat = csr_mat.tolil()
print("转换为 LIL 格式:\n", lil_mat.toarray())

dok_mat = csr_mat.todok()
print("转换为 DOK 格式:\n", dok_mat.toarray())

csc_mat = csr_mat.tocsc()
print("转换为 CSC 格式:\n", csc_mat.toarray())

5. 稀疏矩阵的基本操作

稀疏矩阵支持许多与密集 NumPy 数组相同的操作。

5.1 访问元素

  • 高效访问:通过 getrow()getcol() 或将其转换为 LIL/DOK 格式进行逐元素访问。
  • 低效访问:直接使用 matrix[i, j] 可能会因为内部转换而效率低下,特别是对 CSR/CSC 格式。
csr_mat = sparse.csr_matrix(np.array([[0, 1, 0], [0, 0, 2], [3, 0, 0]]))
print("原始矩阵:\n", csr_mat.toarray())

# 获取单行
row_0 = csr_mat.getrow(0)
print(f"获取第0行 (CSR): {row_0.toarray()}") # Output: [[0 1 0]]

# 获取单列 (对 CSR 效率较低,会进行内部转换)
col_1 = csr_mat.getcol(1)
print(f"获取第1列 (CSR): {col_1.toarray()}") # Output: [[1], [0], [0]]

# 逐元素访问 (推荐转为 DOK/LIL)
lil_mat = csr_mat.tolil()
print(f"LIL 格式下 (0,1) 元素: {lil_mat[0, 1]}")

5.2 算术运算

稀疏矩阵支持元素级加、减、乘、除,以及与标量的操作。

mat1 = sparse.csr_matrix(np.array([[0, 1], [2, 0]]))
mat2 = sparse.csr_matrix(np.array([[3, 0], [0, 4]]))

add_mat = mat1 + mat2
print("稀疏矩阵相加:\n", add_mat.toarray())

mul_element_wise = mat1.multiply(mat2) # 元素级乘法
print("稀疏矩阵元素级相乘:\n", mul_element_wise.toarray())

scale_mat = mat1 * 5
print("稀疏矩阵与标量相乘:\n", scale_mat.toarray())

5.3 矩阵乘法 (@ 运算符)

这是稀疏矩阵最常用的操作之一,因为它是其性能优势最明显的场景。

A = sparse.csr_matrix(np.array([[0, 1, 0], [0, 0, 2], [3, 0, 0]])) # 3x3
B = sparse.csr_matrix(np.array([[10, 0], [0, 20], [0, 0]]))       # 3x2

# 使用 @ 运算符进行矩阵乘法
prod_mat = A @ B
print("稀疏矩阵乘法 (A @ B):\n", prod_mat.toarray())
# 预期结果:
# [[0*10+1*0+0*0, 0*0+1*20+0*0],  -> [0, 20]
#  [0*10+0*0+2*0, 0*0+0*20+2*0],  -> [0, 0]
#  [3*10+0*0+0*0, 3*0+0*20+0*0]]  -> [30, 0]
# [[ 0, 20],
#  [ 0,  0],
#  [30,  0]]

5.4 转换为密集矩阵

可以使用 .toarray() 方法将稀疏矩阵转换回密集的 NumPy 数组。但要注意,如果矩阵非常大且稀疏,这将消耗大量内存。

csr_mat = sparse.csr_matrix(np.array([[0, 1, 0], [0, 0, 2], [3, 0, 0]]))
dense_result = csr_mat.toarray()
print("稀疏矩阵转换为密集 NumPy 数组:\n", dense_result)

6. 稀疏矩阵的优势与劣势

6.1 优势:内存、性能、处理大规模数据

  • 显著的内存节省:只存储非零元素及其位置。
  • 加速计算:许多线性代数操作(尤其是矩阵乘法)可以避免零元素的计算。
  • 处理超大规模数据:使得处理传统密集矩阵无法装入内存的数据集成为可能。
  • 代码简洁scipy.sparse API 使得稀疏操作与 NumPy 类似,易于使用。

6.2 劣势:开销、随机访问、API复杂性

  • 额外开销:对于不太稀疏的矩阵(例如,非零元素占比超过 20-30%),稀疏格式存储索引的额外开销可能导致其内存占用反而高于密集矩阵。
  • 随机访问效率低下:获取或修改单个 (i, j) 元素通常不如密集矩阵高效,尤其是在 CSR 和 CSC 格式中。
  • API 复杂性:需要理解不同的稀疏格式及其适用场景,选择不当可能导致性能下降。
  • 某些操作效率低:例如,转置一个 CSR 矩阵会很慢(通常需要先转换为 CSC)。

7. 知识扩展:稀疏矩阵在实际应用中的场景

稀疏矩阵在许多科学和工程领域都有广泛应用。

7.1 机器学习:特征工程、推荐系统、自然语言处理 (NLP)

  • 特征工程:当使用独热编码(One-Hot Encoding)处理高基数(很多唯一值)分类特征时,生成的特征矩阵通常非常稀疏。
  • 推荐系统:用户-物品评分矩阵通常是高度稀疏的,因为每个用户通常只对少数物品进行评分。
  • 自然语言处理 (NLP)
    • 词袋模型 (Bag-of-Words)TF-IDF (Term Frequency-Inverse Document Frequency) 向量:每个文档在词汇表上的表示通常只包含少数非零项。
    • 文档-词语矩阵:在文本挖掘中,文档是行,词语是列,矩阵中记录词语在文档中出现的频率,这通常是稀疏的。

7.2 图论与网络分析

  • 邻接矩阵:表示图的节点之间连接关系的邻接矩阵,如果图是稀疏的(即节点之间的连接很少),那么该矩阵也是稀疏的。
  • 社交网络、万维网链接结构:这些都可以用巨大的稀疏矩阵表示。

7.3 有限元分析与数值模拟

  • 在解决偏微分方程的数值方法中(如有限元法、有限差分法),生成的系数矩阵通常是非常稀疏的,并且具有特定的稀疏模式(如带状矩阵)。

7.4 深度学习框架中的稀疏张量 (TensorFlow, PyTorch)

现代深度学习框架也支持稀疏张量(sparse tensors),以高效处理稀疏数据,尤其是在处理大规模嵌入层、图神经网络或稀疏梯度的场景。它们通常有自己的稀疏存储实现和优化。

8. 代码示例

import numpy as np
from scipy import sparse
import sys # 用于获取对象内存大小

print("--- 8.1 密集矩阵与稀疏矩阵的内存对比 ---")
# 创建一个 1000x1000 的密集矩阵
dense_mat_size = 1000
dense_data = np.random.rand(dense_mat_size, dense_mat_size)
# 随机生成一些稀疏数据
sparse_data = np.zeros((dense_mat_size, dense_mat_size))
num_non_zeros = int(dense_mat_size * dense_mat_size * 0.01) # 1% 非零元素
rand_rows = np.random.randint(0, dense_mat_size, num_non_zeros)
rand_cols = np.random.randint(0, dense_mat_size, num_non_zeros)
rand_vals = np.random.rand(num_non_zeros)
sparse_data[rand_rows, rand_cols] = rand_vals

# 转换为 CSR 稀疏矩阵
csr_sparse_mat = sparse.csr_matrix(sparse_data)

# 内存占用 (近似值,不包括 Python 对象开销等,但可作大致比较)
print(f"密集矩阵 ({dense_mat_size}x{dense_mat_size}) 内存: {sys.getsizeof(sparse_data) / (1024**2):.2f} MB")
# 对于稀疏矩阵,需要计算其内部数组的内存
# CSR 内部存储 data, indices, indptr
csr_memory = (sys.getsizeof(csr_sparse_mat.data) +
              sys.getsizeof(csr_sparse_mat.indices) +
              sys.getsizeof(csr_sparse_mat.indptr)) / (1024**2)
print(f"稀疏矩阵 (CSR, 1% 非零) 内存: {csr_memory:.2f} MB\n")
# 结果通常是稀疏矩阵小很多


print("--- 8.2 不同格式稀疏矩阵的创建与转换 ---")
# 示例稀疏数据
data = np.array([1, 2, 3, 4])
row_indices = np.array([0, 0, 1, 2])
col_indices = np.array([1, 3, 2, 0])
matrix_shape = (3, 4)

# 1. 创建 COO 矩阵
coo_mat = sparse.coo_matrix((data, (row_indices, col_indices)), shape=matrix_shape)
print("COO 矩阵:\n", coo_mat.toarray())
print("COO data:", coo_mat.data)
print("COO row:", coo_mat.row)
print("COO col:", coo_mat.col, "\n")

# 2. 从 COO 转换为 CSR
csr_mat = coo_mat.tocsr()
print("CSR 矩阵 (从COO转换):\n", csr_mat.toarray())
print("CSR data:", csr_mat.data)
print("CSR indices:", csr_mat.indices) # 列索引
print("CSR indptr:", csr_mat.indptr, "\n") # 行指针

# 3. 从 CSR 转换为 CSC
csc_mat = csr_mat.tocsc()
print("CSC 矩阵 (从CSR转换):\n", csc_mat.toarray())
print("CSC data:", csc_mat.data)
print("CSC indices:", csc_mat.indices) # 行索引
print("CSC indptr:", csc_mat.indptr, "\n") # 列指针

# 4. 创建 LIL 矩阵 (适用于增量构建)
lil_mat = sparse.lil_matrix(matrix_shape)
lil_mat[0, 1] = 10
lil_mat[2, 3] = 20
print("LIL 矩阵:\n", lil_mat.toarray(), "\n")

# 5. 创建 DOK 矩阵 (适用于随机访问)
dok_mat = sparse.dok_matrix(matrix_shape)
dok_mat[0, 0] = 5
dok_mat[1, 2] = 15
print("DOK 矩阵:\n", dok_mat.toarray(), "\n")


print("--- 8.3 稀疏矩阵的基本操作 ---")
# 使用之前创建的 csr_mat
print("原始 CSR 矩阵:\n", csr_mat.toarray())

# 1. 获取行
row_view = csr_mat.getrow(1)
print(f"第1行: {row_view.toarray()}") # Output: [[0 0 2 0]]

# 2. 算术运算 (与标量)
scaled_csr = csr_mat * 2
print("乘以2:\n", scaled_csr.toarray())

# 3. 算术运算 (矩阵相加)
another_csr = sparse.csr_matrix(np.array([[5, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 10]]))
sum_csr = csr_mat + another_csr
print("矩阵相加:\n", sum_csr.toarray())

# 4. 矩阵乘法
matrix_A = sparse.csr_matrix(np.array([[1, 0, 2], [0, 3, 0]])) # 2x3
matrix_B = sparse.csc_matrix(np.array([[4, 0], [0, 5], [6, 0]])) # 3x2
result_mult = matrix_A @ matrix_B
print("矩阵乘法 (A @ B):\n", result_mult.toarray())

代码运行输出示例(部分)

--- 8.1 密集矩阵与稀疏矩阵的内存对比 ---
密集矩阵 (1000x1000) 内存: 7.63 MB
稀疏矩阵 (CSR, 1% 非零) 内存: 0.05 MB

--- 8.2 不同格式稀疏矩阵的创建与转换 ---
COO 矩阵:
 [[0 1 0 2]
 [0 0 3 0]
 [4 0 0 0]]
COO data: [1 2 3 4]
COO row: [0 0 1 2]
COO col: [1 3 2 0] 

CSR 矩阵 (从COO转换):
 [[0 1 0 2]
 [0 0 3 0]
 [4 0 0 0]]
CSR data: [1 2 3 4]
CSR indices: [1 3 2 0]
CSR indptr: [0 2 3 4] 

CSC 矩阵 (从CSR转换):
 [[0 1 0 2]
 [0 0 3 0]
 [4 0 0 0]]
CSC data: [4 1 3 2]
CSC indices: [2 0 1 0]
CSC indptr: [0 1 2 3 4] 

LIL 矩阵:
 [[ 0. 10.  0.  0.]
 [ 0.  0.  0.  0.]
 [ 0.  0.  0. 20.]] 

DOK 矩阵:
 [[ 5.  0.  0.  0.]
 [ 0.  0. 15.  0.]
 [ 0.  0.  0.  0.]] 

--- 8.3 稀疏矩阵的基本操作 ---
原始 CSR 矩阵:
 [[0 1 0 2]
 [0 0 3 0]
 [4 0 0 0]]
第1行: [[0 0 3 0]]
乘以2:
 [[ 0  2  0  4]
 [ 0  0  6  0]
 [ 8  0  0  0]]
矩阵相加:
 [[5 1 0 2]
 [0 0 3 0]
 [4 0 0 10]]
矩阵乘法 (A @ B):
 [[16  0]
 [ 0 15]]

9. 最佳实践与常见误区

9.1 选择合适的格式

  • COO:用于构建稀疏矩阵。
  • LIL/DOK:用于增量构建或频繁随机访问/修改。完成后应转换为 CSR/CSC 进行计算。
  • CSR/CSC:用于高效的数值计算(如矩阵乘法、迭代求解器)。CSR 适合行操作,CSC 适合列操作。

9.2 避免不必要的密集化

频繁地将稀疏矩阵转换为密集矩阵(.toarray())会抵消稀疏矩阵的所有内存和性能优势。只在必要时(例如,最终输出或需要特定密集矩阵算法时)才进行转换。

9.3 理解性能权衡

没有一种稀疏格式是“万能”的。不同的格式在构建、修改、行/列访问和数值计算方面有不同的性能特征。理解这些权衡是高效使用稀疏矩阵的关键。

9.4 注意数据类型

和 NumPy 数组一样,稀疏矩阵也支持各种数据类型。确保数据类型(例如 float32, float64, int32)符合需求,并且保持一致,以避免不必要的内存开销或精度问题。

10. 总结

为您深度解析了 Python 中稀疏矩阵的创建和高效运用。

核心要点回顾

  • 稀疏矩阵:指绝大多数元素为零的矩阵,通过只存储非零元素来节省内存和加速计算。
  • scipy.sparse:Python 中处理稀疏矩阵的标准库,提供了多种存储格式。
  • 主要格式
    • COO (Coordinate List):构建简单,易于转换。
    • LIL (List of Lists):高效的逐元素修改,适合增量构建。
    • DOK (Dictionary of Keys):高效的随机读写。
    • CSR (Compressed Sparse Row):最常用,高效的算术运算和矩阵乘法,行操作优化。
    • CSC (Compressed Sparse Column):高效的算术运算和矩阵乘法,列操作优化。
  • 优势:显著的内存节省、计算效率提升,能够处理大规模数据。
  • 劣势:不适合所有场景(如不太稀疏的矩阵),随机访问效率可能较低。
  • 应用:广泛应用于机器学习、NLP、推荐系统、图论、数值模拟等领域。
  • 最佳实践:根据使用场景选择合适的格式,避免不必要的密集化。

掌握稀疏矩阵及其在 scipy.sparse 中的应用是处理大规模、高维稀疏数据的必备技能,它能够极大地提升您数据处理和模型训练的效率。

更多推荐