Swift 中改变随机分布的函数?

问题描述 投票:0回答:1

Swift 是否有一些内置的方法来改变随机数的分布?我想使用线性方程来定义分布,例如 y = k * x + m。当 k = 0 时,所有数字应均匀分布。当 k = 1 时,分布应遵循该线,因此低 x 值将非常罕见,而高 x 值将很常见。我在 Excel 中四处游玩并尝试了不同的策略,最后想出了这段代码,它似乎有效——但在 Swift 中一定有更简洁的方法来做到这一点?

注意:首先我确实使用了 ClosedRange 数组而不是元组方法,然后使用了 .contains。然后我将其更改为元组数组,因为我的代码没有按预期工作。可能是另一个错误,但我仍然使用元组,因为代码现在可以工作了。

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)
        }
    }
}
swift random distribution
1个回答
0
投票

您的

y = kx+m
是一个 概率密度函数 (PDF)。将其应用于随机数生成的一个很好的方法是逆变换采样函数。我将逐步介绍如何开发它,以便您可以根据自己的特定需求进行调整。在一般情况下,这将通过一年级的微积分来完成,但对于线性情况,使用基本代数和一些小学几何就可以很容易地完成。对于这个例子,我将生成一个介于 0 和 1 之间的随机值。

(对于其他正在阅读的美国人:这是我们学习的斜率截距形式

y = mx+b
。请不要混淆
m
这里是截距,而不是斜率。我希望没有在任何地方混淆它们在这个答案中。)

要试验这个答案,请参阅图像来自的GeoGebra 工作表

所有这一切的 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)
}

但了解为什么这是答案才是真正的目标。

PDF 是一个函数,其两个 x 值之间的面积是该值介于这些值之间的概率。这导致 PDF 对于其范围内的所有值都必须为正,并且它下面的面积必须正好为 1(表示在整个范围内选择某个值的可能性为 100%)。

但是快速查看此曲线的任意版本表明它可能没有正确的区域:

对于

k
的给定值,存在有效的特定
m
值。我们可以通过根据
k
m
计算面积,将其设置为 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,是时候创建一个 累积分布函数了。这是一个表示随机选择的值不大于给定值的可能性的函数。由于与 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..<10_000 {
        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
或更高的幂来做同样的事情。这将需要一些第一年的微积分,但总体方法应该是相似的。

© www.soinside.com 2019 - 2024. All rights reserved.