Numpy: Comportamiento de gufunc en forma de salida incorrecta

Creado en 2 jun. 2020  ·  5Comentarios  ·  Fuente: numpy/numpy

De la discusión aquí: https://github.com/numpy/numpy/pull/15162#discussion_r434122175

El siguiente ejemplo no parece generar un error cuando nuestra forma es incorrecta o transmite las entradas a la salida, pero llena silenciosamente la salida con valores de resultado + basura.

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)

Resultado:

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

Comentario más útil

Ah, es muy 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;

es probablemente todo lo que hay que hacer. Luego, obtenemos una transmisión correcta de las entradas a las salidas, y cualquier pensamiento de desaprobar esto puede ser pospuesto para nosotros mismos en el futuro si alguna vez surge.

EDITAR: Además de la verificación nula de op ...

Todos 5 comentarios

Jeje, estuve pensando en esto por un tiempo… .También haz ping a

La bandera NPY_ITER_NO_BROADCAST , por extraño que parezca, parece que casi siempre se usa de una manera en la que podría simplemente sacar ese operando completamente para el descubrimiento de formas (por ejemplo, como una operación de salida). Pero sería una ruptura incompatible si solo lo usáramos para abordar ese problema.

Así que estoy considerando agregar un nuevo NPY_ITER_OUTPUT_OPERAND . O haga un NPY_ITER_OUTPUT_OPERAND y otro indicador NPY_ITER_DOES_NOT_AFFECT_SHAPE para que el indicador del operando de salida pueda incluir tanto el indicador "asignar" como el indicador "no difusión".

Se siente como una combinación de asignación y no transmisión, probablemente ya lo implique de alguna manera. Pero la pregunta es si queremos hacer tal cambio, en teoría puede afectar a bibliotecas externas usando NpyIter. (A menos que lo hagamos un VisibleDeprecationWarning cuando se active, pero no estoy seguro de que sea muy fácil de implementar).

Creo que eso podría solucionar el problema. Hay casos secundarios interesantes, como un eje que solo es utilizado por una (o más) matrices de salida. Se podría intentar respaldar eso (¿en algún momento?), Pero probablemente agrega demasiada complejidad para no obtener una ganancia real.
Simplemente no veo mucho de un caso de uso para un (),()->(k) gufunc. Algo como no (),()->(3,3) o ->(3,3) tiene sentido, por supuesto, pero no es un problema.

Un poco confundido acerca de todas las banderas, debo admitir. Pero un gufunc moments con la firma ()->(k) quizás podría calcular momentos hasta k en la salida. Por supuesto, increíblemente inverosímil, pero mi sentido sería no excluirlo explícitamente (¡ni codificar explícitamente para permitirlo!).

Un poco más directamente relevante para el problema aquí, donde la salida tiene una forma externa inconsistente, es que hay un precedente de los ufuncs regulares:

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

Esto sugiere que también para gufuncs, la forma exterior debe determinarse a partir de todas las entradas y salidas. Obviamente, causará cálculos innecesarios, pero que así sea ...

Oh, de alguna manera no esperaba que eso realmente funcionara. Supongo que en ese caso podemos simplemente permitir que la salida provoque la transmisión en las entradas (y así ajustar la forma). Básicamente simplemente conservando el comportamiento actual.

por lo tanto, no haga nada aquí, excepto asegurarse de que se llame a (g) ufunc varias veces (será una forma un poco lenta de obtener el resultado en muchos casos, pero ese es el problema de las personas que llaman). Si bien no creo que sea importante apoyar esto, tampoco veo ningún problema en hacerlo.

¡Pero todavía tenemos que solucionar este problema en gufuncs obviamente!

Ah, es muy 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;

es probablemente todo lo que hay que hacer. Luego, obtenemos una transmisión correcta de las entradas a las salidas, y cualquier pensamiento de desaprobar esto puede ser pospuesto para nosotros mismos en el futuro si alguna vez surge.

EDITAR: Además de la verificación nula de op ...

¿Fue útil esta página
0 / 5 - 0 calificaciones

Temas relacionados

inducer picture inducer  ·  3Comentarios

toddrjen picture toddrjen  ·  4Comentarios

Kreol64 picture Kreol64  ·  3Comentarios

astrofrog picture astrofrog  ·  4Comentarios

Foadsf picture Foadsf  ·  3Comentarios