生成一串随机数,总和为 1
- 2025-02-17 09:25:00
- admin 原创
- 93
问题描述:
此问题与获取 N 个随机数(其和为 M)的问题不是重复的,因为:
大多数答案都是关于理论的,而不是用 Python 中具体的编码解决方案来回答这个问题
这里接受的答案比回答该问题的重复答案早 5 年。
重复接受的答案没有回答这个问题
我如何列出 N(比如说 100)个随机数,使得它们的总和为 1?
我可以使用以下方法列出随机数
r = [ran.random() for i in range(1,100)]
我该如何修改它以便列表总和为 1(这是为了概率模拟)。
解决方案 1:
最简单的解决方案确实是取 N 个随机值并除以总和。
更通用的解决方案是使用numpy 中可用的
狄利克雷分布。
通过改变分布的参数,你可以改变单个数字的“随机性”
>>> import numpy as np, numpy.random
>>> print np.random.dirichlet(np.ones(10),size=1)
[[ 0.01779975 0.14165316 0.01029262 0.168136 0.03061161 0.09046587
0.19987289 0.13398581 0.03119906 0.17598322]]
>>> print np.random.dirichlet(np.ones(10)/1000.,size=1)
[[ 2.63435230e-115 4.31961290e-209 1.41369771e-212 1.42417285e-188
0.00000000e+000 5.79841280e-143 0.00000000e+000 9.85329725e-005
9.99901467e-001 8.37460207e-246]]
>>> print np.random.dirichlet(np.ones(10)*1000.,size=1)
[[ 0.09967689 0.10151585 0.10077575 0.09875282 0.09935606 0.10093678
0.09517132 0.09891358 0.10206595 0.10283501]]
根据主要参数,狄利克雷分布要么给出所有值都接近 1./N 的向量,其中 N 是向量的长度,要么给出大多数向量值为 ~0 的向量,并且只有一个 1,或者给出介于这两种可能性之间的某个值。
编辑(原始答案发表 5 年后):关于狄利克雷分布的另一个有用的事实是,如果您生成一组服从伽马分布的随机变量,然后将它们除以它们的总和,您就会自然得到它。
解决方案 2:
最好的方法是列出任意数量的数字,然后将它们除以总和。这样它们就完全随机了。
r = [ran.random() for i in range(1,100)]
s = sum(r)
r = [ i/s for i in r ]
或者,按照@TomKealy的建议,将总和和创建保持在一个循环中:
rs = []
s = 0
for i in range(100):
r = ran.random()
s += r
rs.append(r)
为了获得最快的性能,请使用numpy
:
import numpy as np
a = np.random.random(100)
a /= a.sum()
你可以赋予随机数任何你想要的分布,比如概率分布:
a = np.random.normal(size=100)
a /= a.sum()
---- 时间 ----
In [52]: %%timeit
...: r = [ran.random() for i in range(1,100)]
...: s = sum(r)
...: r = [ i/s for i in r ]
....:
1000 loops, best of 3: 231 µs per loop
In [53]: %%timeit
....: rs = []
....: s = 0
....: for i in range(100):
....: r = ran.random()
....: s += r
....: rs.append(r)
....:
10000 loops, best of 3: 39.9 µs per loop
In [54]: %%timeit
....: a = np.random.random(100)
....: a /= a.sum()
....:
10000 loops, best of 3: 21.8 µs per loop
解决方案 3:
将每个数字除以总数可能无法得到您想要的分布。例如,对于两个数字,对 x,y = random.random(),random.random() 会在正方形 0<=x<1, 0<=y<1 上均匀地选择一个点。除以总和会将该点 (x,y) “投影”到沿 (x,y) 到原点的直线 x+y=1 上。靠近 (0.5,0.5) 的点比靠近 (0.1,0.9) 的点更有可能出现。
对于两个变量,x = random.random(),y=1-x 沿几何线段给出均匀分布。
使用 3 个变量,您将在立方体中随机选取一个点并进行投影(径向投影,通过原点),但三角形中心附近的点比顶点附近的点更有可能出现。结果点位于 x+y+z 平面上的三角形上。如果您需要无偏地选择该三角形中的点,则缩放效果不佳。
这个问题在 n 维中变得复杂,但你可以得到一个低精度(但准确度很高,适合所有实验室科学爱好者!)的估计,通过从所有 n 元组的非负整数集合中均匀选取,加起来等于 N,然后将它们中的每一个除以 N。
我最近想出了一个算法,用于对中等大小的 n,N 进行此操作。它应该适用于 n=100 和 N = 1,000,000 的情况,为您提供 6 位随机数。请参阅我的回答:
创建受约束的随机数?
解决方案 4:
创建一个由 0 和 1 组成的列表,然后添加 99 个随机数。对列表进行排序。连续差值将是加起来为 1 的区间长度。
我不精通 Python,所以如果有更 Pythonic 的方式来做到这一点,请原谅我。但我希望意图是明确的:
import random
values = [0.0, 1.0]
for i in range(99):
values.append(random.random())
values.sort()
results = []
for i in range(1,101):
results.append(values[i] - values[i-1])
print results
以下是 Python 3 中的更新实现:
import random
def sum_to_one(n):
values = [0.0, 1.0] + [random.random() for _ in range(n - 1)]
values.sort()
return [values[i+1] - values[i] for i in range(n)]
print(sum_to_one(100))
解决方案 5:
除了@pjs 的解决方案之外,我们还可以定义一个带有两个参数的函数。
import numpy as np
def sum_to_x(n, x):
values = [0.0, x] + list(np.random.uniform(low=0.0,high=x,size=n-1))
values.sort()
return [values[i+1] - values[i] for i in range(n)]
sum_to_x(10, 0.6)
Out:
[0.079058655684546,
0.04168649034779022,
0.09897491411670578,
0.065152293196646,
0.000544800901222664,
0.12329662037166766,
0.09562168167787738,
0.01641359261155284,
0.058273232428072474,
0.020977718663918954]
解决方案 6:
如果你想对随机选择的数字设置一个最低阈值(即生成的数字至少应该为min_thresh
),
rand_prop = 1 - num_of_values * min_thresh
random_numbers = (np.random.dirichlet(np.ones(10),size=1)[0] * rand_prop) + min_thresh
只需确保您有 num_of_values(要生成的值的数量),以便可以生成所需的数字(num_values <= 1/min_thesh
)
因此,基本上,我们将 1 的某个部分固定为最小阈值,然后在其他部分创建随机数。我们将min_thesh
所有数字相加得到和 1。例如:假设您要生成 3 个数字,min_thresh=0.2。我们创建一个部分,用随机数填充 [1 - (0.2x3) = 0.4]。我们填充该部分,并将 0.2 添加到所有值,这样我们也可以得到 0.6 的填充。
这是随机数生成理论中使用的标准缩放和移位。感谢我的朋友 Jeel Vaishnav(我不确定他是否有 SO 个人资料)和 @sega_sai。
解决方案 7:
受到 @sega_sai 回答的启发,该回答提供了最新且推荐的 numpy 实现[2022 年 3 月]
from numpy.random import default_rng
rng = default_rng()
rng.dirichlet(np.ones(10),size=1)
>>> array([[0.01279836, 0.16891858, 0.01136867, 0.17577222, 0.27944229,
0.06244618, 0.19878224, 0.02481954, 0.01478089, 0.05087103]])
参考:
Numpy 随机抽样文档
随机生成器文档&随机狄利克雷生成器文档
解决方案 8:
生成 100 个随机数,无论范围如何。将生成的数字相加,然后将每个数字除以总数。
解决方案 9:
另一种解决方案是使用 random.choice 并除以总和:
import random
n = 5
rand_num = [random.choice(range(0,100)) for r in range(n)] # create random integers
rand_num = [i/sum(rand_num) for i in rand_num] # normalize them
解决方案 10:
您可以轻松地做到:
r.append(1 - sum(r))
解决方案 11:
本着“将列表中的每个元素除以列表的总和”的精神,此定义将创建一个长度 = PARTS、总和 = TOTAL 的随机数列表,其中每个元素四舍五入为 PLACES(或无):
import random
import time
PARTS = 5
TOTAL = 10
PLACES = 3
def random_sum_split(parts, total, places):
a = []
for n in range(parts):
a.append(random.random())
b = sum(a)
c = [x/b for x in a]
d = sum(c)
e = c
if places != None:
e = [round(x*total, places) for x in c]
f = e[-(parts-1):]
g = total - sum(f)
if places != None:
g = round(g, places)
f.insert(0, g)
log(a)
log(b)
log(c)
log(d)
log(e)
log(f)
log(g)
return f
def tick():
if info.tick == 1:
start = time.time()
alpha = random_sum_split(PARTS, TOTAL, PLACES)
log('********************')
log('***** RESULTS ******')
log('alpha: %s' % alpha)
log('total: %.7f' % sum(alpha))
log('parts: %s' % PARTS)
log('places: %s' % PLACES)
end = time.time()
log('elapsed: %.7f' % (end-start))
结果:
Waiting...
Saved successfully.
[2014-06-13 00:01:00] [0.33561018369775897, 0.4904215932650632, 0.20264927800402832, 0.118862130636748, 0.03107818050878819]
[2014-06-13 00:01:00] 1.17862136611
[2014-06-13 00:01:00] [0.28474809073311597, 0.41609766067850096, 0.17193755673414868, 0.10084844382959707, 0.02636824802463724]
[2014-06-13 00:01:00] 1.0
[2014-06-13 00:01:00] [2.847, 4.161, 1.719, 1.008, 0.264]
[2014-06-13 00:01:00] [2.848, 4.161, 1.719, 1.008, 0.264]
[2014-06-13 00:01:00] 2.848
[2014-06-13 00:01:00] ********************
[2014-06-13 00:01:00] ***** RESULTS ******
[2014-06-13 00:01:00] alpha: [2.848, 4.161, 1.719, 1.008, 0.264]
[2014-06-13 00:01:00] total: 10.0000000
[2014-06-13 00:01:00] parts: 5
[2014-06-13 00:01:00] places: 3
[2014-06-13 00:01:00] elapsed: 0.0054131
解决方案 12:
本着 pjs 方法的精神:
a = [0, total] + [random.random()*total for i in range(parts-1)]
a.sort()
b = [(a[i] - a[i-1]) for i in range(1, (parts+1))]
如果您希望它们四舍五入到小数位:
if places == None:
return b
else:
b.pop()
c = [round(x, places) for x in b]
c.append(round(total-sum(c), places))
return c
解决方案 13:
生成一个n
随机数列表(每个值都是>=a
),其总和x
为 Python。
生成随机数列表。
p = np.random.rand(n)
如果你想要所有大于的值
a
d = a + np.random.multinomial(x-a*n, p/np.sum(p))
解决方案 14:
这是一个非常有趣的问题。接受的答案是完全正确的。但对于那些不明白为什么狄利克雷分布比仅仅将四个随机值除以它们的总和产生更好的随机性的人,请阅读下面的解释和例子。
想象一下你要抛硬币。如果硬币两面的物理特性或你的技能稍微偏向硬币的一面,在尝试一定次数并记录结果后,你将得到一个能清楚显示这种偏差的“概率函数”。但在计算机程序中,这种概率分布对于最小的小数是“均匀分布”的。考虑到你重复测试的次数越多,你对固有概率函数的理解就越深刻,并且随机数的计算机概率函数几乎是均匀分布的,我们看到在规范化随机参数时,N(随机数的总数,这里是 4)是使概率函数重复的欺骗参数。换句话说,当你用相同的概率函数规范化 4 个随机参数时,你通过在一个维度上重复 4 次相同的隐藏概率函数来缩小它们的随机性。通过考虑狄利克雷分布可以解决这个问题,狄利克雷分布考虑了概率函数的多维性,这样它们就不会影响彼此的概率。仍然要记住,您第一次尝试时无法看到这种概率的均匀分布,但您知道它存在于程序中的某个地方,并且如果您一次又一次地重复测试,它就会显示出来。
以下代码是比较狄利克雷随机参数和归一化随机参数的实验。复制并粘贴它并重复运行几次(显示一个实例)。您可以看到,尽管狄利克雷 D1 到 D4 参数完全随机变化,其中 D1 到 D4 都有机会变得很小或很大,但 R1 到 R4 归一化参数的变化要均匀得多。对于许多使用随机百分比作为初始参数的研究人员来说,这是一个巨大的错误!祝一切顺利!
### Dirichlet distribution Randoms
import numpy as np
import matplotlib.pyplot as plt
Dirichlet_distribution = np.random.dirichlet(np.ones(4),size=1) # dirichlet distribution produces normal probablity distribution of 4 possibilities when sum of them will be 1
labels = 'D1', 'D2', 'D3', 'D4'
sizes = [Dirichlet_distribution[0][0], Dirichlet_distribution[0][1], Dirichlet_distribution[0][2], Dirichlet_distribution[0][3]]
fig, ax = plt.subplots(1,2) # one row and two columns
ax[0].pie(sizes, labels=labels)
ax[0].set_title('Dirichlet distribution randomization', fontsize=11)
# Normalized 4 random numbers
r= np.random.rand(1,4) # 1,4 is just the shape, all cells are random values from 0 to +1
r1 = r[0][0]
r2 = r[0][1]
r3 = r[0][2]
r4 = r[0][3]
N = r1+r2+r3+r4
r1 = r1/N
r2 = r1/N
r3 = r1/N
r4 = r1/N
labels_ = 'R1', 'R2', 'R3', 'R4'
sizes_ = [r1, r2, r3, r4]
ax[1].pie(sizes_, labels=labels_)
ax[1].set_title('normalized randomization', fontsize=11)
plt.show()
解决方案 15:
假设 R(n) 为 n 个随机数的集合。现在考虑集合 Q(n),它也由 n 个元素组成,每个元素由函数 f(n) = R(n)/( R(0)+R(1)+....R(n)) 定义。该新集合的总和为 1。请注意,集合 Q 的元素可能不像第一个集合的元素那样“随机”分布,例如,假设 R(n) 由 100<x<1000000 的数字组成,它们中的每一个都会映射到小于 1 的数字,因此,如果 x1=256、x2=512,它们将映射到 z1=1/256 和 z2=1/512。根据我的测试,初始集合的上限越小,第二个集合的分布越好。
扫码咨询,免费领取项目管理大礼包!