具有加权概率的Javascript随机数 [英] Javascript random number with weighted probability

查看:86
本文介绍了具有加权概率的Javascript随机数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

 函数weightedRandom(目标,概率){//计算更有可能接近目标值的加权随机数返回值;//从0到1的数字} 

它应该执行以下操作:

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

例如, weightedRandom(0.8,0.2)将产生一个随机值,该值很可能会聚集在0.8左右,但可能是0到1之间的任何数字.概率为0.5,则返回的结果随机值甚至更多,将接近0.8.我认为也许需要另一个参数来定义群集的宽度(标准偏差?).

我不是数学家,但有人告诉我将

  • (没有最小值/最大值),但是如果从从0到1,您将获得4:6 * 2 = 1:3的比率,因此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 .

    然后,游戏将构建您满意的分布,并将其反转以得到逆变换,方法是使用上面的片段或通过解析将其反转,或者进行某种近似(不能用解析方式记下高斯逆).这样,您可以将任何平面分布的样本转换为所需的分布.

    从上述步骤分布中进行采样将如下所示:

     //将0-1平坦转换为0-10步进函数stepInvTransform(z){return(3 * z< 1?9 * z:(12 * z-1));}//通过inv变换采样var sample = stepInvTransform(Math.random()); 

    I'm trying to create a function with the following signature:

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

    It should do the following:

    • Generate random number from 0 to 1, but not including 1
    • The probability of any given number being picked in that range is NOT evenly distributed
    • There is a greater chance that the number picked is near the target value (target is also a value from 0 to 1)
    • The probability curve would look like a bell curve, where the target value has the highest probability and the values around it taper down, but all values in the 0 to 1 range would still have a chance of getting picked.
    • The weight of this chance can be adjusted with the probability value, where a value of 0 means there is no weighting applied to the randomness and 1 means that almost all numbers picked will be clustered right around the target value.

    For example, weightedRandom(0.8, 0.2) would result in a random value that is 20% likely to be clustered around the value 0.8, but could be any number from 0 up to 1. If the probability was 0.5, then even more of the resulting random values returned would be near 0.8. I think maybe there needs to be another parameter to define the width of the cluster (standard deviation?).

    I am not a mathematician, but I have been told to look at Beta Distributions as a possible tool to help:

    I've found some NPM modules that have beta functions, but I'm not sure how to use them to solve this problem:

    解决方案

    TLDR: randomly choose between two easier distributions, also Inverse transform sampling

    Combine two distributions

    If you had a flat distribution, you could choose any value in your range equally. If you had a Gaussian distribution, you could choose a value near your Gaussian's mean. So consider randomly choosing to do one or other of these.

    If you want the random value to be near the target t 80% of the time, and elsewhere the other 20%. Suppose that 'near' means within 2 standard deviations, and we'll take the variance to be v. So the range (t-2*v) to (t+2*v) needs to cover P(0.8).

    Suppose we will randomly use either a flat distribution or a Gaussian distribution; the probability of a random value falling in a given range is then the sum of the two distributions, weighted by the bias of the distribution choice. If we choose the Gaussian, we will end up with a value within 2 std.dev. 95.45% of the time. If we take the Gaussian X% of the time, then the near probability Pn = P(t-2v to t+2v) = 0.9545*X + (1-X)(4v/r), where r is the full range, and (4v/r) is then the proportion of the flat distribution inside the range.

    To get this Pn to 80%:

    0.8 = 0.9545*X + (1-X)(4v/r). 
    

    We have 2 unknowns, so if we also require a very near probability that the value is within 1 std.dev of the target 60% of the time, then

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

    Rearranging for (2v/r):

    (0.8 - 0.9545*X)/(1-X)*2 = (2v/r)
    (0.6 - 0.6826*x)/(1-X) = (2v/r)
    

    Equating and simplifying

    X = 0.81546
    

    Thus:

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

    I think this gives you the required shape, lets you choose two probabilities for near and very near probabilities, but avoids too much complexity.

    As I'm being asked to provide more implementation, I've found 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

    The first way I considered was to use Inverse transform sampling, which I'll attempt to explain here.

    Suppose we have a distribution where values from 0 to 4 are equally likely, but only half as likely as values from 4 to 10. The total probability is 4a + 6(2*a) = 1, so a=1/16:

    Suppose you had a function which, when given a value between 0 and 1 in, produced a value between 0 and 10; it is still monotonic (no minima/maxima), but if you fed it every 0.01 increment from 0 to 1 you'd get a ratio of 4:6*2 = 1:3, so 3x as many values over 4 as under it. That function would look like this:

    We have linear segment from z=0 to z=1/3, where x(1/3) = 4, and then a linear segment from z=1/3 to z=1 continuing on to x(1)=10. If we choose a random number z from the flat probability distribution between 0 and 1, then x(z) will be distributed with the first 1/3 of the range giving values up to 4, as required, and the remainder above.

    z(x), then, is the inverse transform that takes a flat distribution and makes yields values from the desired distribution. If you want to plot it, it is x<(1/3) ? 9*x : 12*x -1.

    The game is then to construct a distribution that you are happy with, and invert it to get the inverse transform, either by using pieces as above or by analytically inverting it, or some approximation (Gaussian inverse cannot be written down analytically). With this you can transform any flat-distributed sample into the desired distribution.

    Sampling from the above step distribution would be done like this:

    // 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());
    

    这篇关于具有加权概率的Javascript随机数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆