Numpy: Gufunc-Verhalten bei falscher Out-Form

Erstellt am 2. Juni 2020  ·  5Kommentare  ·  Quelle: numpy/numpy

Aus der Diskussion hier: https://github.com/numpy/numpy/pull/15162#discussion_r434122175

Das folgende Beispiel scheint keinen Fehler auszulösen, wenn die Out-Form falsch ist oder die Eingaben an die Ausgabe gesendet werden, sondern füllt die Out-Daten stillschweigend mit den Ergebnissen + Garbage-Werten aus.

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)

Ergebnis:

[[ 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

Hilfreichster Kommentar

Ah, es ist sehr einfach:

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;

ist wahrscheinlich alles was dazu gehört. Dann erhalten wir eine korrekte Übertragung von Eingaben zu Ausgaben, und jeder Gedanke, dies abzulehnen, kann nur für zukünftige Selbst verschoben werden, falls es jemals auftaucht.

EDIT: Plus die Nullprüfung von op ...

Alle 5 Kommentare

Hehe, habe eine Weile darüber nachgedacht ... Ping auch @mhvk für alle Fälle.

Das NPY_ITER_NO_BROADCAST -Flag scheint seltsamerweise fast immer so verwendet zu werden, dass Sie diesen Operanden einfach vollständig für die Formerkennung herausnehmen können (z. B. als Ausgabeoperation). Aber es wäre eine inkompatible Pause, wenn wir sie nur verwenden würden, um dieses Problem anzugehen.

Ich denke also darüber nach, ein neues NPY_ITER_OUTPUT_OPERAND hinzuzufügen. Oder machen Sie ein NPY_ITER_OUTPUT_OPERAND und ein anderes Flag NPY_ITER_DOES_NOT_AFFECT_SHAPE so dass das Flag für den Ausgabeoperanden sowohl das Flag "Allocate" als auch das Flag "No Broadcast" enthalten kann.

Es fühlt sich an wie eine Kombination aus Zuweisung und Nichtübertragung, was dies wahrscheinlich schon etwas impliziert. Die Frage ist jedoch, ob wir eine solche Änderung vornehmen möchten, die theoretisch externe Bibliotheken mit NpyIter beeinflussen kann. (Es sei denn, wir machen es zu einer VisibleDeprecationWarning, wenn es startet, aber wir sind uns nicht sicher, ob die Implementierung super ordentlich ist.)

Ich denke , das könnte das Problem lösen. Es gibt interessante Nebenfälle, z. B. Achsen, die nur von einem (oder mehreren) Ausgabearrays verwendet werden. Man könnte versuchen, das zu unterstützen (irgendwann?), Aber es fügt wahrscheinlich zu viel Komplexität hinzu, um keinen wirklichen Gewinn zu erzielen.
Ich sehe einfach nicht wirklich viel von einem Anwendungsfall für einen (),()->(k) Gufunc. Etwas wie nicht (),()->(3,3) oder ->(3,3) macht natürlich Sinn, ist aber kein Problem.

Etwas verwirrt über all die Flaggen, muss ich zugeben. Aber ein Gufunc moments mit der Signatur ()->(k) könnte vielleicht Momente bis zu k in der Ausgabe berechnen. Natürlich unglaublich weit hergeholt, aber mein Sinn wäre es nicht, es explizit auszuschließen (noch Code explizit, um es zuzulassen!).

Ein bisschen direkter relevant für das Problem hier, wo die Ausgabe eine inkonsistente äußere Form hat, ist, dass es Präzedenzfälle von den regulären Ufuncs gibt:

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

Dies legt auch für Gufuncs nahe, dass die äußere Form aus allen Ein- und Ausgängen bestimmt werden sollte. Offensichtlich wird unnötige Berechnungen verursachen, aber so sei es ...

Oh, ich hatte irgendwie nicht erwartet, dass das tatsächlich funktioniert. Ich nehme an, in diesem Fall können wir einfach zulassen, dass die Ausgabe eine Übertragung in den Eingängen verursacht (und somit die Form anpassen). Grundsätzlich einfach das aktuelle Verhalten beibehalten.

Tun Sie hier einfach nichts, außer sicherzustellen, dass der (g) ufunc mehrmals aufgerufen wird (in vielen Fällen ist es etwas langsam, das Ergebnis zu erhalten, aber das ist das Problem des Anrufers). Ich halte es zwar nicht für wichtig, dies zu unterstützen, sehe aber auch keine Probleme damit.

Aber wir müssen dieses Problem natürlich noch in Gufuncs beheben!

Ah, es ist sehr einfach:

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;

ist wahrscheinlich alles was dazu gehört. Dann erhalten wir eine korrekte Übertragung von Eingaben zu Ausgaben, und jeder Gedanke, dies abzulehnen, kann nur für zukünftige Selbst verschoben werden, falls es jemals auftaucht.

EDIT: Plus die Nullprüfung von op ...

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen