Javascript 加权概率随机数

Javascript 加权概率随机数,javascript,node.js,math,random,Javascript,Node.js,Math,Random,我正在尝试创建具有以下签名的函数: function weightedRandom (target, probability) { // calculate weighted random number that is more likely to be near target value return value; // number from 0 to 1 } 它应做到以下几点: 生成从0到1的随机数,但不包括1 在该范围内拾取任何给定数字的概率不是均匀分布的 拾取的数字接近目标

我正在尝试创建具有以下签名的函数:

function weightedRandom (target, probability) {
  // calculate weighted random number that is more likely to be near target value
  return value; // number from 0 to 1
}
它应做到以下几点:

  • 生成从0到1的随机数,但不包括1
  • 在该范围内拾取任何给定数字的概率不是均匀分布的
  • 拾取的数字接近目标值的可能性更大(目标值也是从0到1的值)
  • 概率曲线看起来像钟形曲线,其中目标值的概率最高,且其周围的值逐渐减小,但0到1范围内的所有值仍有机会被拾取
  • 此概率的权重可以使用概率值调整,其中0表示没有对随机性应用权重,1表示几乎所有选取的数字都将围绕目标值聚集
例如,
weightedRandom(0.8,0.2)
将产生一个随机值,该值有20%可能聚集在值0.8周围,但可以是0到1之间的任何数字。如果概率为0.5,则返回的更多随机值将接近0.8。我认为可能需要另一个参数来定义集群的宽度(标准偏差?)

我不是数学家,但有人告诉我要把它作为一种可能的工具来帮助:

我发现一些NPM模块具有
beta
功能,但我不确定如何使用它们来解决此问题:


我会扩展范围(Math.round()*2),然后使用其他函数
f:[0,2]->[0,1]
它不是1-1映射回0-1间隔,而是将更多值映射到0.8的区域。例如,
f(x)=c*x^2-d*x^3
并调整
c
d
,以使分布适合您


看。您只需使用这些值或派生函数,就可以看到
c
d
是如何交互的。当然,您可以使用其他函数来获得更高的精度,例如阶数更高的多项式,或者将多项式与指数函数混合,以获得各种行为,但这是一个您真正想要的问题,以及您希望它如何表现。

好。概率是很容易做到的:

//get the probability factor:
var prob = +(probability.charAt(2));//0.[2] <-- coerces 2 to number

我认为这与您的预期非常接近。

TLDR:在两个更简单的分布中随机选择,也就是逆变换采样

合并两个分布 如果你有一个平坦的分布,你可以在你的范围内平等地选择任何值。如果你是高斯分布,你可以选择一个接近高斯平均值的值。因此,考虑随机选择做一个或其他这些。

如果您希望随机值在80%的时间内接近目标
t
80%,在其他地方接近其他20%。假设“near”表示在2个标准差内,我们将方差取为
v
。因此范围
(t-2*v)
(t+2*v)
需要覆盖P(0.8)

假设我们将随机使用平坦分布或高斯分布;随机值落在给定范围内的概率是两个分布的总和,通过分布选择的偏差进行加权。如果我们选择高斯分布,我们将得到一个值。如果我们取高斯X%的时间,则近概率Pn=P(t-2v到t+2v)=0.9545*X+(1-X)(4v/r),其中r是全范围,而(4v/r)则是范围内平坦分布的比例

要使此Pn达到80%:

0.8 = 0.9545*X + (1-X)(4v/r). 
我们有两个未知数,所以如果我们还需要一个非常接近的概率,该值在目标60%的时间内在1 std.dev之内,那么

0.6 = 0.6827*X + (1-X)(2v/r). 
重新排列(2v/r):

等值化与简化

X = 0.81546
因此:

逆变换采样 我考虑的第一种方法是使用,我将在这里尝试解释

假设我们有一个分布,其中0到4的值的可能性相等,但只有4到10的值的一半。总概率为4a+6(2*a)=1,因此a=1/16:

假设您有一个函数,当在中给定一个介于0和1之间的值时,它生成一个介于0和10之间的值;它仍然是(没有最小值/最大值),但是如果你从0到1每增加0.01次,你会得到4:6*2=1:3的比率,因此4以上的值是4以下值的3倍。该函数如下所示:

// transform 0-1 flat to 0-10 stepped
function stepInvTransform(z) {
    return (3*z < 1 ? 9*z : (12*z - 1));
}

// sample via inv transform
var sample = stepInvTransform(Math.random());

我们有从z=0到z=1/3的直线段,其中x(1/3)=4,然后是从z=1/3到z=1的直线段,继续到x(1)=10。如果我们从0和1之间的平坦概率分布中选择一个随机数z,那么x(z)将分布在该范围的前1/3处,根据需要给出最多4个值,其余值在上面


z(x)是一种逆变换,它采用平面分布,并根据所需分布生成值。如果你想画出来,这是好主意,但我不知道如何应用它们。问题在于
0.8
只是一个示例,此函数需要支持从0到1的任何
目标。我仍然需要找到一种自动化的方法,为任何给定的
目标
找到合适的
c
d
值。有什么想法吗?好吧,我会拿一张纸,导出函数,然后尝试用“0.8”和“0.2”表示
c
d
的值。然后您可以将其放入代码中,即CalcCD(0.8,0.2)-它输出
c
d
。从那时起,您只需应用上述内容*注意,数学部分可能有点烦人,但如果你找不到适合你工作的软件包,那就值得麻烦了。有趣的方法,我会尝试一下,看看结果是否足够接近我的需要。我希望我能给你多一票支持这样一个深入的数学答案!然而,我不知道如何转换
(0.8 - 0.9545*X)/(1-X)*2 = (2v/r)
(0.6 - 0.6826*x)/(1-X) = (2v/r)
X = 0.81546
var range = [0, 10];
var target = 7.0;
var stddev = 1.0;
var takeGauss = (Math.random() < 0.81546);
if(takeGauss) {
  // perform gaussian sampling (normRand has mean 0), resample if outside range
  while(1) {
    var sample = ((normRand()*stddev) + target);
    if(sample >= range[0] && sample <= range[1]) {
      return sample;
    }
  }
} else {
  // perform flat sampling
  return range[0]+(Math.random()*(range[1]-range[0]));
}
function normRand() {
    var x1, x2, rad;

    do {
        x1 = 2 * Math.random() - 1;
        x2 = 2 * Math.random() - 1;
        rad = x1 * x1 + x2 * x2;
    } while(rad >= 1 || rad == 0);

    var c = Math.sqrt(-2 * Math.log(rad) / rad);

    return x1 * c;
};
// transform 0-1 flat to 0-10 stepped
function stepInvTransform(z) {
    return (3*z < 1 ? 9*z : (12*z - 1));
}

// sample via inv transform
var sample = stepInvTransform(Math.random());