在使用python生成随机数时,肯定会想到random模块。random模块实现了各种分布的伪随机(Pseudo-Random)数生成,我们这次来学习下Python的random模块。
对于整数来说,random模块很简单,这个也是很常用的伪随机生成使用方法;但是对于序列(Sequence)来说,random模块能够随机选取序列元素、生成序列的随机分布,或者随机不重复抽样等。
random模块实现的分布主要包括:uniform、normal(Gaussian)、lognormal、negative exponential、gamma、beta等;还有个von Mises,这个是产生角度分布,算法思想可以参考wiki:http://en.wikipedia.org/wiki/Von_Mises_distribution。
在random模块中,定义了如下几个常量:
- NV_MAGICCONST = 4 * _exp(-0.5)/_sqrt(2.0)
- TWOPI = 2.0*_pi
- LOG4 = _log(4.0)
- SG_MAGICCONST = 1.0 + _log(4.5)
- BPF = 53 # Number of bits in a float
- RECIP_BPF = 2**-BPF
我们先看下random模块定义的方法:
定义了三个类:
这三个类都能产生随机数,其中Random是常用的随机数产生算法,我们主要讨论的随机数产生都是该类的方法;WichmannHill和SystemRandom都继承了Random类,WichmannHill的random方法使用了和Random类相似的随机方法,该算法主要依赖于seed值,有兴趣的同学可以看下理论,我看实现难度不大,但是理论估计是有点难度;SystemRandom是使用操作系统的random产生函数,如unix依赖于/dev/urandom,windows依赖于CryptGenRandom等。
下面我们来看下random模块提供的方法:
设置random包的seed值,该值对于生成随机数至为重要,如果你不设置的话,默认取系统时间。
返回随机数,值范围是[0.0,1.0),该方法在后面的各种分布中多次出现,所以该随机数生成算法是random模块的基础。我们来看下random函数的源码:
- def random(self):
- x, y, z = self._seed
- x = (171 * x) % 30269
- y = (172 * y) % 30307
- z = (170 * z) % 30323
- self._seed = x, y, z
- return (x/30269.0 + y/30307.0 + z/30323.0) % 1.0
该算法实现看起来简单,如果想深入理解这个随机数算法的原理,可以参考An efficient and portable pseudo-random number generator(Applied Statistics 31 (1982) 188-190)这篇文章,该文章后面的算法实现和random的源码略有不同,文章参考这里:http://www2.imperial.ac.uk/~hohs/S9/2007-2008/wichmannhill.pdf
指数分布
Gamma分布,要求alpha>0且beta>0,注意这个不是Gamma函数。当然在计算过程中用到了Gamma函数。Gamma分布的详细信息参考wiki:http://en.wikipedia.org/wiki/Gamma_distribution
这两个都是产生Gauss分布,两者采用不同的思路产生,前者使用统计意义上的计算,后者是依据标准的正态分布定义产生。前者生成速度还要快点,不过在多线程场景下使用要自己加锁控制并发。
对数级别的正态分布。
Beta Distribution,要求alpha>0且beta>0,依赖于gamma分布,通过gamma分布函数生成。
产生三角分布函数,关于Triangular Distribution的详细信息见http://en.wikipedia.org/wiki/Triangular_distribution,wiki里面有比较详细的讨论;看源码会发现该函数的生成同样依赖于random()函数。
产生[a,b]的均匀分布,该分布产生利用了random()函数,实际上产生的值为a+(b-a)*random()
- random.paretovariate(alpha):Pareto Distribution
- random.vonmisesvariate(mu, kappa):Von Mises Distribution,是针对角度的分布。
- random.weibullvariate(alpha, beta):Weibull Distribution<span> </span>
这三个分布稍显专业,平时都不怎么用。
将Python作用到Sequence上,可以看下下面几个函数的效果
choice函数从seq中随机选择一个元素,sample函数从元素列表中选取k个不重复元素,shuffle函数则对元素列表进行一次随机排列。看下示例:
- >>> bits=['a','b','c','d','e','f','g']
- >>> random.choice(bits)
- 'a'
- >>> random.choice(bits)
- 'c'
- >>> random.sample(bits,3)
- ['c', 'b', 'e']
- >>> random.sample(bits,3)
- ['g', 'a', 'c']
- >>> random.shuffle(bits)
- >>> bits
- ['a', 'f', 'd', 'g', 'b', 'e', 'c']
- random.random()
- random.randrange(stop)
- random.randrange(start, stop[, step])
- random.randint(a, b)
- random.getrandbits(k)<span> </span>
random函数产生[0.0,1.0)的随机数,这个在上面提到过;randrange函数是在提到的range(start,end,step)中随机选择;randint函数是在[a,b]中选择,注意两边都是闭区间;getrandbits函数实在bit级别上进行控制,控制每个bit上的随机量。
- >>> random.random()
- 0.6216895046152143
- >>> random.randrange(10)
- 7
- >>> random.randrange(5,10)
- 9
- >>> random.randrange(5,10)
- 6
- >>> random.randrange(5,10)
- 7
- >>> random.randrange(5,10)
- 5
- >>> random.randrange(5,10,2)
- 5
- >>> random.randrange(5,10,2)
- 9
- >>> random.randrange(5,10,2)
- 7
- >>> random.randint(1,100)
- 49
- >>> random.randint(1,100)
- 22
- >>> random.getrandbits(5)
- 27
- >>> random.getrandbits(5)
- 27
- >>> random.getrandbits(5)
- 7
- >>> random.getrandbits(50)
- 259336269183598
- >>> random.getrandbits(500)
- 2123294400168802339423947306484820684208555438132242308767915648686014450077945137455581839262813894933829601172344940098461036686745618482209213922214
- >>> random.getrandbits(5000)
- 4034754137539484179299467597527391507546381474148526154395421207380075606134559180138703425120094793323534549803288574253400061961405474748312233157619180389178319042116709667737968426875719453232825587709299171195574278743656906955409390042725992518977600085740467686975785820692459295094963664868994212901792068886664992509467663579527691907093450609832530263379958186539108800219479153927035711354075963574379567702705107895056023508996779148121923936388548341378859868882424925192771844170677816283953086457527992241139927429905498724725234373412588522642913731667566323782617498119281849336962492061749751663018412752756985945190973968626062918960920916813960732895100632389147211487593464557043805295027860687336511022864551102551287702441650416601162438483314529829096068674259579299557425952074299390480479779787510272694343411355085195312144186003575867190677050695130041384874117375485556935211062489182801151796266461330635349790915122810079710159825931951294785611281623200060903422400093055307371277330083371368887743322247273016355323686859209112857576218973944225428147564652781736490029302922410573312453153416182209839876163339188176414458934726982720923448475009661073041932910018380017325457354357313544645084438508127883399927917698522570586118494954794329286952111267534963812167557444992442451658941414640398737575936221194957286718271011604437241627690160697999192659641775460024935972380618868777518058797753473526196799809754689345865056290961953738953134889706900308020180214258
state是random包内部的一个状态表示,[VERSION,_seed, gauss_next]数组表示,如果你想设置state,请将VERSION设置为1
random内部维护了生成随机数的流程,jumpahead函数会重置函数生成流程,使得能够在同样的状态下生成下一步的随机函数,不受已经生成的随机数的影响。这个函数在单线程情况下影响不大,但是对于多线程,能够控制在每个线程中随机数生成的初始状态一致。在stackoverflow上有个帖子http://stackoverflow.com/questions/2546039/how-should-i-use-random-jumpahead-in-python比较详细的解释了这个问题,在多线程情况下使用随机数时需要这个的问题在后面的帖子中也有提到,可以详细看下。
random包提供的内容还是挺丰富的,除去产生随机数外,对于Sequence的操作也能较好的减小工作量,特别是choice、sample、shuffle函数提供的功能。
既然提到了random函数,我们在深入理解下random函数的生成。在前面提到,unix系统下random的生成依赖于/dev/urandom,实际上,另外一个产生随机数的文件是/dev/random。两者产生的随机数的原理是利用当前系统的熵池来计算出固定一定数量的随机比特,然后将这些比特作为字节流返回。熵池就是当前系统的环境噪音,熵指的是一个系统的混乱程度,系统噪音可以通过很多参数来评估,如内存的使用,文件的使用量,不同类型的进程数量等等。两者的不同点是后者在不能产生随机数会阻塞(block),而前者不会。所以使用/dev/random比使用/dev/urandom产生的随机数要慢,但是随机性要好。如果使用随机数要求不是特别高的话,/dev/urandom产生随机数足够。
关于python的随机数random模块的学习结束。在这里一定要区分random模块和Random类。后者是random模块中定义的类,准确的说,random模块中提供的方法都是Random类中的方法;如果你想使用SystemRandom和WichmannHill类,就需要自己初始化