Подтвердить что ты не робот

Ошибка моделирования вероятности не сходится

В интервью мне была предложена следующая проблема: сначала решить проблему с использованием ручки/бумаги, а затем с помощью программы для проверки результата.

Вопрос заключается в следующем:

Есть три человека 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,8585002101. Что-то не так в коде?

Интервьюер сказал, что тривиально получить результат, чтобы сблизиться до 9 десятичных знаков в пределах 1 миллиона проб, независимо от семени.

Любые идеи о том, где ошибка в моем коде?

ОБНОВЛЕНИЕ 1: Я пробовал приведенный выше код со следующими генераторами, все они кажутся платау примерно в то же время примерно пробным 10 ^ 9.

  • std:: mt19937_64
  • std:: ranlux48_base
  • станд:: minstd_rand0

ОБНОВЛЕНИЕ 2: Размышляя о проблеме, я пошел по следующему треку. Соотношение 27/70 составило 27 и 70, которые оба взаимно просты и где коэффициенты 70 при 4x10 ^ 9 составляют примерно 57x10 ^ 6 или около 1,4% от всех чисел. Следовательно, вероятность получения "точного" соотношения 27/70 из двух чисел, выбранных случайным образом между [0,4x10 ^ 9], составляет примерно 1,4% (так как в пределах 4 × 10 9 9 больше факторов 27). Таким образом, получение точное соотношение очень низкое, и это число будет постоянным независимо от количества испытаний.

Теперь, если говорить о толстых границах - то есть: числа в диапазоне коэффициентов 70 +/5, что увеличивает вероятность выбора пары чисел случайным образом в диапазоне [0,4x10 ^ 9], что будет давать отношение в пределах указанной/относительной толерантности примерно до 14%, но с этой методикой лучшее, что мы можем получить, будет в среднем примерно 5 десятичных знаков точным по сравнению с точным значением. Правильно ли этот способ рассуждения?

4b9b3361

Ответ 1

Во-первых, какая-то элементарная математика показывает, что невозможно получить 9 точек точности только с миллионами проб. Учитывая, что наша вероятность 27/70, мы можем вычислить x/1000000 = 27/70, которая дает x = 385714.28571. Если бы у нас был очень и очень точный равномерный генератор случайных чисел, который генерировал ровно 385714 правильных испытаний, это дало бы нам погрешность приблизительно abs(385714/1000000 - 0.38571428571428573) = 2.857142857304318e-07, которая была бы значительно ниже требуемых 9 точек точности.

Я не думаю, что ваш анализ правильный. Учитывая очень точное распределение, безусловно, можно получить требуемую точность. Тем не менее, любая асимметрия от однородности в распределении серьезно затруднит точность. Если мы проведем 1 миллиард испытаний, лучшая точность, на которую мы можем надеяться, составляет около 2.85 * 10^-10. Если распределение искажено на 100, это будет сбито примерно до 1 * 10^-7. Я не уверен в точности большинства дистрибутивов PRNG, но проблема будет иметь то, что точно соответствует этой степени. Имея быструю игру с std::uniform_real_distribution<double>(0.0, 1.0), она, скорее всего, будет иметь большее отклонение от этого.

Ответ 2

Интервьюер сказал, что тривиально получить результат, чтобы сблизиться до 9 десятичных знаков в пределах 1 миллиона проб, независимо от семени.

Ну, это просто явно смешно. Вы не можете получить оценку в пределах одного из тысячи миллионов с миллионами проб. Если бы сумма была только одна, отличная от теоретического значения, вы бы отключились на один миллион, что в тысячу раз больше, чем "9 знаков после запятой".

Кстати, С++ 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));
    }
  }
}

return 0;

Ответ 3

Методы Монте-Карло имеют тенденцию сходиться медленно - ошибка, которую вы ожидаете после n симуляций, пропорциональна 1/sqrt (n). На самом деле пять цифр точности после 10 ^ 9 испытаний кажутся правильными. Здесь нет числовых вуду.

Если интервьюер говорил о прямом взятии образцов Монте-Карло, как вы это делали, это... неправдоподобно, что он смог получить девять цифр точности после миллиона проб.

Ответ 4

потому что вероятности заданы как рациональные числа (с малыми целыми числами в знаменателе), вы можете просмотреть возможные ситуации как куб размеров 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;
}

Ответ 5

FWIW следующая Java, похоже, сходится на предсказанном ответе сверху примерно на уровне, который вы ожидаете (он вычисляет стандартное отклонение ошибки наихудшего случая)

import java.util.Random;
import java.security.SecureRandom;
/** from question in Qaru */
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