实验验证Binomial公式正确性
郝伟 2021/01/20
本次实验主要是通过随机生成的符合Binomial分布的随机数,以验证其概率计算公式的正确性,其公式定义如下:
其中,即n中k种可能的全排列。
实验分为两步:
第1步,通过 test1() 证明Javar提供的 Random.nextInt()能够提供平均分布的函数。
第2步,利用 Random.nextInt(n) 设计函数 boolean test(double p) 返回概率这p的事件是否可以发生,然后随机生成数据,验证 k 与 n 的关系。
编写了Java代码,参见附录,实现了整个过程。
test1()函数 test1() 主要用于验证 Random.nextInt(n) 生成的随机数是否符合正态分布。
通过,使用源代码的 test1() 函数,生成2亿个数据,得到以下结果:
******************** 结果列表 ******************** counters[0] = 9999557 counters[1] = 9995208 counters[2] = 9999310 counters[3] = 9999332 counters[4] = 9999267 counters[5] = 9999526 counters[6] = 9998762 counters[7] = 9999090 counters[8] = 10004129 counters[9] = 10004558 counters[10] = 10002649 counters[11] = 9996725 counters[12] = 9999594 counters[13] = 10000538 counters[14] = 10000581 counters[15] = 10005193 counters[16] = 9997375 counters[17] = 10000347 counters[18] = 9998698 counters[19] = 9999561 ******************** 统计数据 ******************** 平均值: 10000000 标准差: 10928.38 偏差比: 0.001093
偏差比只有约千分之一,所以可以得到结论:生成的函数符合平均分布。
test2()通过使用函数 test2() 可以得到以下数据:
******************** Binomial 实验(n=20, size=10m) ******************** k值 发生次数 实际概率 理论概率 概率偏差比 0 12 0.000001 0.000001 25.829120% 1 199 0.000020 0.000019 4.333312% 2 1826 0.000183 0.000181 0.773672% 3 10759 0.001076 0.001087 1.038340% 4 46095 0.004610 0.004621 0.239194% 5 147249 0.014725 0.014786 0.411657% 6 370519 0.037052 0.036964 0.236669% 7 740837 0.074084 0.073929 0.209481% 8 1200126 0.120013 0.120134 0.101348% 9 1601530 0.160153 0.160179 0.016318% 10 1762175 0.176218 0.176197 0.011605% 11 1602306 0.160231 0.160179 0.032128% 12 1200108 0.120011 0.120134 0.102846% 13 739723 0.073972 0.073929 0.058796% 14 369674 0.036967 0.036964 0.008071% 15 147952 0.014795 0.014786 0.063801% 16 45972 0.004597 0.004621 0.505396% 17 10893 0.001089 0.001087 0.194196% 18 1835 0.000184 0.000181 1.270366% 19 204 0.000020 0.000019 6.954752% 20 6 0.000001 0.000001 37.085440%
******************** Binomial 实验(n=20, size=100m) ******************** k值 发生次数 实际概率 理论概率 概率偏差比 0 95 0.000001 0.000001 0.385280% 1 1846 0.000018 0.000019 3.216435% 2 18026 0.000180 0.000181 0.517732% 3 108707 0.001087 0.001087 0.010920% 4 462541 0.004625 0.004621 0.105138% 5 1478587 0.014786 0.014786 0.000699% 6 3696003 0.036960 0.036964 0.011867% 7 7387746 0.073877 0.073929 0.069490% 8 12014555 0.120146 0.120134 0.009320% 9 16015085 0.160151 0.160179 0.017660% 10 17622246 0.176222 0.176197 0.014420% 11 16017554 0.160176 0.160179 0.002246% 12 12019029 0.120190 0.120134 0.046562% 13 7390142 0.073901 0.073929 0.037080% 14 3698042 0.036980 0.036964 0.043294% 15 1479722 0.014797 0.014786 0.077462% 16 461158 0.004612 0.004621 0.194177% 17 108729 0.001087 0.001087 0.009316% 18 18185 0.000182 0.000181 0.359761% 19 1899 0.000019 0.000019 0.437709% 20 103 0.000001 0.000001 8.003328%
通过结果,我们可以看到:
1)公式计算结果与实际结果几乎一致;
2)1亿次较1000万次的结果误差更小,符合大数中心定律;
3)k值两端的分布偏差相对中心较大是由于数据量偏小造成的。
package tests; import java.util.Random; public class V20210120_BinorialDistributionTest { static Random rand = new Random(); public static void main(String[] args) { test1(); // test2(); } static long factorial(int n) { if (n < 2) return 1; long v = 1; for (int i = 1; i <= n; i++) v *= i; return v; } static long permutation(int n, int k) { // n! / (n-k)!/k! return factorial(n) / factorial(n - k) / factorial(k); } private static void test2() { int size = 100000000; int n = 20; double p = 0.5; int[] counters = new int[n + 1]; for (int i = 0; i < size; i++) { counters[binomial(n, p)]++; } double[] actualValues = new double[n + 1]; double[] theoryValues = new double[n + 1]; // show(counters); for (int i = 0; i < theoryValues.length; i++) { actualValues[i] = counters[i] * 1.0 / size; theoryValues[i] = permutation(n, i) * Math.pow(p, i) * Math.pow(1 - p, n - i); } System.out.printf("******************** Binomial 实验(n=%d, size=%dm) ********************\n", n, size/1000000); System.out.println("k值\t发生次数\t\t实际概率\t\t理论概率\t\t概率偏差比"); for (int i = 0; i < theoryValues.length; i++) { System.out.printf("%d\t%d\t\t%.6f\t%.6f\t%.6f%%\n", i, counters[i], actualValues[i], theoryValues[i], Math.abs(100 * (theoryValues[i] - actualValues[i]) / theoryValues[i])); } } public static int binomial(int n, double p) { int occurredTimes = 0; for (int i = 0; i < n; i++) occurredTimes += test(p) ? 1 : 0; return occurredTimes; } public static boolean test(double p) { return rand.nextInt(10000) < 10000 * p; } /** * 通过随机1亿个[0,100)的随机数证明Java的随机数是符合平均分布的。 */ public static void test1() { int[] counters = new int[20]; int testTimes = 100_000_000; for (int i = 0; i < testTimes; i++) { counters[rand.nextInt(counters.length)]++; } show(counters); } static void show(int[] arr) { System.out.println("******************** 结果列表 ********************"); for (int i = 0; i < arr.length; i++) System.out.printf("counters[%d] = %d\n", i, arr[i]); System.out.println("******************** 统计数据 ********************"); double avg = mean(arr); double sigma = varience(arr); System.out.printf("平均值: %.0f\n", avg); System.out.printf("标准差: %.2f\n", sigma); System.out.printf("偏差比: %.6f\n", sigma / avg); } private static double varience(int[] arr) { double avg = mean(arr); double sigma = 0; for (int i = 0; i < arr.length; i++) sigma += Math.pow(arr[i] - avg, 2); return Math.sqrt(sigma); } public static double mean(int[] arr) { double mean = 0; for (int i = 0; i < arr.length; i++) mean += arr[i]; return mean / arr.length; } }