Как я могу наиболее эффективно предотвратить то, чтобы моя нормально распределенная случайная величина была нулевой?
Я пишу алгоритм Монте-Карло, в котором в какой-то момент мне нужно разделить случайную величину. Точнее, случайная величина используется в качестве ширины шага для разностного отношения, поэтому я фактически сначала что-то умножаю на переменную, а затем снова делю ее на некоторую локально линейную функцию этого выражения. подобно
double f(double);
std::tr1::variate_generator<std::tr1::mt19937, std::tr1::normal_distribution<> >
r( std::tr1::mt19937(time(NULL)),
std::tr1::normal_distribution<>(0) );
double h = r();
double a = ( f(x+h) - f(x) ) / h;
Это работает нормально большую часть времени, но не работает, когда h=0
, Математически это не проблема, потому что при любом конечном (или даже счетном) отборе нормально распределенных случайных величин все они будут отличны от нуля с вероятностью 1. Но в цифровой реализации я столкнусь с h==0
каждый вызов функции ≈2³² (независимо от того, имеет ли мерсенновый твистер период больше, чем вселенная, он все равно выводит обычный long
s!).
Это довольно просто, чтобы избежать этой проблемы, в настоящее время я делаю
double h = r();
while (h==0) h=r();
но я не считаю это особенно элегантным. Есть ли лучший способ?
Функция, которую я оцениваю, на самом деле не просто ℝ->ℝ вроде
f
есть, но ℝᵐxℝⁿ -> which, в котором я вычисляю градиент по переменным while при численном интегрировании по переменным.. Вся функция наложена на непредсказуемый (но "когерентный") шум, иногда с определенными (но неизвестными) выдающимися частотами, вот что доставляет мне неприятности, когда я пробую его с фиксированными значениями для h
,
5 ответов
Ваш путь кажется достаточно элегантным, может быть, немного другим:
do {
h = r();
} while (h == 0.0);
Отношение двух нормально распределенных случайных величин является распределением Коши. Распределение Коши является одним из тех неприятных распределений с бесконечной дисперсией. Очень противный действительно. Распределение Коши испортит ваш эксперимент в Монте-Карло.
Во многих случаях, когда вычисляется отношение двух случайных величин, знаменатель не является нормальным. Люди часто используют нормальное распределение, чтобы приблизить эту ненормально распределенную случайную величину, потому что
- с нормальными дистрибутивами обычно так легко работать,
- обычно имеют такие хорошие математические свойства,
- нормальное предположение представляется более или менее правильным, и
- Реальное распределение - это медведь.
Предположим, вы делите на расстояние. Расстояние по определению полуположительно определено и часто положительно определено как случайная величина. Таким образом, расстояние от летучей мыши никогда не может быть нормально распределено. Тем не менее, люди часто принимают нормальное распределение для расстояния в тех случаях, когда среднее значение намного больше, чем стандартное отклонение. Когда это нормальное предположение сделано, вам нужно защищаться от этих нереальных ценностей. Одно простое решение - усеченная нормаль.
Если вы хотите сохранить нормальное распределение, вы должны либо исключить 0, либо присвоить 0 новому ранее не встречающемуся значению. Поскольку второе, скорее всего, невозможно в ограниченных пределах информатики, первое - наш единственный вариант.
Функция (f(x+h)-f(x))/h имеет предел как h->0, и поэтому, если вы встретите h==0, вы должны использовать этот предел. Пределом будет f'(x), поэтому, если вы знаете производную, вы можете использовать ее.
Если то, что вы на самом деле делаете, - это создание количества дискретных точек, хотя они приближаются к нормальному распределению, и этого достаточно для вашего распределения, создайте его так, чтобы ни одна из них не имела значения 0.
В зависимости от того, что вы пытаетесь вычислить, возможно, что-то вроде этого будет работать:
double h = r();
double a;
if (h != 0)
a = ( f(x+h) - f(x) ) / h;
else
a = 0;
Если f
линейная функция, она должна (я думаю?) оставаться непрерывной при h = 0.
Вы также можете вместо этого рассмотреть возможность захвата исключений деления на ноль, чтобы избежать затрат на ветку. Обратите внимание, что это может или не может иметь пагубное влияние на производительность - сравните оба пути!
В Linux вам нужно собрать файл, который содержит ваше потенциальное деление на ноль с -fnon-call-exceptions
и установите обработчик SIGFPE:
struct fp_exception { };
void sigfpe(int) {
signal(SIGFPE, sigfpe);
throw fp_exception();
}
void setup() {
signal(SIGFPE, sigfpe);
}
// Later...
try {
run_one_monte_carlo_trial();
} catch (fp_exception &) {
// skip this trial
}
В Windows используйте SEH:
__try
{
run_one_monte_carlo_trial();
}
__except(GetExceptionCode() == EXCEPTION_INT_DIVIDE_BY_ZERO ?
EXCEPTION_EXECUTE_HANDLER : EXCEPTION_CONTINUE_SEARCH)
{
// skip this trial
}
Это имеет то преимущество, что потенциально может меньше влиять на быстрый путь. Ветви нет, хотя может быть некоторая корректировка записей обработчика исключений. В Linux может быть небольшое снижение производительности из-за того, что компилятор генерирует более консервативный код для -fnon-call-exceptions
, Это менее вероятно, будет проблемой, если код, скомпилированный в -fnon-call-exceptions
не выделяет никаких автоматических (стековых) объектов C++. Стоит также отметить, что в этом случае деление на ноль происходит ОЧЕНЬ дорого.