web-dev-qa-db-fra.com

Itération à travers un vecteur scipy.sparse (ou matrice)

Je me demande quelle est la meilleure façon d'itérer des entrées non nulles de matrices creuses avec scipy.sparse. Par exemple, si je fais ce qui suit:

from scipy.sparse import lil_matrix

x = lil_matrix( (20,1) )
x[13,0] = 1
x[15,0] = 2

c = 0
for i in x:
  print c, i
  c = c+1

la sortie est

0 
1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13   (0, 0) 1.0
14 
15   (0, 0) 2.0
16 
17 
18 
19  

il semble donc que l'itérateur touche tous les éléments, pas seulement les entrées non nulles. J'ai jeté un œil à l'API

http://docs.scipy.org/doc/scipy/reference/generated/scipy.sparse.lil_matrix.html

et cherché un peu, mais je n'arrive pas à trouver une solution qui fonctionne.

41
RandomGuy

Edit: méthode de bbtrb (en utilisant coo_matrix ) est beaucoup plus rapide que ma suggestion d'origine, en utilisant différent de zéro . La suggestion de Sven Marnach d'utiliser itertools.izip améliore également la vitesse. Le plus rapide actuellement est using_tocoo_izip:

import scipy.sparse
import random
import itertools

def using_nonzero(x):
    rows,cols = x.nonzero()
    for row,col in Zip(rows,cols):
        ((row,col), x[row,col])

def using_coo(x):
    cx = scipy.sparse.coo_matrix(x)    
    for i,j,v in Zip(cx.row, cx.col, cx.data):
        (i,j,v)

def using_tocoo(x):
    cx = x.tocoo()    
    for i,j,v in Zip(cx.row, cx.col, cx.data):
        (i,j,v)

def using_tocoo_izip(x):
    cx = x.tocoo()    
    for i,j,v in itertools.izip(cx.row, cx.col, cx.data):
        (i,j,v)

N=200
x = scipy.sparse.lil_matrix( (N,N) )
for _ in xrange(N):
    x[random.randint(0,N-1),random.randint(0,N-1)]=random.randint(1,100)

donne ces résultats timeit:

% python -mtimeit -s'import test' 'test.using_tocoo_izip(test.x)'
1000 loops, best of 3: 670 usec per loop
% python -mtimeit -s'import test' 'test.using_tocoo(test.x)'
1000 loops, best of 3: 706 usec per loop
% python -mtimeit -s'import test' 'test.using_coo(test.x)'
1000 loops, best of 3: 802 usec per loop
% python -mtimeit -s'import test' 'test.using_nonzero(test.x)'
100 loops, best of 3: 5.25 msec per loop
61
unutbu

Le moyen le plus rapide devrait être de convertir en coo_matrix:

cx = scipy.sparse.coo_matrix(x)

for i,j,v in Zip(cx.row, cx.col, cx.data):
    print "(%d, %d), %s" % (i,j,v)
32
bbtrb

Pour boucler une variété de matrices clairsemées à partir du scipy.sparse section de code J'utiliserais cette petite fonction wrapper (notez que pour Python-2, vous êtes encouragé à utiliser xrange et izip pour de meilleures performances sur de grandes matrices):

from scipy.sparse import *
def iter_spmatrix(matrix):
    """ Iterator for iterating the elements in a ``scipy.sparse.*_matrix`` 

    This will always return:
    >>> (row, column, matrix-element)

    Currently this can iterate `coo`, `csc`, `lil` and `csr`, others may easily be added.

    Parameters
    ----------
    matrix : ``scipy.sparse.sp_matrix``
      the sparse matrix to iterate non-zero elements
    """
    if isspmatrix_coo(matrix):
        for r, c, m in Zip(matrix.row, matrix.col, matrix.data):
            yield r, c, m

    Elif isspmatrix_csc(matrix):
        for c in range(matrix.shape[1]):
            for ind in range(matrix.indptr[c], matrix.indptr[c+1]):
                yield matrix.indices[ind], c, matrix.data[ind]

    Elif isspmatrix_csr(matrix):
        for r in range(matrix.shape[0]):
            for ind in range(matrix.indptr[r], matrix.indptr[r+1]):
                yield r, matrix.indices[ind], matrix.data[ind]

    Elif isspmatrix_lil(matrix):
        for r in range(matrix.shape[0]):
            for c, d in Zip(matrix.rows[r], matrix.data[r]):
                yield r, c, d

    else:
        raise NotImplementedError("The iterator for this sparse matrix has not been implemented")
2
zeroth

tocoo () matérialise la matrice entière dans une structure différente, qui n'est pas le MO préféré pour python 3. Vous pouvez également considérer cet itérateur, qui est particulièrement utile pour les grandes matrices.

from itertools import chain, repeat
def iter_csr(matrix):
  for (row, col, val) in Zip(
    chain(*(
          repeat(i, r)
          for (i,r) in enumerate(comparisons.indptr[1:] - comparisons.indptr[:-1])
    )),
    matrix.indices,
    matrix.data
  ):
    yield (row, col, val)

Je dois admettre que j'utilise beaucoup de constructions python qui devraient éventuellement être remplacées par des constructions numpy (en particulier énumérer).

NB :

In [43]: t=time.time(); sum(1 for x in rather_dense_sparse_matrix.data); print(time.time()-t)
52.48686504364014
In [44]: t=time.time(); sum(1 for x in enumerate(rather_dense_sparse_matrix.data)); print(time.time()-t)
70.19013023376465
In [45]: rather_dense_sparse_matrix
<99829x99829 sparse matrix of type '<class 'numpy.float16'>'
with 757622819 stored elements in Compressed Sparse Row format>

Alors oui, énumérer est un peu lent (ish)

Pour l'itérateur:

In [47]: it = iter_csr(rather_dense_sparse_matrix)
In [48]: t=time.time(); sum(1 for x in it); print(time.time()-t)
113.something something

Vous décidez donc si cette surcharge est acceptable, dans mon cas, le tocoo a provoqué les MemoryOverflows.

À mon humble avis, un tel itérateur devrait faire partie de l'interface csr_matrix, semblable à items () dans un dict () :)

1
Herbert

J'ai eu le même problème et en fait, si votre préoccupation n'est que la vitesse, le moyen le plus rapide (plus d'un ordre de grandeur plus rapide) est de convertir la matrice clairsemée en une matrice dense (x.todense ()), et d'itérer sur la valeur non nulle éléments dans la matrice dense. (Bien que, bien sûr, cette approche nécessite beaucoup plus de mémoire)

1
Davide C

Essayez filter(lambda x:x, x) au lieu de x.

0
Kabie