Как бороться с underflow в научных вычислениях?
Я работаю над вероятностными моделями, и при выполнении вывода на этих моделях оцененные вероятности могут стать очень маленькими. Чтобы избежать underflow, я в настоящее время работаю в домене журнала (я храню журнал вероятностей). Умножение вероятностей эквивалентно сложению, а суммирование производится по формуле:
log(exp(a) + exp(b)) = log(exp(a - m) + exp(b - m)) + m
здесь m = max(a, b)
.
Я использую некоторые очень большие матрицы, и я должен взять экспоненту по элементам эти матрицы для вычисления умножения матриц-векторов. Этот шаг довольно дорог, и мне было интересно, существуют ли другие методы борьбы с underflow при работе с вероятностями.
Edit: по соображениям эффективности я ищу решение, используя примитивные типы, а не объекты, хранящие представление вещественных чисел произвольной точности.
Edit 2: Я ищу более быстрое решение, чем трюк с доменом журнала, а не более точное решение. Я доволен точностью, которую я сейчас получаю, но мне нужен более быстрый метод. В частности, суммирование происходит во время умножения матриц-векторов, и я хотел бы иметь возможность использовать эффективные методы BLAS.
устранение: после обсуждения с Джонатаном Дурси я решил разложить каждую матрицу и вектор на самый большой элемент и сохранить этот фактор в области журнала. Умножения просты. Прежде чем добавить, я должен факторизовать один добавленных матриц / векторов по соотношению двух факторов. Я обновляю коэффициент каждые десять операций.
5 ответов
эта проблема возникла недавно на сайт обмена стеком вычислительной науки а также, и хотя там немедленное беспокойство было переполнение, проблемы более или менее одинаковы.
преобразование в пространство журналов, безусловно, является одним из разумных подходов. Независимо от того, в каком пространстве вы находитесь, чтобы сделать большое количество сумм правильно, есть несколько методов, которые вы можете использовать для повышения точности суммирования. Компенсированные подходы суммирования, наиболее известные Kahan суммирование, сохранить как сумму, так и то, что фактически является "остатком"; это дает вам некоторые преимущества использования более высокой точности арифметической без всех затрат (и только с использованием примитивных типов). Оставшийся срок также дает вам некоторое представление о том, насколько хорошо вы делаете.
в дополнение к улучшению фактической механики вашего добавления, изменение порядка добавления ваших терминов может иметь большое значение. Сортировка ваших терминов так, чтобы вы суммировали из наименьшее к наибольшему может помочь, так как тогда вы больше не добавляете термины так часто, которые очень разные (что может вызвать значительные проблемы округления); в некоторых случаях, делая log2 N повторяющиеся попарные суммы также могут быть улучшением, чем просто делать прямую линейную сумму, в зависимости от того, как выглядят ваши термины.
полезность всех этих подходов во многом зависит от свойств ваших данных. Произвольная точность математических библиотек, в то время как чрезвычайно дорогостоящее в вычислительном времени (и, возможно, памяти) для использования, имеет то преимущество, что является довольно общим решением.
Я столкнулся с подобной проблемой много лет назад. Решением было разработать аппроксимацию log (1+exp (- x)). Диапазон аппроксимации не должен быть таким большим (x от 0 до 40 будет более чем достаточно), и, по крайней мере, в моем случае точность также не должна быть особенно высокой.
в вашем случае, похоже, вам нужно вычислить log (1+exp (- x1)+exp (- x2)+...). Выбросьте эти большие отрицательные значения. Например, предположим, что a, b и c - три логарифмические вероятности, с 0>a>b>c. Вы можете игнорировать c, если a-c>38. Это не будет способствовать вашей совместной вероятности журнала вообще, по крайней мере, если вы работаете с двойниками.
Вариант 1: Commons Math - Библиотека Математики Apache Commons
Commons Math-это библиотека легких, автономных математических и статистических компонентов, решающих наиболее распространенные проблемы, а не доступно на языке программирования Java или Commons Lang.
Примечание: API защищает конструкторы, чтобы заставить шаблон фабрики, называя завод DfpField (а не несколько более интуитивный DfpFac или DfpFactory). Поэтому вы должны использовать
new DfpField(numberOfDigits).newDfp(myNormalNumber)
чтобы создать экземпляр Dfp, вы можете вызвать .multiply
или что-то в этом роде. Я подумал, что упомяну об этом, потому что это немного сбивает с толку.
Вариант 2: научная библиотека GNU или Повысить Библиотеки C++. В этих случаях вы должны использовать JNI, у для вызова этих собственных библиотек.
Вариант 3: если вы свободны чтобы использовать другие программы и/или языки, вы можете использовать программы / языки для численных вычислений, таких как Октавы, Scilab, и тому подобное.
Вариант 4: BigDecimal Java.
вместо того, чтобы хранить значения в логарифмической форме, я думаю, вам, вероятно, лучше использовать ту же концепцию, что и double
s, а именно представление с плавающей запятой. Например, вы можете хранить каждое значение как два long
s, один для знака и мантиссы и один для экспоненты. (реальные плавающая точка имеет тщательно настроенный дизайн для поддержки множества краевых случаев и избежания потери одного бита; но вам, вероятно, не нужно так много беспокоиться о любом из них, и вы можете сосредоточиться на конструируя его таким образом, что это просто реализовать.)
Я не понимаю, почему это работает, но эта формула, кажется, работает и проще:
c = a + log(1 + exp(b - a))
здесь c = log(exp(a)+exp(b))