2009-06-14 19 views
326

¿Vale la pena mi aprendizaje NumPy?¿Por qué NumPy en lugar de listas de Python?

Tengo aproximadamente 100 series de mercados financieros, y voy a crear una matriz de cubos de 100x100x100 = 1 millón de celdas. Estaré retrocediendo (3 variables) cada x con cada y y z, para llenar la matriz con errores estándar.

He oído que para las "matrices grandes" debería usar NumPy en lugar de las listas de Python, por razones de rendimiento y escalabilidad. La cosa es que sé que Python enumera y parece que funcionan para mí.

¿Vale la pena cambiar la escala del problema anterior a NumPy?

¿Qué sucede si tengo 1000 series (es decir, 1 mil millones de celdas de coma flotante en el cubo)?

+0

Esto no responde a su pregunta, pero podría considerar preguntar acerca de su problema en el intercambio de estadísticas. Me parece que intentas hacer algo de la manera difícil cuando pueden existir soluciones más fáciles. En términos generales, llenar un cubo grande con poca información no es la mejor manera de manejar este tipo de problemas. – Dave31415

+33

qué ejemplo de Clase A de moderación se sobrepasó para cerrar una de las preguntas más populares en Numpy por tales motivos dudosos. Y, irónicamente, he sido miembro de Stack Overflow durante más tiempo que cualquiera de estos supuestos expertos. Gitouttahere jobsworths ... –

+4

Muy común este tipo de sobrealcance en Stack Overflow IME. –

Respuesta

547

Las matrices de NumPy son más compactas que las listas de Python; una lista de listas como las que describes en Python tomaría al menos 20 MB o menos, mientras que una matriz NumPy 3D con flotantes de precisión simple encajaría 4 MB. El acceso en lectura y escritura de artículos también es más rápido con NumPy.

Tal vez no te importa mucho por solo un millón de células, pero definitivamente lo harías por un billón de celdas; ninguno de los enfoques encajaría en una arquitectura de 32 bits, pero con compilaciones de 64 bits, NumPy se saldría con la suya 4 GB más o menos, Python solo necesitaría al menos alrededor de 12 GB (muchos punteros que duplican su tamaño), ¡una pieza de hardware mucho más costosa!

La diferencia se debe principalmente a la "indirecta": una lista de Python es una matriz de punteros a objetos Python, al menos 4 bytes por puntero más 16 bytes incluso para el objeto Python más pequeño (4 para tipo puntero, 4 para recuento de referencias, 4 para el valor, y los asignadores de memoria redondean hasta 16). Una matriz NumPy es una matriz de valores uniformes: los números de precisión simple toman 4 bytes cada uno, los de precisión doble, 8 bytes. ¡Menos flexible, pero usted paga sustancialmente por la flexibilidad de las listas estándar de Python!

+12

Alex - siempre la buena respuesta. Gracias, punto hecho. Iré con Numpy para la escalabilidad y, de hecho, para la eficiencia. Estoy pensando que también pronto necesitaré aprender programación paralela en Python, e invertir en algún hardware compatible con OpenCL;) –

+0

He estado tratando de usar "sys.getsizeof()" para comparar el tamaño de Python listas y matrices NumPy con el mismo número de elementos y no parece indicar que las matrices NumPy fueran mucho más pequeñas. ¿Es este el caso o sys.getsizeof() tiene problemas para averiguar qué tan grande es una matriz NumPy? –

+2

@JackSimpson 'getsizeof' no es confiable. La documentación establece claramente que: * Solo se tiene en cuenta el consumo de memoria atribuido directamente al objeto, no el consumo de memoria de los objetos a los que hace referencia. * Esto significa que si ha anidado listas de Python, el tamaño de los elementos no se toma en cuenta cuenta. – Bakuriu

185

NumPy no solo es más eficiente; también es más conveniente. Obtienes muchas operaciones vectoriales y matriciales de forma gratuita, lo que a veces te permite evitar el trabajo innecesario. Y también se implementan de manera eficiente.

Por ejemplo, se podía leer su cubo directamente desde un archivo en una matriz:

x = numpy.fromfile(file=open("data"), dtype=float).reshape((100, 100, 100)) 

Suma a lo largo de la segunda dimensión:

s = x.sum(axis=1) 

Encuentre el cual las células están por encima de un umbral:

(x > 0.5).nonzero() 

Elimine todas las divisiones con índices par a lo largo de la tercera dimensión:

x[:, :, ::2] 

Además, muchas bibliotecas útiles funcionan con matrices NumPy. Por ejemplo, bibliotecas de análisis estadístico y visualización.

Incluso si no tiene problemas de rendimiento, aprender NumPy vale la pena el esfuerzo.

+0

Gracias: en su tercer ejemplo, ha proporcionado otra buena razón, ya que, de hecho, buscaré celdas por encima del umbral. Además, estaba cargando desde sqlLite. El enfoque de archivo será mucho más eficiente. –

15

Tenga en cuenta también que no hay soporte para series de tiempo basado en NumPy en los scikits series temporales:

http://pytseries.sourceforge.net

Para la regresión, estoy bastante seguro de NumPy será órdenes de magnitud más rápido y más conveniente que las listas incluso para el problema 100^3.

87

Alex mencionó la eficiencia de la memoria, y Roberto menciona la conveniencia, y ambos son buenos puntos. Para algunas ideas más, mencionaré velocidad y funcionalidad.

Funcionalidad: Se integra mucho con NumPy, FFT, convoluciones, búsquedas rápidas, estadísticas básicas, álgebra lineal, histogramas, etc. Y realmente, ¿quién puede vivir sin FFT?

Velocidad: Aquí hay una prueba para hacer una suma sobre una lista y una matriz NumPy, que muestra que la suma en la matriz NumPy es 10 veces más rápida (en esta prueba, el millaje puede variar).

from numpy import arange 
from timeit import Timer 

Nelements = 10000 
Ntimeits = 10000 

x = arange(Nelements) 
y = range(Nelements) 

t_numpy = Timer("x.sum()", "from __main__ import x") 
t_list = Timer("sum(y)", "from __main__ import y") 
print("numpy: %.3e" % (t_numpy.timeit(Ntimeits)/Ntimeits,)) 
print("list: %.3e" % (t_list.timeit(Ntimeits)/Ntimeits,)) 

la que en mis sistemas (mientras estoy ejecutando una copia de seguridad) da:

numpy: 3.004e-05 
list: 5.363e-04 
-4

velocidad en cuanto no estoy tan seguro de. Aquí está un ejemplo rápido: He creado una función (de x) que devuelve una lista de números primos entre 2 y x:

  • función de Python regular utilizando listas:

    def findprimeupto(x): 
        primes = [] 
        n_primes = [] 
    
        for i in range(2, x): 
    
         if not (i in n_primes): 
          primes.append(i) 
          n_primes.append(i) 
    
         for j in range(len(primes)): 
          if i > n_primes[j]: 
           n_primes[j] += primes[j] 
    
        return primes 
    
    import time 
    start_time = time.time() 
    findprimeupto(10000) 
    print("--- %s seconds ---" % str(time.time() - start_time)) 
    
  • y utilizando la función de Python C-como matrices NumPy:

    import numpy 
    
    def findprimeupto(x): 
    
        primes = numpy.array(numpy.zeros(x), dtype=numpy.int32) 
        n_primes = numpy.array(numpy.zeros(x), dtype=numpy.int32) 
        primeslen = 0 
    
        for i in range(2, x): 
    
         flag = 1 
         for j in range(primeslen): 
          if n_primes[j] == i: 
           flag = 0 
           break 
    
         if flag: 
          primes[primeslen] = i 
          n_primes[primeslen] = i 
          primeslen += 1 
    
         for j in range(primeslen): 
          if i > n_primes[j]: 
           n_primes[j] += primes[j] 
    
        return [primeslen, primes] 
    
    
    import time 
    
    start_time = time.time() 
    
    result = findprimeupto(10000) 
    
    #for i in range(result[0]): 
    # print('{:d} '.format(result[1][i]), end="") 
    
    print() 
    print("--- %s seconds ---" % str(time.time() - start_time)) 
    

El anterior aplicación, supuestamente lenta usando las listas, es ejecutado en 0.6 segundos y la posterior, supuestamente rápida implementación de NumPy, necesita 50 segundos. Si alguien puede señalar por qué lo agradecería mucho.

BTW, programa C puro que es más o menos una copia de la versión de NumPy de la función se ejecuta en menos de 0.04   s. La velocidad de C es aún más evidente con grandes x:

#include <stdio.h> 
    #include <stdlib.h> 
    #include <time.h> 

    void findprimesupto(int n, int *primeslen, int *primes, int *n_primes) { 
     int i, j, flag; 

     *primeslen = 0; 

     for (i=2; i <= n; i++) { 
      for (j=0, flag=1; j < *primeslen; j++) 
       if (n_primes[j] == i) { 
        flag = 0; 
        break; 
       } 
      if (flag) { 
       primes[*primeslen] = i; 
       n_primes[*primeslen] = i; 
       (*primeslen)++; 
      } 
      for (j=0; j < *primeslen; j++) 
       if (i > n_primes[j]) 
        n_primes[j] += primes[j]; 
     } 
    } 

    int main() { 
     int n = 10000, primeslen = 0, i; 
     int *primes, *n_primes; 
     clock_t start, diff; 

     start = clock(); 
     primes = malloc(n * sizeof(int)); 
     n_primes = malloc(n * sizeof(int)); 

     findprimesupto(n, &primeslen, primes, n_primes); 

     /* for (i=0; i < primeslen; i++) 
      printf("%d ", primes[i]); 

     printf("\n"); 
     */ 

     diff = clock() - start; 
     printf("Time: %f s\n", (float) diff/(float) CLOCKS_PER_SEC); 

     free(primes); 
     free(n_primes); 

     return 0; 
    } 
+29

La velocidad de NumPy depende de hacer un buen uso de las capacidades de NumPy para operaciones vectorizadas. Necesita volver a escribir su ejemplo de NumPy en un estilo más compatible con NumPy, reemplazando el nivel de Python por bucles con operaciones vectorizadas de NumPy. No se puede simplemente reemplazar listas con matrices NumPy y esperar que el código se ejecute más rápido. –

+1

Estoy tratando de encontrar los cambios apropiados en el código usando funciones numpy nativas, pero este parece ser un caso particularmente difícil ya que difiere significativamente de los problemas de álgebra lineal estándar para los que numpy es adecuado. Sospecho que las funciones de acceso a la matriz numpy son lentas, de hecho acabo de publicar una prueba en otro hilo que demuestre eso, pero no veo cómo puedo evitarlo. – Arijan

+0

Hay otra cosa. Si defino matrices de flotadores en lugar de números enteros, es decir, si el cambio declaraciones de números primos y n_primes a: primos = numpy.zeros (x) n_primes = numpy.zeros (x) entonces el tiempo de ejecución se reduce de 50 a 9s. Esto indicaría problemas con el encasillado (es decir, todas las funciones numpy suponen que los argumentos son de tipo float) que de hecho podrían engullir el tiempo. Con todo, Numpy no es muy adecuado para matrices de enteros de propósito general. Sin embargo, es una pena. – Arijan

29

Aquí hay una buena respuesta de la FAQ en el scipy.org website:

¿Qué ventajas ofrecen las matrices NumPy sobre listas (anidada) Python?

Las listas de Python son contenedores eficaces de uso general. Admiten (bastante) eficiente inserción, eliminación, adición y concatenación, y la lista de Python las hace fáciles de construir y manipular.Sin embargo, tienen ciertas limitaciones: no admiten operaciones "vectorizadas" como la adición y multiplicación de elementos, y el hecho de que pueden contener objetos de diferentes tipos significa que Python debe almacenar información de tipo para cada elemento, y debe ejecutar código de despacho de tipo cuando se opera en cada elemento. Este también significa que muy pocas operaciones de lista pueden llevarse a cabo mediante bucles de C eficientes - cada iteración requeriría verificaciones de tipo y otras contabilidad de Python API.

Cuestiones relacionadas