Как умножить.outer() в NumPy, предполагая, что 0 * бесконечность = 0?

Я пытаюсь использовать numpy.multiply.outer для многомерных массивов, и мне действительно нужно предположить, что любой 0 * infinity, который он видит, оценивается как ноль. Как я могу сделать это эффективно?

>>> import numpy
>>> numpy.multiply.outer([0.], [float('inf')])
Warning (from warnings module):
  File "__main__", line 2
RuntimeWarning: invalid value encountered in multiply
array([[ nan]])

person user541686    schedule 10.12.2016    source источник
comment
В основном есть 2 варианта - изменить np.inf на что-то рациональное перед использованием, или изменить предупреждения на ignore и исправить значения постфактум (при необходимости).   -  person hpaulj    schedule 10.12.2016
comment
@hpaulj: я не могу заранее изменить infs на что-то рациональное, потому что у меня нет гарантии, что они умножаются на ноль. Я не могу изменить их постфактум, потому что я действительно не знаю, откуда взялись NaN.   -  person user541686    schedule 10.12.2016
comment
Вы должны предоставить тестовый пример, который включает в себя все нюансы, которые вас интересуют. Один с inf или nan, который вы хотите сохранить, а также тот, который вы хотите изменить.   -  person hpaulj    schedule 10.12.2016


Ответы (4)


Вам нужно беспокоиться о других источниках значений nan? Если нет, вы всегда можете просто исправить это на отдельном шаге:

import numpy as np

r = np.multiply.outer([0.], [float('inf')])
np.where(np.isnan(r), 0, r)

Вам решать, хотите ли вы подавить предупреждения.

person ShadowRanger    schedule 10.12.2016
comment
Да, я все еще хочу получить нормальное поведение для других случаев. Но +1, так как для некоторых людей это обходной путь (хотя и очевидный). - person user541686; 10.12.2016

Одним из решений может быть отказ от использования np.multiply.outer и поиск решения с использованием поэлементного умножения на матрицах, которые уже были проверены, чтобы увидеть, соответствуют ли они интересующему условию (ноль в одном массиве, inf в другом массиве).

import numpy as np
A = np.array([0., 0., 0.4, 2])
B = np.array([float('inf'), 1., 3.4, np.inf])

# Conditions of interest
c1 = (A == 0)
c2 = (B == np.inf)
condition1 = np.multiply.outer(c1, c2)
c3 = (A == np.inf)
c4 = (B == 0)
condition2 = np.multiply.outer(c3, c4)
condition = condition1 | condition2

AA = np.multiply.outer(A, np.ones(B.shape))
BB = np.multiply.outer(np.ones(A.shape), B)

AA[condition] = 0.
BB[condition] = 0.

AA*BB

Однако это может не соответствовать запросу плаката об «эффективности».

person p-robot    schedule 10.12.2016
comment
Эх ... +1, так как это все же лучше, чем зацикливание, но да, не совсем то, на что я надеялся. - person user541686; 10.12.2016
comment
Да, я понимаю, что это не самое элегантное (или эффективное) решение, но оно позволяет избежать подавления предупреждений. Ваше здоровье. - person p-robot; 10.12.2016

Вот как подавить предупреждения:

среднее значение, среднее значение и предупреждение: среднее значение пустого фрагмента

In [528]: import warnings

In [530]: x = np.array([0,1,2],float)
In [531]: y = np.array([np.inf,3,2],float)
In [532]: np.outer(x,y)
/usr/local/lib/python3.5/dist-packages/numpy/core/numeric.py:1093: RuntimeWarning: invalid value encountered in multiply
  return multiply(a.ravel()[:, newaxis], b.ravel()[newaxis,:], out)
Out[532]: 
array([[ nan,   0.,   0.],
       [ inf,   3.,   2.],
       [ inf,   6.,   4.]])

In [535]: with warnings.catch_warnings():
     ...:     warnings.simplefilter('ignore',category=RuntimeWarning)
     ...:     z = np.outer(x,y)
     ...:     
In [536]: z
Out[536]: 
array([[ nan,   0.,   0.],
       [ inf,   3.,   2.],
       [ inf,   6.,   4.]])

замените nan на 1:

In [542]: z[np.isnan(z)]=1
In [543]: z
Out[543]: 
array([[  1.,   0.,   0.],
       [ inf,   3.,   2.],
       [ inf,   6.,   4.]])

In [547]: z[np.isinf(z)]=9999
In [548]: z
Out[548]: 
array([[  1.00000000e+00,   0.00000000e+00,   0.00000000e+00],
       [  9.99900000e+03,   3.00000000e+00,   2.00000000e+00],
       [  9.99900000e+03,   6.00000000e+00,   4.00000000e+00]])

=================

Мы могли бы создать mask, используя тип тестирования, который демонстрирует @P-robot:

In [570]: np.outer(np.isinf(x),y==0)|np.outer(x==0,np.isinf(y))
Out[570]: 
array([[ True, False, False],
       [False, False, False],
       [False, False, False]], dtype=bool)
In [571]: mask=np.outer(np.isinf(x),y==0)|np.outer(x==0,np.isinf(y))
In [572]: with warnings.catch_warnings():
     ...:     warnings.simplefilter('ignore',category=RuntimeWarning)
     ...:     z = np.outer(x,y)
     ...:     
In [573]: z[mask]=1
In [574]: z
Out[574]: 
array([[  1.,   0.,   0.],
       [ inf,   3.,   2.],
       [ inf,   6.,   4.]])

Или с более беспорядочными входами:

In [587]: x = np.array([0,1,2,np.inf],float)
In [588]: y = np.array([np.inf,3,np.nan,0],float)
In [589]: mask=np.outer(np.isinf(x),y==0)|np.outer(x==0,np.isinf(y))
...
In [591]: with warnings.catch_warnings():
     ...:     warnings.simplefilter('ignore',category=RuntimeWarning)
     ...:     z = np.outer(x,y)
     ...:     
In [592]: z[mask]=1
In [593]: z
Out[593]: 
array([[  1.,   0.,  nan,   0.],
       [ inf,   3.,  nan,   0.],
       [ inf,   6.,  nan,   0.],
       [ inf,  inf,  nan,   1.]])
person hpaulj    schedule 10.12.2016
comment
Это предполагает, что ни в одном из входных массивов уже нет NaN, верно? В противном случае вы можете что-то замаскировать. - person user541686; 10.12.2016
comment
Я добавил тест на маскировку в соответствии с ответом @P-robot's. - person hpaulj; 10.12.2016

Хотя я согласен с ответом @ShadowRanger, дешевым взломом может быть использование np.nan_to_num, который заменяет infs большими конечными числами, что дает вам inf * 0 = 0.

Чтобы преобразовать нежелательные оставшиеся высокие конечные числа обратно в inf (учитывая некоторые другие операции, помимо вашего вопроса), вы можете использовать умножение большого числа на что-либо> 1, а затем разделить на ту же сумму (чтобы не влиять на другие числа). Например.:

In [1]: np.nan_to_num(np.inf)
Out[1]: 1.7976931348623157e+308

In [2]: np.nan_to_num(np.inf)*1.1
RuntimeWarning: overflow encountered in double_scalars
Out[2]: inf

In [3]: np.nan_to_num(np.inf)*1.1/1.1
RuntimeWarning: overflow encountered in double_scalars
Out[3]: inf

До потока отрицательных голосов это явно не лучшая практика и потенциально может иметь побочные эффекты в зависимости от вашего варианта использования, но я просто подумал, что могу предложить альтернативу.

person ZaxR    schedule 24.06.2018