Comment enregistrer/charger un scipy clairsemé csr_matrix
Dans un format portable? La matrice éparse scipy est créée sur Python 3 (Windows 64 bits) pour fonctionner sur Python 2 (Linux 64 bits). Initialement, j'ai utilisé des cornichons (avec protocol = 2 et fix_imports = True) mais cela n'a pas fonctionné en passant de Python 3.2.2 (Windows 64 bits) à Python 2.7. 2 (Windows 32 bits) et a obtenu l'erreur:
TypeError: ('data type not understood', <built-in function _reconstruct>, (<type 'numpy.ndarray'>, (0,), '[98]')).
Ensuite, essayé numpy.save
Et numpy.load
Ainsi que scipy.io.mmwrite()
et scipy.io.mmread()
et aucune de ces méthodes ne fonctionnait non plus.
modifier: SciPy 1.19 a maintenant scipy.sparse.save_npz
et scipy.sparse.load_npz
.
from scipy import sparse
sparse.save_npz("yourmatrix.npz", your_matrix)
your_matrix_back = sparse.load_npz("yourmatrix.npz")
Pour les deux fonctions, l'argument file
peut également être un objet de type fichier (c'est-à-dire le résultat de open
) au lieu d'un nom de fichier.
Vous avez une réponse du groupe d'utilisateurs Scipy:
Un csr_matrix a 3 attributs de données qui comptent:
.data
,.indices
, et.indptr
. Tous sont de simples ndarrays, doncnumpy.save
y travaillera. Enregistrez les trois tableaux avecnumpy.save
ounumpy.savez
, chargez-les avecnumpy.load
, puis recréez l'objet matrice clairsemée avec:new_csr = csr_matrix((data, indices, indptr), shape=(M, N))
Ainsi, par exemple:
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'])
Bien que vous écriviez, scipy.io.mmwrite
et scipy.io.mmread
ne fonctionne pas pour vous, je veux juste ajouter comment ils fonctionnent. Cette question est le non. 1 hit sur Google, j'ai donc moi-même commencé par np.savez
et pickle.dump
avant de passer aux fonctions scipy simples et évidentes. Ils travaillent pour moi et ne devraient pas être supervisés par ceux qui ne les ont pas encore essayés.
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)
Voici une comparaison des performances des trois réponses les plus votées à l'aide du bloc-notes Jupyter. L'entrée est une matrice clairsemée aléatoire de 1 M x 100 K avec une densité de 0,001, contenant 100 M de valeurs non nulles:
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.
(notez que le format a été changé de 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
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.
Remarque : cPickle ne fonctionne pas avec de très gros objets (voir cette réponse ). D'après mon expérience, cela n'a pas fonctionné pour une matrice de 2,7M x 50k avec 270M de valeurs non nulles. np.savez
la solution a bien fonctionné.
(basé sur ce test simple pour les matrices CSR) cPickle
est la méthode la plus rapide, mais elle ne fonctionne pas avec de très grandes matrices, np.savez
n'est que légèrement plus lent, tandis que io.mmwrite
est beaucoup plus lent, produit un fichier plus gros et restaure au mauvais format. Alors np.savez
est le gagnant ici.
Vous pouvez maintenant utiliser scipy.sparse.save_npz
: https://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.save_npz.html
En supposant que vous ayez scipy sur les deux machines, vous pouvez simplement utiliser pickle
.
Cependant, assurez-vous de spécifier un protocole binaire lors du décapage des tableaux numpy. Sinon, vous vous retrouverez avec un énorme fichier.
En tout cas, vous devriez pouvoir faire ceci:
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)
Vous pouvez ensuite le charger avec:
import cPickle as pickle
with open('test_sparse_array.dat', 'rb') as infile:
x = pickle.load(infile)
Depuis scipy 0.19.0, vous pouvez enregistrer et charger des matrices éparses de cette façon:
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")
Ajout de mes deux cents: pour moi, npz
n'est pas portable car je ne peux pas l'utiliser pour exporter ma matrice facilement vers des clients non-Python (par exemple PostgreSQL - heureux d'être corrigé). J'aurais donc aimé obtenir une sortie CSV pour la matrice clairsemée (un peu comme si vous l'obteniez, vous print()
la matrice clairsemée). Comment y parvenir dépend de la représentation de la matrice clairsemée. Pour une matrice CSR, le code suivant crache la sortie CSV. Vous pouvez vous adapter à d'autres représentations.
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=',')
C'est environ 2 fois plus lent que save_npz
dans l'implémentation actuelle, d'après ce que j'ai testé.
C'est ce que j'ai utilisé pour enregistrer un 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
Je dois dire que j'ai trouvé que le np.load de NumPy (..) était très lent. Ceci est ma solution actuelle, je pense qu'elle fonctionne beaucoup plus rapidement:
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)
On m'a demandé d'envoyer la matrice dans un format simple et générique:
<x,y,value>
Je me suis retrouvé avec ceci:
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]]))
Cela fonctionne pour moi:
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)
L'astuce consistait à appeler .tolist()
pour convertir le tableau d'objets de forme 0 en l'objet d'origine.