具有加权概率的Javascript随机数

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

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)会产生一个随机值,该值可能会聚集在0.8左右,但可以是从0到1的任意数字.如果概率为0.5,那么甚至更多返回的结果随机值将接近0.8.我想可能还需要另一个参数来定义簇的宽度(标准偏差?).

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

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

> https://github.com/AndreasMadsen/mathfn
> https://github.com/jstat/jstat

解决方法

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

合并两个发行版

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

如果您希望随机值在80%的时间内接近目标t,则在其他地方接近20%.假设’near’意味着在2个标准差内,我们将方差视为v.因此范围(t-2 * v)到(t 2 * v)需要覆盖P(0.8).

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

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

0.6 = 0.6827*X + (1-X)(2v/r).

重新排列(2v / r):

(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]));
}

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

当我被要求提供更多实现时,我发现了a normal variate generator (thanks Prof 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;
};

逆变换采样

我考虑的第一种方法是使用Inverse transform sampling,我将在这里解释.

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

假设你有一个函数,当给定0到1之间的值时,产生一个0到10之间的值;它仍然是monotonic(没有最小值/最大值),但如果你从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());

相关文章

前言 做过web项目开发的人对layer弹层组件肯定不陌生,作为l...
前言 前端表单校验是过滤无效数据、假数据、有毒数据的第一步...
前言 图片上传是web项目常见的需求,我基于之前的博客的代码...
前言 导出Excel文件这个功能,通常都是在后端实现返回前端一...
前言 众所周知,js是单线程的,从上往下,从左往右依次执行,...
前言 项目开发中,我们可能会碰到这样的需求:select标签,禁...