具有加权概率的Javascript随机数

Tau*_*ren 3 javascript random math node.js

我正在尝试使用以下签名创建一个函数:

function weightedRandom (target, probability) {
  // calculate weighted random number that is more likely to be near target value
  return value; // number from 0 to 1
}
Run Code Online (Sandbox Code Playgroud)

它应该做到以下几点:

  • 生成从0到1的随机数,但不包括1
  • 在该范围内挑选任何给定数字的概率不均匀分布
  • 挑选的数字接近目标值的可能性更大(目标也是从0到1的值)
  • 概率曲线看起来像钟形曲线,其中目标值具有最高概率并且其周围的值逐渐减小,但是0到1范围内的所有值仍然有可能被挑选.
  • 可以使用概率值调整此机会的权重,其中值0表示不对随机性应用加权,1表示几乎所有拾取的数字将在目标值周围聚集.

例如,weightedRandom(0.8, 0.2)会导致20%的随机值可能聚集在值0.8附近,但可能是从0到1的任何数字.如果概率为0.5,那么返回的结果随机值将会更多接近0.8.我想可能还需要另一个参数来定义簇的宽度(标准偏差?).

我不是数学家,但我被告知要将Beta发行版作为一种可能的工具来帮助:

在此输入图像描述

我发现了一些具有beta功能的NPM模块,但我不知道如何使用它们来解决这个问题:

Phi*_*l H 5

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

合并两个发行版

如果您的分布平坦,您可以平均选择范围内的任何值.如果你有高斯分布,你可以选择接近高斯平均值的值.所以考虑随机选择做其中一个或另一个.

如果您希望随机值在t80%的时间内接近目标,而在其他地方则为其他20%.假设'near'意味着在2个标准差内,我们将采用方差v.这样的范围内(t-2*v),以(t+2*v)需要以覆盖P(0.8).

假设我们将随机使用平面分布或高斯分布; 然后,随机值落在给定范围内的概率是两个分布的总和,由分布选择的偏差加权.如果我们选择高斯,我们将得到一个2 std.dev内的值.95.45%的时间.如果我们采用高斯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). 
Run Code Online (Sandbox Code Playgroud)

我们有2个未知数,所以如果我们还需要一个非常接近概率的值,该值在60%的时间内在目标的1 std.dev之内,那么

0.6 = 0.6827*X + (1-X)(2v/r). 
Run Code Online (Sandbox Code Playgroud)

重新排列(2v/r):

(0.8 - 0.9545*X)/(1-X)*2 = (2v/r)
(0.6 - 0.6826*x)/(1-X) = (2v/r)
Run Code Online (Sandbox Code Playgroud)

等同和简化

X = 0.81546
Run Code Online (Sandbox Code Playgroud)

从而:

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]));
}
Run Code Online (Sandbox Code Playgroud)

我认为这可以为您提供所需的形状,让您为近概率和非常接近的概率选择两个概率,但避免过多的复杂性.

当我被要求提供更多实现时,我发现了一个正常的变量生成器(感谢Ian Neath教授):

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;
};
Run Code Online (Sandbox Code Playgroud)

逆变换采样

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

假设我们有一个分布,0到4的值同样可能,但只有4到10的值的一半.总概率是4a + 6(2*a)= 1,所以a = 1/16:

步进概率函数从第16位到第8位在4处加倍

假设你有一个函数,当给定0到1之间的值时,产生一个0到10之间的值; 它仍然是单调的(没有最小值/最大值),但是如果你从0到1每0.01递增一次,你会得到4:6*2 = 1:3的比率,所以4倍于4以下的值.该函数看起来像这样:

在此输入图像描述

我们具有从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)是逆变换,它采用平坦分布并从期望分布产生屈服值.如果你想绘制它,它就是x<(1/3) ? 9*x : 12*x -1.

然后游戏构建一个你满意的分布,并通过使用上面的碎片或通过分析反演它或一些近似(高斯逆不能分析写下)来反转它以获得逆变换.通过这种方式,您可以将任何平面分布的样本转换为所需的分布.

从上面的步骤分布中取样将如下所示:

// 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());
Run Code Online (Sandbox Code Playgroud)