生成N个均匀分布的随机数,使其总和为M。

5

这个问题以前已经被问过了,但我从来没有见过一个好的答案。

  1. I want to generate 8 random numbers that sum to 0.5.

  2. I want each number to be randomly chosen from a uniform distribution (ie, the simple function below will not work because the numbers will not be uniformly distributed).

    def rand_constrained(n,tot):
        r = [random.random() for i in range(n)]  
        s = sum(r)
        r = [(i/s*tot) for i in r] 
        return r
    
代码应该是可推广的,这样你就可以生成N个均匀分布的随机数,它们的和为M(其中M是一个正浮点数)。如果可能的话,请解释一下(或者用图表展示),为什么你的解决方案在适当的范围内生成均匀分布的随机数。
与此相关的问题没有解决问题: 在Python中生成多个随机数以达到某个值(目前被接受的答案不是均匀分布——另一个均匀分布的答案只适用于整数) 获取N个随机数,使它们的和为M(Java中的同一问题,目前被接受的答案是完全错误的,也没有任何均匀分布的答案) 在R中生成N个随机整数,使它们的和为M(同样的问题,但在R中使用正常分布而不是均匀分布)
非常感谢您的帮助。

您可能希望将这个问题提交给数学StackExchange社区的专家们。 - user1269942
这个问题遭受了通常的问题,没有清楚地指定所需的联合分布。你怎么能确定你想要的属性可以同时满足呢? - user2357112
1
http://stats.stackexchange.com/questions/14059/generate-uniformly-distributed-weights-that-sum-to-unity - Émilien Tlapale
@user2357112 我不确定我想要的属性是否可以同时满足。昨晚我思考了很久,意识到这不是一个简单的问题。也许这是不可能的。 - spacetyper
@LeeDanielCrocker,我没注意到,谢谢。我会看一下的。 - spacetyper
显示剩余3条评论
5个回答

4
您所要求的似乎是不可能的。
不过,我会重新解释您的问题,使其更有意义并且可以解决。您需要的是在七维超平面上的概率分布。由于超平面的范围是无限的,整个超平面上的均匀分布将行不通。您可能想要的是所有都为正数的区域。那个区域是一个单纯形,三角形的推广,单纯形上的均匀分布是Dirichlet分布的一个特例。
您可能会发现Dirichlet分布维基百科文章中string cutting这一节特别有启发性。
实现
维基百科文章在Random Number Generation部分给出了Python中的以下实现:
params = [a1, a2, ..., ak]
sample = [random.gammavariate(a,1) for a in params]
sample = [v/sum(sample) for v in sample]

你可能想要的是所有 ai=1 的情况,这将在单纯形上产生均匀分布。这里的 k 对应于你问题中的数字 N。为了使样本总和为 M 而不是 1,只需将 sample 乘以 M
更新
感谢 Severin Pappadeux 指出 gammavariate 在极少数情况下可能返回无穷大。这在数学上是“不可能”的,但可能是由于浮点数实现的副产品。我建议处理这种情况的方法是在首次计算 sample 后检查它;如果任何组成部分的 sample 是无穷大,则将所有非无穷大组件设置为 0,将所有无穷大组件设置为 1。然后当计算 xi 时,将得到像 xi=1,所有其他 x = 0xi=1/2,xj=1/2,所有其他 x=0 这样的结果,集体称为“角落样本”和“边缘样本”。
另一个极低概率的可能性是伽玛变量之和溢出。我猜我们可以运行整个基础伪随机数序列而不会发生这种情况,但在理论上可能会发生(取决于基础伪随机数生成器)。在计算伽玛变量之后,但在计算x之前,可以通过重新缩放sample来处理这种情况,例如将sample的所有元素除以N。就个人而言,我不会费心处理它,因为发生这种情况的概率太低了;由于其他原因导致程序崩溃的概率更高。

感谢您关于在七维超平面上寻找单纯形的评论,这种方式更加直观。不过我仍在努力思考如何实现它。 - spacetyper
https://dev59.com/xGMl5IYBdhLWcg3wMkko#18600737 - Lee Daniel Crocker
在 gammavariate 中如何处理无穷大? - Severin Pappadeux
@LeeDanielCrocker 同样的事情...狄利克雷分布可以在不进行排序步骤的情况下完成。此外,如果需要略微不同的行为,则可以调整狄利克雷分布中的参数。 - Edward Doolittle
@gammavariate 可能会返回非常大的数字,但不会是无穷大。从 gammavariate 中获得“非常大”的数字的概率呈指数级衰减,因此在实际情况下基本为零。我唯一担心的是在 sum(sample) 中出现溢出的可能性;这种概率可以针对各种 N 进行计算;我怀疑除了极大的 N 值之外,基本上为 0。 - Edward Doolittle
显示剩余3条评论

2

与其从均匀分布中选择总和为'M'的'n'个数字,我们可以从范围为'0-M'的均匀分布中选择'n-1'个随机区间,然后提取这些区间。

from random import uniform as rand

def randConstrained(n, M):
     splits = [0] + [rand(0, 1) for _ in range(0,n-1)] + [1]
     splits.sort()
     diffs = [x - splits[i - 1] for i, x in enumerate(splits)][1:]
     result = map(lambda x:x*M, diffs)
     return result

res = randConstrained(8,0.5)
print res
print sum(res)

输出

[0.0004411388173262698,
 0.014832306834761111,
 0.009695872790939863,
 0.04539251424140245,
 0.18791325446494067,
 0.07615024971405443,
 0.07792489571128014,
 0.08764976742529507]

0.5

4
需要注意的是,使用此解决方案时,“res”的任何单个组件都不是均匀分布的。 - user2357112
没错。https://dev59.com/xGMl5IYBdhLWcg3wMkko#18600737 - Lee Daniel Crocker

0

对于一个完全通用的解决方案(“我想要在lowhigh之间有n个数字,它们的总和为m”):

from random import uniform as rand

def randConstrained(n, m, low, high):
    tot = m
    if not low <= 0 <= high:
        raise ValueError("Cannot guarantee a solution when the input does not allow for 0s")
    answer = []
    for _ in range(n-1):
        answer.append(low + rand(0,tot) * (high-low))
        tot -= answer[-1]
    answer.append(m-sum(answer))
    return answer

对于您的情况,可以按以下方式使用:

In [35]: nums = randConstrained(8, 0.5, 0, 1)

In [36]: nums
Out[36]: 
[0.2502590281277123,
 0.082663797709837,
 0.14586995648173873,
 0.011270073049224807,
 0.009328970756471237,
 0.00021993111786291258,
 0.0001831479074098452,
 0.000205094849743237]

random.random 不需要参数。 - user2357112
4
更好了,但是处理“low”和“high”的方式是错误的,并且分布系统地将太多的权重放在生成的第一个数字上。 - user2357112
1
@inspectorG4dget,正如 OP 所请求的那样,每个数字并非从随机分布中选择,例如您直接选择最后一个数字。 - Kavin Eswaramoorthy
1
无法保证一组随机选定的数字总和等于所需的总和,其中至少一个数字的确定性不可避免。另一种可能是允许使用负数。 - inspectorG4dget

0

这被称为单纯形采样,与狄利克雷分布密切相关。Sum(x_i) = 1,其中每个x_i都是U(0,1)。在单纯形采样后,在您的情况下,只需将每个x_i乘以0.5。

无论如何,将c++代码从https://github.com/Iwan-Zotow/SimplexSampling转换为python(希望没有太多错误)

它可以正确处理无穷大

def simplex_sampling(n):
    r = []
    sum = 0.0
    for k in range(0,n):
        x = random.random()
        if x == 0.0:
            return (1.0, make_corner_sample(n, k))

        t = -math.log(x)
        r.append(t)
        sum += t

     return (sum, r)

def make_corner_sample(n, k):
    r = []
    for i in range(0, n):
        if i == k:
            r.append(1.0)
        else:
            r.append(0.0)

    return r

 # main
 sum, r = simplex_sampling(8)

 norm = 0.5 / sum # here is your 0.5 total

 for k in range(0, 8):
     r[k] *= norm

这很好,因为它处理了单个gammavariate = infinity的最可能的异常情况。我在我的答案中确定了一些其他的异常情况,但是发生这些情况的几率非常小。 - Edward Doolittle

0

这与k4vin的解决方案相同,但我在random.uniform中遇到了导入错误。

import random

def rand_constrained(n, total):
    # l is a sorted list of n-1 random numbers between 0 and total
    l = sorted([0] + [total * random.random() for i in range(n - 1)] + [total])
    # Return the intervals between each successive element
    return [l[i + 1] - l[i] for i in range(n)]

print(rand_constrained(3, 10))
# [0.33022261483938276, 8.646666440311822, 1.0231109448487956]

但是Matplotlib在安装时出现了问题,所以我现在无法绘制它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接