Quelle est la façon la plus efficace de mapper une fonction sur un tableau numpy? La façon dont je l'ai fait dans mon projet actuel est la suivante:
import numpy as np
x = np.array([1, 2, 3, 4, 5])
# Obtain array of square of each element in x
squarer = lambda t: t ** 2
squares = np.array([squarer(xi) for xi in x])
Cependant, cela semble être probablement très inefficace, car j'utilise une compréhension de liste pour construire le nouveau tableau en tant que liste Python avant de le reconvertir en un tableau numpy.
Pouvons-nous faire mieux?
python
performance
numpy
Ryan
la source
la source
squarer(x)
?x = np.array([1, 2, 3, 4, 5]); x**2
travauxRéponses:
J'ai testé toutes les méthodes suggérées plus
np.array(map(f, x))
avecperfplot
(un petit projet à moi).Si la fonction que vous essayez déjà de vectoriser est vectorisée (comme l'
x**2
exemple dans le message d'origine), son utilisation est beaucoup plus rapide que toute autre chose (notez l'échelle du journal):Si vous avez réellement besoin de vectorisation, peu importe la variante que vous utilisez.
Code pour reproduire les parcelles:
la source
f(x)
côté votre intrigue. Il n'est peut-être pas applicable à tousf
, mais il s'applique ici, et c'est facilement la solution la plus rapide lorsqu'elle est applicable.vf = np.vectorize(f); y = vf(x)
gagne pour des entrées courtes.pip install -U perfplot
), je vois le message:AttributeError: 'module' object has no attribute 'save'
lors du collage de l'exemple de code.Que diriez-vous d'utiliser
numpy.vectorize
.la source
The vectorize function is provided primarily for convenience, not for performance. The implementation is essentially a for loop.
Dans d'autres questions, j'ai trouvé que celavectorize
pourrait doubler la vitesse d'itération de l'utilisateur. Mais la véritable accélération se fait avec de vraiesnumpy
opérations de tableau.squarer(x)
fonctionnerait déjà pour les tableaux non 1d.vectorize
n'a vraiment aucun avantage sur une compréhension de liste (comme celle de la question), pas sursquarer(x)
.TL; DR
Comme indiqué par @ user2357112 , une méthode "directe" d'application de la fonction est toujours le moyen le plus rapide et le plus simple de mapper une fonction sur des tableaux Numpy:
Évitez généralement
np.vectorize
, car il ne fonctionne pas bien et a (ou a eu) un certain nombre de problèmes . Si vous manipulez d'autres types de données, vous souhaiterez peut-être étudier les autres méthodes indiquées ci-dessous.Comparaison des méthodes
Voici quelques tests simples pour comparer trois méthodes pour mapper une fonction, cet exemple utilisant avec Python 3.6 et NumPy 1.15.4. Tout d'abord, les fonctions de configuration pour tester:
Test avec cinq éléments (triés du plus rapide au plus lent):
Avec des centaines d'éléments:
Et avec des milliers d'éléments de tableau ou plus:
Les différentes versions de Python / NumPy et l'optimisation du compilateur auront des résultats différents, alors faites un test similaire pour votre environnement.
la source
count
argument et une expression de générateur,np.fromiter
c'est beaucoup plus rapide.'np.fromiter((f(xi) for xi in x), x.dtype, count=len(x))'
f(x)
, qui bat tout le reste de plus d'un ordre de grandeur .f
a 2 variables et que le tableau est 2D?Il y a numexpr , numba et cython autour, le but de cette réponse est de prendre ces possibilités en considération.
Mais disons d'abord l'évidence: peu importe comment vous mappez une fonction Python sur un tableau numpy, elle reste une fonction Python, ce qui signifie pour chaque évaluation:
Float
).La machine utilisée pour boucler le tableau ne joue donc pas un grand rôle en raison de la surcharge mentionnée ci-dessus - elle reste beaucoup plus lente que l'utilisation de la fonctionnalité intégrée de numpy.
Jetons un œil à l'exemple suivant:
np.vectorize
est choisi comme représentant de la classe d'approches de fonction en python pur. En utilisantperfplot
(voir le code en annexe de cette réponse) nous obtenons les durées de fonctionnement suivantes:Nous pouvons voir que l'approche numpy est 10x-100x plus rapide que la version pure python. La diminution des performances pour les tailles de baie plus importantes est probablement due au fait que les données ne correspondent plus au cache.
Il convient également de mentionner, qui
vectorize
utilise également beaucoup de mémoire, l'utilisation de la mémoire est donc souvent le goulot d'étranglement (voir la question SO connexe ). Notez également que la documentation de numpy surnp.vectorize
indique qu'elle est "fournie principalement pour des raisons de commodité et non pour des performances".D'autres outils doivent être utilisés, lorsque des performances sont souhaitées, outre l'écriture d'une extension C à partir de zéro, il existe les possibilités suivantes:
On entend souvent que la performance numpy est aussi bonne que possible, car elle est en pur C sous le capot. Pourtant, il y a encore beaucoup à faire!
La version numpy vectorisée utilise beaucoup de mémoire supplémentaire et d'accès à la mémoire. Numexp-library essaie de paver les tableaux numpy et ainsi obtenir une meilleure utilisation du cache:
Conduit à la comparaison suivante:
Je ne peux pas tout expliquer dans l'intrigue ci-dessus: nous pouvons voir des frais généraux plus importants pour numexpr-library au début, mais parce qu'il utilise mieux le cache, il est environ 10 fois plus rapide pour les tableaux plus gros!
Une autre approche consiste à compiler jit la fonction et à obtenir ainsi un véritable UFunc pur-C. Voici l'approche de numba:
C'est 10 fois plus rapide que l'approche numpy originale:
Cependant, la tâche est embarrassablement parallélisable, nous pourrions donc également l'utiliser
prange
pour calculer la boucle en parallèle:Comme prévu, la fonction parallèle est plus lente pour les petites entrées, mais plus rapide (presque facteur 2) pour les grandes tailles:
Alors que numba est spécialisé dans l'optimisation des opérations avec les tableaux numpy, Cython est un outil plus général. Il est plus compliqué d'extraire les mêmes performances qu'avec numba - il s'agit souvent de llvm (numba) vs compilateur local (gcc / MSVC):
Cython entraîne des fonctions un peu plus lentes:
Conclusion
De toute évidence, le test d'une seule fonction ne prouve rien. Il convient également de garder à l'esprit que pour l'exemple de fonction choisi, la bande passante de la mémoire était le col de la bouteille pour les tailles supérieures à 10 ^ 5 éléments - nous avons donc eu les mêmes performances pour numba, numexpr et cython dans cette région.
En fin de compte, la réponse ultime dépend du type de fonction, du matériel, de la distribution Python et d'autres facteurs. Par exemple Anaconda distribution utilise VML d'Intel pour les fonctions de numpy et donc surclasse Numba ( à moins qu'il utilise SVML, voir ce SO-post ) pour les fonctions transcendantes facilement aiment
exp
,sin
,cos
et même - voir par exemple les éléments suivants SO-post .Pourtant, à partir de cette enquête et de mon expérience jusqu'à présent, je dirais que le numba semble être l'outil le plus simple avec les meilleures performances tant qu'aucune fonction transcendantale n'est impliquée.
Tracer les temps de fonctionnement avec perfplot -package :
la source
Les opérations arithmétiques sur les tableaux sont automatiquement appliquées par élément, avec des boucles de niveau C efficaces qui évitent toute surcharge de l'interpréteur qui s'appliquerait à une boucle ou à une compréhension de niveau Python.
La plupart des fonctions que vous souhaitez appliquer à un tableau NumPy élément par élément fonctionneront, même si certaines peuvent nécessiter des modifications. Par exemple,
if
ne fonctionne pas par élément. Vous voudriez les convertir pour utiliser des constructions commenumpy.where
:devient
la source
Dans de nombreux cas, numpy.apply_along_axis sera le meilleur choix. Il augmente les performances d'environ 100x par rapport aux autres approches - et pas seulement pour les fonctions de test triviales, mais aussi pour les compositions de fonctions plus complexes de numpy et scipy.
Quand j'ajoute la méthode:
au code perfplot, j'obtiens les résultats suivants:
la source
Je crois que dans une version plus récente (j'utilise 1.13) de numpy, vous pouvez simplement appeler la fonction en passant le tableau numpy à la fonction que vous avez écrite pour le type scalaire, il appliquera automatiquement l'appel de fonction à chaque élément sur le tableau numpy et vous renverra un autre tableau numpy
la source
**
opérateur qui applique le calcul à chaque élément t det
. C'est engourdi ordinaire. L'envelopper dans lelambda
ne fait rien de plus.Il semble que personne n'ait mentionné une méthode
ufunc
intégrée de production en usine dans le paquet numpy:np.frompyfunc
que j'ai testé à nouveaunp.vectorize
et l'ai surpassé d'environ 20 à 30%. Bien sûr, il fonctionnera bien comme le code C prescrit ou mêmenumba
(que je n'ai pas testé), mais il peut être une meilleure alternative quenp.vectorize
J'ai également testé des échantillons plus gros et l'amélioration est proportionnelle. Voir également la documentation ici
la source
Comme mentionné dans cet article , utilisez simplement des expressions de générateur comme ceci:
la source
Toutes les réponses ci-dessus se comparent bien, mais si vous devez utiliser une fonction personnalisée pour le mappage, et vous l'avez
numpy.ndarray
, et vous devez conserver la forme du tableau.Je n'ai comparé que deux, mais il conservera la forme de
ndarray
. J'ai utilisé le tableau avec 1 million d'entrées pour comparaison. Ici, j'utilise la fonction carrée, qui est également intégrée à numpy et a une grande amélioration des performances, car comme il y avait besoin de quelque chose, vous pouvez utiliser la fonction de votre choix.Production
ici, vous pouvez clairement voir les
numpy.fromiter
travaux excellents compte tenu de l'approche simple, et si la fonction intégrée est disponible, veuillez l'utiliser.la source
Utilisation
numpy.fromfunction(function, shape, **kwargs)
Voir " https://docs.scipy.org/doc/numpy/reference/generated/numpy.fromfunction.html "
la source