Variáveis ​​Gaussianas Aleatórias

118

Existe uma classe na biblioteca padrão do .NET que me dá a funcionalidade para criar variáveis ​​aleatórias que seguem a distribuição Gaussiana?

Sebastian Müller
fonte
http://mathworld.wolfram.com/Box-MullerTransformation.html Usando duas variáveis ​​aleatórias, você pode gerar valores aleatórios ao longo de uma distribuição gaussiana. Não é uma tarefa difícil.
Jarrett Meyer
1
Gostaria apenas de adicionar um resultado matemático que não é imediatamente útil para distribuições normais (devido ao CDF complexo), mas é útil para muitas outras distribuições. Se você colocar números aleatórios uniformemente distribuídos em [0,1] (com Random.NextDouble()) no inverso do CDF de QUALQUER distribuição, você obterá números aleatórios que seguem ESSA distribuição. Se a sua aplicação não precisa de variáveis ​​distribuídas normalmente com precisão, então a Distribuição Logística é uma aproximação muito próxima do normal e tem um CDF facilmente invertível.
Ozzah
1
O pacote MedallionRandom NuGet contém um método de extensão para recuperar valores normalmente distribuídos de um Randomusando a transformação Box-Muller (mencionada em várias respostas abaixo).
ChaseMedallion

Respostas:

181

A sugestão de Jarrett de usar uma transformação Box-Muller é boa para uma solução rápida e suja. Uma implementação simples:

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
fonte
3
Eu testei e comparei com Mersenne Twister RNG e NormalDistribution da MathNet. Sua versão é duas vezes mais rápida e o resultado final é basicamente o mesmo (inspeção visual dos "sinos").
Johann Gerell,
4
@Johann, se você está procurando por velocidade pura, o algoritmo Zigorat é geralmente reconhecido como a abordagem mais rápida. Além disso, a abordagem acima pode ser feita mais rapidamente transportando um valor de uma chamada para a próxima.
Drew Noakes em
Olá, como deve stdDevser definida a variável? Eu entendo que isso pode ser configurado para requisitos específicos, mas há algum limite (ou seja, valores máx. / Mín.)?
hofnarwillie
@hofnarwillie stdDev é o parâmetro de escala da distribuição normal, que pode ser qualquer número positivo. Quanto maior for, mais dispersos serão os números gerados. Para uma distribuição normal padrão, use os parâmetros mean = 0 e stdDev = 1.
yoyoyoyosef
1
@Jack acho que não. Apenas -2 * Math.Log (u1) está dentro do sqrt, e o log será sempre negativo ou zero, pois u1 <= 1
yoyoyoyosef
62

Esta pergunta parece ter sido movida para cima da geração Gaussiana do Google para .NET, então resolvi postar uma resposta.

Fiz alguns métodos de extensão para a classe .NET Random , incluindo uma implementação da transformação Box-Muller. Uma vez que são extensões, desde que o projeto esteja incluído (ou você referencie a DLL compilada), você ainda pode fazer

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

Espero que ninguém se importe com o plug desavergonhado.

Amostra de histograma de resultados (um aplicativo de demonstração para desenhar isso está incluído):

insira a descrição da imagem aqui

Soberbo
fonte
Sua classe de extensão tem algumas coisas que eu estava procurando! obrigado!
Thomas,
1
você tem um pequeno bug em seu método NextGaussian. NextDouble () Retorna um número de ponto flutuante aleatório maior ou igual a 0,0 e menor que 1,0. Portanto, você deve ter u1 = 1,0 - NextDouble () .... outro log (0) explodirá
Mitch Wheat
21

Math.NET fornece essa funcionalidade. Veja como:

double mean = 100;
double stdDev = 10;

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

Você pode encontrar a documentação aqui: http://numerics.mathdotnet.com/api/MathNet.Numerics.Distributions/Normal.htm

Gordon Slysz
fonte
Ótima resposta! Esta função está disponível no NuGet no pacote MathNet.Numerics . É sempre bom não ter que rolar por conta própria.
jpmc26
8

Criei uma solicitação para esse recurso no Microsoft Connect. Se é algo que procura, vote e aumente a sua visibilidade.

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

Este recurso está incluído no Java SDK. Sua implementação está disponível como parte da documentação e é facilmente transportada para C # ou outras linguagens .NET.

Se você está procurando velocidade pura, o algoritmo Zigorat é geralmente reconhecido como a abordagem mais rápida.

Não sou um especialista neste tópico - descobri a necessidade disso ao implementar um filtro de partículas para minha biblioteca de futebol robótico simulado RoboCup 3D e fiquei surpreso quando isso não foi incluído na estrutura.


Enquanto isso, aqui está um invólucro para Randomque fornece uma implementação eficiente do método polar Box Muller:

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;
    }
}
Drew Noakes
fonte
Eu obtive alguns valores disso embora. alguém pode verificar o que há de errado?
mk7
@ mk7, uma função de probabilidade gaussiana centrada em torno de zero tem a mesma probabilidade de fornecer valores negativos e positivos.
Drew Noakes em
Você está certo! Já que gostaria de obter uma lista de peso em uma população típica com PDF gaussiano, estou definindo mu para, digamos, 75 [em kg] e sigma para 10. Preciso definir uma nova instância de GaussianRandom para gerar cada peso aleatório?
mk7
Você pode continuar desenhando amostras de uma instância.
Drew Noakes em
5

Math.NET Iridium também afirma implementar "geradores aleatórios não uniformes (normal, poisson, binomial, ...)".

Jason DeFontes
fonte
Mas não está funcionando corretamente. Tentei plotá-lo, Dando número aleatório uniforme.
Nikhil Chilwant
4

Aqui está outra solução rápida e suja para gerar variáveis ​​aleatórias com distribuição normal . Ele desenha algum ponto aleatório (x, y) e verifica se esse ponto está sob a curva de sua função de densidade de probabilidade, caso contrário, repita.

Bônus: Você pode gerar variáveis ​​aleatórias para qualquer outra distribuição (por exemplo, a distribuição exponencial ou distribuição de poisson ) apenas substituindo a função de densidade.

    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 * σ * σ));
    }

Importante: Selecione o intervalo de y e os parâmetros σ e μ para que a curva da função não seja cortada em seus pontos máximo / mínimo (por exemplo, em x = média). Pense dos intervalos de x e y como uma caixa envolvente, em que a curva deve caber dentro.

Doomjunky
fonte
4
Tangenial, mas esta é realmente a primeira vez que percebi que você pode usar símbolos Unicode para variáveis ​​em vez de algo estúpido como _sigma ou _phi ...
Slothario
@Slothario Agradeço aos desenvolvedores em todos os lugares por usarem 'algo idiota': |
user2864740
2

Eu gostaria de expandir a resposta de @yoyoyoyosef tornando-a ainda mais rápida e escrevendo uma classe de wrapper. A sobrecarga incorrida pode não significar duas vezes mais rápido, mas acho que deve ser quase duas vezes mais rápido. Não é thread-safe, entretanto.

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();
    }
}
Hameer Abbasi
fonte
2

Expandindo as respostas de @Noakes e @Hameer, também implementei uma classe 'Gaussiana', mas para simplificar o espaço de memória, tornei-a filha da classe Random para que você também possa chamar o básico Next (), NextDouble () , etc da classe Gaussian também sem ter que criar um objeto Random adicional para manipulá-lo. Também eliminei as propriedades da classe global _available e _nextgauss, pois não as considerava necessárias, uma vez que esta classe é baseada em instância, deve ser thread-safe, se você der a cada thread seu próprio objeto Gaussiano. Também movi todas as variáveis ​​alocadas em tempo de execução para fora da função e as tornei propriedades de classe, isso reduzirá o número de chamadas para o gerenciador de memória, uma vez que as 4 duplas teoricamente nunca devem ser desalocadas até que o objeto seja destruído.

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));
    }
}

fonte
Variáveis ​​locais também são amigas.
user2864740
1

Expandindo a resposta de Drew Noakes, se você precisa de melhor desempenho do que Box-Muller (cerca de 50-75% mais rápido), Colin Green compartilhou uma implementação do algoritmo Zigurate em C #, que você pode encontrar aqui:

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

O Zigurate usa uma tabela de pesquisa para lidar com valores que caem suficientemente longe da curva, os quais ele aceitará ou rejeitará rapidamente. Em torno de 2,5% das vezes, ele precisa fazer cálculos adicionais para determinar em que lado da curva está um número.

Neil
fonte
0

Você pode tentar o Infer.NET. Porém, ainda não é licenciado comercialmente. Aqui está o link

É uma estrutura probabilística para .NET desenvolvida em minha pesquisa da Microsoft. Eles têm tipos .NET para distribuições de Bernoulli, Beta, Gamma, Gaussian, Poisson e provavelmente mais alguns que deixei de fora.

Pode realizar o que você deseja. Obrigado.

Aaron Stainback
fonte
0

Esta é a minha implementação simples inspirada no Box Muller. Você pode aumentar a resolução para atender às suas necessidades. Embora funcione muito bem para mim, essa é uma aproximação de intervalo limitado, então tenha em mente que as caudas são fechadas e finitas, mas certamente você pode expandi-las conforme necessário.

    //
    // 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;
   }
} 
Daniel Howard
fonte
Esta é a minha implementação simples inspirada no Box Muller. Você pode aumentar a resolução para atender às suas necessidades. Isso é muito rápido, simples e funciona para meus aplicativos de rede neural que precisam de um tipo gaussiano aproximado de função de densidade de probabilidade para fazer o trabalho. Espero que ajude alguém a economizar tempo e ciclos de CPU. Embora funcione muito bem para mim, essa é uma aproximação de intervalo limitado, então tenha em mente que as caudas são fechadas e finitas, mas certamente você pode expandi-las conforme necessário.
Daniel Howard
1
Olá Daniel, sugeri uma edição que incorpora a descrição do seu comentário na própria resposta. Ele também remove o '//' que estava comentando o código real em sua resposta. Você mesmo pode fazer a edição se quiser / se for rejeitada :)
mbrig
-1

Acho que não. E eu realmente espero que não, já que o framework já está inchado o suficiente, sem essa funcionalidade especializada preenchendo-o ainda mais.

Dê uma olhada em http://www.extremeoptimization.com/Statistics/UsersGuide/ContinuousDistributions/NormalDistribution.aspx e http://www.vbforums.com/showthread.php?t=488959 para soluções .NET de terceiros.

David Arno
fonte
7
Desde quando a distribuição gaussiana é 'especializada'? É muito mais geral do que, digamos, AJAX ou DataTables.
TraumaPony de
@TraumaPony: você está seriamente tentando sugerir que mais desenvolvedores usem distribuição Gaussiana do que AJAX regularmente?
David Arno,
3
Possivelmente; o que estou dizendo é que é muito mais especializado. Ele só tem um uso - aplicativos da web. As distribuições gaussianas têm um número incrível de usos não relacionados.
TraumaPony
@DavidArno, você está sugerindo seriamente que menos funcionalidade melhora um framework.
Jodrell,
1
@Jodrell, para citar um exemplo específico, acho que a decisão de tornar o MVC uma estrutura separada, em vez de parte da estrutura .NET principal, foi boa.
David Arno,