Come esprimere questa espressione complicata usando sezioni intorpidite


14

Desidero attuare la seguente espressione in Python: dove ed sono matrici NumPy di taglia , e è una matrice numpy di dimensioni . La dimensione potrebbe arrivare a circa 10000 e la funzione fa parte di un circuito interno che verrà valutato molte volte, quindi la velocità è importante.

Xio=Σj=1io-1Kio-j,jun'io-jun'j,
XynKn×nn

Idealmente, vorrei evitare del tutto un ciclo for, anche se immagino che non ce ne sia la fine. Il problema è che sto avendo problemi a vedere come farlo senza avere un paio di loop nidificati, e questo probabilmente lo renderà piuttosto lento.

Qualcuno può vedere come esprimere l'equazione sopra usando numpy in un modo che sia efficiente e preferibilmente anche leggibile? Più in generale, qual è il modo migliore per affrontare questo genere di cose?


Ho avuto una domanda simile un paio di giorni fa. L'ho chiesto a StackOverflow. Dai un'occhiata a questo post . Uso scipy.weave invece di cython. Qualcuno sa se questo fa qualche differenza (considerevole) nelle prestazioni?
settembre

Risposte:


17

Ecco la soluzione Numba. Sulla mia macchina la versione Numba è> 1000 volte più veloce della versione Python senza il decoratore (per una matrice 200x200, 'k' e un vettore di 200 lunghezze 'a'). Puoi anche usare il decoratore @autojit che aggiunge circa 10 microsecondi per chiamata in modo che lo stesso codice funzioni con più tipi.

from numba import jit, autojit

@jit('f8[:](f8[:,:],f8[:])')
#@autojit
def looped_ver(k, a):
    x = np.empty_like(a)
    for i in range(x.size):
        sm = 0.0
        for j in range(0, i+1):
            sm += k[i-j,j] * a[i-j] * a[j]
        x[i] = sm
    return x

Divulgazione: sono uno degli sviluppatori Numba.


Grazie, sembra piuttosto semplice. Non sapevo nemmeno di numba! Cython, PyPy, Numba ... è un mondo confuso.
Nathaniel

3
Travis, molto simpatico, ti dispiace aggiungere una nota in fondo alla tua risposta che sei uno degli sviluppatori numba?
Aron Ahmadia

1
Con , la versione di Cython è anche molto più veloce rispetto al Python in loop (~ 700x, per me). Sarei curioso di sapere come questa prestazione cambia con matrici più grandi e se sperimentano gli stessi colli di bottiglia (di memoria?). n=200
Nat Wilson,

@NatWilson - se lo fai come una domanda su Scicomp, sarei felice di provare ad affrontarlo per te :)
Aron Ahmadia

4

Ecco un inizio. Innanzitutto, mi scuso per eventuali errori.

Ho sperimentato un paio di approcci diversi. Ero un po 'confuso dai limiti della somma - il limite superiore dovrebbe essere , piuttosto che i - 1 ?ioio-1

Modifica: No, il limite superiore era corretto come indicato nella domanda. L'ho lasciato com'è qui perché un'altra risposta ora utilizza lo stesso codice, ma la correzione è semplice.

Prima una versione in loop:

def looped_ver(k, a):
    x = np.empty_like(a)
    for i in range(x.size):
        sm = 0
        for j in range(0, i+1):
            sm += k[i-j,j] * a[i-j] * a[j]
        x[i] = sm
    return x

L'ho fatto un singolo ciclo con fette intorpidite:

def vectorized_ver(k, a):
    ktr = zeros_like(k)
    ar = zeros_like(k)
    sz = len(a)
    for i in range(sz):
        ktr[i,:i+1] = k[::-1].diagonal(-sz+i+1)
        a_ = a[:i+1]
        ar[i,:i+1] = a_[::-1] * a_
    return np.sum(ktr * ar, 1)

La versione numpy con un ciclo esplicito è circa 25 volte più veloce sul mio computer quando .n=5000

Quindi ho scritto una versione Cython del codice (più leggibile) in loop.

import numpy as np
import cython
cimport numpy as np

@cython.boundscheck(False)
@cython.wraparound(False)
def cyth_ver(double [:, ::1] k not None,
              double [:] a not None):
    cdef double[:] x = np.empty_like(a)
    cdef double sm
    cdef int i, j

    for i in range(len(a)):
        sm = 0.0
        for j in range(i+1):
            sm = sm + k[i-j,j] * a[i-j] * a[j]
        x[i] = sm
    return x

Sul mio laptop, questo è circa 200 volte più veloce della versione in loop (e 8 volte più veloce della versione vettoriale a 1 loop). Sono sicuro che altri possono fare di meglio.

Ho giocato con una versione di Julia e sembrava (se l'ho cronometrato correttamente) paragonabile al codice Cython.


X0io-1

Ah, capisco. L'ho raccolto dalla somma originale, ma non ero sicuro che fosse l'intento.
Nat Wilson

1

Quello che vuoi sembra essere una convoluzione; Penso che il modo più rapido per raggiungerlo sarebbe la numpy.convolvefunzione.

Potrebbe essere necessario correggere gli indici in base alle tue esatte esigenze, ma penso che ti piacerebbe provare qualcosa di simile:

import numpy as np
a = [1, 2, 3, 4, 5]
k = [2, 4, 6, 8, 10]

result = np.convolve(a, k*a[::-1])
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.