Guarde / cargue scipy sparse csr_matrix en formato de datos portátil


80

¿Cómo se guarda / carga un scipy sparse csr_matrixen un formato portátil? La matriz dispersa scipy se crea en Python 3 (Windows de 64 bits) para ejecutarse en Python 2 (Linux de 64 bits). Inicialmente, usé pickle (con protocolo = 2 y fix_imports = True) pero esto no funcionó al pasar de Python 3.2.2 (Windows de 64 bits) a Python 2.7.2 (Windows de 32 bits) y obtuve el error:

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

A continuación, intentó numpy.savey numpy.loadal igual que scipy.io.mmwrite()y scipy.io.mmread()y ninguno de estos métodos funcionó bien.


2
mmwrite / mmread debería funcionar, ya que es un formato de archivo de texto. el posible problema con Linux frente a Windows puede ser el final de línea, CRLF frente a LF
pv.

Respuestas:


121

editar: SciPy 1.19 ahora tiene scipy.sparse.save_npzy scipy.sparse.load_npz.

from scipy import sparse

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

Para ambas funciones, el fileargumento también puede ser un objeto similar a un archivo (es decir, el resultado de open) en lugar de un nombre de archivo.


Recibí una respuesta del grupo de usuarios de Scipy:

Un csr_matrix tiene 3 atributos de datos que la materia: .data, .indices, y .indptr. Todos son ndarrays simples, por numpy.savelo que funcionarán en ellos. Guarde las tres matrices con numpy.saveo numpy.savez, cárguelas de nuevo con numpy.loady luego vuelva a crear el objeto de matriz dispersa con:

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

Así por ejemplo:

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
¿Alguna idea de si hay alguna razón por la que esto no se implementó como un método en los objetos de matriz dispersa? Sin embargo, el método scipy.io.savemat parece funcionar de manera suficientemente confiable ...
mathtick

6
Nota: Si el nombre de archivo en save_sparse_csr no tiene la extensión .npz, se agregará automáticamente. Esto no se hace automáticamente en la función load_sparse_csr.
física

@physicalattraction una solución fácil es agregar esto al comienzo de la función de cargadorif not filename.endswith('.npz'): filename += '.npz'
Alexander Shchur

11
Scipy 1.19 ahora tiene scipy.sparse.save_npzy load.
hpaulj

3
@hpaulj Podría ser útil para los nuevos usuarios responder correctamente: la versión es scipy 0.19
P. Camilleri

37

Aunque escribe scipy.io.mmwritey scipy.io.mmreadno funciona para usted, solo quiero agregar cómo funcionan. Esta pregunta es el no. 1 éxito de Google, así que yo mismo comencé con np.savezy pickle.dumpantes de cambiar a las funciones scipy simples y obvias. Funcionan para mí y no deberían ser supervisados ​​por aquellos que aún no los probaron.

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)

¿Es esta la última solución en comparación con otras respuestas?
dineshdileep

Sí, actualmente es el último. Puede ordenar las respuestas por el momento de la creación haciendo clic en la más antigua en la pestaña debajo de la pregunta.
Frank Zalkow

Este método falla cuando solo se escribe import scipy. Se necesita un explícito from scipy import ioo import scipy.io.
blootsvoets

1
Esto parece funcionar mucho más lento que el np.savezy cPicklesoluciones y productos ~ archivo más grande de 3x. Consulte mi respuesta para conocer los detalles de la prueba.
Dennis Golomazov

26

Aquí hay una comparación de rendimiento de las tres respuestas más votadas usando el cuaderno Jupyter. La entrada es una matriz dispersa aleatoria de 1M x 100K con densidad 0.001, que contiene 100M de valores distintos de cero:

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.

(tenga en cuenta que el formato ha cambiado de csr a 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.

Nota : cPickle no funciona con objetos muy grandes (consulte esta respuesta ). En mi experiencia, no funcionó para una matriz de 2.7M x 50k con valores distintos de cero de 270M. np.savezLa solución funcionó bien.

Conclusión

(basado en esta prueba simple para matrices CSR) cPicklees el método más rápido, pero no funciona con matrices muy grandes, np.savezes solo un poco más lento, mientras que io.mmwritees mucho más lento, produce archivos más grandes y restaura el formato incorrecto. También lo np.savezes el ganador aquí.


2
¡Gracias! Solo una nota que al menos para mí (Py 2.7.11) la línea from scipy.sparse import iono funciona. En su lugar, hazlo from scipy import io. Docs
patrick

1
@patrick gracias por la actualización. El cambio de importación debe haberse realizado en formato scipy.
Dennis Golomazov


11

Suponiendo que tiene scipy en ambas máquinas, puede usar pickle.

Sin embargo, asegúrese de especificar un protocolo binario al decapar matrices numpy. De lo contrario, terminará con un archivo enorme.

En cualquier caso, debería poder hacer esto:

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)

Luego puede cargarlo con:

import cPickle as pickle

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

usar pickle fue mi solución original (con protocol = 2 y fix_imports = True) pero no funcionó al pasar de Python 3.2.2 a Python 2.7.2. He añadido esta información a la pregunta.
Henry Thornton

Tenga en cuenta que, aunque esta parece ser la solución más rápida (de acuerdo con la prueba simple en mi respuesta ), cPickleno funciona con matrices muy grandes ( enlace ).
Dennis Golomazov

9

A partir de scipy 0.19.0, puede guardar y cargar matrices dispersas de esta manera:

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

EDITAR Aparentemente, es bastante simple:

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

Lo que producirá ((i, j), value)tuplas, que son fáciles de serializar y deserializar. No estoy seguro de cómo se compara el rendimiento con el código a continuación csr_matrix, pero definitivamente es más simple. Dejo la respuesta original a continuación ya que espero que sea informativa.


Agregando mis dos centavos: para mí, npzno es portátil ya que no puedo usarlo para exportar mi matriz fácilmente a clientes que no son de Python (por ejemplo, PostgreSQL, me alegro de que me corrijan). Así que me hubiera gustado obtener una salida CSV para la matriz dispersa (de forma muy similar a como la obtendría con print()la matriz dispersa). Cómo lograr esto depende de la representación de la matriz dispersa. Para una matriz CSR, el siguiente código escupe salida CSV. Puede adaptarse para otras representaciones.

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=',')

Es aproximadamente 2 veces más lento que save_npzen la implementación actual, por lo que he probado.


1

Esto es lo que usé para salvar 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

Debo decir que encontré np.load (..) de NumPy muy lento . Esta es mi solución actual, siento que funciona mucho más rápido:

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)

1

Esto funciona para mi:

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)

El truco consistía en llamar .tolist()para convertir la matriz de objetos de forma 0 en el objeto original.


0

Se me pidió que enviara la matriz en un formato simple y genérico:

<x,y,value>

Terminé con esto:

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]]))
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.