拓冰建站拓冰建站
首页 / 资讯中心 / 正文

MXNet CSRNDArray 实战指南:掌握压缩稀疏行存储格式的创建、转换与算子推断

MXNet CSRNDArray 实战指南掌握压缩稀疏行存储格式的创建、转换与算子推断【免费下载链接】mxnetLightweight, Portable, Flexible Distributed/Mobile Deep Learning with Dynamic, Mutation-aware Dataflow Dep Scheduler; for Python, R, Julia, Scala, Go, Javascript and more项目地址: https://gitcode.com/gh_mirrors/mxnet1/mxnetCSRNDArray 是 MXNet 在默认稠密 NDArray 之外提供的压缩稀疏行Compressed Sparse RowCSR格式二维数组专为行数多、列数多、但每行非零元素极少的高维稀疏数据设计。本文以官方 CSR 教程为主体骨架结合仓库中 python/mxnet/ndarray/sparse.py 的实现与 tests/python/unittest/test_sparse_ndarray.py、tests/python/unittest/test_sparse_operator.py 的测试用例完整讲解 CSR 存储原理、csr_matrix/array/tostype等全部创建与转换 API、存储类型推断规则以及 LibSVM 数据加载流程。读完本文你将能够针对推荐系统、文本分类等稀疏场景正确构建 CSRNDArray、在稀疏与稠密格式间自由切换并理解稀疏算子的输出存储类型推断机制。为什么需要 CSRNDArray稀疏数据的内存与计算动机许多真实数据集都由高维稀疏特征向量构成。以推荐系统为例类别与用户数量可达百万量级但每个用户实际产生的购买记录很少因此用户 × 类别矩阵中绝大多数元素都是零典型密度仅约 1%。如果沿用默认稠密结构存储这种矩阵内存和计算资源都会被大量零元素白白消耗。CSRNDArray将矩阵以 CSR 格式存储并让算子走针对稀疏结构特化的算法从而带来两个核心收益文档原文明确给出内存占用显著降低只保存非零值及其位置信息部分运算显著加速例如稀疏矩阵与稠密向量的乘法dot(csr, dense)。需要特别强调的是CSR 格式的设计目标原文档加粗强调面向列数很多、且每行只有少量非零元素的二维矩阵。如果你的矩阵密度很高CSR 反而可能因为额外的indices/indptr元数据而得不偿失。CSRNDArray与 SciPy 的 CSR 实现有相似之处但它继承了 NDArray 的非阻塞异步求值与自动并行化能力——这两点在 SciPy 的 CSR 版本中并不存在。也正是 CSRNDArray 的引入给 NDArray 家族带来了一个新的属性stypestorage type存储类型标识。现在除了常见的ndarray.shape、ndarray.dtype、ndarray.context你还可以查询ndarray.stype典型稠密 NDArray 的stype值为defaultCSRNDArray 的stype值为csr。这一点在测试文件 tests/python/unittest/test_sparse_ndarray.py 中被反复验证例如其中通过mx.nd.ones(shape).tostype(stype)构造各存储类型数组进行对比测试。前置条件与运行环境要完整运行本文示例需要MXNet按操作系统参考仓库根目录 README.md 中的安装说明例如pip install mxnet。Jupyter教程中的多数代码以交互式形式呈现可用pip install jupyter安装。NDArray 基础建议先熟悉 MXNet 的 NDArray 基本操作。SciPy可选文档中有一节示例用 SciPy 构造 CSR 矩阵未安装时该节代码会被try/except ImportError跳过。GPU可选文档的 GPU 一节依赖 GPU无 GPU 时把变量gpu_device设为mx.cpu()即可。压缩稀疏行矩阵的存储原理一个 CSRNDArray 用三条独立的 1D 数组表示一个 2D 矩阵data、indptr与indices。行i的列索引存放在indices[indptr[i]:indptr[i1]]按升序排列对应的非零值存放在data[indptr[i]:indptr[i1]]data矩阵的 CSR 数据数组按行优先顺序保存全部非零元素indices矩阵的 CSR 索引数组保存data中每个非零元素的列号indptr矩阵的 CSR 索引指针数组保存矩阵每行第一个非零元素在data中的偏移。一个 3×4 矩阵的手工压缩示例给定矩阵[[7, 0, 8, 0] [0, 0, 0, 0] [0, 9, 0, 0]]计算 data按行优先剔除所有零得到data [7, 8, 9]。计算 indices遍历data记录每个元素所在的列号——7 在第 0 列、8 在第 2 列、9 在第 1 列得到indices [0, 2, 1]。计算 indptr它记录每行第一个非零元素在data中的偏移且恒以 0 开头indptr[0] 0。后续每个值是截止到该行的非零元素累计个数第一行有 2 个非零 →indptr[1] 2第二行全零累计仍为 2 →indptr[2] 2第三行有 1 个非零累计为 3 →indptr[3] 3。于是indptr [0, 2, 2, 3]重建验证第一行用data[0:2]与indices[0:2]第二行全零行用data[2:2]与indices[2:2]空切片第三行用data[2:3]与indices[2:3]。注意 MXNet 的两条硬性约束文档与 python/mxnet/ndarray/sparse.py 的 docstring 一致同一行的列索引必须按升序排列且同一行不允许出现重复列索引。创建 CSRNDArray 的五种方式mx.nd.sparse.csr_matrix是核心构造入口其 docstringpython/mxnet/ndarray/sparse.py列出了完整的分派规则实现上按arg1的类型与元组长度走不同分支。1. 由 (data, indices, indptr) 三元组创建传入 Python 列表import mxnet as mx # Create a CSRNDArray with python lists shape (3, 4) data_list [7, 8, 9] indices_list [0, 2, 1] indptr_list [0, 2, 2, 3] a mx.nd.sparse.csr_matrix((data_list, indices_list, indptr_list), shapeshape) # Inspect the matrix a.asnumpy()输出array([[ 7., 0., 8., 0.], [ 0., 0., 0., 0.], [ 0., 9., 0., 0.]], dtypefloat32)传入 NumPy 数组import numpy as np # Create a CSRNDArray with numpy arrays data_np np.array([7, 8, 9]) indptr_np np.array([0, 2, 2, 3]) indices_np np.array([0, 2, 1]) b mx.nd.sparse.csr_matrix((data_np, indices_np, indptr_np), shapeshape) b.asnumpy()输出array([[7, 0, 8, 0], [0, 0, 0, 0], [0, 9, 0, 0]])两者内容完全一致。从源码看arg_len 3时进入_csr_matrix_from_definitionpython/mxnet/ndarray/sparse.pydata、indices、indptr会被分别转换为对应 dtype 的 NDArrayindptr与indices使用_STORAGE_AUX_TYPES[csr]规定的辅助类型随后做形状校验。shape参数可选——若省略会按(len(indptr) - 1, max(indices) 1)自动推断。2. 由 SciPy CSR 矩阵创建try: import scipy.sparse as spsp # generate a csr matrix in scipy c spsp.csr.csr_matrix((data_np, indices_np, indptr_np), shapeshape) # create a CSRNDArray from a scipy csr object d mx.nd.sparse.array(c) print(d:{}.format(d.asnumpy())) except ImportError: print(scipy package is required)输出d:[[7 0 8 0] [0 0 0 0] [0 9 0 0]]3. 由稠密数据压缩得到tostype当只有稠密数据、尚未手工计算indices/indptr时用tostype一键压缩big_array mx.nd.round(mx.nd.random.uniform(low0, high1, shape(1000, 100))) print(big_array) big_array_csr big_array.tostype(csr) # Access indices array indices big_array_csr.indices # Access indptr array indptr big_array_csr.indptr # Access data array data big_array_csr.data # The total size of data, indices and indptr arrays is much lesser than the dense big_array!由于该 1000×100 矩阵只有约一半元素非零round 后为 0/1data、indices、indptr三条数组的总大小已明显小于稠密版本——密度越低节省越显著。4. 由另一个 CSRNDArray 创建并指定 dtypemx.nd.sparse.array接受 CSRNDArray 或scipy.sparse.csr_matrix作为源python/mxnet/ndarray/sparse.py可用dtype指定元素类型接受 NumPy 类型默认float32# Float32 is used by default e mx.nd.sparse.array(a) # Create a 16-bit float array f mx.nd.array(a, dtypenp.float16) (e.dtype, f.dtype)输出(numpy.float32, numpy.float16)5. 其他便捷构造方式源码补充csr_matrix的 docstring 还支持csr_matrix((M, N))构造形状为(M, N)的空 CSRNDArrayarg_len 2且第二个元素不是(row, col)元组时走empty(csr, ...)分支csr_matrix((data, (row, col)))以 COO 三元组输入内部经scipy.sparse.coo_matrix(...).tocsr()转换python/mxnet/ndarray/sparse.pycsr_matrix(D)/csr_matrix(S)由稠密数组D内部dns.tostype(csr)或稀疏数组S直接构造。测试文件 tests/python/unittest/test_sparse_ndarray.py 中还覆盖了错误形状、非法tostype等边界情况的ValueError断言。检查 CSRNDArray 的内部结构CSR 数组提供了多种检查手段.asnumpy()、.data、.indices、.indptr。.asnumpy()把内容填充到稠密numpy.ndarray后返回a.asnumpy()array([[ 7., 0., 8., 0.], [ 0., 0., 0., 0.], [ 0., 9., 0., 0.]], dtypefloat32)直接访问内部存储注意data、indices、indptr各自返回一个独立的 NDArraystype则是csr# Access data array data a.data # Access indices array indices a.indices # Access indptr array indptr a.indptr {a.stype: a.stype, data:data, indices:indices, indptr:indptr}{a.stype: csr, data: [ 7. 8. 9.] NDArray 3 cpu(0), indices: [0 2 1] NDArray 3 cpu(0), indptr: [0 2 2 3] NDArray 4 cpu(0)}存储类型转换tostype 与 cast_storageMXNet 提供两条转换路径。用tostype方法python/mxnet/ndarray/sparse.py# Create a dense NDArray ones mx.nd.ones((2,2)) # Cast the storage type from default to csr csr ones.tostype(csr) # Cast the storage type from csr to default dense csr.tostype(default) {csr:csr, dense:dense}{csr: CSRNDArray 2x2 cpu(0), dense: [[ 1. 1.] [ 1. 1.]] NDArray 2x2 cpu(0)}用cast_storage算子在mx.nd.sparse命名空间下# Create a dense NDArray ones mx.nd.ones((2,2)) # Cast the storage type to csr csr mx.nd.sparse.cast_storage(ones, csr) # Cast the storage type to default dense mx.nd.sparse.cast_storage(csr, default) {csr:csr, dense:dense}输出与上一段完全一致。cast_storage在测试文件 tests/python/unittest/test_sparse_operator.py 的test_cast_storage_ex中有专项验证。拷贝语义copy / copyto / 切片copy()对数组及其数据做深拷贝返回新数组copyto方法与切片操作符[]也可以把数据深拷贝到已存在的目标数组。a mx.nd.ones((2,2)).tostype(csr) b a.copy() c mx.nd.sparse.zeros(csr, (2,2)) c[:] a d mx.nd.sparse.zeros(csr, (2,2)) a.copyto(d) {b is a: b is a, b.asnumpy():b.asnumpy(), c.asnumpy():c.asnumpy(), d.asnumpy():d.asnumpy()}{b is a: False, b.asnumpy(): array([[ 1., 1.], [ 1., 1.]], dtypefloat32), c.asnumpy(): array([[ 1., 1.], [ 1., 1.]], dtypefloat32), d.asnumpy(): array([[ 1., 1.], [ 1., 1.]], dtypefloat32)}关键语义当源数组与目标数组的存储类型不一致时用copyto或切片[]拷贝不会改变目标数组的存储类型——目标保持自身原有的stypee mx.nd.sparse.zeros(csr, (2,2)) f mx.nd.sparse.zeros(csr, (2,2)) g mx.nd.ones(e.shape) e[:] g g.copyto(f) {e.stype:e.stype, f.stype:f.stype, g.stype:g.stype}{e.stype: csr, f.stype: csr, g.stype: default}即稠密源g拷入稀疏目标e/f后目标仍是csr。copyto的实现位于 python/mxnet/ndarray/sparse.py。索引与切片仅支持沿第 0 轴CSRNDArray 支持用[]沿axis 0行方向切片返回拷贝出的新 CSRNDArraya mx.nd.array(np.arange(6).reshape(3,2)).tostype(csr) b a[1:2].asnumpy() c a[:].asnumpy() {a:a, b:b, c:c}{a: CSRNDArray 3x2 cpu(0), b: array([[ 2., 3.]], dtypefloat32), c: array([[ 0., 1.], [ 2., 3.], [ 4., 5.]], dtypefloat32)}限制文档明确CSRNDArray 目前不支持多维索引也不支持沿特定轴非第 0 轴切片。相应的切片测试可参见 tests/python/unittest/test_sparse_operator.py 中的test_sparse_slice。稀疏算子与存储类型推断对稀疏数组有特化实现的算子集中在mx.nd.sparse命名空间。以稀疏矩阵与稠密向量相乘为例shape (3, 4) data [7, 8, 9] indptr [0, 2, 2, 3] indices [0, 2, 1] a mx.nd.sparse.csr_matrix((data, indices, indptr), shapeshape) # a csr matrix as lhs rhs mx.nd.ones((4, 1)) # a dense vector as rhs out mx.nd.sparse.dot(a, rhs) # invoke sparse dot operator specialized for dot(csr, dense) {out:out}{out: [[ 15.] [ 0.] [ 9.]] NDArray 3x1 cpu(0)}结果验证第一行7×1 8×1 15第二行全零为 0第三行9×1 9。sparse.dot在 tests/python/unittest/test_sparse_operator.py 的test_sparse_dot中有大量参数组合测试包括transpose_a/transpose_b与forward_stype推断、确定性测试等。存储类型推断规则对任意稀疏算子输出数组的存储类型由输入推断得出。你可以直接查输出数组的stype属性验证b a * 2 # b will be a CSRNDArray since zero multiplied by 2 is still zero c a mx.nd.ones(shape(3, 4)) # c will be a dense NDArray {b.stype:b.stype, c.stype:c.stype}{b.stype: csr, c.stype: default}直观解释a * 2保持零元素仍为零输出自然保持csr而a ones(...)把零元素变成了非零只能以稠密default输出。存储回退Storage Fallback机制对于没有稀疏特化实现的算子仍然可以喂入稀疏输入但要付出一定性能代价——MXNet 的稠密算子要求所有输入与输出都是稠密格式。具体规则文档原文提供稀疏输入时MXNet 会临时把稀疏输入转换为稠密格式再执行稠密算子提供稀疏输出时MXNet 会把稠密算子产生的稠密输出转换回指定的稀疏格式。示例e mx.nd.sparse.zeros(csr, a.shape) d mx.nd.log(a) # dense operator with a sparse input e mx.nd.log(a, oute) # dense operator with a sparse output {a.stype:a.stype, d.stype:d.stype, e.stype:e.stype} # stypes of a and e will be not changed{a.stype: csr, d.stype: default, e.stype: csr}a与e的存储类型保持不变d因稠密算子输出为稠密default而e因为是显式传入的稀疏输出参数稠密结果被转回csr。存储回退的完整行为在 tests/python/unittest/test_sparse_operator.py 的test_sparse_storage_fallback中有系统验证。注意发生存储回退时会有 warning 打印如果在 Jupyter 中运行警告会输出到终端控制台而非 notebook 单元格内。数据加载NDArrayIter 与 LibSVMIter用 NDArrayIter 分批读取 CSRNDArray# Create the source CSRNDArray data mx.nd.array(np.arange(36).reshape((9,4))).tostype(csr) labels np.ones([9, 1]) batch_size 3 dataiter mx.io.NDArrayIter(data, labels, batch_size, last_batch_handlediscard) # Inspect the data batches [batch.data[0] for batch in dataiter][ CSRNDArray 3x4 cpu(0), CSRNDArray 3x4 cpu(0), CSRNDArray 3x4 cpu(0)]NDArrayIter的 docstringpython/mxnet/io/io.py明确支持mx.nd.sparse.CSRNDArray与scipy.sparse.csr_matrix作为数据源。last_batch_handlediscard表示丢弃无法凑满一个 batch 的尾部数据。用 LibSVMIter 加载 libsvm 格式文件libsvm 文件格式为label col_idx1:value1 col_idx2:value2 ... col_idxN:valueN每行记录一个样本的标签以及非零元素的列索引与取值。例如对 6 列矩阵1 2:1.5 4:-3.5表示标签为1样本数据为[[0, 0, 1.5, 0, -3.5, 0]]。# Create a sample libsvm file in current working directory import os cwd os.getcwd() data_path os.path.join(cwd, data.t) with open(data_path, w) as fout: fout.write(1.0 0:1 2:2\n) fout.write(1.0 0:3 5:4\n) fout.write(1.0 2:5 8:6 9:7\n) fout.write(1.0 3:8\n) fout.write(-1 0:0.5 9:1.5\n) fout.write(-2.0\n) fout.write(-3.0 0:-0.6 1:2.25 2:1.25\n) fout.write(-3.0 1:2 2:-1.25\n) fout.write(4 2:-1.2\n) # Load CSRNDArrays from the file data_train mx.io.LibSVMIter(data_libsvmdata_path, data_shape(10,), label_shape(1,), batch_size3) for batch in data_train: print(data_train.getdata()) print(data_train.getlabel())CSRNDArray 3x10 cpu(0) [ 1. 1. 1.] NDArray 3 cpu(0) CSRNDArray 3x10 cpu(0) [ 1. -1. -2.] NDArray 3 cpu(0) CSRNDArray 3x10 cpu(0) [-3. -3. 4.] NDArray 3 cpu(0)可以看到每个 batch 的数据都是CSRNDArray标签是稠密 NDArray。LibSVMIter在 C 侧的实现在 src/io/iter_libsvm.cc其参数包括data_libsvm零基索引的 LibSVM 数据文件或目录路径、label_libsvm可选的独立标签文件等且注册说明指出它只支持round_batchTrue的批处理模式。两个易错点文档明确文件中的列索引每行必须按升序排列列索引是**零基zero-based**的而不是 libsvm 惯例中常见的一基one-based。上面的示例文件正是从第 0 列开始编号的。进阶主题GPU 支持默认情况下 CSRNDArray 的算子运行在 CPU 上。要在 GPU 上创建 CSRNDArray需显式指定 contextimport sys gpu_devicemx.gpu() # Change this to mx.cpu() in absence of GPUs. try: a mx.nd.sparse.zeros(csr, (100, 100), ctxgpu_device) a except mx.MXNetError as err: sys.stderr.write(str(err))如果没有 GPU这段代码会抛出错误此时把gpu_device改为mx.cpu()即可在 CPU 上运行。延伸阅读稀疏相关 API 完整文档见mx.nd.sparse命名空间源码入口为 python/mxnet/ndarray/sparse.py稀疏 NDArray 与稀疏算子的测试用例见 tests/python/unittest/test_sparse_ndarray.py 与 tests/python/unittest/test_sparse_operator.py继续学习如何用稀疏符号训练线性回归模型参见同目录下的 train.md仓库还提供了更丰富的稀疏实战示例例如 example/sparse 目录下的 factorization_machine、linear_classification、matrix_factorization 与 wide_deep 等案例。【免费下载链接】mxnetLightweight, Portable, Flexible Distributed/Mobile Deep Learning with Dynamic, Mutation-aware Dataflow Dep Scheduler; for Python, R, Julia, Scala, Go, Javascript and more项目地址: https://gitcode.com/gh_mirrors/mxnet1/mxnet创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门