Реализация функции распределения вероятностей в Java
Я пытаюсь реализовать функцию распределения вероятности в java, где она возвращает ith
запись в массив с вероятностью:
Fi = 6i(n-i) / (n3 - n)
здесь n
- длина массива, т. е. для длины массива 4:
P1 = 3/10, P2 = 4/10, P3 = 3/10, P4 = 0
обратите внимание, что эта функция предполагает нумерацию от 1 до n
а для 0 n-1
как в Java.
на данный момент я просто использую равномерное распределение, т. е.
int i = (int)(Math.random()*((arraySize)-1));
С -1, поэтому он не выбирает последний элемент (т. е. Pn = 0, как в приведенной выше формуле).
кто - нибудь с любыми идеями или советами по реализации этого?
4 ответов
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
это по существу то, что говорит 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.
для этого необходимо разделить диапазон [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, и в этот момент Вы найдете свое случайное значение.
вы можете попробовать использовать навигационную карту с распределением вероятности. В отличие от обычных карт 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);
}
}