Функция 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. Таким образом, для обратной совместимости вы можете использовать первый.