Случайные гауссовские переменные

118

Есть ли в стандартной библиотеке .NET класс, который дает мне возможность создавать случайные переменные, соответствующие распределению Гаусса?

Себастьян Мюллер
источник
http://mathworld.wolfram.com/Box-MullerTransformation.html Используя две случайные величины, вы можете генерировать случайные значения в соответствии с распределением Гаусса. Это совсем не сложная задача.
Джарретт Мейер,
1
Я просто хотел бы добавить математический результат, который не сразу полезен для нормальных распределений (из-за сложной CDF), но полезен для многих других распределений. Если вы поместите равномерно распределенные случайные числа в [0,1] (с Random.NextDouble()) в инверсию CDF ЛЮБОГО распределения, вы получите случайные числа, которые соответствуют ЭТОМ распределению. Если вашему приложению не нужны точно нормально распределенные переменные, тогда логистическое распределение очень близко к нормальному и имеет легко обратимый CDF.
Ozzah
1
Пакет NuGet MedallionRandom содержит метод расширения для получения нормально распределенных значений из объекта с Randomиспользованием преобразования Бокса-Мюллера (упомянутого в нескольких ответах ниже).
ChaseMedallion

Ответы:

182

Предложение Джарретта об использовании преобразования Бокса-Мюллера хорошо для быстрого и грязного решения. Простая реализация:

Random rand = new Random(); //reuse this if you are generating many
double u1 = 1.0-rand.NextDouble(); //uniform(0,1] random doubles
double u2 = 1.0-rand.NextDouble();
double randStdNormal = Math.Sqrt(-2.0 * Math.Log(u1)) *
             Math.Sin(2.0 * Math.PI * u2); //random normal(0,1)
double randNormal =
             mean + stdDev * randStdNormal; //random normal(mean,stdDev^2)
yoyoyoyosef
источник
3
Я протестировал его и сравнил с MathNet Mersenne Twister RNG и NormalDistribution. Ваша версия более чем в два раза быстрее, и конечный результат в основном такой же (визуальный осмотр «колокольчиков»).
Иоганн Герелл,
4
@Johann, если вы ищете чистую скорость, то алгоритм Зигората общепризнан как самый быстрый подход. Более того, описанный выше подход можно ускорить, перенеся значение от одного вызова к другому.
Дрю Ноукс
Привет, что нужно stdDevустановить для переменной? Я понимаю, что это можно настроить в соответствии с конкретными требованиями, но есть ли какие-либо границы (например, значения max / min)?
hofnarwillie
@hofnarwillie stdDev - это масштабный параметр нормального распределения, который может быть любым положительным числом. Чем он больше, тем более разбросанными будут генерируемые числа. Для стандартного нормального распределения используйте параметры mean = 0 и stdDev = 1.
yoyoyoyosef
1
@ Джек, я так не думаю. Только -2 * Math.Log (u1) находится внутри sqrt, и журнал всегда будет отрицательным или нулевым, поскольку u1 <= 1
yoyoyoyosef
63

Похоже, этот вопрос переместился на вершину Google для поколения .NET Gaussian, поэтому я решил, что опубликую ответ.

Я сделал несколько методов расширения для класса .NET Random , включая реализацию преобразования Бокса-Мюллера. Поскольку они являются расширениями, пока проект включен (или вы ссылаетесь на скомпилированную DLL), вы все равно можете делать

var r = new Random();
var x = r.NextGaussian();

Надеюсь, никто не против бесстыдной пробки.

Пример гистограммы результатов (прилагается демонстрационное приложение для ее рисования):

введите описание изображения здесь

Superbest
источник
В вашем классе расширения есть кое-что, что я искал! Спасибо!
Thomas
1
у вас есть небольшая ошибка в вашем методе NextGaussian. NextDouble () Возвращает случайное число с плавающей запятой, которое больше или равно 0,0, но меньше 1,0. Итак, у вас должно быть u1 = 1.0 - NextDouble () .... другой журнал (0) взорвется
Митч Уит
21

Math.NET предоставляет эту функцию. Вот как:

double mean = 100;
double stdDev = 10;

MathNet.Numerics.Distributions.Normal normalDist = new Normal(mean, stdDev);
double randomGaussianValue=   normalDist.Sample();

Вы можете найти документацию здесь: http://numerics.mathdotnet.com/api/MathNet.Numerics.Distributions/Normal.htm

Гордон Слиз
источник
Отличный ответ! Эта функция доступна в NuGet в пакете MathNet.Numerics . Всегда здорово, что тебе не нужно кататься самостоятельно.
jpmc26
8

Я создал запрос на такую ​​функцию в Microsoft Connect. Если это то, что вы ищете, проголосуйте за это и увеличьте его видимость.

https://connect.microsoft.com/VisualStudio/feedback/details/634346/guassian-normal-distribution-random-numbers

Эта функция включена в Java SDK. Его реализация доступна как часть документации и легко переносится на C # или другие языки .NET.

Если вы ищете чистую скорость, то алгоритм Зигората общепризнан как самый быстрый подход.

Я не эксперт в этой теме - я столкнулся с необходимостью в этом при реализации фильтра частиц для моей 3D-библиотеки роботов-футбольных роботов RoboCup и был удивлен, когда это не было включено в структуру.


А пока вот оболочка, Randomкоторая обеспечивает эффективную реализацию полярного метода Бокса Мюллера:

public sealed class GaussianRandom
{
    private bool _hasDeviate;
    private double _storedDeviate;
    private readonly Random _random;

    public GaussianRandom(Random random = null)
    {
        _random = random ?? new Random();
    }

    /// <summary>
    /// Obtains normally (Gaussian) distributed random numbers, using the Box-Muller
    /// transformation.  This transformation takes two uniformly distributed deviates
    /// within the unit circle, and transforms them into two independently
    /// distributed normal deviates.
    /// </summary>
    /// <param name="mu">The mean of the distribution.  Default is zero.</param>
    /// <param name="sigma">The standard deviation of the distribution.  Default is one.</param>
    /// <returns></returns>
    public double NextGaussian(double mu = 0, double sigma = 1)
    {
        if (sigma <= 0)
            throw new ArgumentOutOfRangeException("sigma", "Must be greater than zero.");

        if (_hasDeviate)
        {
            _hasDeviate = false;
            return _storedDeviate*sigma + mu;
        }

        double v1, v2, rSquared;
        do
        {
            // two random values between -1.0 and 1.0
            v1 = 2*_random.NextDouble() - 1;
            v2 = 2*_random.NextDouble() - 1;
            rSquared = v1*v1 + v2*v2;
            // ensure within the unit circle
        } while (rSquared >= 1 || rSquared == 0);

        // calculate polar tranformation for each deviate
        var polar = Math.Sqrt(-2*Math.Log(rSquared)/rSquared);
        // store first deviate
        _storedDeviate = v2*polar;
        _hasDeviate = true;
        // return second deviate
        return v1*polar*sigma + mu;
    }
}
Дрю Ноукс
источник
Хотя я получил некоторые значения -ve. может кто нибудь проверить что не так?
mk7
@ mk7, гауссова функция вероятности с центром вокруг нуля с такой же вероятностью даст отрицательные значения, как и положительные.
Дрю Ноукс
Ты прав! Так как я хотел бы получить список весов в типичной популяции с гауссовским PDF, я устанавливаю mu, скажем, 75 [в кг], а sigma равным 10. Нужно ли мне устанавливать новый экземпляр GaussianRandom для генерации каждый случайный вес?
mk7
Вы можете продолжать рисовать образцы из одного экземпляра.
Дрю Ноукс
5

Math.NET Iridium также утверждает, что реализует «неоднородные генераторы случайных чисел (нормальные, пуассоновские, биномиальные, ...)».

Джейсон ДеФонтес
источник
Но он не работает должным образом. Пытался это заговорить, давая единый случайный нет.
Nikhil Chilwant
4

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

Бонус: вы можете сгенерировать случайные величины для любого другого распределения (например, экспоненциального распределения или распределения Пуассона ), просто заменив функцию плотности.

    static Random _rand = new Random();

    public static double Draw()
    {
        while (true)
        {
            // Get random values from interval [0,1]
            var x = _rand.NextDouble(); 
            var y = _rand.NextDouble(); 

            // Is the point (x,y) under the curve of the density function?
            if (y < f(x)) 
                return x;
        }
    }

    // Normal (or gauss) distribution function
    public static double f(double x, double μ = 0.5, double σ = 0.5)
    {
        return 1d / Math.Sqrt(2 * σ * σ * Math.PI) * Math.Exp(-((x - μ) * (x - μ)) / (2 * σ * σ));
    }

Важно: выберите интервал y и параметры σ и μ, чтобы кривая функции не была обрезанной в точках максимума / минимума (например, при x = среднее). Подумайте об интервалах x и y как об ограничивающем прямоугольнике, в который должна вписаться кривая.

Doomjunky
источник
4
Tangenial, но на самом деле это первый раз, когда я понял, что вы можете использовать символы Unicode для переменных вместо чего-то тупого типа _sigma или _phi ...
Slothario
@Slothario Я благодарю разработчиков во всем мире за то, что они использовали «что-то глупое»: |
user2864740 09
2

Я хотел бы расширить ответ @ yoyoyoyosef, сделав его еще быстрее и написав класс-оболочку. Накладные расходы не могут означать, что в два раза быстрее, но я думаю, что они должны быть почти в два раза быстрее. Однако это не является потокобезопасным.

public class Gaussian
{
     private bool _available;
     private double _nextGauss;
     private Random _rng;

     public Gaussian()
     {
         _rng = new Random();
     }

     public double RandomGauss()
     {
        if (_available)
        {
            _available = false;
            return _nextGauss;
        }

        double u1 = _rng.NextDouble();
        double u2 = _rng.NextDouble();
        double temp1 = Math.Sqrt(-2.0*Math.Log(u1));
        double temp2 = 2.0*Math.PI*u2;

        _nextGauss = temp1 * Math.Sin(temp2);
        _available = true;
        return temp1*Math.Cos(temp2);
     }

    public double RandomGauss(double mu, double sigma)
    {
        return mu + sigma*RandomGauss();
    }

    public double RandomGauss(double sigma)
    {
        return sigma*RandomGauss();
    }
}
Хамир Аббаси
источник
2

Расширяя ответы @Noakes и @ Hameer, я также реализовал класс 'Gaussian', но для упрощения пространства памяти я сделал его дочерним по отношению к классу Random, чтобы вы также могли вызывать базовые функции Next (), NextDouble () и т. д. из класса Gaussian без необходимости создавать дополнительный объект Random для его обработки. Я также исключил свойства глобального класса _available и _nextgauss, поскольку я не видел в них необходимости, поскольку этот класс основан на экземплярах, он должен быть потокобезопасным, если вы дадите каждому потоку свой собственный гауссовский объект. Я также переместил все переменные, выделенные во время выполнения, из функции и сделал их свойствами класса, это уменьшит количество вызовов диспетчера памяти, поскольку 4 двойных числа теоретически никогда не должны деактивироваться, пока объект не будет уничтожен.

public class Gaussian : Random
{

    private double u1;
    private double u2;
    private double temp1;
    private double temp2;

    public Gaussian(int seed):base(seed)
    {
    }

    public Gaussian() : base()
    {
    }

    /// <summary>
    /// Obtains normally (Gaussian) distrubuted random numbers, using the Box-Muller
    /// transformation.  This transformation takes two uniformly distributed deviates
    /// within the unit circle, and transforms them into two independently distributed normal deviates.
    /// </summary>
    /// <param name="mu">The mean of the distribution.  Default is zero</param>
    /// <param name="sigma">The standard deviation of the distribution.  Default is one.</param>
    /// <returns></returns>

    public double RandomGauss(double mu = 0, double sigma = 1)
    {
        if (sigma <= 0)
            throw new ArgumentOutOfRangeException("sigma", "Must be greater than zero.");

        u1 = base.NextDouble();
        u2 = base.NextDouble();
        temp1 = Math.Sqrt(-2 * Math.Log(u1));
        temp2 = 2 * Math.PI * u2;

        return mu + sigma*(temp1 * Math.Cos(temp2));
    }
}

источник
Локальные переменные тоже друзья.
user2864740 09
1

Расширяя ответ Дрю Ноукса, если вам нужна более высокая производительность, чем у Box-Muller (примерно на 50-75% быстрее), Колин Грин поделился реализацией алгоритма Ziggurat на C #, которую вы можете найти здесь:

http://heliosphan.org/zigguratalgorithm/zigguratalgorithm.html

Ziggurat использует справочную таблицу для обработки значений, которые падают достаточно далеко от кривой, которые он быстро принимает или отклоняет. Примерно в 2,5% случаев ему приходится выполнять дальнейшие вычисления, чтобы определить, на какой стороне кривой находится число.

Нил
источник
0

Вы можете попробовать Infer.NET. Однако это еще не коммерческая лицензия. Вот там ссылка

Это вероятностная структура для .NET, разработанная в моем исследовании Microsoft. У них есть типы .NET для распределений Бернулли, Бета, Гамма, Гаусса, Пуассона и, вероятно, еще некоторые, которые я не упомянул.

Это может сделать то, что вы хотите. Спасибо.

Аарон Стейнбэк
источник
0

Это моя простая реализация, вдохновленная Боксом Мюллером. Вы можете увеличить разрешение по своему усмотрению. Хотя это отлично работает для меня, это приближение ограниченного диапазона, поэтому имейте в виду, что хвосты закрытые и конечные, но, конечно, вы можете расширять их по мере необходимости.

    //
    // by Dan
    // islandTraderFX
    // copyright 2015
    // Siesta Key, FL
    //    
// 0.0  3231 ********************************
// 0.1  1981 *******************
// 0.2  1411 **************
// 0.3  1048 **********
// 0.4  810 ********
// 0.5  573 *****
// 0.6  464 ****
// 0.7  262 **
// 0.8  161 *
// 0.9  59 
//Total: 10000

double g()
{
   double res = 1000000;
   return random.Next(0, (int)(res * random.NextDouble()) + 1) / res;
}

public static class RandomProvider
{
   public static int seed = Environment.TickCount;

   private static ThreadLocal<Random> randomWrapper = new ThreadLocal<Random>(() =>
       new Random(Interlocked.Increment(ref seed))
   );

   public static Random GetThreadRandom()
   {
       return randomWrapper.Value;
   }
} 
Дэниел Ховард
источник
Это моя простая реализация, вдохновленная Боксом Мюллером. Вы можете увеличить разрешение по своему усмотрению. Это очень быстро, просто и работает для моих нейросетевых приложений, которым для выполнения работы требуется приблизительный гауссовский тип функции плотности вероятности. Надеюсь, это поможет кому-то сэкономить время и циклы процессора. Хотя это отлично работает для меня, это приближение ограниченного диапазона, поэтому имейте в виду, что хвосты закрытые и конечные, но, конечно, вы можете расширять их по мере необходимости.
Дэниел Ховард,
1
Привет, Даниэль, я предложил отредактировать, чтобы включить описание из вашего комментария в сам ответ. Он также удаляет "//", который комментировал реальный код в вашем ответе. Вы можете сделать редактирование самостоятельно, если хотите / если оно будет отклонено :)
mbrig
-1

Я не думаю, что есть. И я очень надеюсь, что это не так, поскольку фреймворк уже достаточно раздут, без такой специализированной функциональности, которая еще больше наполняет его.

Взгляните на http://www.extremeoptimization.com/Statistics/UsersGuide/ContinuousDistributions/NormalDistribution.aspx и http://www.vbforums.com/showthread.php?t=488959 для сторонних решений .NET.

Дэвид Арно
источник
7
С каких это пор гауссово распределение стало «специализированным»? Это гораздо более общий подход, чем, скажем, AJAX или DataTables.
TraumaPony
@TraumaPony: вы серьезно пытаетесь предложить большему количеству разработчиков использовать распределение Гаусса, чем использовать AJAX на регулярной основе?
Дэвид Арно,
3
Возможно; Я хочу сказать, что он гораздо более специализированный. У него только одно использование - веб-приложения. Гауссовские распределения имеют невероятное количество не связанных между собой применений.
TraumaPony
@DavidArno, вы серьезно предлагаете, чтобы меньшая функциональность улучшала структуру.
Джодрелл
1
@Jodrell, чтобы процитировать конкретный пример, я думаю, что решение сделать MVC отдельной структурой, а не частью основной платформы .NET, было хорошим.
Дэвид Арно