ділення numpy з RuntimeWarning: у double_scalars виявлено недійсне значення


79

Я написав такий сценарій:

import numpy

d = numpy.array([[1089, 1093]])
e = numpy.array([[1000, 4443]])
answer = numpy.exp(-3 * d)
answer1 = numpy.exp(-3 * e)
res = answer.sum()/answer1.sum()
print res

Але я отримав такий результат і з помилкою сталася:

nan
C:\Users\Desktop\test.py:16: RuntimeWarning: invalid value encountered in double_scalars
  res = answer.sum()/answer1.sum()

Здається, що вхідний елемент був занадто малий, щоб пітон перетворив їх на нулі, але насправді поділ має свої результати.

Як вирішити подібного роду проблеми?

Відповіді:


83

Ви не можете це вирішити. Просто answer1.sum()==0, і ви не можете виконати ділення на нуль.

Це відбувається тому, що answer1є експоненцією 2 дуже великих, від’ємних чисел, так що результат округлюється до нуля.

nan повертається в цьому випадку через ділення на нуль.

Тепер, щоб вирішити вашу проблему, ви могли б:

  • зверніться до бібліотеки для високоточної математики, наприклад mpmath . Але це менше задоволення.
  • як альтернативу більшій зброї, виконайте деякі математичні маніпуляції, як описано нижче.
  • вибирайте спеціальну scipy/numpyфункцію, яка робить саме те, що ви хочете! Перевірте відповідь @Warren Weckesser.

Тут я поясню, як зробити деякі математичні маніпуляції, які допомагають вирішити цю проблему. Ми маємо це для чисельника:

exp(-x)+exp(-y) = exp(log(exp(-x)+exp(-y)))
                = exp(log(exp(-x)*[1+exp(-y+x)]))
                = exp(log(exp(-x) + log(1+exp(-y+x)))
                = exp(-x + log(1+exp(-y+x)))

де вище x=3* 1089і y=3* 1093. Тепер аргумент цієї експоненції є

-x + log(1+exp(-y+x)) = -x + 6.1441934777474324e-06

Що стосується знаменника, ви можете поступити аналогічним чином, але отримати те, що log(1+exp(-z+k))вже округлено до 0, так що аргумент експоненціальної функції у знаменнику просто округлений до -z=-3000. Тоді ви маєте, що ваш результат є

exp(-x + log(1+exp(-y+x)))/exp(-z) = exp(-x+z+log(1+exp(-y+x)) 
                                   = exp(-266.99999385580668)

що вже надзвичайно близьке до результату, який ви отримали б, якщо б зберегли лише 2 провідних доданки (тобто перше число 1089в чисельнику та перше число 1000у знаменнику):

exp(3*(1089-1000))=exp(-267)

Для цього давайте подивимось, наскільки ми близькі від рішення Wolfram alpha ( посилання ):

Log[(exp[-3*1089]+exp[-3*1093])/([exp[-3*1000]+exp[-3*4443])] -> -266.999993855806522267194565420933791813296828742310997510523

Різниця між цим числом та показником, наведеним вище, полягає в +1.7053025658242404e-13тому, тому наближення, яке ми зробили за знаменником, було чудовим.

Кінцевий результат є

'exp(-266.99999385580668) = 1.1050349147204485e-116

З вольфрама альфа є ( посилання )

1.105034914720621496.. × 10^-116 # Wolfram alpha.

і знову ж, тут також можна використовувати numpy.


Але в цьому випадку мені потрібно отримати значення ділення на 2 дуже малі значення.
Хайнц

@Heinz Я думаю, ви мали на увазі випадок, коли невелика кількість ділиться на малу. У такому випадку змінити алгоритм масштабування обох чисел набагато краще, ніж знайти механічні повороти. Наприклад, візьмемо логарифм аналітичних рівнянь, які намагається імітувати ваш код. Є багато проблем зі стабільністю обчислень, коли задіяні невеликі числа. Приємніше уникати будь-якого з них, якщо це можливо.
Mai

20

Ви можете використовувати np.logaddexp(що реалізує ідею у відповіді @ gg349):

In [33]: d = np.array([[1089, 1093]])

In [34]: e = np.array([[1000, 4443]])

In [35]: log_res = np.logaddexp(-3*d[0,0], -3*d[0,1]) - np.logaddexp(-3*e[0,0], -3*e[0,1])

In [36]: log_res
Out[36]: -266.99999385580668

In [37]: res = exp(log_res)

In [38]: res
Out[38]: 1.1050349147204485e-116

Або ви можете використовувати scipy.special.logsumexp:

In [52]: from scipy.special import logsumexp

In [53]: res = np.exp(logsumexp(-3*d) - logsumexp(-3*e))

In [54]: res
Out[54]: 1.1050349147204485e-116
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.