Como posso gerar facilmente números aleatórios seguindo uma distribuição normal em C ou C ++?
Não quero usar Boost.
Sei que Knuth fala longamente sobre isso, mas não tenho seus livros em mãos agora.
Como posso gerar facilmente números aleatórios seguindo uma distribuição normal em C ou C ++?
Não quero usar Boost.
Sei que Knuth fala longamente sobre isso, mas não tenho seus livros em mãos agora.
Respostas:
Existem muitos métodos para gerar números com distribuição gaussiana a partir de um RNG regular .
A transformação Box-Muller é comumente usada. Ele produz valores corretamente com uma distribuição normal. A matemática é fácil. Você gera dois números aleatórios (uniformes) e, ao aplicar uma fórmula a eles, obtém dois números aleatórios normalmente distribuídos. Retorne um e salve o outro para a próxima solicitação de um número aleatório.
std::normal_distribution
que faz exatamente o que você pede sem se aprofundar em detalhes matemáticos.
C ++ 11 oferece std::normal_distribution
, que é o caminho que eu faria hoje.
Aqui estão algumas soluções em ordem de complexidade crescente:
Adicione 12 números aleatórios uniformes de 0 a 1 e subtraia 6. Isso corresponderá à média e ao desvio padrão de uma variável normal. Uma desvantagem óbvia é que o intervalo é limitado a ± 6 - ao contrário de uma distribuição normal verdadeira.
A transformação Box-Muller. Isso está listado acima e é relativamente simples de implementar. Se você precisar de amostras muito precisas, no entanto, esteja ciente de que a transformada Box-Muller combinada com alguns geradores uniformes sofre de uma anomalia chamada Efeito Neave 1 .
Para melhor precisão, sugiro desenhar uniformes e aplicar a distribuição normal cumulativa inversa para chegar às variáveis normalmente distribuídas. Aqui está um algoritmo muito bom para distribuições normais cumulativas inversas.
1. HR Neave, "Sobre o uso da transformação de Box-Muller com geradores de números pseudo-aleatórios congruenciais multiplicativos", Applied Statistics, 22, 92-97, 1973
Um método rápido e fácil é somar um número de números aleatórios distribuídos uniformemente e obter sua média. Veja o Teorema do Limite Central para uma explicação completa de por que isso funciona.
Eu criei um projeto de código aberto C ++ para benchmark de geração de número aleatório normalmente distribuído .
Ele compara vários algoritmos, incluindo
cpp11random
usa C ++ 11 std::normal_distribution
com std::minstd_rand
(na verdade é uma transformação Box-Muller em clang).Os resultados da float
versão de precisão única ( ) no iMac Corei5-3330S@2.70GHz, clang 6.1, 64 bits:
Para correção, o programa verifica a média, desvio padrão, assimetria e curtose das amostras. Verificou-se que o método CLT pela soma de 4, 8 ou 16 números uniformes não apresenta boa curtose como os demais métodos.
O algoritmo Zigurate tem melhor desempenho que os outros. No entanto, ele não é adequado para paralelismo SIMD, pois precisa de consulta de tabela e ramificações. Box-Muller com conjunto de instruções SSE2 / AVX é muito mais rápido (x1,79, x2,99) do que a versão não SIMD do algoritmo zigurate.
Portanto, vou sugerir o uso de Box-Muller para arquitetura com conjuntos de instruções SIMD, e pode zigurar caso contrário.
PS o benchmark usa um LCG PRNG mais simples para gerar números aleatórios distribuídos uniformes. Portanto, pode não ser suficiente para alguns aplicativos. Mas a comparação de desempenho deve ser justa porque todas as implementações usam o mesmo PRNG, portanto, o benchmark testa principalmente o desempenho da transformação.
Aqui está um exemplo de C ++, baseado em algumas das referências. Isso é rápido e sujo, é melhor você não reinventar e usar a biblioteca boost.
#include "math.h" // for RAND, and rand
double sampleNormal() {
double u = ((double) rand() / (RAND_MAX)) * 2 - 1;
double v = ((double) rand() / (RAND_MAX)) * 2 - 1;
double r = u * u + v * v;
if (r == 0 || r > 1) return sampleNormal();
double c = sqrt(-2 * log(r) / r);
return u * c;
}
Você pode usar um gráfico QQ para examinar os resultados e ver o quão bem ele se aproxima de uma distribuição normal real (classifique suas amostras 1..x, transforme as classificações em proporções de contagem total de x, ou seja, quantas amostras, obtenha os valores z e plote-os. Uma linha reta para cima é o resultado desejado).
Use std::tr1::normal_distribution
.
O namespace std :: tr1 não faz parte do boost. É o namespace que contém as adições de biblioteca do C ++ Technical Report 1 e está disponível em compiladores Microsoft e gcc atualizados, independentemente do boost.
É assim que você gera os exemplos em um compilador C ++ moderno.
#include <random>
...
std::mt19937 generator;
double mean = 0.0;
double stddev = 1.0;
std::normal_distribution<double> normal(mean, stddev);
cerr << "Normal: " << normal(generator) << endl;
generator
deve realmente ser semeado.
Você pode usar o GSL . Alguns exemplos completos são fornecidos para demonstrar como usá-lo.
Dê uma olhada em: http://www.cplusplus.com/reference/random/normal_distribution/ . É a maneira mais simples de produzir distribuições normais.
Se estiver usando C ++ 11, você pode usar std::normal_distribution
:
#include <random>
std::default_random_engine generator;
std::normal_distribution<double> distribution(/*mean=*/0.0, /*stddev=*/1.0);
double randomNumber = distribution(generator);
Existem muitas outras distribuições que você pode usar para transformar a saída do mecanismo de números aleatórios.
Segui a definição do PDF fornecida em http://www.mathworks.com/help/stats/normal-distribution.html e cheguei a isto:
const double DBL_EPS_COMP = 1 - DBL_EPSILON; // DBL_EPSILON is defined in <limits.h>.
inline double RandU() {
return DBL_EPSILON + ((double) rand()/RAND_MAX);
}
inline double RandN2(double mu, double sigma) {
return mu + (rand()%2 ? -1.0 : 1.0)*sigma*pow(-log(DBL_EPS_COMP*RandU()), 0.5);
}
inline double RandN() {
return RandN2(0, 1.0);
}
Talvez não seja a melhor abordagem, mas é bastante simples.
rand()
de RANDU
retornar um zero, pois Ln (0) é indefinido.
cos(2*pi*rand/RAND_MAX)
, enquanto você multiplica por (rand()%2 ? -1.0 : 1.0)
.
A lista de perguntas frequentes do comp.lang.c compartilha três maneiras diferentes de gerar facilmente números aleatórios com uma distribuição Gaussiana.
Você pode dar uma olhada: http://c-faq.com/lib/gaussian.html
Implementação do Box-Muller:
#include <cstdlib>
#include <cmath>
#include <ctime>
#include <iostream>
using namespace std;
// return a uniformly distributed random number
double RandomGenerator()
{
return ( (double)(rand()) + 1. )/( (double)(RAND_MAX) + 1. );
}
// return a normally distributed random number
double normalRandom()
{
double y1=RandomGenerator();
double y2=RandomGenerator();
return cos(2*3.14*y2)*sqrt(-2.*log(y1));
}
int main(){
double sigma = 82.;
double Mi = 40.;
for(int i=0;i<100;i++){
double x = normalRandom()*sigma+Mi;
cout << " x = " << x << endl;
}
return 0;
}
Existem vários algoritmos para a distribuição normal cumulativa inversa. Os mais populares em finanças quantitativas são testados em http://chasethedevil.github.io/post/monte-carlo--inverse-cumulative-normal-distribution/
Na minha opinião, não há muito incentivo para usar algo diferente do algoritmo AS241 de Wichura : é uma máquina de precisão, confiável e rápido. Os gargalos raramente estão na geração de números aleatórios gaussianos.
Além disso, mostra a desvantagem das abordagens do tipo Zigurate.
A principal resposta aqui defende a Box-Müller, você deve estar ciente de que ela tem deficiências conhecidas. Cito https://www.sciencedirect.com/science/article/pii/S0895717710005935 :
na literatura, Box-Muller é às vezes considerado ligeiramente inferior, principalmente por duas razões. Primeiro, se alguém aplicar o método Box-Muller a números de um gerador de congruência linear ruim, os números transformados fornecem uma cobertura extremamente pobre do espaço. Lotes de números transformados com caudas em espiral podem ser encontrados em muitos livros, mais notavelmente no livro clássico de Ripley, que foi provavelmente o primeiro a fazer esta observação "
1) A maneira graficamente intuitiva de gerar números aleatórios gaussianos é usando algo semelhante ao método de Monte Carlo. Você geraria um ponto aleatório em uma caixa ao redor da curva gaussiana usando seu gerador de números pseudo-aleatórios em C. Você pode calcular se esse ponto está dentro ou abaixo da distribuição gaussiana usando a equação da distribuição. Se esse ponto estiver dentro da distribuição gaussiana, você obteve seu número aleatório gaussiano como o valor x do ponto.
Este método não é perfeito porque tecnicamente a curva gaussiana segue em direção ao infinito, e você não poderia criar uma caixa que se aproxima do infinito na dimensão x. Mas a curva Guassiana se aproxima de 0 na dimensão y muito rápido, então eu não me preocuparia com isso. A restrição do tamanho de suas variáveis em C pode ser mais um fator limitante para sua precisão.
2) Outra forma seria usar o Teorema do Limite Central, que afirma que quando variáveis aleatórias independentes são adicionadas, elas formam uma distribuição normal. Mantendo esse teorema em mente, você pode aproximar um número aleatório gaussiano adicionando uma grande quantidade de variáveis aleatórias independentes.
Esses métodos não são os mais práticos, mas isso é esperado quando você não deseja usar uma biblioteca pré-existente. Tenha em mente que esta resposta vem de alguém com pouca ou nenhuma experiência em cálculo ou estatística.
Método de Monte Carlo
A maneira mais intuitiva de fazer isso seria usar um método de monte carlo. Pegue um intervalo adequado -X, + X. Valores maiores de X resultarão em uma distribuição normal mais precisa, mas leva mais tempo para convergir. uma. Escolha um número aleatório z entre -X a X. b. Mantenha a probabilidade de N(z, mean, variance)
onde N é a distribuição gaussiana. Solte caso contrário e volte para a etapa (a).
Dê uma olhada no que encontrei.
Esta biblioteca usa o algoritmo Zigurate.
O computador é um dispositivo determinístico. Não há aleatoriedade no cálculo. Além disso, o dispositivo aritmético na CPU pode avaliar a soma sobre algum conjunto finito de números inteiros (realizando avaliação em campo finito) e conjunto finito de números racionais reais. E também executou operações bit a bit. A matemática trata de conjuntos maiores como [0,0, 1,0] com número infinito de pontos.
Você pode ouvir algum fio dentro do computador com algum controlador, mas teria distribuição uniforme? Eu não sei. Mas se for assumido que seu sinal é o resultado de acumular valores enormes quantidades de variáveis aleatórias independentes, então você receberá variáveis aleatórias distribuídas aproximadamente normais (Isso foi provado na Teoria da Probabilidade)
Existem algoritmos chamados - gerador pseudo aleatório. Como eu senti, o objetivo do gerador pseudo aleatório é emular a aleatoriedade. E o critério de goodnes é: - a distribuição empírica é convergente (em algum sentido - pontual, uniforme, L2) para teórico - os valores que você recebe do gerador aleatório parecem ser independentes. Claro que não é verdade do 'ponto de vista real', mas presumimos que seja verdade.
Um dos métodos populares - você pode somar 12 irv com distribuições uniformes ... Mas para ser honesto durante a derivação Teorema do Limite Central com ajuda da Transformada de Fourier, Série de Taylor, é necessário ter n -> + inf suposições algumas vezes. Então, por exemplo, teoricamente - Pessoalmente, não entendo como as pessoas realizam a soma de 12 IRV com distribuição uniforme.
Tive teoria da probabilidade na universidade. E, particularmente para mim, é apenas uma questão de matemática. Na universidade, vi o seguinte modelo:
double generateUniform(double a, double b)
{
return uniformGen.generateReal(a, b);
}
double generateRelei(double sigma)
{
return sigma * sqrt(-2 * log(1.0 - uniformGen.generateReal(0.0, 1.0 -kEps)));
}
double generateNorm(double m, double sigma)
{
double y2 = generateUniform(0.0, 2 * kPi);
double y1 = generateRelei(1.0);
double x1 = y1 * cos(y2);
return sigma*x1 + m;
}
Assim como fazer foi apenas um exemplo, acho que existem outras formas de implementá-lo.
Prova de que está correto pode ser encontrada neste livro "Moscow, BMSTU, 2004: XVI Probability Theory, Example 6.12, p.246-247" de Krishchenko Alexander Petrovich ISBN 5-7038-2485-0
Infelizmente não sei da existência de tradução deste livro para o inglês.