在Swift中更改随机分布的函数?

t40tm48m  于 2023-03-11  发布在  Swift
关注(0)|答案(1)|浏览(130)

Swift是否有一些内置的方法来改变随机数的分布?我想用一个线性方程来定义分布,类似于y = k * x + m。当k = 0时,所有的数字应该平均分布。当k = 1时,分布应该遵循这条线,所以低x值是非常罕见的,而高x值是常见的。我在Excel中尝试了不同的策略,最后得出了以下代码,这看起来很有效--但是在Swift中一定有更整洁的方法来做到这一点?
注意:首先我使用了ClosedRange数组而不是元组方法,然后使用了. contains。然后我将其改为元组数组,因为我的代码没有按预期工作。可能是另一个bug,但我仍然使用元组,因为代码现在可以工作了。

import Foundation

/* function to create an array of tuples with upper and lower
 limits based on a linear distribution (y = k * x + m) */
func createDistributions(numbers: ClosedRange<Int>, k: Double) -> [(Double, Double)] {
    var dist = [(Double, Double)]()
    let m: Double = 0.5
    let nVal: Int = numbers.count
    var pStop: Double = 0.0

    for x in numbers {
        let t = (Double(x) + 0.5) / Double(nVal)
        let y = (k * (t - 0.5) + m) * 2.0 / Double(nVal)
        let start = pStop
        let stop = y + start
        
        dist.append((start, stop))
        pStop = stop
    }
    
    return dist
}

// create distributions based on k-value of 1.0
var result = createDistributions(numbers: 0...34, k: 1.0)

// loop ten times, creating a Double random number each time
for _ in 0...9 {
    let ran = Double.random(in: 0...1)
    
    // check in which indexed array the random number belongs to by checking lower and upper limit
    for i in 0..<result.count {
        
        // the random number belongs to the i:th element, print i
        if ran >= result[i].0 && ran <= result[i].1 {
            print(i)
        }
    }
}
a1o7rhls

a1o7rhls1#

您的y = kx+m是一个probability density function(PDF)。将此应用于随机数生成的一个非常好的方法是inverse transform sampling function。我将逐步介绍如何开发它,以便您可以根据自己的特定需要进行调整。一般情况下,这将在第一年微积分中完成,但对于线性的情况,用基本代数和一些小学几何就足够容易了,在这个例子中,我将生成一个0和1之间的随机值。
(For其他美国人沿着阅读:这是我们学习的y = mx+b的斜率-截距形式,请不要混淆m是截距,而不是斜率,希望我没有在答案中混淆它们。)
要试验这个答案,请查看图像来自的GeoGebra worksheet
所有这些的TL;DR是:

let u = Double.random(in: 0...1)
if k == 0 {
    return u
} else {
    return (sqrt(k*k + k*(8*u - 4) + 4) + k - 2)/(2*k)
}

但真实的的目标是了解为什么这是答案。
概率密度函数是一个函数,它的两个x值之间的面积是该值位于这两个值之间的概率,这就导致了概率密度函数对于其范围内的所有值都必须为正,并且其下的面积必须正好为1(表示在整个范围内选择某个值的概率为100%)。
但快速浏览一下这条曲线的任意版本,就会发现它可能没有正确的面积:

对于给定的k值,m有一个特定值是有效的。我们可以通过计算km的面积,将其设置为1,然后求解m来计算该值。图形的面积是一个底为1的矩形(我们选择的取值范围为0-1)和高度m,加上一个底为1、高为k的三角形。

Area = Rectangle + Triangle = 1
       m + k/2 = 1
       m = 1 - k/2

然后代入F(x):

F(x) = kx + 1 - k/2

我们还限制m不能小于0,这将k限制在[0,2]范围内,当k为0时,所有值的可能性相等,当k为2时,值与其可能性之间存在线性关系。

有了有效的PDF,就可以创建一个cumulative distribution function。这是一个表示随机选择的值不大于给定值的可能性的函数。这些函数受到约束的原因与PDF相同。它们必须在有效范围内从零单调增加到一。

这个面积可以像计算整个面积一样,通过将一个矩形和一个三角形相加来计算:

CDF(x) = Rectangle + Triangle
       = mx + (x/2 * (F(x) - m))
       = ... some algebra later ...
       = (k*x^2)/2 + (1-k/2)*x

请注意,此函数正确地通过了(0,0)和(1,1),并且在整个范围内为正。值不可能小于零,值小于或等于一的概率为100%。
差不多了,一个逆变换示例应用CDF的逆,这并不特别复杂,但需要大量的代数运算,所以让WolframAlpha来做:

solve y = (k*x^2)/2 + (1-k/2)*x for x
==>
x = y and k = 0
x = -(sqrt(k^2 + 8 k y - 4 k + 4) - k + 2)/(2 k) and k!=0
x = (sqrt(k^2 + k (8 y - 4) + 4) + k - 2)/(2 k) and k!=0

对于k=0,x=y。在其他地方,有两个解。这里只有正值才有意义,所以忽略负值。

红线是你想要的函数(这是k=1.5时的函数),要走到这一步还需要很长的路,但现在代码很简单了:

// `k` ranges from 0 to 2, which is confusing. Map it to range 0...1
func randomValue(distribution d: Double) -> Double {
    assert((0...1).contains(d))
    let u = Double.random(in: 0...1)

    // k ranges from 0 to 2
    let k = d * 2

    if k == 0 {
        return u
    } else {
        return (sqrt(k*k + k*(8*u - 4) + 4) + k - 2)/(2*k)
    }
}

为了测试一下

func testRun(distribution d: Double) {
    print("Distribution for \(d)")
    let n = 10_000

    // How many results begin with a given digit after the decimal point?
    var h: [Substring:Int] = [:]
    for _ in 0..<n {
        let value = randomValue(distribution: d)
        let firstDigit = "\(value)".prefix(3).suffix(1)
        h[firstDigit, default: 0] += 1
    }

    for digit in h.keys.sorted() {
        let ratio = Double(h[digit]!)/Double(n)
        print("\(digit) -> \(ratio.formatted(.percent.precision(.fractionLength(0))))")
    }
}

testRun(distribution: 0)
testRun(distribution: 0.5)
testRun(distribution: 1)

===>
Distribution for 0.0
0 -> 10%
1 -> 10%
2 -> 10%
3 -> 11%
4 -> 10%
5 -> 10%
6 -> 10%
7 -> 10%
8 -> 10%
9 -> 10%
Distribution for 0.5
0 -> 6%
1 -> 6%
2 -> 7%
3 -> 9%
4 -> 10%
5 -> 11%
6 -> 11%
7 -> 13%
8 -> 13%
9 -> 14%
Distribution for 1.0
0 -> 1%
1 -> 3%
2 -> 5%
3 -> 7%
4 -> 9%
5 -> 11%
6 -> 13%
7 -> 15%
8 -> 17%
9 -> 19%

一个线性方程只能把这个推到这么远,我不相信你可以得到一个更大的差异低概率和高概率值之间只有一个线性PDF(一个更好的数学家可能会纠正我这里;这不是我的专长)。如果你愿意,我会探索将此应用于更高阶的多项式。除了F(x) = kx + m,你还可以对F(x) = kx^2 + m甚至更高的幂做同样的事情。这将需要一些第一年的微积分,但总体方法应该是相似的。

相关问题