Реализация функции распределения вероятностей в Java - PullRequest
6 голосов
/ 02 июля 2011

Я пытаюсь реализовать функцию распределения вероятностей в Java, где она возвращает запись i<sup>th</sup> в массиве с вероятностью:

F<sub>i</sub> = 6i(n-i) / (n<sup>3</sup> - n)

где n - длина массива, то есть для длины массива 4:

P<sub>1</sub> = 3/10, P<sub>2</sub> = 4/10, P<sub>3</sub> = 3/10, P<sub>4</sub> = 0

Обратите внимание, что эта функция предполагает нумерацию от 1 до n, а не от 0 до n-1, как в Java.

Сейчас я просто использую равномерное распределение, т.е.

 int i = (int)(Math.random()*((arraySize)-1));

с -1, поэтому он не выбирает последний элемент (т.е. P n = 0, как в приведенной выше формуле).

У кого-нибудь есть идеи или советы по реализации этого?

Ответы [ 4 ]

2 голосов
/ 02 июля 2011

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

p = [0.3, 0.4, 0.3. 0.0]
c = [0.3, 0.7, 1.0, 1.0] // cumulative sum

generate x uniformly in continuous range [0,1]
find max i such that c[i] < x.
2 голосов
/ 02 июля 2011
double rand = Math.random(); // generate a random number in [0,1]
F=0;
// you test if rand is in [F(1)+..+F(i):F(1)+..+F(i)+F(i+1)] it is in this rnge with proba P(i) and therefore if it is in this range you return i
for (int i=1,i<array.size();i++ ){
   F+=F(i);
   if rand < F
       return i;
}
return array.size(); // you went through all the array then rand==1 (this probability is null) and you return n
1 голос
/ 02 июля 2011

Вы можете попробовать использовать навигационную карту с распределением вероятностей. В отличие от обычных карт, NaviableMap определяет абсолютный порядок своих ключей. И если ключ отсутствует на карте, он может сказать вам, какой ключ ближе всего, или какой самый маленький ключ больше аргумента. Я использовал ceilingEntry, который возвращает запись карты с наименьшим ключом, который больше или равен данному ключу.

Если вы используете TreeMap в качестве реализации NavigableMap, то поиск распределений со многими классами будет быстрее, поскольку он выполняет двоичный поиск, а не начинает с первого ключа, а затем проверяет каждый ключ по очереди.

Другим преимуществом NaviableMap является то, что вы получаете интересующий вас класс данных, а не индекс для другого массива или списка, что может сделать код чище.

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

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays;
import java.util.NavigableMap;
import java.util.TreeMap;


public class Main {

    public static void main(String[] args) {

        String[] classes = {"A", "B", "C", "D"};
        BigDecimal[] probabilities = createProbabilities(classes.length);
        BigDecimal[] distribution = createDistribution(probabilities);

        System.out.println("probabilities: "+Arrays.toString(probabilities));
        System.out.println("distribution: "+Arrays.toString(distribution)+"\n");

        NavigableMap<BigDecimal, String> map = new TreeMap<BigDecimal, String>();

        for (int i = 0; i < distribution.length; i++) {
            map.put(distribution[i], classes[i]);
        }

        BigDecimal d = new BigDecimal(Math.random());

        System.out.println("probability: "+d);

        System.out.println("result: "+map.ceilingEntry(d).getValue());

    }

    private static BigDecimal[] createDistribution(BigDecimal[] probabilities) {
        BigDecimal[] distribution = new BigDecimal[probabilities.length];

        distribution[0] = probabilities[0];
        for (int i = 1; i < distribution.length; i++) {
            distribution[i] = distribution[i-1].add(probabilities[i]); 
        }
        return distribution;
    }

    private static BigDecimal[] createProbabilities(int n) {
        BigDecimal[] probabilities = new BigDecimal[n];

        for (int i = 0; i < probabilities.length; i++) {
            probabilities[i] = F(i+1, n);
        }
        return probabilities;
    }

    private static BigDecimal F(int i, int n) {
//      6i(n-i) / (n3 - n)
        BigDecimal j = new BigDecimal(i);
        BigDecimal m = new BigDecimal(n);
        BigDecimal six = new BigDecimal(6);

        BigDecimal dividend = m.subtract(j).multiply(j).multiply(six);
        BigDecimal divisor = m.pow(3).subtract(m);

        return dividend.divide(divisor, 64, RoundingMode.HALF_UP);
    }
}
1 голос
/ 02 июля 2011

Для этого вы хотите разделить диапазон [0, 1] на регионы, которые имеют требуемый размер. Так что в этом случае:

0 -> 0.0 - 0.3
1 -> 0.3 - 0.7
2 -> 0.7 - 1.0
3 -> 1.0 - 1.0

Затем сгенерируйте случайное число с помощью Math.random() и посмотрите, в какой интервал оно попадает.

В общем, вы хотите сделать что-то вроде следующего псевдокода:

double r = Math.random();
int i = -1;

while (r >= 0)
{
  i++;
  r -= F(i);
}

// i is now the value you want.

Вы генерируете значение в [0, 1], затем вычитаете размер каждого интервала до тех пор, пока не опуститесь ниже 0, после чего вы нашли свое случайное значение.

...