Cómo expresar esta expresión complicada usando rebanadas numpy

14

Deseo implementar la siguiente expresión en Python: donde e son matrices numpy de tamaño , y es una matriz numpy de tamaño . El tamaño puede ser de hasta aproximadamente 10000, y la función es parte de un bucle interno que se evaluará muchas veces, por lo que la velocidad es importante.

Xyo=j=1yo-1kyo-j,junyo-junj,
Xynorteknorte×nortenorte

Idealmente, me gustaría evitar un ciclo for por completo, aunque supongo que no es el fin del mundo si hay uno. El problema es que tengo problemas para ver cómo hacerlo sin tener un par de bucles anidados, y eso probablemente lo haga bastante lento.

¿Alguien puede ver cómo expresar la ecuación anterior usando numpy de una manera que sea eficiente y preferiblemente también legible? En términos más generales, ¿cuál es la mejor manera de abordar este tipo de cosas?

Nathaniel
fuente
Tuve una pregunta similar hace un par de días. Lo pregunté en stackoverflow. Mira esta publicación . Yo uso scipy.weave en lugar de cython. ¿Alguien sabe si esto hace alguna diferencia (considerable) de rendimiento?
seb

Respuestas:

17

Aquí está la solución de Numba. En mi máquina, la versión de Numba es> 1000 veces más rápida que la versión de Python sin el decorador (para una matriz de 200x200, 'k' y un vector de longitud de 200 'a'). También puede usar el decorador @autojit que agrega aproximadamente 10 microsegundos por llamada para que el mismo código funcione con varios tipos.

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

Divulgación: Soy uno de los desarrolladores de Numba.

Travis Oliphant
fuente
Gracias, eso parece bastante sencillo. ¡Ni siquiera sabía sobre numba! Cython, PyPy, Numba ... es un mundo confuso.
Nathaniel
3
Travis, muy bien, ¿te importaría agregar una revelación al final de tu respuesta de que eres uno de los desarrolladores de numba?
Aron Ahmadia
1
norte=200
@NatWilson: si haces esto como una pregunta en scicomp, me encantaría intentar abordarlo por ti :)
Aron Ahmadia
4

Aquí hay un comienzo. Primero, mis disculpas por cualquier error.

yoyo-1

Editar: No, el límite superior era correcto según lo dispuesto en la pregunta. Lo dejé como está aquí porque otra respuesta ahora usa el mismo código, pero la solución es simple.

Primero una versión en bucle:

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

Hice un solo bucle con rodajas numpy:

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)

norte=5000

Luego escribí una versión de Cython del código en bucle (más legible).

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

En mi computadora portátil, esta es aproximadamente 200 veces más rápida que la versión en bucle (y 8 veces más rápida que la versión vectorizada de 1 bucle). Estoy seguro de que otros pueden hacerlo mejor.

Jugué con una versión de Julia, y parecía (si lo cronometraba correctamente) comparable al código de Cython.

Nat Wilson
fuente
X0 0yo-1
Ah, ya veo. Lo deduje del resumen original, pero no estaba seguro de que esa fuera la intención.
Nat Wilson
1

Lo que quieres parece ser una convolución; Creo que la forma más rápida de lograrlo sería la numpy.convolvefunción.

Es posible que deba corregir los índices de acuerdo con sus necesidades exactas, pero creo que le gustaría probar algo como:

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

result = np.convolve(a, k*a[::-1])
Thomas Baruchel
fuente