Numpy: comportement de gufunc sur une forme de sortie incorrecte

Créé le 2 juin 2020  ·  5Commentaires  ·  Source: numpy/numpy

De la discussion ici: https://github.com/numpy/numpy/pull/15162#discussion_r434122175

L'exemple suivant ne semble pas générer une erreur lorsque la forme de sortie est incorrecte ou diffuser les entrées vers la sortie, mais remplit silencieusement la sortie avec les valeurs résultat + garbage.

import numpy as np
from numpy.core import _umath_tests as umt
from numpy.testing import assert_raises
a = np.arange(6).reshape(3, 2)
b = np.ones(2)
out = np.empty((5, 3))
umt.inner1d(a, b, out)
print(out)

Résultat:

[[ 1.00000000e+000  5.00000000e+000  9.00000000e+000]
 [ 6.91217735e-310 -1.45681599e+144 -1.45681599e+144]
 [-1.45681599e+144 -1.45681599e+144 -1.45681599e+144]
 [-1.45681599e+144 -1.45681599e+144 -1.45681599e+144]
 [-1.45681599e+144 -1.45681599e+144  6.32404027e-322]]
00 - Bug numpy.core numpy.ufunc

Commentaire le plus utile

Ah, c'est très simple:

diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c
index 19876d641..85820e3a0 100644
--- a/numpy/core/src/umath/ufunc_object.c
+++ b/numpy/core/src/umath/ufunc_object.c
@@ -2614,7 +2614,7 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc,
      * dimensions.
      */
     broadcast_ndim = 0;
-    for (i = 0; i < nin; ++i) {
+    for (i = 0; i < nop; ++i) {
         int n = PyArray_NDIM(op[i]) - op_core_num_dims[i];
         if (n > broadcast_ndim) {
             broadcast_ndim = n;

est probablement tout ce qu'il y a à faire. Ensuite, nous obtenons une diffusion correcte des entrées vers les sorties, et toute idée de déprécier cela peut simplement être reportée pour les futurs moi si jamais cela se produit.

EDIT: Plus le contrôle nul de op ...

Tous les 5 commentaires

Hehe, y pensait pendant un moment… .Prenez également @mhvk au cas où.

Le drapeau NPY_ITER_NO_BROADCAST , assez curieusement, semble presque toujours utilisé d'une manière où vous pouvez simplement retirer complètement cet opérande pour la découverte de forme (par exemple comme une opération de sortie). Mais ce serait une rupture incompatible si nous l’utilisions simplement pour résoudre ce problème.

J'envisage donc d'ajouter un nouveau NPY_ITER_OUTPUT_OPERAND . Ou créez un NPY_ITER_OUTPUT_OPERAND et un autre drapeau NPY_ITER_DOES_NOT_AFFECT_SHAPE afin que le drapeau d'opérande de sortie puisse inclure à la fois les indicateurs "allocate" et "no broadcast".

Cela ressemble à une combinaison d'allocation et de non-diffusion, ce qui implique probablement déjà quelque peu cela. Mais la question est de savoir si nous voulons faire un tel changement, en théorie cela peut affecter les bibliothèques externes utilisant NpyIter. (Sauf si nous en faisons un VisibleDeprecationWarning quand il entre en jeu, mais pas sûr que ce soit super sympa à mettre en œuvre.)

Je pense que cela pourrait régler le problème. Il y a des cas secondaires intéressants, comme un axe qui est utilisé uniquement par un des réseaux de sortie (ou plus). On pourrait essayer de soutenir cela (à un moment donné?), Mais cela ajoute probablement trop de complexité sans réel gain.
Je ne vois tout simplement pas vraiment de cas d'utilisation pour un gufunc (),()->(k) . Quelque chose comme pas (),()->(3,3) ou ->(3,3) sens bien sûr, mais ce n'est pas un problème.

Un peu confus à propos de tous les drapeaux, je dois l'admettre. Mais un gufunc moments avec la signature ()->(k) pourrait peut-être calculer des moments jusqu'à k dans la sortie. Bien sûr, incroyablement tiré par les cheveux, mais mon sens serait de ne pas l'exclure explicitement (ni de coder explicitement pour le permettre!).

Un peu plus directement pertinent pour le problème ici, où la sortie a une forme extérieure incohérente, est qu'il existe un précédent des ufuncs réguliers:

np.add(1, 1, out=np.empty((3,)))
# array([2., 2., 2.])

Cela suggère également que pour les gufuncs, la forme extérieure doit être déterminée à partir de toutes les entrées et sorties. Évidemment, cela entraînera des calculs inutiles, mais qu'il en soit ainsi ...

Oh, je ne m'attendais pas à ce que cela fonctionne réellement. Je suppose que dans ce cas, nous pouvons simplement permettre à la sortie de provoquer une diffusion dans les entrées (et donc d'ajuster la forme). Fondamentalement, il suffit de conserver le comportement actuel.

donc, ne faites rien ici, sauf pour vous assurer que le (g) ufunc est appelé plusieurs fois (ce sera un moyen un peu lent d'obtenir le résultat dans de nombreux cas, mais c'est le problème des appelants). Bien que je ne pense pas qu'il soit important de soutenir cela, je ne vois pas non plus de problème à le faire.

Mais nous devons toujours résoudre ce problème dans les gufuncs évidemment!

Ah, c'est très simple:

diff --git a/numpy/core/src/umath/ufunc_object.c b/numpy/core/src/umath/ufunc_object.c
index 19876d641..85820e3a0 100644
--- a/numpy/core/src/umath/ufunc_object.c
+++ b/numpy/core/src/umath/ufunc_object.c
@@ -2614,7 +2614,7 @@ PyUFunc_GeneralizedFunction(PyUFuncObject *ufunc,
      * dimensions.
      */
     broadcast_ndim = 0;
-    for (i = 0; i < nin; ++i) {
+    for (i = 0; i < nop; ++i) {
         int n = PyArray_NDIM(op[i]) - op_core_num_dims[i];
         if (n > broadcast_ndim) {
             broadcast_ndim = n;

est probablement tout ce qu'il y a à faire. Ensuite, nous obtenons une diffusion correcte des entrées vers les sorties, et toute idée de déprécier cela peut simplement être reportée pour les futurs moi si jamais cela se produit.

EDIT: Plus le contrôle nul de op ...

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

inducer picture inducer  ·  3Commentaires

MorBilly picture MorBilly  ·  4Commentaires

keithbriggs picture keithbriggs  ·  3Commentaires

kevinzhai80 picture kevinzhai80  ·  4Commentaires

astrofrog picture astrofrog  ·  4Commentaires