2015-11-18 23 views
5

Ho scritto del codice in Python che funziona bene ma è molto lento; Penso a causa dei cicli for. Spero che si possano accelerare le seguenti operazioni usando i comandi di numpy. Fammi definire l'obiettivo.vettorizzazione numpy invece che per loop

Supponiamo di disporre di un array numpy 2D all_CMs di dimensioni row x col. Ad esempio, considera una matrice 6 x 11 (vedi disegno sotto).

  1. voglio calcolare la media per tutte le righe, cioè somma ⱼ aᵢⱼ risultato un array. Questo, ovviamente, può essere fatto facilmente. (Chiamo questo valore CM_tilde)

  2. Ora, per ogni fila voglio calcolare la media di alcuni valori selezionati, ovvero tutti i valori di sotto di una certa soglia calcolando la somma e dividendolo per il numero di tutte le colonne (N). Se il valore supera questa soglia definita, viene aggiunto il valore CM_tilde (media dell'intera riga). Questo valore è chiamato CM

  3. Successivamente, il valore CM viene sottratto da ciascun elemento della riga

Inoltre voglio avere una matrice NumPy o lista in cui sono elencati tutti quei CM valori .

La figura:

figure

Il seguente codice funziona, ma molto lento (soprattutto se gli array ricevendo grande)

CM_tilde = np.mean(data, axis=1) 
N = data.shape[1] 
data_cm = np.zeros((data.shape[0], data.shape[1], data.shape[2])) 
all_CMs = np.zeros((data.shape[0], data.shape[2])) 
for frame in range(data.shape[2]): 
    for row in range(data.shape[0]): 
     CM=0 
     for col in range(data.shape[1]): 
      if data[row, col, frame] < (CM_tilde[row, frame]+threshold): 
       CM += data[row, col, frame] 
      else: 
       CM += CM_tilde[row, frame] 
     CM = CM/N 
     all_CMs[row, frame] = CM 
     # calculate CM corrected value 
     for col in range(data.shape[1]): 
      data_cm[row, col, frame] = data[row, col, frame] - CM 
    print "frame: ", frame 
return data_cm, all_CMs 

Tutte le idee?

+0

Nel passaggio 2, si sostituisce essenzialmente qualsiasi valore che è superiore alla soglia dal CM_tilde, e quindi * * calcolare la media su tutta la fila, compresi i valori sostituiti? – Evert

+0

Inizia usando 'np.where' per sostituire il ciclo for. Quindi, utilizzando la trasmissione, è possibile rimuovere i 2 anelli esterni. Vedere la documentazione di [dove] (http://docs.scipy.org/doc/numpy-1.10.1/reference/generated/numpy.where.html) – mtadd

risposta

12

E 'abbastanza facile da vettorizzare quello che stai facendo:

import numpy as np 

#generate dummy data 
nrows=6 
ncols=11 
nframes=3 
threshold=0.3 
data=np.random.rand(nrows,ncols,nframes) 

CM_tilde = np.mean(data, axis=1) 
N = data.shape[1] 

all_CMs2 = np.mean(np.where(data < (CM_tilde[:,None,:]+threshold),data,CM_tilde[:,None,:]),axis=1) 
data_cm2 = data - all_CMs2[:,None,:] 

Confrontando questo con gli originali:

In [684]: (data_cm==data_cm2).all() 
Out[684]: True 

In [685]: (all_CMs==all_CMs2).all() 
Out[685]: True 

La logica è che lavoriamo con array di dimensioni [nrows,ncols,nframes] contemporaneamente. Il trucco principale consiste nell'utilizzare la trasmissione Python, trasformando CM_tilde della dimensione [nrows,nframes] in CM_tilde[:,None,:] della dimensione [nrows,1,nframes]. Python utilizzerà quindi gli stessi valori per ogni colonna, poiché si tratta di una dimensione singleton di questo CM_tilde modificato.

Utilizzando np.where scegliamo (sulla base del threshold) se vogliamo ottenere il corrispondente valore di data, o, ancora, il valore della trasmissione CM_tilde. Un nuovo utilizzo di np.mean ci consente di calcolare all_CMs2.

Nella fase finale abbiamo utilizzato la trasmissione sottraendo direttamente questo nuovo all_CMs2 dagli elementi corrispondenti di data.

Potrebbe aiutare a vettorializzare il codice in questo modo osservando gli indici impliciti delle variabili temporanee. Quello che voglio dire è che la tua variabile temporanea CM vive all'interno di un ciclo su [nrows,nframes] e il suo valore viene reimpostato ad ogni iterazione. Ciò significa che CM è in effetti una quantità CM[row,frame] (successivamente assegnata esplicitamente all'array 2d all_CMs) e da qui è facile vedere che è possibile costruirla sommando una quantità appropriata di CMtmp[row,col,frames] lungo la sua dimensione di colonna. Se è utile, puoi nominare la parte np.where(...) come CMtmp per questo scopo e quindi calcolare np.mean(CMtmp,axis=1) da quello. Lo stesso risultato, ovviamente, ma probabilmente più trasparente.

+0

Grazie mille; questo è molto più veloce rispetto ai loop – pallago

+1

10001 è un bel valore per il rappresentante, sarebbe un peccato se qualcuno lo sottoponesse a downvotes. –

+0

@BhargavRao \ o/grazie, signore! :) Oppure, grazie per non downvoting: D –

1

Ecco la mia vettorizzazione della funzione. Ho lavorato da dentro e ho commentato le versioni precedenti mentre procedevo. Quindi il primo ciclo che ho vettorializzato ha contrassegni di commento ###.

Non è così pulito e ben ragionato come risposta @Andras's, ma si spera che sia istruttivo, dando un'idea di come è possibile affrontare questo problema in modo incrementale.

def foo2(data, threshold): 
    CM_tilde = np.mean(data, axis=1) 
    N = data.shape[1] 
    #data_cm = np.zeros((data.shape[0], data.shape[1], data.shape[2])) 
    ##all_CMs = np.zeros((data.shape[0], data.shape[2])) 
    bmask = data < (CM_tilde[:,None,:] + threshold) 
    CM = np.zeros_like(data) 
    CM[:] = CM_tilde[:,None,:] 
    CM[bmask] = data[bmask] 
    CM = CM.sum(axis=1) 
    CM = CM/N 
    all_CMs = CM.copy() 
    """ 
    for frame in range(data.shape[2]): 
     for row in range(data.shape[0]): 
      ###print(frame, row) 
      ###mask = data[row, :, frame] < (CM_tilde[row, frame]+threshold) 
      ###print(mask) 
      ##mask = bmask[row,:,frame] 
      ##CM = data[row, mask, frame].sum() 
      ##CM += (CM_tilde[row, frame]*(~mask)).sum() 

      ##CM = CM/N 
      ##all_CMs[row, frame] = CM 
      ## calculate CM corrected value 
      #for col in range(data.shape[1]): 
      # data_cm[row, col, frame] = data[row, col, frame] - CM[row,frame] 
     print "frame: ", frame 
    """ 
    data_cm = data - CM[:,None,:] 
    return data_cm, all_CMs 

uscita corrisponde per questo piccolo banco di prova, che più di tutto mi ha aiutato a ottenere le giuste dimensioni.

threshold = .1 
data = np.arange(4*3*2,dtype=float).reshape(4,3,2) 
Problemi correlati