SLAM的數學基礎(3):幾種常見的概率分佈的實現及驗證

轉自:https://www.cnblogs.com/cyberniklee/p/7977142.html

分佈,在計算機學科裏一般是指概率分佈,是概率論的基本概念之一。分佈反映的是隨機或某個系統中的某個變量,它的取值的範圍和規律。

常見的分佈有:二項分佈、泊松分佈、正態分佈、指數分佈等,下面對它們進行一一介紹。

 

PS:本文中談到的PDF、PMF、CDF均爲公認的縮寫方式:

PDF:概率密度函數(probability density function);

PMF:概率質量函數(probability mass function);

CDF:累積分佈函數(cumulative distribution function)。

 

二項分佈

說起二項分佈,離不開伯努利實驗,二項分佈就是重複N次的伯努利實驗(伯努利實驗,是指一種只有兩種相反結果的隨機試驗,比如拋硬幣,結果只有正面和反面;又比如投籃,只有投中和沒有投中兩種結果)。它的PMF可寫作:

1279165-20171204150621872-1937701778.pnguploading.4e448015.gif轉存失敗重新上傳取消

其中k爲在n次實驗中命中的次數,成功的概率爲p。

二項分佈的CDF可以寫作:

例子:拋10次硬幣,有2次正面朝上的概率是多少?下面分別用C++實現和用numpy證明結果

C++實現:

複製代碼

#include <vector>
#include <iostream>
#include <iomanip>

double calc_binomial(int n, int k, double p)
{
  if(n < 0 || k < 0) 
    return 0.0;

  std::vector< std::vector< double > > binomials((n + 1), std::vector< double >(k + 1));

  binomials[0][0] = 1.0;

  for(int i = 1; i < (n + 1); ++i)
    binomials[i][0] = (1.0 - p) * binomials[i - 1][0];
  for(int j = 1; j < (k + 1); ++j)
    binomials[0][j] = 0.0;

  for(int i = 1; i < (n + 1); ++i)
    for (int j = 1; j < (k + 1); ++j)
      binomials[i][j] = (1.0 - p) * binomials[i - 1][j] + p * binomials[i - 1][j - 1];
  return binomials[n][k];
}

int main()
{
  std::cout << std::fixed << std::setprecision(8) << calc_binomial(10, 2, 0.50) << std::endl;
}

複製代碼

結果爲:0.04394531

Python實現:

複製代碼

import numpy as np
from scipy import stats
import matplotlib.pyplot as plt

def calc_binomial():
    n = 10
    p = 0.5
    k = 2
    binomial = stats.binom.pmf(k,n,p)
    print binomial

calc_binomial()

複製代碼

結果爲:0.0439453125

反之,知道投10次硬幣朝上的平均概率爲0.3(即平均有3次朝上),試着從10000次實驗中找出規律。

用C++實現:

複製代碼

#include <iostream>
#include <cmath>
#include <iomanip>
#include <vector>
#include <cstdlib>
#include <ctime>

int gen_binomial_rand(int n, double p)
{
  int k = 0;

  for(int i = 0; i < n; i++)
  {
    double current_probability = ((double)rand() / (double)RAND_MAX);
    if(current_probability < p)
    {
      k++;
    }
  }

  return k;
}

int main()
{
  srand((unsigned)time(NULL));

  int gn = 10;
  double gp = 0.3;
  int times = 10000;
  int sum_of_times = 0;

  std::vector< int > result(gn);

  for(int t = 0; t < times; t++)
  {
    int single_result = gen_binomial_rand(gn, gp);
    if(single_result < gn)
    {
      result[single_result]++;
    }
  }

  std::cout << std::endl;
  for(int i = 0; i < gn; i++)
  {
    sum_of_times += result[i];
    std::cout << result[i] << ",";
  }
  std::cout << std::endl;

  std::cout << "Total: " << sum_of_times << std::endl;

  return 0;
}

複製代碼

結果爲:

323,1199,2310,2631,1951,1103,367,97,18,1,
Total: 10000

拿到Python裏面用圖表看一下:

複製代碼

import numpy as np
from scipy import stats
import matplotlib.pyplot as plt

def show_binom_rvs():
    n = np.array([323,1199,2310,2631,1951,1103,367,97,18,1])
    plt.plot(n)
    plt.show()
show_binom_rvs()

複製代碼

 顯示爲:

我們再來用Python的numpy和scipy的庫來驗證一下:

複製代碼

import numpy as np
from scipy import stats
import matplotlib.pyplot as plt

def calc_binom_rvs():
    binom_rvs = stats.binom.rvs(n=10,p=0.3,size=10000)
    plt.hist(binom_rvs, bins=10)
    plt.show()

calc_binom_rvs()

複製代碼

得到結果圖片:

可以看到兩次的圖形包絡是近似的。

 

泊松分佈

在日常生活中,我們經常會遇到一些事情,這些事情發生的頻率比較固定,但是發生的時間是不固定的,泊松分佈就是用來描述單位時間內隨機事件的發生概率。比如:知道一個醫院平均每小時有3個小孩出生,那麼下一個小時出生2個小孩的概率是多少?

泊松分佈的PMF可以寫作:

其中,t爲連續時間長度,k爲事件發生的次數,λ爲發生事件的數學期望(如單位時間內發生事件的均值),e爲自然底數。

就上面的例子,知道一個醫院平均每小時有3個小孩出生,那麼下一個小時出生2個小孩的概率是多少?用C++實現:

複製代碼

#include <iostream>
#include <cmath>
#include <iomanip>

double calc_poisson(int k, int lambda)
{
  double result;

  result = pow(lambda, k) * exp(-lambda);

  int factorial = 1;

  for(int i = 1; i <= k; i++)
  {
    factorial *= i;
  }
  
  result = result / factorial;

  return result;
}

int main()
{
  std::cout << std::fixed << std::setprecision(8) << calc_poisson(2, 3) << std::endl;
}

複製代碼

 結果是:0.22404181

用Python驗證:

複製代碼

import numpy as np
from scipy import stats
import matplotlib.pyplot as plt

def calc_poisson():

    lambd = 3
    k = 2
    y = stats.poisson.pmf(k,lambd)
    print y

calc_poisson()

複製代碼

結果是:0.224041807655

下面再用C++實現生成泊松隨機數,並用Python檢驗:

C++實現:

複製代碼

#include <iostream>
#include <cmath>
#include <iomanip>
#include <vector>
#include <cstdlib>
#include <ctime>

double binary_random()
{
  double rand_number = (rand() % 100);
  rand_number /= 100;
  return rand_number;
}

int calc_poisson(int lambda)
{
  int k = 0;
  double p = 1.0;
  double l = exp(-lambda);

  while(p >= l)
  {
    double r = binary_random();
    p *= r;
    k++;
  }
 
  return (k - 1);
}


int main()
{
  int t = 10000;
  int lambda = 3;
  int distribution = 20;
  int dist_cells[distribution] = {0};

  srand((unsigned)time(NULL));

  for(int i = 0; i < t; i++)
  {
    int n = calc_poisson(lambda);
    dist_cells[n]++;
  }

  for(int i = 0; i < distribution; i++)
  {
    std::cout << dist_cells[i] << ",";
  }
  std::cout << std::endl;
 
 
  return 0;
}

複製代碼

運行結果爲:467,1604,2298,2264,1608,952,466,217,87,29,6,2,0,0,0,0,0,0,0,0,

放入Python顯示並和Python生成的比較:

複製代碼

import numpy as np
from scipy import stats
import matplotlib.pyplot as plt

def gen_poisson_rvs():
    dist = 20
    cpp_result = np.array([467,1604,2298,2264,1608,952,466,217,87,29,6,2,0,0,0,0,0,0,0,0])
    py_result = np.random.poisson(lam=3,size=10000)

    plt.hist(py_result,bins=dist,range=[0,dist],color='g')
    plt.plot(cpp_result,color='r')
    plt.show()

gen_poisson_rvs()

複製代碼

運行並顯示圖表爲:

指數分佈

指數分佈,描述的是在某一事件發生後,在連續時間間隔內繼續發生的概率。比如上面的例子,知道一個醫院平均每小時有3個小孩出生,剛剛已經有一個小孩出生了,那麼下一個小孩在15分鐘內出生的概率是多少?

指數分佈的CDF可以寫作:

其中t爲時間長度,e爲自然底數。

下面用C++實現:

複製代碼

#include <iostream>
#include <cmath>
#include <iomanip>

double calc_exponential(double lambda, double t)
{
  double result;
  
  result = (1 - exp((-lambda) * t));

  return result;
}

int main()
{
  std::cout << std::fixed << std::setprecision(8) << calc_exponential(3, 0.25) << std::endl;
}

複製代碼

 結果爲:0.52763345

 用Python驗證:

複製代碼

import numpy as np
from scipy import stats
import matplotlib.pyplot as plt

def calc_expon():
    lambd = 3
    x = np.arange(0,1,0.25)
    y = 1 - np.exp(-lambd *x)
    print y

calc_expon()

複製代碼

 結果爲:[ 0.          0.52763345  0.77686984  0.89460078]

其中x=0.25時結果爲0.52763345

下面是生成lambda=3的指數分佈隨機數,樣本數是10000。同樣是用C++實現,Python驗證。

C++實現:

複製代碼

#include <iostream>
#include <cmath>
#include <iomanip>
#include <vector>
#include <cstdlib>
#include <ctime>

double calc_exponential(double lambda)
{
  double expon_rand = 0.0;
  while(1)
  {
    expon_rand = ((double)rand()/(double)RAND_MAX);
    if(expon_rand != 1)
    {
      break;
    }
  }
  expon_rand = ((-1 / lambda) * log(1 - expon_rand));
  return expon_rand;
}


int main()
{ 
  int t = 10000;
  double lambda = 3;
  const int distribution = 20;
  double dist_cells[distribution] = {0};

  srand((unsigned)time(NULL));

  for(int i = 0; i < t; i++)
  {
    int n = calc_exponential(lambda);
    dist_cells[n]++;
  }

  for(int i = 0; i < distribution; i++)
  {
    std::cout << dist_cells[i] << ",";
  }
  std::cout << std::endl;
  
  return 0;
}

複製代碼

結果是:9515,469,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,

放入Python並用Python生成的對比:

複製代碼

import numpy as np
from scipy import stats
import matplotlib.pyplot as plt

def gen_expon_rvs():
    cpp_result = np.array([9515,469,16,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0])

    lambd_recip = 0.33
    py_result = stats.expon.rvs(scale=lambd_recip,size=10000)
    plt.plot(cpp_result,color='r')
    plt.hist(py_result,color='b')
    plt.xlim(0,20)
    plt.show()

gen_expon_rvs()

複製代碼

得到圖片:

PS:其中最大值不一致的情形並不是計算錯誤,而是X軸的分佈單位不一致,Python的是浮點的,而C++的代碼是整形的,所以看到Python的分佈最大值比較小是因爲平均到了小數。

 

正態分佈(高斯分佈)

翻開任何一本講統計的數學書,對於正態分佈大抵會有相似的描述:若一個隨機變量X服從一個數學期望爲λ,標準差爲σ的概率分佈,且PDF爲:

則稱這個隨機變量爲正態隨機變量。

當λ=0,σ=1時,稱其爲標準正態分佈,PDF簡化爲:

在現實中,有大量的案例是符合正態分佈的,比如全中國18歲以上男性人口的身高分佈,170cm左右的佔絕大部分,160和180的佔較少部分,150以下和190以上的佔極少部分。

說起正態分佈的例子,有個著名的實驗是不得不提的,那就是高爾頓釘板實驗。

高爾頓釘板是在一塊豎起的木板上釘上一排排互相平行、水平間隔相等的鐵釘,並且每一排釘子數目都比上一排多一個,一排中各個釘子下好對準上面一排兩上相鄰鐵釘的正中央。從入口處放入一個直徑略小於兩顆釘子間隔的小球,當小球從兩釘之間的間隙下落時,由於碰到下一排鐵釘,它將以相等的可能性向左或向右落下,接着小球再通過兩釘的間隙,又碰到下一排鐵休。如此繼續下去,小球最後落入下方條狀的格子內。在等可能性(即小球落在左邊和落在右邊的概率均爲50%)的情況下,小球落下後滿足正態分佈。

下面的代碼用C++計算模擬高爾頓實驗過程,並把結果放到Python顯示出來。

C++模擬實驗過程:

複製代碼

#include <iostream>
#include <cmath>
#include <iomanip>
#include <vector>
#include <cstdlib>
#include <ctime>

int binary_random()
{
  double rand_number = (rand() / (double)RAND_MAX);
  if(rand_number > 0.5)
    return 1;
  else return 0;
}

void galton_test(int num_of_cells, int num_of_balls)
{
  srand((unsigned)time(NULL));

  std::vector< int > cells(num_of_cells);
  
  int rand;

  for(int i = 1; i <= num_of_balls; i++)
  {
    int cell = 0;
    for(int j = 1; j < num_of_cells; j++)
    {
      int rand = binary_random();
      cell += rand;
    }
    cells[cell]++;
  }

  std::cout << std::endl;
  for(int i = 0; i < num_of_cells; i++)
  {
    std::cout << cells[i] << ",";
  }
  std::cout << std::endl;
}

int main()
{
  galton_test(20, 5000);
}

複製代碼

結果爲:0,0,3,14,45,95,264,481,720,886,911,741,452,240,95,45,6,1,1,0,

結果每次都不一樣,但將其放入以下Python代碼顯示:

複製代碼

import numpy as np
from scipy import stats
import matplotlib.pyplot as plt

def show_galton():
    n = np.array([0,0,3,14,45,95,264,481,720,886,911,741,452,240,95,45,6,1,1,0])

    plt.plot(n)

    plt.show()

show_galton()

複製代碼

得到以下圖片:

可以看出,是個明顯的鐘型曲線,說明高爾頓實驗是滿足正態分佈的。在這個實驗中,我們還可以去調整num_of_cells, num_of_balls的值,可以看出,當num_of_cells的值越大,曲線越陡峭,越小,曲線越平坦;num_of_balls的值越大,曲線就越像正態分佈。說到這裏可能大家就會想的到,這個實驗中,num_of_cells的值可以認爲就是正態分佈的σ,而我們設定的隨機數0,1會影響到正態分佈的λ,有興趣的朋友可以改一下上面的例程,將隨機數生成改爲大於0.5,或小於0.5,可以觀察到最後的曲線中軸線會向左偏和向右偏。

說到這裏,下面的公式應該是理所當然的了:

若一個隨機變量X服從一個數學期望爲λ,尺度參數爲σ的概率分佈,記作

說到這裏,通過上面的實驗大家應該大概知道高斯分佈是個什麼東西了。那麼如何編程實現生成符合高斯分佈的隨機數呢?

生成高斯分佈的隨機數有多種方法:

(1)     Box-Muller變換算法

(2)     利用中心極限定理迭代法

(3)     Ziggurat算法

等。其中,在效率和通用性方面比較均衡的是Box-Muller算法,C++11和Python的數學庫裏面基本都是用的它。

它的原理是:

隨機抽出從[0,1]中符合均勻分佈的數a和b,然後令:

那麼這兩個數都是符合正態分佈的。

若想產生服從期望是λ,標準差是σ的正態分佈,那麼:

下面,我們用C++來實現:

 

複製代碼

#include <iostream>
#include <cmath>
#include <iomanip>
#include <vector>
#include <cstdlib>
#include <ctime>
#include <limits>

double calc_gaussian(double sigma, double lambda)
{
  static const double epsilon = std::numeric_limits<double>::min();
  static const double two_pi = (2.0 * 3.14159265358979323846);

  static double z1;
  static bool generate;
  generate = !generate;

  if (!generate)
    return z1 * sigma + lambda;

  double a, b;
  do
  {
    a = rand() * (1.0 / RAND_MAX);
    b = rand() * (1.0 / RAND_MAX);
  }while ( a <= epsilon );

  double z0;
  z0 = sqrt(-2.0 * log(a)) * cos(two_pi * b);
  z1 = sqrt(-2.0 * log(a)) * sin(two_pi * b);
  return z0 * sigma + lambda;
}


int main()
{ 
  int t = 10000;
  double lambda = 10;
  double sigma = 1;
  const int distribution = 20;
  double dist_cells[distribution] = {0};

  srand((unsigned)time(NULL));

  for(int i = 0; i < t; i++)
  {
    int n = calc_gaussian(sigma, lambda);
    dist_cells[n]++;
  }

  for(int i = 0; i < distribution; i++)
  {
    std::cout << dist_cells[i] << ",";
  }
  std::cout << std::endl;
  
  
  return 0;
}

複製代碼

得到結果:0,0,0,0,0,0,12,190,1374,3372,3519,1325,196,12,0,0,0,0,0,0,

放入Python顯示:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章