在可移植的数据格式中保存/加载scipy稀疏csr_matrix。

95

如何以可移植的格式保存/加载scipy稀疏的csr_matrix?该scipy稀疏矩阵是在Python 3上创建的(Windows 64位),以在Python 2上运行(Linux 64位)。最初,我使用了pickle(使用protocol = 2和fix_imports = True),但这在从Python 3.2.2(Windows 64位)到Python 2.7.2(Windows 32位)时无法正常工作,并出现了以下错误:

TypeError: ('data type not understood', <built-in function _reconstruct>, (<type 'numpy.ndarray'>, (0,), '[98]')).

接下来,我尝试了使用numpy.savenumpy.load,以及scipy.io.mmwrite()scipy.io.mmread(),但这些方法都没有起作用。


2
mmwrite/mmread 应该可以工作,因为它是一个文本文件格式。Linux和Windows之间可能出现的问题可能是行结束符,CRLF vs. LF。 - pv.
10个回答

143

编辑:现在scipy 0.19拥有scipy.sparse.save_npzscipy.sparse.load_npz

from scipy import sparse

sparse.save_npz("yourmatrix.npz", your_matrix)
your_matrix_back = sparse.load_npz("yourmatrix.npz")

对于这两个函数,file参数也可以是文件类对象(即open的结果),而不仅限于文件名。


来自Scipy用户组的答案如下:

csr_matrix有三个数据属性很重要:.data.indices.indptr。所有都是简单的ndarray,因此numpy.save适用于它们。使用numpy.savenumpy.savez保存这三个数组,使用numpy.load加载它们,然后使用以下代码重新创建稀疏矩阵对象:

new_csr = csr_matrix((data, indices, indptr), shape=(M, N))

例如:

def save_sparse_csr(filename, array):
    np.savez(filename, data=array.data, indices=array.indices,
             indptr=array.indptr, shape=array.shape)

def load_sparse_csr(filename):
    loader = np.load(filename)
    return csr_matrix((loader['data'], loader['indices'], loader['indptr']),
                      shape=loader['shape'])

3
Sparse矩阵对象中为什么没有将此功能作为方法实现的想法?虽然scipy.io.savemat方法似乎足够可靠... - mathtick
6
注意:如果在 save_sparse_csr 中的文件名没有 .npz 扩展名,系统将自动添加该扩展名。但在 load_sparse_csr 函数中不会自动执行此操作。 - physicalattraction
@physicalattraction 一个简单的解决方案是在加载器函数的开头添加以下内容: if not filename.endswith('.npz'): filename += '.npz' - Oleksandr Shchur
12
Scipy 1.19现在有scipy.sparse.save_npzload函数。 - hpaulj
3
@hpaulj 可能对新用户有用的纠正答案:版本是scipy 0.19 - P. Camilleri

40
虽然您写了 scipy.io.mmwritescipy.io.mmread,但它们对您无效,我只想补充一下它们的工作原理。这个问题是谷歌搜索排名第一,所以我自己在尝试了 np.savezpickle.dump 之后才转向简单明显的 scipy 函数。它们对我很有效,不应该被那些还没有尝试过的人忽略。
from scipy import sparse, io

m = sparse.csr_matrix([[0,0,0],[1,0,0],[0,1,0]])
m              # <3x3 sparse matrix of type '<type 'numpy.int64'>' with 2 stored elements in Compressed Sparse Row format>

io.mmwrite("test.mtx", m)
del m

newm = io.mmread("test.mtx")
newm           # <3x3 sparse matrix of type '<type 'numpy.int32'>' with 2 stored elements in COOrdinate format>
newm.tocsr()   # <3x3 sparse matrix of type '<type 'numpy.int32'>' with 2 stored elements in Compressed Sparse Row format>
newm.toarray() # array([[0, 0, 0], [1, 0, 0], [0, 1, 0]], dtype=int32)

与其他答案相比,这是最新的解决方案吗? - dineshdileep
是的,目前这是最新的版本。您可以通过在问题下方的选项卡中单击“最旧”的方式按创建时间排序答案。 - Frank Zalkow
当只写入import scipy时,此方法会失败。需要显式地使用from scipy import ioimport scipy.io - blootsvoets
1
这种方法似乎比np.savezcPickle的解决方案慢得多,并且生成的文件大约是原来的3倍。请参见我的答案以获取测试详细信息。 - Dennis Golomazov

29

以下是使用Jupyter Notebook对三个最受欢迎的答案进行性能比较。 输入是一个密度为0.001的1M x 100K的随机稀疏矩阵,包含1亿个非零值:

from scipy.sparse import random
matrix = random(1000000, 100000, density=0.001, format='csr')

matrix
<1000000x100000 sparse matrix of type '<type 'numpy.float64'>'
with 100000000 stored elements in Compressed Sparse Row format>

io.mmwrite / io.mmread

from scipy.sparse import io

%time io.mmwrite('test_io.mtx', matrix)
CPU times: user 4min 37s, sys: 2.37 s, total: 4min 39s
Wall time: 4min 39s

%time matrix = io.mmread('test_io.mtx')
CPU times: user 2min 41s, sys: 1.63 s, total: 2min 43s
Wall time: 2min 43s    

matrix
<1000000x100000 sparse matrix of type '<type 'numpy.float64'>'
with 100000000 stored elements in COOrdinate format>    

Filesize: 3.0G.

注意:格式已从csr更改为coo。

np.savez / np.load

import numpy as np
from scipy.sparse import csr_matrix

def save_sparse_csr(filename, array):
    # note that .npz extension is added automatically
    np.savez(filename, data=array.data, indices=array.indices,
             indptr=array.indptr, shape=array.shape)

def load_sparse_csr(filename):
    # here we need to add .npz extension manually
    loader = np.load(filename + '.npz')
    return csr_matrix((loader['data'], loader['indices'], loader['indptr']),
                      shape=loader['shape'])


%time save_sparse_csr('test_savez', matrix)
CPU times: user 1.26 s, sys: 1.48 s, total: 2.74 s
Wall time: 2.74 s    

%time matrix = load_sparse_csr('test_savez')
CPU times: user 1.18 s, sys: 548 ms, total: 1.73 s
Wall time: 1.73 s

matrix
<1000000x100000 sparse matrix of type '<type 'numpy.float64'>'
with 100000000 stored elements in Compressed Sparse Row format>

Filesize: 1.1G.

cPickle

->

cPickle(Python的pickle模块的C语言实现)

import cPickle as pickle

def save_pickle(matrix, filename):
    with open(filename, 'wb') as outfile:
        pickle.dump(matrix, outfile, pickle.HIGHEST_PROTOCOL)
def load_pickle(filename):
    with open(filename, 'rb') as infile:
        matrix = pickle.load(infile)    
    return matrix    

%time save_pickle(matrix, 'test_pickle.mtx')
CPU times: user 260 ms, sys: 888 ms, total: 1.15 s
Wall time: 1.15 s    

%time matrix = load_pickle('test_pickle.mtx')
CPU times: user 376 ms, sys: 988 ms, total: 1.36 s
Wall time: 1.37 s    

matrix
<1000000x100000 sparse matrix of type '<type 'numpy.float64'>'
with 100000000 stored elements in Compressed Sparse Row format>

Filesize: 1.1G.

注意: cPickle 不适用于非常大的对象(见 这个答案)。 根据我的经验,它不能处理一个大小为 2.7M x 50k 的矩阵,该矩阵具有 2.7 亿个非零值。 np.savez 解决方案效果很好。

结论

(基于 CSR 矩阵的简单测试) cPickle 是最快的方法,但它不能处理非常大的矩阵,np.savez 只稍微慢一些,而 io.mmwrite 则慢得多,生成的文件更大,而且恢复到错误的格式。因此,在这里 np.savez 获胜。


3
注意,至少对于我(Py 2.7.11),这行代码 from scipy.sparse import io 无法运行。相反,只需执行 from scipy import io 即可。文档 - patrick
1
@patrick 谢谢你的更新。导入更改必须已经在 scipy 中完成了。 - Dennis Golomazov

17

11

假设你在两台机器上都安装了scipy,那么你可以直接使用 pickle

但是,在将numpy数组打包成pickle文件时,请确保指定二进制协议,否则你会得到一个巨大的文件。

无论如何,你应该能够执行以下操作:

import cPickle as pickle
import numpy as np
import scipy.sparse

# Just for testing, let's make a dense array and convert it to a csr_matrix
x = np.random.random((10,10))
x = scipy.sparse.csr_matrix(x)

with open('test_sparse_array.dat', 'wb') as outfile:
    pickle.dump(x, outfile, pickle.HIGHEST_PROTOCOL)
你可以使用以下代码加载它:
import cPickle as pickle

with open('test_sparse_array.dat', 'rb') as infile:
    x = pickle.load(infile)

使用pickle是我的原始解决方案(使用protocol=2和fix_imports=True),但它无法从Python 3.2.2转换到Python 2.7.2。已将此信息添加到问题中。 - Henry Thornton
请注意,尽管根据我的答案的简单测试结果看起来这似乎是最快的解决方案,但cPickle无法处理非常大的矩阵(链接)。 - Dennis Golomazov

10

从scipy 0.19.0开始,您可以通过以下方式保存和加载稀疏矩阵:

from scipy import sparse

data = sparse.csr_matrix((3, 4))

#Save
sparse.save_npz('data_sparse.npz', data)

#Load
data = sparse.load_npz("data_sparse.npz")

2

编辑 显然可以很简单地:

def sparse_matrix_tuples(m):
    yield from m.todok().items()

这将生成((i, j), value)元组,非常容易进行序列化和反序列化。不确定与下面用于csr_matrix的代码在性能上如何比较,但它肯定更简单。我将原始答案保留在下面,希望它有所启发。


我想补充一点:对我来说,npz 不太方便,因为我无法将其轻松地导出到非 Python 客户端(例如 PostgreSQL -- 如有更正请指出)。因此,我希望能够获得稀疏矩阵的 CSV 输出(就像您打印稀疏矩阵时会得到的输出一样)。如何实现这一点取决于稀疏矩阵的表示方式。对于 CSR 矩阵,以下代码可以输出 CSV。您可以根据其他表示方法进行调整。

import numpy as np

def csr_matrix_tuples(m):
    # not using unique will lag on empty elements
    uindptr, uindptr_i = np.unique(m.indptr, return_index=True)
    for i, (start_index, end_index) in zip(uindptr_i, zip(uindptr[:-1], uindptr[1:])):
        for j, data in zip(m.indices[start_index:end_index], m.data[start_index:end_index]):
            yield (i, j, data)

for i, j, data in csr_matrix_tuples(my_csr_matrix):
    print(i, j, data, sep=',')

从我的测试结果来看,它的运行速度比当前实现中的save_npz慢了大约两倍。


1
这对我起作用:

import numpy as np
import scipy.sparse as sp
x = sp.csr_matrix([1,2,3])
y = sp.csr_matrix([2,3,4])
np.savez(file, x=x, y=y)
npz = np.load(file)

>>> npz['x'].tolist()
<1x3 sparse matrix of type '<class 'numpy.int64'>'
    with 3 stored elements in Compressed Sparse Row format>

>>> npz['x'].tolist().toarray()
array([[1, 2, 3]], dtype=int64)

这个技巧就是调用 .tolist() 来将形状为 0 的对象数组转换回原始对象。

1

这是我用来保存 lil_matrix 的方法。

import numpy as np
from scipy.sparse import lil_matrix

def save_sparse_lil(filename, array):
    # use np.savez_compressed(..) for compression
    np.savez(filename, dtype=array.dtype.str, data=array.data,
        rows=array.rows, shape=array.shape)

def load_sparse_lil(filename):
    loader = np.load(filename)
    result = lil_matrix(tuple(loader["shape"]), dtype=str(loader["dtype"]))
    result.data = loader["data"]
    result.rows = loader["rows"]
    return result

我必须说,我发现NumPy的np.load(..)非常慢。这是我目前的解决方案,我感觉运行速度要快得多:

from scipy.sparse import lil_matrix
import numpy as np
import json

def lil_matrix_to_dict(myarray):
    result = {
        "dtype": myarray.dtype.str,
        "shape": myarray.shape,
        "data":  myarray.data,
        "rows":  myarray.rows
    }
    return result

def lil_matrix_from_dict(mydict):
    result = lil_matrix(tuple(mydict["shape"]), dtype=mydict["dtype"])
    result.data = np.array(mydict["data"])
    result.rows = np.array(mydict["rows"])
    return result

def load_lil_matrix(filename):
    result = None
    with open(filename, "r", encoding="utf-8") as infile:
        mydict = json.load(infile)
        result = lil_matrix_from_dict(mydict)
    return result

def save_lil_matrix(filename, myarray):
    with open(filename, "w", encoding="utf-8") as outfile:
        mydict = lil_matrix_to_dict(myarray)
        json.dump(mydict, outfile)

0

我被要求以简单通用的格式发送矩阵:

<x,y,value>

我最终得到了这个:
def save_sparse_matrix(m,filename):
    thefile = open(filename, 'w')
    nonZeros = np.array(m.nonzero())
    for entry in range(nonZeros.shape[1]):
        thefile.write("%s,%s,%s\n" % (nonZeros[0, entry], nonZeros[1, entry], m[nonZeros[0, entry], nonZeros[1, entry]]))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接