
1. 引言在科学计算和机器学习领域我们经常需要处理大规模矩阵数据。然而许多实际场景中的矩阵都包含大量零元素例如推荐系统中的用户-物品评分矩阵、自然语言处理中的词频矩阵、图结构中的邻接矩阵等。如果使用普通的稠密矩阵存储这些数据不仅会浪费大量内存还会导致计算效率低下。SciPy 提供了丰富的稀疏矩阵支持能够高效地存储和操作这类数据。本文将系统介绍 SciPy 稀疏矩阵的核心概念、常见存储格式、创建方法、常用操作以及实战案例帮助读者全面掌握这一重要工具。2. 什么是稀疏矩阵稀疏矩阵是指大部分元素为零的矩阵。与之相对稠密矩阵是指大部分元素非零的矩阵。稀疏矩阵的稀疏度通常用非零元素占比来衡量例如一个 10000×10000 的矩阵如果只有 10000 个非零元素那么它的非零元素占比仅为 0.01%。对于这样的矩阵如果使用普通二维数组存储需要 10000×10000×8 字节假设 float64约 800MB 内存而使用稀疏矩阵存储只需要存储非零元素的位置和值内存占用可以降低几个数量级。import numpy as np from scipy.sparse import csr_matrix 创建一个稠密矩阵 dense np.array([ [0, 0, 3, 0], [4, 0, 0, 0], [0, 0, 0, 5], [0, 2, 0, 0] ]) 转换为稀疏矩阵 sparse csr_matrix(dense) print(稠密矩阵内存占用:, dense.nbytes, 字节) print(稀疏矩阵内存占用:, sparse.data.nbytes sparse.indices.nbytes sparse.indptr.nbytes, 字节) print(非零元素:, sparse.nnz)3. 常见的稀疏矩阵格式SciPy 提供了多种稀疏矩阵格式每种格式在存储效率和操作性能上各有优劣。下面介绍最常用的几种。3.1 CSR 格式Compressed Sparse RowCSR 格式按行压缩存储非零元素是 SciPy 中最常用的格式之一。它使用三个数组表示矩阵data 存储非零元素的值indices 存储每个非零元素对应的列索引indptr 存储每行第一个非零元素在 data 中的位置偏移。from scipy.sparse import csr_matrix 创建 CSR 矩阵 row np.array([0, 0, 1, 2, 2, 2]) col np.array([0, 2, 2, 0, 1, 2]) data np.array([1, 2, 3, 4, 5, 6]) csr csr_matrix((data, (row, col)), shape(3, 3)) print(CSR 矩阵:) print(csr.toarray()) print(data:, csr.data) print(indices:, csr.indices) print(indptr:, csr.indptr)3.2 CSC 格式Compressed Sparse ColumnCSC 格式与 CSR 格式类似但按列压缩存储。它适合按列访问和列切片操作的场景在矩阵乘法中也有广泛应用。from scipy.sparse import csc_matrix 创建 CSC 矩阵 csc csc_matrix((data, (row, col)), shape(3, 3)) print(CSC 矩阵:) print(csc.toarray()) print(data:, csc.data) print(indices:, csc.indices) print(indptr:, csc.indptr)3.3 COO 格式Coordinate FormatCOO 格式是最直观的稀疏矩阵表示方式它直接记录每个非零元素的行索引、列索引和值。这种格式非常适合从外部数据构建稀疏矩阵但进行算术运算时效率较低。from scipy.sparse import coo_matrix 创建 COO 矩阵 coo coo_matrix((data, (row, col)), shape(3, 3)) print(COO 矩阵:) print(coo.toarray()) print(row:, coo.row) print(col:, coo.col) print(data:, coo.data)3.4 其他格式除了上述三种格式SciPy 还提供了 DOKDictionary of Keys、LILList of Lists、BSRBlock Sparse Row等格式。DOK 和 LIL 适合动态构建矩阵BSR 适合存储分块稀疏矩阵。from scipy.sparse import dok_matrix, lil_matrix DOK 格式适合增量构建 dok dok_matrix((3, 3), dtypenp.float32) dok[0, 1] 2.5 dok[2, 0] 3.5 print(DOK 矩阵:) print(dok.toarray()) LIL 格式适合逐行修改 lil lil_matrix((3, 3)) lil[0, 0] 1 lil[1, 2] 4 print(LIL 矩阵:) print(lil.toarray())4. 稀疏矩阵的创建方法除了从稠密矩阵转换SciPy 还提供了多种直接创建稀疏矩阵的方法。4.1 从数据数组创建from scipy.sparse import csr_matrix 方式一从 (data, (row, col)) 创建 data np.array([1, 2, 3, 4]) row np.array([0, 1, 2, 3]) col np.array([0, 1, 2, 3]) matrix csr_matrix((data, (row, col)), shape(4, 4)) print(对角矩阵:) print(matrix.toarray())4.2 创建特殊矩阵from scipy.sparse import identity, diags, random 单位矩阵 eye identity(5, formatcsr) print(单位矩阵:) print(eye.toarray()) 对角矩阵 d diags([1, 2, 3, 4], offsets[0], shape(4, 4)) print(对角矩阵:) print(d.toarray()) 随机稀疏矩阵 r random(5, 5, density0.2, formatcsr) print(随机稀疏矩阵密度 0.2:) print(r.toarray())4.3 从文件加载from scipy.io import mmread, savemat from scipy.sparse import csr_matrix 保存为 Matrix Market 格式 matrix csr_matrix(np.random.rand(5, 5) 0.2, dtypenp.float64) 实际保存时使用 from scipy.io import mmwrite mmwrite(matrix.mtx, matrix) 读取 Matrix Market 格式 loaded mmread(matrix.mtx) print(loaded.toarray())5. 稀疏矩阵的常用操作5.1 矩阵转换与格式互转from scipy.sparse import csr_matrix, csc_matrix, coo_matrix 创建 CSR 矩阵 csr csr_matrix([[1, 0, 0], [0, 2, 0], [0, 0, 3]]) 格式互转 csc csr.tocsc() coo csr.tocoo() dense csr.toarray() print(CSR 转 CSC 成功:, isinstance(csc, csc_matrix)) print(CSR 转 COO 成功:, isinstance(coo, coo_matrix)) print(CSR 转稠密矩阵:) print(dense)5.2 矩阵运算from scipy.sparse import csr_matrix A csr_matrix([[1, 0, 2], [0, 3, 0], [4, 0, 5]]) B csr_matrix([[1, 1, 0], [0, 1, 1], [1, 0, 1]]) 加法 C A B print(A B:) print(C.toarray()) 乘法矩阵乘法 D A B print(A B:) print(D.toarray()) 逐元素乘法 E A.multiply(B) print(A.multiply(B):) print(E.toarray()) 转置 F A.T print(A 的转置:) print(F.toarray())5.3 切片与索引from scipy.sparse import csr_matrix matrix csr_matrix([ [1, 0, 0, 2], [0, 3, 0, 0], [4, 0, 5, 0], [0, 0, 0, 6] ]) 获取单个元素 print(matrix[1, 1]:, matrix[1, 1]) 获取一行 row matrix[1] print(第 2 行:, row.toarray()) 切片 sub matrix[0:2, 0:2] print(左上角 2×2 子矩阵:) print(sub.toarray()) 修改元素 matrix[0, 0] 10 print(修改后:) print(matrix.toarray())5.4 统计与聚合from scipy.sparse import csr_matrix matrix csr_matrix([ [1, 0, 2], [0, 3, 0], [4, 0, 5] ]) 非零元素个数 print(非零元素个数:, matrix.nnz) 每行非零元素个数 print(每行非零元素个数:, matrix.getnnz(axis1)) 每列非零元素个数 print(每列非零元素个数:, matrix.getnnz(axis0)) 求和 print(所有元素之和:, matrix.sum()) print(每行之和:, matrix.sum(axis1)) print(每列之和:, matrix.sum(axis0))6. 实战案例推荐系统中的用户-物品评分矩阵下面通过一个完整的实战案例演示如何使用 SciPy 稀疏矩阵处理推荐系统中的用户-物品评分数据。6.1 构建评分矩阵import numpy as np from scipy.sparse import csr_matrix, save_npz, load_npz 模拟用户-物品评分数据 用户 ID、物品 ID、评分 user_ids np.array([0, 0, 1, 1, 2, 2, 3, 3, 4, 4]) item_ids np.array([0, 2, 1, 3, 0, 4, 2, 3, 1, 4]) ratings np.array([5, 3, 4, 2, 5, 1, 3, 4, 2, 5]) num_users 5 num_items 5 构建稀疏评分矩阵 rating_matrix csr_matrix((ratings, (user_ids, item_ids)), shape(num_users, num_items)) print(评分矩阵:) print(rating_matrix.toarray()) print(非零评分数量:, rating_matrix.nnz)6.2 计算用户相似度from scipy.sparse import csr_matrix from sklearn.preprocessing import normalize 对评分矩阵按行归一化 normalized normalize(rating_matrix, norml2, axis1) 计算用户相似度矩阵余弦相似度 user_similarity normalized normalized.T print(用户相似度矩阵:) print(user_similarity.toarray())6.3 基于物品的协同过滤推荐from scipy.sparse import csr_matrix 计算物品相似度矩阵 item_similarity normalized.T normalized print(物品相似度矩阵:) print(item_similarity.toarray()) 为某个用户生成推荐 def recommend(user_id, rating_matrix, item_similarity, top_k3): # 获取用户已评分的物品 user_ratings rating_matrix[user_id].toarray().flatten() rated_items np.where(user_ratings 0)[0] # 计算未评分物品的预测评分 scores {} for item in range(rating_matrix.shape[1]): if item in rated_items: continue # 基于相似物品的加权评分 sim_items item_similarity[item].toarray().flatten() sim_items[rated_items] 0 # 只考虑已评分物品 if sim_items.sum() gt; 0: scores[item] (sim_items user_ratings) / sim_items.sum() 返回 Top-K 推荐 recommendations sorted(scores.items(), keylambda x: x[1], reverseTrue)[:top_k] return recommendations 为用户 0 生成推荐 recs recommend(0, rating_matrix, item_similarity) print(为用户 0 的推荐结果:) for item, score in recs: print(f 物品 {item}: 预测评分 {score:.2f})6.4 保存与加载稀疏矩阵from scipy.sparse import save_npz, load_npz 保存为 .npz 格式 save_npz(rating_matrix.npz, rating_matrix) print(评分矩阵已保存) 加载稀疏矩阵 loaded load_npz(rating_matrix.npz) print(加载成功形状:, loaded.shape) print(非零元素数量:, loaded.nnz)7. 性能对比稀疏矩阵 vs 稠密矩阵为了直观展示稀疏矩阵的优势下面进行一个简单的性能对比实验。import numpy as np import time from scipy.sparse import csr_matrix, random 创建一个 10000×10000 的稀疏矩阵密度 0.1% n 10000 density 0.001 生成稀疏矩阵 sparse_mat random(n, n, densitydensity, formatcsr) print(f稀疏矩阵非零元素: {sparse_mat.nnz}) 内存对比 dense_memory n * n * 8 # float64 sparse_memory sparse_mat.data.nbytes sparse_mat.indices.nbytes sparse_mat.indptr.nbytes print(f稠密矩阵内存: {dense_memory / 1024 / 1024:.1f} MB) print(f稀疏矩阵内存: {sparse_memory / 1024 / 1024:.2f} MB) print(f内存节省: {dense_memory / sparse_memory:.0f} 倍) 矩阵乘法性能对比 vector np.random.rand(n) 稀疏矩阵乘法 start time.time() result_sparse sparse_mat vector time_sparse time.time() - start 稠密矩阵乘法仅对小规模演示 print(f稀疏矩阵向量乘法耗时: {time_sparse * 1000:.2f} ms)8. 常见问题与注意事项在使用 SciPy 稀疏矩阵时有几个常见问题需要特别注意。8.1 格式选择不同格式适用于不同场景CSR 适合行访问和矩阵乘法CSC 适合列访问COO 适合构建矩阵DOK 和 LIL 适合动态修改。在实际应用中应根据具体需求选择合适的格式。8.2 避免隐式转换为稠密矩阵对稀疏矩阵执行某些操作时可能会隐式转换为稠密矩阵导致内存爆炸。例如对稀疏矩阵使用布尔索引时应格外小心。from scipy.sparse import csr_matrix matrix csr_matrix([[1, 0], [0, 2]]) 避免这样做可能触发稠密转换 result matrix[matrix 0] 推荐做法使用稀疏矩阵的专用方法 nonzero_indices matrix.nonzero() print(非零元素位置:, nonzero_indices)8.3 稀疏矩阵与 NumPy 的互操作稀疏矩阵与 NumPy 数组之间的转换需要注意数据类型和格式。使用toarray()方法可以转换为稠密数组但应确保矩阵规模在可接受范围内。from scipy.sparse import csr_matrix matrix csr_matrix([[1, 0], [0, 2]]) 转换为 NumPy 数组 dense matrix.toarray() print(NumPy 数组:) print(dense) 从 NumPy 数组创建 new_matrix csr_matrix(dense) print(从 NumPy 数组创建成功)9. 总结本文系统介绍了 SciPy 稀疏矩阵的核心概念、常见格式、创建方法、常用操作和实战案例。稀疏矩阵是处理大规模稀疏数据的重要工具在推荐系统、自然语言处理、图计算等领域有着广泛应用。掌握稀疏矩阵的关键在于理解不同格式的特点和适用场景并根据实际需求选择合适的格式。在实际项目中建议先评估数据的稀疏程度和操作类型再决定使用哪种稀疏矩阵格式从而在内存效率和计算性能之间取得最佳平衡。