ошибки округления инверсии матрицы numpy

Я получаю очень странное значение для моей записи (1,1) для моей матрицы BinvA
Я просто пытаюсь инвертировать матрицу B и выполнить умножение (B^-1)A.

Я понимаю, что когда я выполняю расчет вручную, мой (1,1) должен быть равен 0, но вместо этого я получаю 1.11022302e-16. Как я могу это исправить? Я знаю, что числа с плавающей запятой не могут быть представлены с полной точностью, но почему это дает мне такой неточный ответ и не округляет до 0. Есть ли способ сделать его более точным?

Это мой код:

import numpy as np

A = np.array([[2,2],[4,-1]],np.int)
A = A.transpose()


B = np.array([[1,3],[-1,-1]],np.int)
B = B.transpose()

Binv = np.linalg.inv(B) #calculate the inverse

BinvA = np.dot(Binv,A) 
print(BinvA)

Мое заявление о печати:

[[  1.11022302e-16  -2.50000000e+00]
 [ -2.00000000e+00  -6.50000000e+00]]

person FireFistAce    schedule 02.07.2014    source источник
comment
что плохого в этой точности? вы пытаетесь решить линейное уравнение? Вы хотите округлить все записи?   -  person Moj    schedule 02.07.2014
comment
@Moj Ну, точность - это проблема. Меня беспокоит, что если простое матричное умножение нельзя округлить до разумной точности, более сложные вычисления будут очень неточными. Я работаю над кодом для расчета матриц перехода и проекции, это было просто для проверки того, насколько хорошо я их умножал.   -  person FireFistAce    schedule 02.07.2014


Ответы (2)


Когда вы вычисляете обратное, ваши массивы преобразуются в float64, чей машинный эпсилон равен 1e-15. Эпсилон — это относительный шаг квантования числа с плавающей запятой.

Если есть сомнения, мы можем запросить у numpy информацию о типе данных с плавающей запятой, используя функцию finfo. В таком случае

np.finfo('float64')
finfo(resolution=1e-15, 
      min=-1.7976931348623157e+308, max=1.7976931348623157e+308, 
      dtype=float64)

Итак, технически ваше значение меньше eps является очень точным представлением 0 для типа float64.

Если вас беспокоит только представление, вы можете указать numpy не печатать маленькие числа с плавающей запятой (1 eps или меньше от 0) с помощью:

np.set_printoptions(suppress=True)

После этого ваш оператор печати возвращает:

[[ 0.  -2.5]
 [-2.  -6.5]]

Обратите внимание, что это общая числовая проблема, общая для всех реализаций с плавающей запятой. Вы можете найти больше информации об ошибках округления с плавающей запятой на SO:

или в сети:

person user2304916    schedule 02.07.2014
comment
Но как насчет того, когда я хочу выполнить больше расчетов типа вычислительной физики, например, рассчитать энергию частицы, такой как электрон, которая обычно может быть 1,6 e-19 Дж, в других случаях даже ниже, например 6,626 e-34, как я это представляю? - person FireFistAce; 02.07.2014
comment
Как вы можете видеть из моего ответа, эпсилон не является минимальным числом с плавающей запятой. Минимальное представимое значение в float64 равно min=-1.7976931348623157e+308 (см. вывод finfo), но относительная точность будет 1e-15. - person user2304916; 02.07.2014
comment
эта часть немного сбивает с толку, поэтому позвольте мне посмотреть, правильно ли я понимаю, что 1e-15 означает количество десятичных знаков, поэтому оно с точностью до 15 знаков после запятой. Но что, если мой расчет говорит, что требуется 5 значащих цифр, но они включают умножение очень маленьких чисел, таких как точка (1,6 e-19, 6,626 e-34), вы говорите, что это все равно даст мне точный результат до 15 значащих цифр? - person FireFistAce; 02.07.2014
comment
1e-15 — это минимальная представимая относительная разница между двумя числами float64. Вы не можете представить действительное число, вы получаете только приближение, и эта ошибка называется ошибкой округления. Если вы выполняете последовательность операций, ваша ошибка округления может легко увеличиться, поэтому ваша точность будет хуже, чем 1e-15. Существует большое поле под названием числовой анализ, в котором изучается распространение ошибок округления и способы его ограничения. . - person user2304916; 02.07.2014
comment
Обратите внимание на относительную разницу. Если вы вычтете два числа около 1e-200, минимальная представимая разница будет около 1e-215. Подумайте о двоичной форме научной записи с 53 значащими битами. - person Patricia Shanahan; 03.07.2014
comment
@Patricia Shanahan спасибо, что немного прояснили это, потому что я предполагал в python и numpy, что научная запись эквивалентна значащим цифрам, поэтому, просто чтобы уточнить, используя научную запись, я могу выполнять вычисления примерно до 1e-308, после чего я получаю недополнение, и это будет быть точным только до 15 значащих цифр? - person FireFistAce; 03.07.2014
comment
@FireFistAce Да, вот и все. В частности, двоичная плавающая точка IEEE-754 поддерживает грациозную потерю значимости, что позволяет при снижении точности обрабатывать еще меньшие числа. - person Patricia Shanahan; 03.07.2014

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

import numpy as np
from decimal import Decimal
A = np.array([[2,2],[4,-1]],np.int)
for i, a in np.ndenumerate(A):
    A[i] = Decimal(a)
    print type(A[i])

Но, увы, Decimals не входит в число типов данных, поддерживаемых "из коробки" в numpy, поэтому каждый раз, когда вы пытаетесь вставить Decimal в массив, он повторно преобразует его в число с плавающей запятой.

Одной из возможностей было бы установить тип данных, таким образом:

def decimal_array(arr):
    X = np.array(arr, dtype = Decimal)
    for i, x in np.ndenumerate(X): X[i] = Decimal(x)
    return X

A = decimal_array([[2,2],[4,-1]])
B = decimal_array([[1,3],[-1,-1]])

A = A.transpose()
B = B.transpose()
Binv = np.linalg.inv(B) #calculate the inverse

Но теперь, если вы

print Binv.dtype

вы увидите, что инверсия преобразовала его обратно в плавающий. Причина в том, что linalg.inv (как и многие другие функции) ищет "common_type" B, который является скаляром, к которому, по ее мнению, он может принудить элементы вашего массива.

Хотя, возможно, это не безнадежно. Я посмотрел, сможете ли вы решить эту проблему, создав собственный тип dtype, но оказалось, что скаляры (целые числа, числа с плавающей запятой и т. д.) вообще не являются типами dtype. Вместо этого вы, вероятно, захотите зарегистрировать новый скаляр — это Decimal — как указано в файле статья о скалярах. Вы увидите ссылку на Numpy C-API (не бойтесь). Найдите на странице слова «регистрация» и «скаляр», чтобы начать.

person rcs    schedule 03.07.2014
comment
Мне любопытно, почему вы рекомендуете десятичную для этого. Судя по вопросу и комментариям, контекст представляет собой научные вычисления, для которых нет ничего особенного в базе 10. Действительно ли для рассматриваемых вычислений требуется более 53 значащих битов? - person Patricia Shanahan; 03.07.2014
comment
Пакет Python decimal имеет то замечательное свойство, что он работает так же, как арифметика, которую люди изучают в школе», в том числе устойчивость к странностям eps с арифметикой с плавающей запятой. Но, по общему признанию, принятый ответ вполне может быть лучшим решением вопроса ОП, потому что он такой быстрый. - person rcs; 04.07.2014
comment
Как десятичное число предотвращает странности eps при выполнении вычислений, таких как инверсия матрицы, которые требуют общего деления? - person Patricia Shanahan; 04.07.2014