概率模拟误差不会收敛

时间:2012-11-23 03:48:48

标签: java c++ algorithm math simulation

在一次采访中,我最初使用笔/纸解决了以下问题,然后通过程序验证结果。

问题如下:

有三个人A,B和C.每个人都能够分别以6 / 7,4 / 5和3/4的概率击中目标。如果他们每次射击一次射击的概率是多少,那么他们中的两个将会击中目标?

答案是:

P(...) = P(A)*P(B)*(1-P(C)) +
         P(B)*P(C)*(1-P(A)) +
         P(C)*P(A)*(1-P(B))
       = 27.0/70.0
       = 38.57142857142857142857142857142857142857....%

以下是我解决问题的方法:

#include <cstdio>
#include <cctype>
#include <ctime>
#include <random>


int main()
{
   std::mt19937 engine(time(0));

   engine.discard(10000000);

   std::uniform_real_distribution<double> uniform_real(0.0,1.0);

   double prA = (6.0 / 7.0);
   double prB = (4.0 / 5.0);
   double prC = (3.0 / 4.0);

   std::size_t trails = 4000000000;
   std::size_t total_success = 0;

   for (std::size_t i = 0; i < trails; ++i)
   {
      int current_success = 0;
      if (uniform_real(engine) < prA) ++current_success;
      if (uniform_real(engine) < prB) ++current_success;
      if (uniform_real(engine) < prC) ++current_success;

      if (current_success == 2)
         ++total_success;

      double prob = (total_success * 1.0) / (i+1);

      if ((i % 1000000) == 0)
      {
         printf("%05d Pr(...) = %12.10f  error:%15.13f\n",
                i,
                prob,
                std::abs((27.0/70.0) - prob));
      }
   }

   return 0;
}

问题如下,无论我运行多少试验,概率平线大约在0.3857002101左右。代码中有什么问题吗?

采访者表示,无论种子如何,在100万次试验中将结果收敛到小数点后9位精确度是微不足道的。

关于错误在我的代码中的位置的任何想法?

更新1: 我已经尝试了上面的代码与以下生成器,他们似乎platau大约在同一时间大致试验10 ^ 9。

  1. std :: mt19937_64
  2. std :: ranlux48_base
  3. 的std :: minstd_rand0
  4. 更新2: 考虑到这个问题,我走了下面的轨道。比率27/70由27和70组成,它们都是互质的,并且在4×10 ^ 9下的因子为约57×10 ^ 6或所有数字的约1.4%。因此,在[0,4x10 ^ 9]之间随机选择的两个数字中获得27/70的“精确”比率的概率大约是1.4%(因为在4x10 ^ 9内有更多因素为27) - 所以得到确切的比例非常低,无论试验次数如何,这个数字都是恒定的。

    现在,如果要谈论厚边界 - 即:70 + / 5因子范围内的数字,这会增加在[0,4x10 ^ 9]范围内随机选择一对数字的概率将指定/相关容差内的比率约为14%左右,但是使用这种技术,与精确值相比,我们可以获得的最佳平均值大约为5位小数。这种推理方式是否正确?

5 个答案:

答案 0 :(得分:8)

  

采访者表示,无论种子如何,在100万次试验中将结果收敛到小数点后9位精确度是微不足道的。

嗯,这显然是荒谬的。一百万次试验中,你无法得到千分之一的估计值。如果总数只有一个不同于理论值,那么你将减去一百万分之一,这比“小数点后9位”大一千倍。

顺便说一句,c ++ 11带有一个非常好的uniform_int_distribution函数,它实际上正确地处理了舍入:它将统一生成器的总范围分成所需范围的精确倍数和余数,并丢弃在余数中生成的值,因此生成的值不会被舍入所偏差。我对你的测试程序进行了一些修改,它在十亿次试验中确实收敛到六位数,这与我的预期相符:

int main() {
  std::mt19937 engine(time(0));

  std::uniform_int_distribution<int> a_distr(0,6);
  std::uniform_int_distribution<int> b_distr(0,4);
  std::uniform_int_distribution<int> c_distr(0,3);

  std::size_t trials = 4000000000;
  std::size_t total_success = 0;

  for (std::size_t i = 1; i <= trials; ++i) {
    int current_success = 0;
    if (a_distr(engine)) ++current_success;
    if (b_distr(engine)) ++current_success;
    if (c_distr(engine)) ++current_success;

    if (current_success == 2) ++total_success;

    if ((i % 1000000) == 0) {
      printf("%05d Pr(...) = %12.10f  error:%15.13f\n",
             i,
             double(total_success) / i,
             std::abs((27.0/70.0) - double(total_success) / i));
    }
  }
}

返回0;

答案 1 :(得分:8)

首先,一些基本的数学表明,只有一百万次试验才能获得9个精度。鉴于我们的概率为27/70,我们可以计算出x/1000000 = 27/70 x = 385714.28571。如果我们有一个非常非常精确的均匀随机数发生器,它可以准确地生成385714个正确的试验,这将给我们一个大约abs(385714/1000000 - 0.38571428571428573) = 2.857142857304318e-07的误差,这个误差远远超出了所需的9个精度位置。

我不认为你的分析是正确的。给定非常准确的分布,当然可以获得所需的精度。然而, - 分布均匀性的偏斜将严重妨碍精度。如果我们进行10亿次试验,我们所希望的最佳精度是2.85 * 10^-10左右。如果分布偏差甚至为100,那么这将被推至约1 * 10^-7。我不确定大多数PRNG发行版的准确性,但问题是如何准确到这个程度。快速使用std::uniform_real_distribution<double>(0.0, 1.0),看起来肯定会有更多的差异。

答案 2 :(得分:7)

蒙特卡罗方法倾向于缓慢收敛 - 在n次模拟之后您期望的误差与1 / sqrt(n)成比例。实际上,10 ^ 9次试验后的五位精确度似乎是正确的。这里没有数字伏都教。

如果面试官正在谈论直接蒙特卡罗的抽样方法,那么......经过一百万次试验后,他可以获得九位数的准确性是不可信的。

答案 3 :(得分:3)

因为概率是以有理数给出的(在分母中有小整数),你可以将可能的情况视为维度为7x5x4的立方体(这使得140(分母的乘积)子立方体)。您可以按如下方式显式访问每个子多维数据集,而不是随机跳转,并获得140次迭代中的确切数字:

#include <cstdio>
#include <cctype>
#include <ctime>
#include <random>

int main()
{
  std::size_t total_success = 0, num_trials = 0;

  for (unsigned a = 1; a <= 7; ++a)
  {
    unsigned success_a = 0;

    if (a <= 6)
      // a hits 6 out of 7 times
      success_a = 1;

    for (unsigned b = 1; b <= 5; ++b)
    {
      unsigned success_b = 0;

      if (b <= 4)
        // b hits 4 out of 5 times
        success_b = 1;

        for (unsigned c = 1; c <= 4; ++c)
        {
          unsigned success_c = 0;

          // c hits 3 out of 4 times
          if (c <= 3)
            success_c = 1;

          // count cases where exactly two of them hit
          if (success_a + success_b + success_c == 2)
            ++total_success;

          ++num_trials;

        } // loop over c
    } // loop over b
  } // loop over a

  double prob = (total_success * 1.0) / num_trials;

  printf("Pr(...) = %12.10f  error:%15.13f\n",
         prob,
         std::abs((27.0/70.0) - prob));

   return 0;
}

答案 4 :(得分:1)

FWIW以下Java似乎正在收集上面的预测答案,大概是你预期的速度(它计算最坏情况误差的标准偏差)

import java.util.Random;
import java.security.SecureRandom;
/** from question in Stack Overflow */
public class SoProb
{
  public static void main(String[] s)
  {
    long seed = 42;


/*
In an interview, I was given the following problem to solve initially using pen/paper, then via a program to verify the result.

The question is as follows:

There are three people A,B and C. Each person is capable of hitting a target with a probability of 6/7, 4/5 and 3/4 respectively. What is the probability that if they were to each fire one shot that exactly two of them will hit the target?

The answer is:

P(...) = P(A)*P(B)*(1-P(C)) +
         P(B)*P(C)*(1-P(A)) +
         P(C)*P(A)*(1-P(B))
       = 27.0/70.0
       = 38.57142857142857142857142857142857142857....%

Below is my solution to the problem:
*/

/*
int main()
{
   std::mt19937 engine(time(0));
*/

   Random r = new Random(seed);
   // Random r = new SecureRandom(new byte[] {(byte)seed});
   // std::uniform_real_distribution<double> uniform_real(0.0,1.0);

   double prA = (6.0 / 7.0);
   double prB = (4.0 / 5.0);
   double prC = (3.0 / 4.0);
   // double prB = (6.0 / 7.0);
   // double prC = (4.0 / 5.0);
   // double prA = (3.0 / 4.0);

   double pp = prA*prB*(1-prC) +
         prB*prC*(1-prA) +
         prC*prA*(1-prB);
   System.out.println("Pp " + pp);
   System.out.println("2870 " + (27.0 / 70.0));

   // std::size_t trails = 4000000000;
   int trails = Integer.MAX_VALUE;
   // std::size_t total_success = 0;
   int total_success = 0;

   int aCount = 0;
   int bCount = 0;
   int cCount = 0;

   int pat3 = 0; // A, B
   int pat5 = 0; // A, C
   int pat6 = 0; // B, C
   double pat3Prob = prA * prB * (1.0 - prC);
   double pat5Prob = prA * prC * (1.0 - prB);
   double pat6Prob = prC * prB * (1.0 - prA);
   System.out.println("Total pats " + 
     (pat3Prob + pat5Prob + pat6Prob));

   for (int i = 0; i < trails; ++i)
   {
      int current_success = 0;
      // if (uniform_real(engine) < prA) ++current_success;
      int pat = 0;
      if (r.nextDouble() < prA) 
      {
        ++current_success;
        aCount++;
        pat += 1;
      }
      // if (uniform_real(engine) < prB) ++current_success;
      if (r.nextDouble() < prB) 
      {
        ++current_success;
        bCount++;
        pat += 2;
      }
      // if (uniform_real(engine) < prC) ++current_success;
      if (r.nextDouble() < prC) 
      {
        ++current_success;
        cCount++;
        pat += 4;
      }
      switch (pat)
      {
        case 3:
          pat3++;
          break;
        case 5:
          pat5++;
          break;
        case 6:
          pat6++;
          break;
      }

      if (current_success == 2)
         ++total_success;

      double prob = (total_success + 1.0) / (i+2);

      if ((i % 1000000) == 0)
      {
         /*
         printf("%05d Pr(...) = %12.10f  error:%15.13f\n",
                i,
                prob,
                std::abs((27.0/70.0) - prob));
         */
         System.out.println(i + "P rob = " + prob +
           " error " +  Math.abs((27.0 / 70.0) - prob));
         Double maxVar = 0.25 / i;
         System.out.println("Max stddev " + Math.sqrt(maxVar));
         double ap = (aCount + 1.0) / (i + 2.0);
         double bp = (bCount + 1.0) / (i + 2.0);
         double cp = (cCount + 1.0) / (i + 2.0);
         System.out.println("A error " + (ap - prA));
         System.out.println("B error " + (bp - prB));
         System.out.println("C error " + (cp - prC));
         double p3Prob = (pat3 + 1.0) / (i + 2.0);
         double p5Prob = (pat5 + 1.0) / (i + 2.0);
         double p6Prob = (pat6 + 1.0) / (i + 2.0);
         System.out.println("P3 error " + (p3Prob - pat3Prob));
         System.out.println("P5 error " + (p5Prob - pat5Prob));
         System.out.println("P6 error " + (p6Prob - pat6Prob));
         System.out.println("Pats " + (pat3 + pat5 + pat6) +
           " success " + total_success);
      }
   }

  }

}

当前输出:

1099000000P rob = 0.3857148864682168错误6.00753931045972E-7

Max stddev 1.508242443516904E-5

错误-2.2208501193610175E-6

B错误1.4871155568862982E-5

C错误1.0978161945063292E-6

P3错误-1.4134927830977695E-7

P5错误-5.363291293969397E-6

P6错误6.1072143395513034E-6

Pats 423900660成功423900660