1
votes

Comment rendre la fonction basée sur une séquence plus rapide dans Numpy?

Considérant une fonction ci-dessous:

import numpy as np

a = np.ones(16).reshape(4,4)

def fn(a):
    b = np.array(a)
    for i in range(b.shape[0]):
        for j in range(b.shape[1] - 1):
            b[i][j+1] += b[i][j]
    return b

print(fn(a))

C'est-à-dire pour une fonction générale qui calcule t + 1 basé sur t dans un tableau, puis-je rendre cela plus rapide? Je sais qu'il existe un np.vectorize mais ne semble pas approprié pour ce cas.


4 commentaires

vectorize ne promet pas de performances, même là où cela fonctionne.


@hpaulj J'ai testé une boucle for sur un tableau 2d, 1. boucle i, j pour les deux dimensions et calcul en place 2. définir une fonction vectorisée et appeler v_fn (arr) , la deuxième solution est 2x plus vite.


Je faisais spécifiquement référence à la fonction np.vectorize , pas au concept général de vectorisation . Clairement, dans ce cas, vous n'avez pas besoin d'itérer sur la dimension i . La dimension j a besoin de quelque chose comme un ufunc.accumulate , ou numba pour gagner beaucoup de vitesse.


@hpaulj la "fonction vectorisée" est implémentée avec np.vectorize . Un exemple stupide est fn = lambda x: x * x , avec v_fn = np.vectorize (fn) . C'est en effet plus rapide qu'une boucle intégrée. Mais j'ai votre idée.


3 Réponses :


1
votes

Vous pouvez utiliser cumsum Je pense que ce serait utile.

np.cumsum(a,axis=1)  

Ou vous pouvez utiliser np.cumsum () :

import numpy as np
import pandas as pd
a = np.ones(16).reshape(4,4)
df =pd.DataFrame(a)
df.cumsum(axis=1)


1 commentaires

Existe-t-il une solution plus générale? Si la fonction ne fait pas de somme cumulative. J'ai trouvé que numba ressemble à ce que je demande mais je ne l'ai jamais utilisé.



0
votes

Ce que vous recherchez s'appelle accumulate voici un exemple:

import numpy as np
from itertools import accumulate

def fn(a):
    acc = accumulate(a, lambda prev, row: prev + row)
    return np.array(list(acc))

a = np.arange(16).reshape(4, 4)
print(fn(a))
# [[ 0  1  2  3]
#  [ 4  6  8 10]
#  [12 15 18 21]
#  [24 28 32 36]]

Il n'y a pas de fonction d'accumulation optimisée dans numpy car il n'est pas vraiment possible d'écrire accumulate d'une manière à la fois performante et générale. L'implémentation python est générale, mais fonctionnera un peu comme un lok codé à la main.

Pour obtenir des performances optimales, vous aurez probablement besoin de trouver ou d'écrire une implémentation de bas niveau de la fonction d'accumulation spécifique dont vous avez besoin. Vous avez déjà mentionné numba et vous pouvez également vous pencher sur cython.


0 commentaires

1
votes

Il est possible de réduire les deux boucles for à une boucle for avec un peu de surcharge de copie en plus.

In [100]: a = np.ones(625).reshape(25, 25) 

In [101]: %timeit fn(a) 
303 µs ± 2.05 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

In [102]: b = a.copy() 

In [103]: %timeit slightly_vectorized(b) 
99.8 µs ± 501 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

Pour que cela fonctionne pour une fonction générique , vous pouvez rechercher une fonction équivalente dans numpy ou en implémenter une en utilisant des opérations numpy (une vectorisée). Pour l'exemple que vous avez fourni, je viens d'utiliser numpy.sum () qui fait le travail pour nous.

En termes de performances, cette approche serait bien meilleure que d'utiliser deux pour boucles au niveau des index, en particulier pour les tableaux plus grands. Dans l'approche que j'ai utilisée ci-dessus, nous travaillons avec des tranches de colonnes.


Voici les minutages qui suggèrent plus de accélération 3X par rapport à l'implémentation native de python.


Python natif:

In [104]: def slightly_vectorized(b): 
     ...:     for col in range(b.shape[1]-1): 
     ...:         b[:, col+1] = np.sum(a[:, :col+2], axis=1) 
     ...:     return b 

Légèrement vectorisé:

def fn(a):
    b = np.array(a)
    for i in range(b.shape[0]):
        for j in range(b.shape[1] - 1):
            b[i][j+1] += b[i][j]
    return b

In [86]: a 
Out[86]: 
array([[1., 1., 1., 1.],
       [1., 1., 1., 1.],
       [1., 1., 1., 1.],
       [1., 1., 1., 1.]])

In [87]: b = a.copy() 

In [88]: for col in range(b.shape[1]-1): 
    ...:     b[:, col+1] = np.sum(a[:, :col+2], axis=1) 

In [89]: b
Out[89]: 
array([[1., 2., 3., 4.],
       [1., 2., 3., 4.],
       [1., 2., 3., 4.],
       [1., 2., 3., 4.]])


2 commentaires

Cependant, vous ne pouvez pas toujours trouver une fonction équivalente.


@ knh190 Je suis d'accord! Mais presque toutes les fonctions peuvent être implémentées en utilisant des ufuncs numpy basiques. Vous pouvez ensuite appliquer cette fonction nouvellement implémentée