我需要在一个间隔内生成一组随机数,这个随机数也恰好具有平均值。例如min = 1000,max = 10000,平均值为7000.我知道如何在一个范围内创建数字,但我正在努力平均值。有没有我可以使用的功能?
答案 0 :(得分:2)
使用所谓的接受拒绝方法,您最容易找到的内容。
将您的间隔分成更小的间隔。 指定概率密度函数(PDF),也可以是非常简单的函数,如阶梯函数。对于高斯分布,你会有比中间步骤更低的左右步骤,即(见下图更广泛的图像)。
在整个时间间隔内生成一个随机数。如果生成的数字大于此时PDF的值,则拒绝生成的数字。
重复这些步骤,直到获得所需的分数
编辑1
好的,所以基本想法如图(a)所示。
x
的PDF,如果它满足:1)f(x) >= 0
和2)它的归一化(意味着它求和,或积分,直到值1)。 max
)和&#34;零点&#34; (z1 < z2
)PDF。有些PDF可以在无穷大中得到零点。在这种情况下,确定您自己选择(z1, z2)
PDF(z1>x>z2) < eta
的截止点eta
。基本上是指设置一些小的值eta
,然后说你的零点是PDF(x)
的值小于eta的值。Ch(z1, z2, max)
。这是您生成随机变量的时间间隔。 z1<x<z2
。 y
范围内生成第二个不相关的随机变量(0, max)
。如果y
的值小于PDF(x)
,则拒绝随机生成的值(x,y)
并返回步骤4.如果生成的值y
大于PDF(x)
接受值x
作为分布上的随机生成点并return
它。这里是为高斯PDF再现类似行为的代码。
#include "Random.h"
#include <fstream>
using namespace std;
double gaus(double a, double b, double c, double x)
{
return a*exp( -((x-b)*(x-b)/(2*c*c) ));
}
double* random_on_a_gaus_distribution(double inter_a, double inter_b)
{
double res [2];
double a = 1.0; //currently parameters for the Gaussian
double b = 2.0; //are defined here to avoid having
double c = 3.0; //a long function declaration line.
double x = kiss::Ran(inter_a, inter_b);
double y = kiss::Ran(0.0, 1.0);
while (y>gaus(a,b,c,x)) //keep creating values until step 5. is satisfied.
{
x = kiss::Ran(inter_a, inter_b); //this is interval (z1, z2)
y = kiss::Ran(0.0, 1.0); //this is the interval (0, max)
}
res[0] = x;
res[1] = y;
return res; //I return (x,y) for plot reasons, only x is the randomly
} //generated value you're looking for.
void main()
{
double* x;
ofstream f;
f.open("test.txt");
for(int i=0; i<100000; i++)
{
//see bellow how I got -5 and 10 to be my interval (z1, z2)
x = random_on_a_gaus_distribution(-5.0, 10.0);
f << x[0]<<","<<x[1]<<endl;
}
f.close();
}
第1步
首先,我们在名为gaus
的函数中定义高斯PDF的一般外观。简单。
然后我们定义一个使用定义良好的高斯函数的函数random_on_a_gaus_distribution
。在实验\测量中,我们通过拟合函数得到系数a, b, c
。我为这个例子挑选了一些随机的(1,2,3),你可以选择满足你的HW分配的那些(即:使得高斯平均值为7000的系数)。
第2步和第3步
我用wolfram mathematica绘制了高斯。使用参数1,2,3也可以看到max
和(z1, z2)
的最合适值。你可以see the graph yourself。最大的功能是1.0,并通过古老的科学方法称为eyeballin&#39;我估计截止点是-5.0和10.0。
为了使random_on_a_gaus_distribution
更加通用,您可以更严格地遵循步骤2)并定义eta
,然后在连续点中计算您的函数,直到PDF小于eta。这样做的危险在于你的截止点可能相距甚远,这可能需要很长时间才能完成单调的功能。此外,你必须自己找到最大值。这通常很棘手,但是一个更简单的问题是最小化函数的负数。对于一般情况,这也可能是棘手的,但不是&#34;可撤销&#34;。最简单的方法就是像我一样作弊,只为几个函数进行硬编码。
第4步和第5步
然后你抨击。只需不断创造新点和新点,直到达到满意的效果。 DO NOTICE 返回的号码x
是一个随机数。您将无法在两个连续创建的x
值之间找到逻辑链接,或者首先创建x
和百万分之一。
但是,我们发布的x
周围的接受x_max
值的数量大于x
的时间间隔内创建的PDF(x) < PDF(x_max)
值的数量。
这只是意味着您的随机数将在所选区间内加权,使得随机变量x
的较大PDF值将对应于在该值周围的小间隔中接受的更多随机点。 xi
的任何其他PDF(xi)<PDF(x)
值。
我同时返回x和y以便能够绘制下面的图形,但是您要返回的内容实际上只是x
。我用matplotlib做了这个情节。
最好只显示分布上随机创建的变量的直方图。这表明PDF函数的平均值附近的x
值最有可能被接受,因此将创建具有这些近似值的随机创建的变量。
此外,我假设你会对Kiss随机数生成器的实现感兴趣。 非常重要的是你有一个非常好的发电机。我敢说,亲吻可能不会削减它(经常使用mersene twister)。
Random.h
#pragma once
#include <stdlib.h>
const unsigned RNG_MAX=4294967295;
namespace kiss{
// unsigned int kiss_z, kiss_w, kiss_jsr, kiss_jcong;
unsigned int RanUns();
void RunGen();
double Ran0(int upper_border);
double Ran(double bottom_border, double upper_border);
}
namespace Crand{
double Ran0(int upper_border);
double Ran(double bottom_border, double upper_border);
}
Kiss.cpp
#include "Random.h"
unsigned int kiss_z = 123456789; //od 1 do milijardu
unsigned int kiss_w = 378295763; //od 1 do milijardu
unsigned int kiss_jsr = 294827495; //od 1 do RNG_MAX
unsigned int kiss_jcong = 495749385; //od 0 do RNG_MAX
//KISS99*
//Autor: George Marsaglia
unsigned int kiss::RanUns()
{
kiss_z=36969*(kiss_z&65535)+(kiss_z>>16);
kiss_w=18000*(kiss_w&65535)+(kiss_w>>16);
kiss_jsr^=(kiss_jsr<<13);
kiss_jsr^=(kiss_jsr>>17);
kiss_jsr^=(kiss_jsr<<5);
kiss_jcong=69069*kiss_jcong+1234567;
return (((kiss_z<<16)+kiss_w)^kiss_jcong)+kiss_jsr;
}
void kiss::RunGen()
{
for (int i=0; i<2000; i++)
kiss::RanUns();
}
double kiss::Ran0(int upper_border)
{
unsigned velicinaIntervala = RNG_MAX / upper_border;
unsigned granicaIzbora= velicinaIntervala*upper_border;
unsigned slucajniBroj = kiss::RanUns();
while(slucajniBroj>=granicaIzbora)
slucajniBroj = kiss::RanUns();
return slucajniBroj/velicinaIntervala;
}
double kiss::Ran (double bottom_border, double upper_border)
{
return bottom_border+(upper_border-bottom_border)*kiss::Ran0(100000)/(100001.0);
}
另外还有标准的C随机发生器: CRands.cpp
#include "Random.h"
//standardni pseudo random generatori iz C-a
double Crand::Ran0(int upper_border)
{
return rand()%upper_border;
}
double Crand::Ran (double bottom_border, double upper_border)
{
return (upper_border-bottom_border)*rand()/((double)RAND_MAX+1);
}
值得对上述(b)图表发表评论。当您的行为表现非常糟糕时,PDF(x)
在大数字和非常小数字之间会有很大差异。
问题是区间区域Ch(x)
将匹配PDF井的极值,但是因为我们为y
的小值创建了随机变量PDF(x)
;接受这个价值的机会很小!生成的y
值更有可能在此时始终大于PDF(x)
。这意味着您将花费大量的周期来创建不会被选中的数字,并且您所选择的所有随机数将非常本地绑定到PDF的max
。
这就是为什么在任何地方都没有相同的Ch(x)
间隔通常有用,而是定义一组参数化的间隔。然而,这为代码增加了相当复杂的程度。
你在哪里设定限额?如何处理边缘案件?何时以及如何确定您确实需要突然使用这种方法?现在计算max
可能不那么简单,具体取决于您最初设想的方法。
此外,您现在必须纠正这样一个事实:在Ch(x)
框高度偏低原始PDF的区域中,更容易接受更多数字。
这可以通过在较低边界中创建的数字加权由较高和较低边界的高度比率来校正,基本上您再一次重复y
步骤。创建一个从0到1的随机数z
,并将其与lower_height / higher_height的比率进行比较,保证为&lt; 1。如果z
小于比率:接受x
以及它是否更大的拒绝。
通过编写一个接受对象指针的函数,也可以实现代码的推广。通过定义您自己的类function
,它通常会描述函数,在某一点上有一个eval方法,能够存储您的参数,计算并存储它自己的最大/最小值和零/截止点,你不必像我一样传递或定义它们。
祝你好运!
答案 1 :(得分:2)
tl; dr :将0到1的均匀分布提升到幂(1 - m) / m
,其中m
是所需的平均值(介于0和1之间)。根据需要移动/缩放。
无论如何,对于分布,如何从均匀[0,1]分布开始并将值提高到某个任意幂。将它们平方并且分布向右移动。平方根,他们向左移动。你可以去任何你想要的极端,并尽可能地推动发行。
def randompow(p):
return random.random() ** p
(一切都用Python编写,但应该很容易翻译。如果有些不清楚,请问。random.random()
将浮点数从0返回到1)
那么,我们如何调整这种力量呢?那么,平均值似乎随着不同的权力而变化?
看起来像某种S形曲线。有很多sigmoid functions,但是双曲线切线看起来效果很好。
那里不是100%,让我们尝试在X方向上缩放它......
# x are the values from -3 to 3 (log transformed from the powers used)
# y are the empirically-determined means given all those powers
def fitter(tanscale):
xsc = tanscale * x
sigtan = np.tanh(xsc)
sigtan = (1 - sigtan) / 2
resid = sigtan - y
return sum(resid**2)
fit = scipy.optimize.minimize(fitter, 1)
钳工称最佳比例因子为1.1514088816214016。残差实际上很低,听起来不错。
实现我没有谈到的所有数学的倒数看起来像:
def distpow(mean):
p = 1 - (mean * 2)
p = np.arctanh(p) / 1.1514088816214016
return 10**p
这使我们有能力在第一个函数中使用以获得分布的任何平均值。工厂函数可以返回一种方法,用所需的平均值
从分布中生成一堆数字def randommean(mean):
p = distpow(mean)
def f():
return random.random() ** p
return f
怎么做?合理地小到3-4位小数:
for x in [0.01, 0.1, 0.2, 0.4, 0.5, 0.6, 0.8, 0.9, 0.99]:
f = randommean(x)
# sample the distribution 10 million times
mean = np.mean([f() for _ in range(10000000)])
print('Target mean: {:0.6f}, actual: {:0.6f}'.format(x, mean))
Target mean: 0.010000, actual: 0.010030
Target mean: 0.100000, actual: 0.100122
Target mean: 0.200000, actual: 0.199990
Target mean: 0.400000, actual: 0.400051
Target mean: 0.500000, actual: 0.499905
Target mean: 0.600000, actual: 0.599997
Target mean: 0.800000, actual: 0.799999
Target mean: 0.900000, actual: 0.899972
Target mean: 0.990000, actual: 0.989996
一个更简洁的函数,只给出一个给出平均值(不是工厂函数)的值:
def randommean(m):
p = np.arctanh(1 - (2 * m)) / 1.1514088816214016
return random.random() ** (10 ** p)
编辑:符合平均值的自然对数而不是log10给出了一个可疑接近0.5的残差。做一些数学来简化arctanh给出:
def randommean(m):
'''Return a value from the distribution 0 to 1 with average *m*'''
return random.random() ** ((1 - m) / m)
从这里开始,移动,重新缩放和完善分发应该相当容易。截断到整数可能最终会将均值移动1(或半个单位?),因此这是一个未解决的问题(如果重要的话)。
答案 2 :(得分:0)
您只需定义两个以[1000,7000]运行的分布dist1
和以[7000,10000]运行的dist2
。
让我们将m1
的平均值称为dist1,将m2
的平均值称为dist2
。
您正在寻找dist1
和dist2
之间的混合物,其均值为7000。
您必须调整权重(w1,w2 = 1-w1),例如:
7000 = w1 * m1 + w2 * m2
导致:
w1 = (m2 - 7000) / (m2 - m1)
使用OpenTURNS库,代码如下:
import openturns as ot
dist1 = ot.Uniform(1000, 7000)
dist2 = ot.Uniform(7000, 10000)
m1 = dist1.getMean()[0]
m2 = dist2.getMean()[0]
w = (m2 - 7000) / (m2 - m1)
dist = ot.Mixture([dist1, dist2], [w, 1 - w])
print ("Mean of dist = ", dist.getMean())
>>> Mean of dist = [7000]
现在,您可以通过调用dist.getSample(N)
来绘制大小为N的样本。例如:
print(dist.getSample(10))
>>> [ X0 ]
0 : [ 3019.97 ]
1 : [ 7682.17 ]
2 : [ 9035.1 ]
3 : [ 8873.59 ]
4 : [ 5217.08 ]
5 : [ 6329.67 ]
6 : [ 9791.22 ]
7 : [ 7786.76 ]
8 : [ 7046.59 ]
9 : [ 7088.48 ]