Java 中的离散概率分布

Discrete Probability Distribution in Java

我有一组整数,每个整数都有一个分配的概率,从早期的实验中得出,例如:

0 = 0.5
1 = 0.2
2 = 0.3

根据概率分布的规范,这些权重总和为 1.0。 我现在正在寻找一种有效的方法来对其中一个值进行采样,同时考虑给定的概率,例如(伪代码):

Distribution distribution = new DiscreteDistribution(new double[]{0.5, 0.3, 0.2});
distribution.sample();

根据给定的数字,这应该导致一半时间为 0。但是,不要假设其中有任何模式或规律。

我一直在使用 Apache Commons Math for my previous experiments, but it does not seem to provide a solution for this scenario, neither does Colt

我想知道这是否是因为我错过了一个简单的解决方案。天真的实现看起来或多或少是直截了当的,但要有效地做到这一点却相当复杂。这就是为什么我正在寻找一个既定的实施。

考虑到 quantile 函数的简单性和手动实现的琐碎性,我认为明确地写出来没有任何害处。

在 [0, 1] 中抽取随机数 r 后,使用

if (r <= 0.5/*micro-optimisation: most likely case first*/){
    return 0;
} else if (r <= 0.8/*then the next most likely case*/){
    return 2;
} else {
    return 1;
}

对于 3 个以上的数字,事情可能会变得更有趣,考虑建立一个 table 来表示这种情况下的分位数函数,但代价是性能有所下降。

(就速度而言,很难击败我的解决方案,在最坏的情况下,您有几个分支 - 并且您正在帮助 分支预测器 最好的方法,随机数绘制将是性能瓶颈所在。

一个非常简单的通用解决方案是:

class Distribution<T>{
    List<Double> probs = new ArrayList<>();
    List<T> events = new ArrayList<>();
    double sumProb;
    Random rand = new Random();

    Distribution(Map<T,Double> probs){
        for(T event : probs.keySet()){
            sumProb += probs.get(event);
            events.add(event);
            this.probs.add(probs.get(event));
        }
    }

    public T sample(){
        T value;
        double prob = rand.nextDouble()*sumProb;
        int i;
        for(i=0; prob>0; i++){
            prob-= probs.get(i);
        }
        return events.get(i-1);
    }
}

根据需要随意更改它,例如添加其他构造函数。当然这里还有很多需要改进的地方,从效率开始,但它是你以后可以重复使用的东西。

调用 Random.nextDouble() 是一项相当昂贵的操作。在这种情况下,您最好使用 Random.nextInt(n)

int num = rand.nextInt(10);
return num <= 5 ? 0 : num <= 8 ? 1 : 2;

这可能是一种更动态的方法,支持指定为双精度数组的任何概率分布:

public static int getRandomOutcome(double[] probaDist) {
    List<Double> sortedProbaDist = new ArrayList<>(probaDist.length);
    for (double d : probaDist) { sortedProbaDist.add(d); }

    Collections.sort(sortedProbaDist);

    double randomNumber = Math.random();
    
    double acc = 0;
    for (int i=0; i<sortedProbaDist.size(); i++) {
        acc += sortedProbaDist.get(i);
        if (randomNumber < acc) {
            return i;
        }
    }

    return probaDist.length;
}

请注意,该方法不会检查概率之和是否等于(接近)1。