Функция isclose в numpy отличается от математической

У меня есть код, который выполняет проверку градиента. Недавно я изменил функция сравнения от math.isclose до numpy.isclose, чтобы более последовательно использовать numpy, и, к моему удивлению, некоторые из моих утверждений начали сбой.

Я привел пример к следующему коду

import math
import numpy

a = 0.27415101
b = 0.27415383
rel_tol = 1e-5
abs_tol = 1e-6

print(math.isclose(a, b, rel_tol=rel_tol, abs_tol=abs_tol))
print(numpy.isclose(a, b, rtol=rel_tol, atol=abs_tol))

Выход

False
True

По-видимому math.isclose и numpy.isclose действительно разные.

Какой я должен использовать и почему?

Python: 3.6.3, Numpy: 1.13.3.

Ответы

Ответ 1

Это было обсуждено совсем недавно на GitHub. Формулы, используемые в math и numpy действительно отличаются:

# This is numpy.isclose
abs(a - b) <= (atol + rtol * abs(b))

# This is math.isclose
abs(a - b) <= max(rtol * max(abs(a), abs(b)), atol)

Как вы можете видеть, версия numpy является асимметричной (и это четко указано в документе). Кроме того, atol может вмешиваться в rtol, вызывая ложное равенство, как в вашем случае.

Не трудно придумать пример, когда срабатывает асимметрия numpy.isclose:

a = 0.142253
b = 0.142219
rel_tol = 1e-4
abs_tol = 1.9776e-05
print(np.isclose(a, b, rtol=rel_tol, atol=abs_tol))  # False
print(np.isclose(b, a, rtol=rel_tol, atol=abs_tol))  # True

math.isclose не страдает от любой из этих проблем, поэтому предпочтительнее:

print(math.isclose(a, b, rel_tol=rel_tol, abs_tol=abs_tol))  # False
print(math.isclose(b, a, rel_tol=rel_tol, abs_tol=abs_tol))  # False

Не было решено, какое правильное исправление должно быть (было предложено ввести другую функцию или добавить дополнительный аргумент или поместить новую версию np.isclose в numpy.__future__), но пока только документация была расширена.

Ответ 2

Для конечных значений numpy.isclose использует

absolute(a - b) <= (atol + rtol * absolute(b))

и math.isclose использует

abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)

numpy объединяет относительные и абсолютные коэффициенты, а вместо аргумента с более высокой амплитудой применяется rtol to b. Функции похожи по духу, и вы должны быть в порядке, используя либо, либо используя свою собственную проверку.

numpy.isclose векторизовать по массивам и math.isclose, конечно, не так, что, вероятно, будет решающим фактором при работе с массивами. math.isclose у проверки есть преимущества (симметричный тест обычно менее удивителен, по умолчанию abs_tol 0 избегает делать предположения о шкале ввода, max вместо + позволяет иногда делать допуски в два раза выше, чем ожидалось), а вы может реализовать версию, совместимую с массивом math.isclose, достаточно легко. Перевод math.isclose исходного кода на что-то совместимое с numpy:

def math_compatible_isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
    a, b = numpy.asarray(a), numpy.asarray(b)

    equal = (a == b)
    either_infinite = numpy.isinf(a) | numpy.isinf(b)

    abs_diff = numpy.abs(b-a)
    b_rtol_check = (abs_diff <= numpy.abs(rel_tol * b))
    a_rtol_check = (abs_diff <= numpy.abs(rel_tol * a))
    atol_check = (abs_diff <= abs_tol)
    tol_check = atol_check | a_rtol_check | b_rtol_check

    return equal | (~either_infinite & tol_check)

Ответ 3

Это становится понятным после чтения документов для каждого из них -

np.isclose -

Значения допуска положительные, обычно очень маленькие. относительная разница (rtol * abs (b)) и абсолютная разница atol добавлены вместе для сравнения с абсолютной разницей между a и b.

math.isclose -

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

В двух словах, "близость" вычисляется по-разному между функциями, поэтому они не должны использоваться взаимозаменяемо. В зависимости от того, что использовать, это зависит от вашего варианта использования. Оба делают то же самое (хотя np.isclose может работать с массивами, а не только с скалярами), но вам нужно соответствующим образом скорректировать значения допусков.

Еще один фактор, который следует учитывать, заключается в том, что math.isclose был добавлен в python3.5. Таким образом, для обратной совместимости вы можете использовать первый.