具有单个目标和击中目标几率的加权随机浮点数

Weighted random float number with single target and chance of hitting target

提问人:Jason Ericson 提问时间:12/14/2021 最后编辑:SayseJason Ericson 更新时间:12/16/2021 访问量:530

问:

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

换句话说,我想要一个函数,当运行很多次时,会产生一个分布图,如下所示:

直方图

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

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

反正弦

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

加宽的中点

以及通过目标值上下移动中点的能力:

目标更改为 0.7(由 MS Paint 提供,因为我无法从数学上弄清楚这部分)

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

我正在使用 C#,但这可能与语言无关。任何为我指明正确方向的帮助都是值得赞赏的。也很乐意进一步澄清。

与 C# unity-game-engine 语言无关

评论


答:

1赞 DekuDesu 12/14/2021 #1

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

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

enter image description here

用法:

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 进行正态分布:其中定义为错误函数: enter image description here enter image description here。我添加了一个系数来缩放输出以将其保持在 0d - 1d 之间。erf1.77

它应该生成类似于下面的数字:enter image description here

在这里,您可以找到备用课程:

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

评论

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

Target = 0.7, Strength = 1

目标 = 0.2,强度 = 1

Target = 0.2, Strength = 1

目标 = 0.7,强度 = 3

Target = 0.7, Strength = 3

目标 = 0.7,强度 = 0

(这是均匀分布 - 它可能看起来有点锯齿状,但我测试过,这只是 python 的随机数生成器。Target = 0.7, Strength = 0

评论

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