python numpy 生成 总和为固定值指定个数的随机数

本文分享如何使用Python的numpy库生成指定长度且总和固定的随机数序列,支持生成小数和整数序列。

最近有一个需求需要生成一个总和固定,指定长度的随机数序列,特意记录下代码,与大家分享一下。
既可以生成随机数小数序列,也可以生成随机数整数序列。

import numpy as np

def allocation_amount(num_people, amount):
    # 生成小数随机数
    a = [np.random.uniform(0, amount) for i in range(num_people-1)]
    # 生成整数随机数
    # a = [np.random.randint(0, amount) for i in range(num_people-1)]
    a.append(0)
    a.append(amount)
    a.sort()
    print(a)
    b = [a[i+1]-a[i] for i in range(num_people)]  # 列表推导式,计算列表中每两个数之间的间隔
    print(b)
    b = np.array(b)
    return b

# 随机生成一个长度为5, 总和为2022的列表
bonus = allocation_amount(5, 2022)
print(bonus)
print(np.sum(bonus))

------------- EOF ---------------

<think>我们想要生成N个随机数,每个数在0到1之间,并且它们的总和为1。 常见方法有两种:使用均匀分布然后归一化,或者使用Dirichlet分布(生成和为1的随机向量)。 方法1:归一化 步骤: 1. 生成N个非负随机数(例如,从均匀分布中采样)。 2. 计算这些随机数总和。 3. 将每个随机数除以这个总和。 注意:这样得到的随机数在0到1之间,且和为1。但是,这种方法生成随机数并不是均匀分布在概率单纯形上(即每个向量被抽到的概率并不相等),而是倾向于在单纯形内部更均匀,但边缘概率较低。 方法2:Dirichlet分布 Dirichlet分布是定义在概率单纯形上的分布。当参数全为1时,相当于均匀分布在整个单纯形上。 使用numpy的random.dirichlet函数可以生成这样的随机向量。 我们比较一下两种方法: 示例(归一化方法): 生成3个随机数,使其和为1。 步骤: import random n = 3 random_numbers = [random.random() for _ in range(n)] total = sum(random_numbers) normalized = [x / total for x in random_numbers] 但是,如果希望每个随机数具有特定的分布特性,可能需要调整。 然而,归一化方法有一个特点:生成的随机向量在单纯形上并不是均匀分布的。实际上,归一化方法在单纯形上产生的点更集中在中心区域。而Dirichlet分布当参数为(1,1,...,1)时,会均匀分布在单纯形上。 因此,如果我们希望均匀地生成概率单纯形上的点(即每个点被抽到的概率相同),应该使用Dirichlet分布。 使用Dirichlet分布生成: import numpy as np # 生成一个长度为3的向量,每个元素非负,且和为1,且均匀分布在单纯形上。 samples = np.random.dirichlet([1]*3, size=1)[0] 注意:参数[1]*3表示每个分量的浓度参数都是1,这样就是均匀分布。 但是,如果不需要均匀分布在整个单纯形上,归一化方法可能更简单。 另外,需要注意的是,归一化方法生成随机数,每个分量的期望是相等的吗? 在归一化方法中,我们先生成N个独立的均匀分布随机变量U1, U2, ..., UN,然后计算S=U1+...+UN,最后得到Xi=Ui/S。 每个Xi的期望:由于对称性,每个Xi的期望相同,且总和为1,所以E[Xi]=1/N。 同样,Dirichlet分布当参数全为1时,每个分量的边缘分布是Beta(1, N-1),其期望也是1/N。 因此,两种方法在期望上相同,但分布形状不同。 问题要求:生成N个随机数,使其总和等于1。 根据需求,我们可以选择其中一种方法。 如果用户没有特别要求分布形式,我们可以提供两种方法。 但是注意:归一化方法生成随机数可能包含0(如果允许0的话,但这里我们生成的是非负,且归一化后不会出现负数,但可能出现接近0的数),而Dirichlet分布也可以生成接近0的数。 另外,还有一种方法:生成N-1个区间(0,1)上的随机数,排序,然后取相邻差值。这种方法也可以得到和为1的随机数,但实际效果和归一化方法类似,也不是均匀分布在单纯形上。 具体步骤(差值法): 1. 生成N-1个在(0,1)区间内的随机数,并排序:0 < u1 < u2 < ... < u_{N-1} < 1。 2. 令u0=0, uN=1。 3. 令xi = ui - u_{i-1},i=1,...,N。 这样得到的x1,...,xN也是和为1,且每个非负。但这种方法生成的随机向量在单纯形上的分布也不是均匀的,而是倾向于边界。 因此,我们通常使用归一化方法或Dirichlet分布。 下面我们分别用两种方法实现: 方法1(归一化): 注意:如果生成随机数中有负数,则归一化后可能出现负数,但我们这里用random.random()生成的是[0,1)的随机数,所以归一化后都是非负的,且和为1。 方法2(Dirichlet分布): 使用numpy.random.dirichlet,参数为全1的列表(长度N),生成一个向量。 代码示例: 归一化方法: import random def generate_normalized(n): randoms = [random.random() for _ in range(n)] total = sum(randoms) return [x/total for x in randoms] 但是,这里有一个问题:random.random()生成的是[0,1)的浮点数,有可能出现总和非常小的情况,导致归一化后某些数很大?实际上,由于每个数都是非负的,归一化后每个数都在[0,1]之间,并且和为1。 但是,如果n很大,那么总和会接近n/2(因为每个随机数的期望是0.5),所以每个数除以总和后,平均在1/n附近。 方法2(Dirichlet): import numpy as np def generate_dirichlet(n): # 参数alpha是一个列表,长度为n,每个元素为1,表示对称Dirichlet分布 return np.random.dirichlet([1]*n, size=1)[0].tolist() 注意:如果不希望使用numpy,那么可以使用归一化方法。但Dirichlet分布生成均匀分布的特性更好。 然而,根据文献,生成均匀分布在单纯形上的随机向量,还有一种方法是:生成指数分布的随机数然后归一化(这等价于Dirichlet分布的一种特例,即当Dirichlet分布的参数为(1,1,...,1)时,可以通过指数分布生成)。所以,如果我们不想用numpy,也可以用指数分布生成然后归一化。 方法3(指数分布归一化): 步骤: 1. 生成n个独立的指数分布随机数(参数为1)。 2. 归一化(除以总和)。 指数分布随机数可以用random模块的expovariate(1)生成。 代码: import random def generate_exponential(n): randoms = [random.expovariate(1) for _ in range(n)] total = sum(randoms) return [x/total for x in randoms] 这种方法生成的随机向量在单纯形上是均匀分布的(与Dirichlet分布参数全为1等价)。 因此,我们提供三种方法,但归一化方法(使用均匀分布)与指数分布归一化(或Dirichlet)的分布不同。 用户可以根据需要选择: 1. 如果希望均匀分布在单纯形上(即每个可能的向量被抽到的概率相同),则使用指数分布归一化或Dirichlet分布。 2. 如果无此要求,可以使用均匀分布归一化(简单)。 注意:指数分布归一化需要生成指数分布随机数,而Dirichlet分布需要numpy,但指数分布归一化可以用标准random模块实现。 因此,我们给出两个版本: 版本1(使用标准库,均匀分布归一化): 代码简单,适用于一般情况。 版本2(使用标准库,指数分布归一化): 生成均匀分布在单纯形上的随机向量。 版本3(使用numpy,Dirichlet分布): 同样生成均匀分布在单纯形上的随机向量。 下面我们分别实现: 注意:用户要求生成N个随机数,所以函数参数为n。 考虑到用户可能没有安装numpy,我们优先使用标准库。 但是,指数分布归一化在数学上等价于Dirichlet(1,1,...,1)分布,因此两者效果相同。 我们写一个函数,让用户可以选择方法。 然而,用户的问题没有指定分布要求,所以我们可以默认使用均匀分布归一化,因为简单且不需要额外假设。 但是,注意:均匀分布归一化方法生成的随机向量并不均匀分布在单纯形上。例如,当n=3时,点会更多地集中在单纯形的中心区域。而指数归一化(或Dirichlet)则均匀分布。 因此,如果用户需要均匀分布,则应该使用指数归一化或Dirichlet。 根据问题,用户没有明确要求,所以我们提供两种,并说明区别。 代码实现: 方法1:均匀分布归一化 方法2:指数分布归一化(均匀分布在单纯形上) 方法3:Dirichlet分布(需要numpy,同样均匀分布在单纯形上) 我们可以这样写: 如果用户安装了numpy,可以使用Dirichlet,否则使用指数分布归一化来实现均匀分布。 但为了不依赖numpy,我们主要用标准库实现。 因此,我们写两个函数: 函数1:使用均匀分布归一化 函数2:使用指数分布归一化(均匀分布在整个单纯形上) 然后让用户选择。 但是,用户可能不知道区别,我们可以根据用户需求建议: 如果用户只是需要一组和为1的非负随机数,那么方法1(均匀归一化)就足够了。 如果用户需要这些随机向量在单纯形上均匀分布(例如用于蒙特卡洛积分等),那么使用方法2。 下面我们给出代码示例: 注意:生成指数分布随机数,我们使用random.expovariate(1),它生成参数为1的指数分布(即密度为exp(-x))。 代码: import random def generate_sum_to_one(n, method='uniform'): """ 生成n个非负随机数,它们的和为1。 参数: n: 随机数个数 method: 可选'uniform'(均匀分布归一化)或'exponential'(指数分布归一化,均匀分布在单纯形上) 返回: 一个包含n个浮点数的列表,它们的和为1。 """ if method == 'uniform': randoms = [random.random() for _ in range(n)] elif method == 'exponential': randoms = [random.expovariate(1) for _ in range(n)] else: raise ValueError("方法必须是'uniform'或'exponential'") total = sum(randoms) return [x / total for x in randoms] 测试: 生成3个随机数,检查总和是否为1。 示例运行: >>> generate_sum_to_one(3, 'uniform') [0.123, 0.456, 0.421] 总和为1 >>> generate_sum_to_one(3, 'exponential') [0.2, 0.3, 0.5] 总和为1 注意:指数分布归一化可能生成一些很小的数(因为指数分布可以取到很大的值,但归一化后,如果某个数很大,其他数就会很小),所以生成随机数中可能会有接近0的数,也有接近1的数(但不会超过1)。 另外,我们也可以提供Dirichlet分布的方法,但需要numpy,所以作为可选: try: import numpy as np except ImportError: pass def generate_dirichlet(n): return np.random.dirichlet([1]*n, size=1)[0].tolist() 如果用户希望使用Dirichlet分布,并且安装了numpy,可以调用这个函数。 但是,由于问题要求使用Python生成,我们主要使用标准库。 因此,最终回答:我们提供了两种使用标准库的方法,并解释它们的区别。 注意:还有一种情况,如果用户要求随机数是在固定区间上均匀采样,然后归一化,那么方法1就是这样的。而方法2(指数分布)则不是从固定区间均匀采样,而是从指数分布采样。 因此,我们根据用户需求选择。 最后,我们生成N个随机数,使其总和等于1,可以使用上述函数。 示例:生成5个和为1的随机数(使用均匀归一化方法) 代码: random_numbers = generate_sum_to_one(5) 如果希望均匀分布在单纯形上,则: random_numbers = generate_sum_to_one(5, method='exponential') 相关问题: 1. 如何生成和为1的非负随机整数? 注意:整数的话,要求每个随机整数非负且和为1,那么除了一个1其他都是0。所以通常我们生成的是浮点数。 如果要求生成整数,那么只有一种可能:一个1和n-1个0。所以随机选择一个位置为1,其他为0。 2. 如何生成和为S(任意正数)的N个非负随机数? 先生成和为1的N个非负随机数,然后每个数乘以S即可。 3. 如何生成和为1的随机数,但要求每个随机数在某个区间内(比如[0.1,0.5])? 这需要约束条件,可能比较复杂。可以使用拒绝采样或截断分布等方法。 4. 生成和为1的随机数有哪些应用场景? 例如:概率分布、资源分配、蒙特卡洛模拟等。 因此,我们根据问题,提供上述函数。 注意:在科学计算中,使用Dirichlet分布更常见,因为它可以灵活调整参数(浓度参数)来改变分布的形状。例如,当参数都小于1时,分布倾向于集中在单纯形的角上;当参数都大于1时,倾向于集中在中心。但我们的问题中,参数全为1,就是均匀分布。 最后,我们写一个完整的代码示例。</think>### 生成和为1的N个随机数Python实现 要生成N个非负随机数总和为1,本质是在**概率单纯形**(Probability Simplex)上均匀采样。以下是三种科学可靠的方法及其原理: --- #### 方法1:归一化均匀分布(简单高效) ```python import random def generate_normalized(n): """生成n个随机数并归一化为总和1""" randoms = [random.random() for _ in range(n)] # 生成n个[0,1)随机数 total = sum(randoms) return [x / total for x in randoms] # 归一化 # 示例:生成5个随机数 print(generate_normalized(5)) # 可能输出:[0.12, 0.33, 0.08, 0.25, 0.22](总和≈1) ``` - **原理**:生成独立同分布的均匀随机数后归一化 - **期望特性**:每个数的期望值为 $E[X_i] = \frac{1}{n}$ - **缺点**:生成的向量在单纯形上非均匀分布,倾向于中心区域[^1] --- #### 方法2:指数分布归一化(均匀覆盖单纯形) ```python import random def generate_exponential(n): """基于指数分布实现单纯形均匀采样""" randoms = [random.expovariate(1) for _ in range(n)] # 参数λ=1的指数分布 total = sum(randoms) return [x / total for x in randoms] # 示例:生成3个随机数 print(generate_exponential(3)) # 可能输出:[0.15, 0.60, 0.25](严格满足总和=1) ``` - **数学原理**:指数分布归一化等价于 $\text{Dirichlet}(1,1,\cdots,1)$ 分布 - **优势**:在单纯形上实现**均匀分布**,每个点概率密度相同[^2] - **指数分布性质**:密度函数 $f(x;\lambda) = \lambda e^{-\lambda x}$ --- #### 方法3:Dirichlet分布(概率分布专用) ```python import numpy as np def generate_dirichlet(n): """使用Dirichlet分布生成和为1的随机向量""" return np.random.dirichlet(np.ones(n)).tolist() # 示例:生成4个随机数 print(generate_dirichlet(4)) # 输出:[0.27, 0.13, 0.42, 0.18](严格满足∑xᵢ=1, xᵢ≥0) ``` - **参数意义**:`np.ones(n)` 表示浓度参数全为1,对应均匀单纯形分布 - **适用场景**:需要调整分布形态时(如修改浓度参数) --- ### 关键概念说明 1. **概率单纯形**:N维空间中满足 $\sum_{i=1}^N x_i = 1$ 且 $x_i \geq 0$ 的点集 $$ \left\{ (x_1,\dots,x_N) \in \mathbb{R}^N \mid \sum x_i = 1, \, x_i \geq 0 \right\} $$ 2. **方法选择建议**: - 优先选方法2(指数分布归一化),兼具均匀性和无依赖库 - 需要高级概率特性时用方法3(Dirichlet) - 方法1适用于快速简单场景 --- ### 数学验证 生成10,000组随机数验证方法2的均匀性: ```python import matplotlib.pyplot as plt # 生成10000组三维随机向量 samples = [generate_exponential(3) for _ in range(10000)] # 可视化在二维单纯形上的分布 plt.figure(figsize=(8,6)) plt.hexbin( [s[0] for s in samples], [s[1] for s in samples], gridsize=30, cmap='Blues' ) plt.title("随机向量在概率单纯形上的分布") plt.xlabel("x₁"), plt.ylabel("x₂") plt.show() ``` 结果将显示均匀的密度分布,印证方法2的数学性质。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值