¿Por qué la comprensión de la lista es mucho más rápida que la numpy para multiplicar matrices?

Recientemente respondí a ESTA pregunta que quería la multiplicación de 2 listas, algún usuario sugirió la siguiente forma usando numpy, junto con la mía, que creo que es la correcta:

(aT*b).T 

También encontré que aray.resize() tiene un mismo rendimiento como ese. De cualquier manera, otra respuesta sugirió una solución usando la lista de comprensión:

 [[m*n for n in second] for m, second in zip(b,a)] 

Pero después del punto de referencia, vi que la comprensión de la lista funciona mucho más rápido que el numpy:

 from timeit import timeit s1=""" a=[[2,3,5],[3,6,2],[1,3,2]] b=[4,2,1] [[m*n for n in second] for m, second in zip(b,a)] """ s2=""" a=np.array([[2,3,5],[3,6,2],[1,3,2]]) b=np.array([4,2,1]) (aT*b).T """ print ' first: ' ,timeit(stmt=s1, number=1000000) print 'second : ',timeit(stmt=s2, number=1000000,setup="import numpy as np") 

resultado:

  first: 1.49778485298 second : 7.43547797203 

Como se puede ver, el número es aproximadamente 5 veces más rápido. pero lo más sorprendente fue que es más rápido sin usar transposición, y para el siguiente código:

 a=np.array([[2,3,5],[3,6,2],[1,3,2]]) b=np.array([[4],[2],[1]]) a*b 

La lista de comprensión aún fue 5 veces más rápida. Entonces, aparte de este punto, la lista de comprensión se realiza en C aquí, usamos 2 bucles nesteds y una función zip . ¿Cuál puede ser la razón? ¿Es debido a la operación * en numpy?

También tenga en cuenta que no hay ningún problema con el tiempo timeit aquí puse la parte de import en la setup .

También lo probé con un arrastre más grande, la diferencia disminuye, pero aún no tiene sentido:

 s1=""" a=[[2,3,5],[3,6,2],[1,3,2]]*10000 b=[4,2,1]*10000 [[m*n for n in second] for m, second in zip(b,a)] """ s2=""" a=np.array([[2,3,5],[3,6,2],[1,3,2]]*10000) b=np.array([4,2,1]*10000) (aT*b).T """ print ' first: ' ,timeit(stmt=s1, number=1000) print 'second : ',timeit(stmt=s2, number=1000,setup="import numpy as np") 

resultado:

  first: 10.7480301857 second : 13.1278889179 

La creación de matrices numpy es mucho más lenta que la creación de listas:

 In [153]: %timeit a = [[2,3,5],[3,6,2],[1,3,2]] 1000000 loops, best of 3: 308 ns per loop In [154]: %timeit a = np.array([[2,3,5],[3,6,2],[1,3,2]]) 100000 loops, best of 3: 2.27 µs per loop 

También puede haber costos fijos incurridos por las llamadas de función NumPy antes de que la función del cálculo se pueda realizar mediante una función C / Fortran subyacente rápida. Esto puede incluir asegurar que las entradas sean matrices NumPy,

Estos costos de configuración / fijos son algo que se debe tener en cuenta antes de asumir que las soluciones NumPy son inherentemente más rápidas que las soluciones puramente de Python. NumPy brilla cuando configura matrices grandes una vez y luego realiza muchas operaciones rápidas de NumPy en las matrices. Puede fallar en superar a Python puro si los arreglos son pequeños porque el costo de instalación puede ser mayor que el beneficio de descargar los cálculos a las funciones comstackdas de C / Fortran. Para matrices pequeñas, simplemente puede que no haya suficientes cálculos para que valga la pena.


Si aumenta un poco el tamaño de las matrices y mueve la creación de las matrices a la configuración, entonces NumPy puede ser mucho más rápido que Python puro:

 import numpy as np from timeit import timeit N, M = 300, 300 a = np.random.randint(100, size=(N,M)) b = np.random.randint(100, size=(N,)) a2 = a.tolist() b2 = b.tolist() s1=""" [[m*n for n in second] for m, second in zip(b2,a2)] """ s2 = """ (aT*b).T """ s3 = """ a*b[:,None] """ assert np.allclose([[m*n for n in second] for m, second in zip(b2,a2)], (aT*b).T) assert np.allclose([[m*n for n in second] for m, second in zip(b2,a2)], a*b[:,None]) print 's1: {:.4f}'.format( timeit(stmt=s1, number=10**3, setup='from __main__ import a2,b2')) print 's2: {:.4f}'.format( timeit(stmt=s2, number=10**3, setup='from __main__ import a,b')) print 's3: {:.4f}'.format( timeit(stmt=s3, number=10**3, setup='from __main__ import a,b')) 

rendimientos

 s1: 4.6990 s2: 0.1224 s3: 0.1234