2012-12-07 14 views
5

Questa domanda si concentra su numpy.calcolo efficiente del prodotto parafac/CP in numpy

Ho un set di matrici che condividono tutti lo stesso numero di colonne e hanno un numero diverso di righe. Chiamiamoli A, B, C, D, ecc e lascia che le loro dimensioni siano IaxK IbxK, IcxK, ecc

Quello che voglio è calcolare in modo efficiente il tensore IaxIbxIc ... definito come segue: P (ia, ib, ic, id, ie, ...) = \ sum_k A (ia, k) B (ib, k) C (ic, k) ...

Quindi se ho due fattori, finisco con un prodotto a matrice semplice.

Certo che posso calcolare questo "a mano" attraverso prodotti esterni, qualcosa di simile:

def parafac(factors,components=None): 
     ndims = len(factors) 
     ncomponents = factors[0].shape[1] 
     total_result=array([]) 
     if components is None: 
      components=range(ncomponents) 

     for k in components: 
      #for each component (to save memory) 
      result = array([]) 
      for dim in range(ndims-1,-1,-1): 
       #Augments model with next dimension 
       current_dim_slice=[slice(None,None,None)] 
       current_dim_slice.extend([None]*(ndims-dim-1)) 
       current_dim_slice.append(k) 
       if result.size: 
        result = factors[dim].__getitem__(tuple(current_dim_slice))*result[None,...] 
       else: 
        result = factors[dim].__getitem__(tuple(current_dim_slice)) 
      if total_result.size: 
       total_result+=result 
      else: 
       total_result=result 
     return total_result 

Eppure, vorrei qualcosa di molto più computazionalmente efficiente, come basandosi su builtin funzioni NumPy, ma non riesco a trovare funzioni rilevanti, qualcuno può aiutarmi?

Saluti, grazie

risposta

4

Grazie a tutti voi per le vostre risposte, ho trascorso la giornata su questo e alla fine ho trovato la soluzione, così ho posto qui per la cronaca

Questa soluzione richiede NumPy 1.6 e si avvale di einsum, che è potente voodoo magia

fondamentalmente, se si ha fattore = [A, B, C, D] con A, B, C e D matrici con lo stesso numero di colonne, allora sarebbe calcolare il modello parafac utilizzando:

import numpy 
P=numpy.einsum('az,bz,cz,dz->abcd',A,B,C,D) 

così, una riga!

Nel caso generale, io alla fine con questo:

def parafac(factors): 
    ndims = len(factors) 
    request='' 
    for temp_dim in range(ndims): 
     request+=string.lowercase[temp_dim]+'z,' 
    request=request[:-1]+'->'+string.lowercase[:ndims] 
    return einsum(request,*factors) 
+0

È davvero potente voodoo, e anche corre circa il doppio della velocità di quello che ho prodotto – Jaime

+0

Nice. Hai confrontato la velocità di questa versione con quella originale? Ho provato entrambi utilizzando quattro array con forme (10,3), (24,3), (15,3) e (75,3). La tua versione originale impiega circa 2ms, e la versione che usa 'einsum' richiede circa 7.5ms. –

+0

Sembra che einsum possa beneficiare di architetture multicore mentre le mie cose originali no. Inoltre, ho notato sperimentalmente che ha avuto una maggiore scalabilità (i veri casi di interesse sono piuttosto per matrici di alcune migliaia di righe e qualcosa come 50 colonne). Proverò questo – antoine

1

avendo in mente quel prodotto esterno è prodotto Kronecker mascherato il problema dovrebbe essere risolto da questo semplice funzioni:

def outer(vectors): 
    shape=[v.shape[0] for v in vectors] 
    return reduce(np.kron, vectors).reshape(shape) 
def cp2Tensor(l,A): 
    terms=[]  
    for r in xrange(A[0].shape[1]): 
     term=l[r]*outer([A[n][:,r] for n in xrange(len(A))]) 
     terms.append(term) 
    return sum(terms) 

cp2Tensor prende elenco di numeri reali e la lista delle matrici.

Modificato dopo il commento di Jaime.

+0

Non funziona ... Se lo si applica a 2 vettori di dimensioni (5,8) e (4,8), è prendine una nuova (20, 64) che poi provi a rimodellare a (5,4) ... Nel migliore dei casi, ti manca il passo di sommatoria prima di rimodellare, anche se non sono abbastanza sicuro che il risultato sarà ciò che è stato ha chiesto – Jaime

0

Ok, quindi il seguente funziona. Prima un esempio pratico di ciò che sta succedendo ...

a = np.random.rand(5, 8) 
b = np.random.rand(4, 8) 
c = np.random.rand(3, 8) 
ret = np.ones(5,4,3,8) 
ret *= a.reshape(5,1,1,8) 
ret *= b.reshape(1,4,1,8) 
ret *= c.reshape(1,1,3,8) 
ret = ret.sum(axis=-1) 

E una funzione completa

def tensor(elems) : 
    cols = elems[0].shape[-1] 
    n_elems = len(elems) 
    ret = np.ones(tuple([j.shape[0] for j in elems] + [cols])) 
    for j,el in enumerate(elems) : 
     ret *= el.reshape((1,) * j + (el.shape[0],) + 
          (1,) * (len(elems) - j - 1) + (cols,)) 
    return ret.sum(axis=-1) 
Problemi correlati