具有单个目标和命中目标机会的加权随机浮点数

Posted

技术标签:

【中文标题】具有单个目标和命中目标机会的加权随机浮点数【英文标题】:Weighted random float number with single target and chance of hitting target 【发布时间】:2022-01-17 08:35:52 【问题描述】:

我正在尝试创建一个随机浮点生成器(范围为 0.0-1.0),我可以在其中提供单个目标值,以及一个增加或减少该目标被击中机会的强度值。例如,如果我的目标是 0.7,并且我的强度值很高,我希望该函数返回的值大多在 0.7 左右。

换句话说,我想要一个函数,当运行很多次时,会产生一个类似这样的分布图:

Histogram

类似于钟形曲线,是的,但有严格的范围限制(而不是正态分布的 -inf/+inf 范围限制)。限制正态分布并不理想,我希望分布自然地以范围限制结束。

我一直在尝试的方法是提出一个公式,将值从均匀分布转换为我所设想的神话分布。类似于反正弦的东西:

Inverse Sine

能够通过强度值扩大中间点:

Widened Midpoint

还有通过目标值上下移动中点的能力:

Target changed to 0.7 (courtesy of MS Paint because I couldn't figure this part out mathematically)

这个理论“强度值”的范围有待商榷。我可以想象一个有限的值,比如在 0 和 1 之间,其中 0 表示它是均匀分布的,1 表示它有 100% 的机会击中目标;或者,我可以想象一个值越接近 100% 的机会,它就越高,而从未达到它。任何一条都行。

我正在使用 C# 工作,但这可能与语言无关。任何为我指明正确方向的帮助表示赞赏。也很高兴进一步澄清。

【问题讨论】:

【参考方案1】:

我不是数学家,但我看了一下,觉得我得到了一些可能对你有用的东西。

我所做的只是采用正态分布公式: 并使用 0.7 作为 mu 将分布移向 0.7。我添加了 0.623 的前导系数以将值移动到 0 和 1 之间,并将其从公式迁移到 C#,这可以在下面找到。

用法:

DistributedRandom random = new DistributedRandom();

// roll for the chance to hit
double roll = random.NextDouble();

// add a strength modifier to lower or strengthen the roll based on level or something
double actualRoll = 0.7d * roll;

定义

public class DistributedRandom : Random

    public double Mean  get; set;  = 0.7d;

    private const double limit = 0.623d;
    private const double alpha = 0.25d;
    private readonly double sqrtOf2Pi;
    private readonly double leadingCoefficient;

    public DistributedRandom()
    
        sqrtOf2Pi = Math.Sqrt(2 * Math.PI);
        leadingCoefficient = 1d / (alpha * sqrtOf2Pi);
        leadingCoefficient *= limit;
    

    public override double NextDouble()
    
        double x = base.NextDouble();

        double exponent = -0.5d * Math.Pow((x - Mean) / alpha, 2d);

        double result = leadingCoefficient * Math.Pow(Math.E,exponent);

        return result;
    

编辑: 如果您不是在寻找类似于您提供的分布直方图的输出,而是想要更类似于您绘制的 sigmoid 函数的输出,我创建了一个替代版本。

感谢 Ruzihm 指出这一点。

我继续使用 CDF 进行正态分布:,其中erf 定义为误差函数:。我添加了1.77 的系数来缩放输出,使其保持在 0d - 1d 范围内。

它应该产生类似这样的数字:

在这里您可以找到替代课程:

public class DistributedRandom : Random

    public double Mean  get; set;  = 0.7d;

    private const double xOffset = 1d;
    private const double yOffset = 0.88d;
    private const double alpha = 0.25d;
    private readonly double sqrtOf2Pi = Math.Sqrt(2 * Math.PI);
    private readonly double leadingCoefficient;
    private const double cdfLimit = 1.77d;
    private readonly double sqrt2 = Math.Sqrt(2);
    private readonly double sqrtPi = Math.Sqrt(Math.PI);
    private readonly double errorFunctionCoefficient;
    private readonly double cdfDivisor;

    public DistributedRandom()
    
        leadingCoefficient = 1d / (alpha * sqrtOf2Pi);
        errorFunctionCoefficient = 2d / sqrtPi;
        cdfDivisor = alpha * sqrt2;
    

    public override double NextDouble()
    
        double x = base.NextDouble();

        return CDF(x) - yOffset;
    

    private double DistributionFunction(double x)
    
        double exponent = -0.5d * Math.Pow((x - Mean) / alpha, 2d);

        double result = leadingCoefficient * Math.Pow(Math.E, exponent);

        return result;
    

    private double ErrorFunction(double x)
    
        return errorFunctionCoefficient * Math.Pow(Math.E,-Math.Pow(x,2));
    

    private double CDF(double x)
    
        x = DistributionFunction(x + xOffset)/cdfDivisor;

        double result = 0.5d * (1 + ErrorFunction(x));

        return cdfLimit * result;
    

【讨论】:

您的公式显示的形状类似于询问者正在寻找的 PDF,但您可能希望将 NextDouble 的输出输入到该函数积分的倒数*中(这将是更像 CDF),您可能还需要缩放该输出以获得 0 到 1 之间的范围。 this post对此有解释 我想出了1.77d * 0.5d * (1+erf((x-0.7d)/(0.25d * sqrt(2))))-1,老实说,它看起来非常相似。我不确定额外的 CPU 周期是否值得为类似图表计算 CDF。虽然,我将完全透明,但我仅基于正态分布的 wiki 和我的编程经验,而不是基于我的数学经验。也许我做错了。 @Ruzihm @Ruzihm 没关系,我明白你的意思,我会用它更新我的帖子。我真的是脑死了 不错!但它应该是相反的,它应该看起来更像问题中显示的逆sin,它在 y=mean 周围处于最水平位置。 哇,我真的很感谢这里的努力。我认为@Ruzihm 是对的,我在这里寻找类似于你的第二个公式的逆运算。我走上了与您最初所做的类似的道路,试图为最终的直方图创建公式,然后才意识到这并不是我想要的。我可以检查所有这些的正态分布,但我的主要问题是它的对称形状。我正在寻找一个最终分布图,其中 100% 的值落在 0 和 1 之间,但如果有意义的话,曲线的“驼峰”会根据目标滑动。【参考方案2】:

我想出了一个可行的解决方案。这并不像我想要的那样优雅,因为它需要每个结果 2 个随机数,但它绝对满足要求。基本上它需要一个随机数,使用另一个以指数方式向 1 弯曲的随机数,然后向目标倾斜。

我用python写出来是因为它的直方图更容易可视化:

import math
import random

# Linearly interpolate between a and b by t.
def lerp(a, b, t):
    return ((1.0 - t) * a) + (t * b)

# What we want the median value to be.
target = 0.7
# How often we will hit that median value. (0 = uniform distribution, higher = greater chance of hitting median)
strength = 1.0

values = []
for i in range(0, 1000):
    # Start with a base float between 0 and 1.
    base = random.random()

    # Get another float between 0 and 1, that trends towards 1 with a higher strength value.
    adjust = random.random()
    adjust = 1.0 - math.pow(1.0 - adjust, strength)

    # Lerp the base float towards the target by the adjust amount.
    value = lerp(base, target, adjust)

    values.append(value)

# Graph histogram
import matplotlib.pyplot as plt
import scipy.special as sps
count, bins, ignored = plt.hist(values, 50, density=True)
plt.show()

目标 = 0.7,强度 = 1

目标 = 0.2,强度 = 1

目标 = 0.7,强度 = 3

目标 = 0.7,强度 = 0

(这意味着均匀分布 - 它可能看起来有点锯齿状,但我测试过,这只是 python 的随机数生成器。)

【讨论】:

我认为这是一个相当不错的结果。但是目前不能保证一半的基本结果会在目标的左侧和右侧,因此它并不是真正的中位数。稍作改动,您就可以获得该行为:if base < .5: base = base/.5 * target else: base = target + (base-.5)/.5 * (1-target)replit.com/@Ruzihm/ProbDist 如果strength=0,这将导致不同的行为。 话虽如此,asker 的直方图并没有显示众数也是中位数,所以我认为这个答案是完全可以接受的。我认为将其作为一种选择可以改善未来可能想要这种行为的访问者的答案 更正我的上述评论 *预期,不保证

以上是关于具有单个目标和命中目标机会的加权随机浮点数的主要内容,如果未能解决你的问题,请参考以下文章

将浮点数四舍五入到目标C中的下一个整数?

php随机浮点数都有哪些?比如从0.1到3.0中随机一个浮点数出来?

我应该使用浮点数还是类作为神经网络最后一层的输出?

返回随机数:整数浮点数

随机浮点数生成

生成 0 到 1 之间的随机浮点数