繁体   English   中英

具有加权概率的Javascript随机数

[英]Javascript random number with weighted probability

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

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。 我想也许需要另一个参数来定义集群的宽度(标准偏差?)。

我不是数学家,但有人告诉我将Beta 分布视为一种可能的帮助工具:

在此处输入图片说明

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

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

合并两个分布

如果你有一个平坦的分布,你可以平等地选择你范围内的任何值。 如果您有高斯分布,则可以选择接近高斯均值的值。 因此,请考虑随机选择执行其中一项或多项。

如果你想随机值是接近目标t的80%的时间,和其他地方的另外20%。 假设“接近”意味着在 2 个标准偏差内,我们将方差设为v 所以范围(t-2*v)(t+2*v)需要覆盖 P(0.8)。

假设我们将随机使用平坦分布或高斯分布; 随机值落在给定范围内的概率是两个分布的总和,由分布选择的偏差加权。 如果我们选择高斯,我们最终会得到一个在 2 std.dev 以内的值95.45% 的时间 如果我们取高斯 X% 的时间,那么接近概率 Pn = P(t-2v to 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 个标准偏差之内,那么

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

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

当我被要求提供更多实现时,我找到了一个正常的变量生成器(感谢 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;
};

逆变换采样

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

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

步概率函数从十六分之一到八分之一在 4 处翻倍

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

在此处输入图片说明

我们有从 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 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());

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

看到这个 您可以使用这些值或导出函数并查看cd如何交互。 当然,您可以使用其他函数来获得更高的精度,例如具有更多次数的多项式或将多项式与指数函数混合以获得各种行为,但这是您真正想要什么以及您希望它如何表现的问题。

出色地。 概率是一件很容易做到的事情:

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

这是您要给 RNG 的最大尝试次数。 但是,为了了解您离目标有多近,必须考虑到这一点,我们可能会用一个 while 循环来做这件事。

var targetDigit = (''+ target).charAt(2),
    prob = +((''+probability).charAt(2)),
    rn,
    closest = 0;//keep track of closest hit
if (probability == 0)
    return Math.random();
do
{
    rn = Math.random();
    //reduce chances for too great of distances, regenerate, but don't count!
    if (Math.abs(rn.toFixed(1).charAt(2)-targetDigit) >= target/2)
        rn = Math.random();
    if (rn.toFixed(1).charAt(2) === targetDigit)
        return rn;//on the money
    if (Math.abs(target - rn) < Math.abs(target - closest))
        closest = rn;
} while (prob--);//while probability left
return closest;//return the closest generated value

当我尝试这段代码时,参数.8作为目标, .4作为概率,我得到以下返回值:

0.8257349738919855 // hit!
0.7696360136541552 // -0.1, close
0.7542727420001457 // -0.1, close
0.9837384401271013 // +0.1, close
0.3078854698178465 // -0.5, far
0.8178311114230021 // hit!
0.6079441227457084 // -0.2, fairly close

我认为这与您的期望很接近。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM