Zapisz / załaduj scipy sparse csr_matrix w przenośnym formacie danych

80

Jak zapisać / załadować Scipy Sparse csr_matrixw formacie przenośnym? Scipy Sparse matrix jest tworzona w Pythonie 3 (Windows 64-bitowy) do uruchamiania w Pythonie 2 (Linux 64-bitowy). Początkowo użyłem pickle (z protokołem = 2 i fix_imports = True), ale to nie zadziałało, przechodząc z Pythona 3.2.2 (Windows 64-bitowy) do Pythona 2.7.2 (Windows 32-bitowy) i otrzymałem błąd:

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

Następnie wypróbowano numpy.savei numpy.loadjak również scipy.io.mmwrite()i scipy.io.mmread()i żadna z tych metod też nie zadziałała.

Henry Thornton
źródło
2
mmwrite / mmread powinno działać, ponieważ jest to format pliku tekstowego. możliwym problemem w przypadku Linuksa kontra Windows mogą być zakończenia linii, CRLF kontra LF
pv.

Odpowiedzi:

121

edycja: SciPy 1.19 ma teraz scipy.sparse.save_npzi scipy.sparse.load_npz.

from scipy import sparse

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

W przypadku obu funkcji fileargumentem może być również obiekt plikopodobny (tj. Wynik open) zamiast nazwy pliku.


Mam odpowiedź od grupy użytkowników Scipy:

Csr_matrix ma 3 dane atrybuty Dziedzina: .data, .indices, i .indptr. Wszystkie są prostymi ndarrayami, więc numpy.savebędzie na nich działać. Zapisz trzy tablice za pomocą numpy.savelub numpy.savez, załaduj je z powrotem za pomocą numpy.load, a następnie ponownie utwórz rzadki obiekt macierzy za pomocą:

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

Na przykład:

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'])
Henry Thornton
źródło
3
Masz jakiś pomysł, czy istnieje jakiś powód, dla którego nie została ona zaimplementowana jako metoda w rzadkich obiektach macierzy? Jednak metoda scipy.io.savemat wydaje się działać wystarczająco niezawodnie ...
mathtick
6
Uwaga: Jeśli nazwa pliku w save_sparse_csr nie ma rozszerzenia .npz, zostanie ono dodane automatycznie. Nie jest to wykonywane automatycznie w funkcji load_sparse_csr.
fizyczne
@physicalattraction prostym rozwiązaniem jest dodanie tego na początku funkcji ładowarkiif not filename.endswith('.npz'): filename += '.npz'
Alexander Shchur
11
Scipy 1.19 ma teraz scipy.sparse.save_npzi load.
hpaulj
3
@hpaulj Poprawna odpowiedź może być przydatna dla nowych użytkowników: wersja jest scipy 0.19
P. Camilleri
37

Chociaż piszesz scipy.io.mmwritei scipy.io.mmreadnie pracujesz dla Ciebie, chcę tylko dodać, jak one działają. To pytanie brzmi: nie. 1 trafienie w Google, więc sam zacząłem od np.savezi pickle.dumpprzed przejściem do prostych i oczywistych funkcji scipy. Pracują dla mnie i nie powinny być nadzorowane przez tych, którzy jeszcze ich nie wypróbowali.

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)
Frank Zalkow
źródło
Czy to najnowsze rozwiązanie w porównaniu z innymi odpowiedziami?
dineshdileep
Tak, obecnie jest to najnowsza. Możesz uporządkować odpowiedzi według czasu ich utworzenia, klikając najstarsze w zakładce pod pytaniem.
Frank Zalkow,
Ta metoda kończy się niepowodzeniem podczas pisania import scipy. Jawny from scipy import iolub import scipy.iojest potrzebny.
blootsvoets
1
To wydaje się działać znacznie wolniej niż np.savezi cPicklerozwiązań oraz produktów ~ 3x większy plik. Zobacz moją odpowiedź, aby poznać szczegóły testu.
Dennis Golomazov
26

Oto porównanie wydajności trzech najbardziej pozytywnych odpowiedzi przy użyciu notebooka Jupyter. Dane wejściowe to losowa macierz rzadka 1M x 100K o gęstości 0,001, zawierająca 100M wartości niezerowych:

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.

(zwróć uwagę, że format został zmieniony z csr na 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.

Uwaga : cPickle nie działa z bardzo dużymi obiektami (zobacz tę odpowiedź ). Z mojego doświadczenia wynika, że ​​nie działało to dla matrycy 2,7M x 50k z 270M wartościami niezerowymi. np.savezrozwiązanie działało dobrze.

Wniosek

(oparty na tym prostym teście dla macierzy CSR) cPicklejest najszybszą metodą, ale nie działa z bardzo dużymi macierzami, np.savezjest tylko trochę wolniejszy, podczas gdy io.mmwritejest znacznie wolniejszy, tworzy większy plik i przywraca zły format. Więc np.savezjest tutaj zwycięzcą.

Dennis Golomazov
źródło
2
Dzięki! Tylko uwaga, że ​​przynajmniej dla mnie (Py 2.7.11) linia from scipy.sparse import ionie działa. Zamiast tego po prostu zrób from scipy import io. Dokumenty
patrick,
1
@patrick dzięki za aktualizację. Zmiana importu musiała zostać dokonana w scipy.
Dennis Golomazov
11

Zakładając, że masz scipy na obu komputerach, możesz po prostu użyć pickle .

Jednak pamiętaj, aby określić protokół binarny podczas wytrawiania tablic numpy. W przeciwnym razie skończysz z ogromnym plikiem.

W każdym razie powinieneś móc to zrobić:

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)

Następnie możesz załadować go za pomocą:

import cPickle as pickle

with open('test_sparse_array.dat', 'rb') as infile:
    x = pickle.load(infile)
Joe Kington
źródło
używanie pickle było moim oryginalnym rozwiązaniem (z protokołem = 2 i fix_imports = True), ale nie zadziałało przechodząc z Pythona 3.2.2 do Pythona 2.7.2. Dodałem te informacje do pytania.
Henry Thornton
Zwróć uwagę, że choć wydaje się to najszybszym rozwiązaniem (według prostego testu w mojej odpowiedzi ), cPicklenie działa z bardzo dużymi matrycami ( link ).
Dennis Golomazov
9

Począwszy od scipy 0.19.0, możesz zapisywać i ładować rzadkie macierze w ten sposób:

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")
x0s
źródło
2

EDYCJA Najwyraźniej wystarczy:

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

Który da ((i, j), value)krotki, które można łatwo serializować i deserializować. Nie jestem pewien, jak wypada pod względem wydajności w porównaniu z poniższym kodem csr_matrix, ale jest zdecydowanie prostszy. Zostawiam oryginalną odpowiedź poniżej, ponieważ mam nadzieję, że jest pouczająca.


Dodanie moich dwóch centów: dla mnie npznie jest przenośne, ponieważ nie mogę go użyć do łatwego eksportu mojej macierzy do klientów innych niż Python (np. PostgreSQL - cieszę się, że zostałem poprawiony). Więc chciałbym uzyskać wyjście CSV dla rzadkiej macierzy (podobnie jak w print()przypadku rzadkiej macierzy). Sposób osiągnięcia tego zależy od reprezentacji rzadkiej macierzy. W przypadku macierzy CSR poniższy kod wypluwa dane wyjściowe w formacie CSV. Możesz dostosować się do innych reprezentacji.

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

Jest około 2 razy wolniej niż save_npzw obecnej implementacji, z tego co testowałem.

Yuval
źródło
1

To jest to, czego użyłem do zapisania pliku 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

Muszę powiedzieć, że np.load (..) NumPy jest bardzo powolny . To jest moje obecne rozwiązanie, czuję, że działa znacznie szybciej:

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)
dlorch
źródło
1

To działa dla mnie:

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)

Sztuczka polegała na wywołaniu .tolist()konwersji tablicy obiektów shape 0 na oryginalny obiekt.

Thomas Ahle
źródło
0

Poproszono mnie o przesłanie matrycy w prostym i ogólnym formacie:

<x,y,value>

Skończyło się na tym:

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]]))
Guy S.
źródło