random
--- 產生偽隨機數¶
原始碼:Lib/random.py
本章中所提及的 module(模組)用來實現各種分佈的虛擬隨機數產生器。
對於整數,可以從範圍中進行均勻選擇。對於序列,有一個隨機元素的均勻選擇,一個用來原地 (in-place) 產生隨機排列清單的函式,以及一個用來隨機取樣不重置的函式。
在實數線上,有一些函式用於處理均勻分佈、常態分佈(高斯分佈)、對數常態分佈、負指數分佈、gamma 分佈和 Beta 分佈。對於產生角度分佈,可以使用馮·米塞斯分佈 (von Mises distribution)。
幾乎所有 module 函式都相依於基本函式random()
,此函式在半開放範圍0.0<=X<1.0
內均勻地產生一個隨機 float(浮點數)。Python 使用 Mersenne Twister(梅森旋轉演算法)作為核心的產生器,它產生 53 位元精度 float,其週期為 2**19937-1,透過 C 語言進行底層的實作既快速又支援執行緒安全 (threadsafe)。Mersenne Twister 是現存最廣泛被驗證的隨機數產生器之一,但是基於完全確定性,它並不適合所有目的,並且完全不適合加密目的。
該 module 提供的函式實際上是random.Random
class(類別)中一個隱藏實例的綁定方法 (bound method)。你可以實例化自己的Random
實例,以得到不共享狀態的產生器。
如果你想使用你自己設計的基本產生器,Random
也可以進行子類別化 (subclass)。有關詳細資訊,請參考該類別的文件。
random
module 也提供了SystemRandom
class,使用系統函式os.urandom()
從作業系統提供的來源產生隨機數。
警告
本章所提及的虛擬隨機數產生器不應該使用於安全目的。有關安全性或加密用途,請參考secrets
module。
也參考
M. Matsumoto and T. Nishimura, "Mersenne Twister: A 623-dimensionallyequidistributed uniform pseudorandom number generator", ACM Transactions onModeling and Computer Simulation Vol. 8, No. 1, January pp.3--30 1998.
進位互補乘法 (Complementary-Multiply-with-Carry) 用法,可作為隨機數產生器的一個可相容替代方案,具有較長的週期和相對簡單的更新操作。
備註
全域隨機數產生器和Random
的實例是執行緒安全的。然而,在自由執行緒構建中,對全域產生器或同一Random
實例的並行呼叫可能會遇到爭用和性能不佳。請考慮改為每個執行緒使用單獨的Random
實例。
簿記函式 (bookkeeping functions)¶
- random.seed(a=None,version=2)¶
初始化隨機數產生器。
如果a 被省略或為
None
,則使用目前系統時間。如果隨機來源由作業系統提供,則使用它們而不是系統時間(有關可用性的詳細資訊,請參考os.urandom()
函式)。如果a 為 int(整數),則直接使用它。
如使用版本 2(預設值),
str
、bytes
或bytearray
物件將轉換為int
,並使用其所有位元。若使用版本 1(為復現於舊版本 Python 中產生隨機序列而提供),
str
和bytes
的演算法會產生範圍更窄的種子 (seed)。在 3.2 版的變更:移至版本 2 方案,該方案使用字串種子中的所有位元。
- random.getstate()¶
回傳一個物件,捕獲產生器的目前內部狀態。此物件可以傳遞給
setstate()
以恢復狀態。
- random.setstate(state)¶
state 應該要從之前對
getstate()
的呼叫中獲得,並且以setstate()
將產生器的內部狀態恢復到呼叫getstate()
時的狀態。
回傳位元組的函式¶
- random.randbytes(n)¶
產生n 個隨機位元組。
此方法不應使用於產生安全性權杖 (Token)。請改用
secrets.token_bytes()
。在 3.9 版被加入.
回傳整數的函式¶
- random.randrange(stop)¶
- random.randrange(start,stop[,step])
傳回從
range(start,stop,step)
中隨機選擇的元素。這大致相當於
choice(range(start,stop,step))
,但支援任意大的範圍,並針對常見情況進行了最佳化。位置引數模式與
range()
函式相符。不應使用關鍵字引數,因為它們可能會以意想不到的方式被直譯。例如
randrange(start=100)
會被直譯為randrange(0,100,1)
。在 3.2 版的變更:
randrange()
在產生均勻分佈的值方面更為複雜。以前,它使用像int(random()*n)
這樣的樣式,這可能會產生稍微不均勻的分佈。在 3.12 版的變更:已經不再支援非整數類型到等效整數的自動轉換。像是
randrange(10.0)
和randrange(Fraction(10,1))
的呼叫將會引發TypeError
。
- random.randint(a,b)¶
回傳一個隨機整數N,使得
a<=N<=b
。為randrange(a,b+1)
的別名。
- random.getrandbits(k)¶
回傳一個具有k 個隨機位元的非負 Python 整數。此方法會隨 Mersenne Twister 產生器一起提供,一些其他的產生器也可能將其作為 API 的可選部分。如果可用,
getrandbits()
使randrange()
能夠處理任意大的範圍。在 3.9 版的變更:此方法現在接受k 為零。
回傳序列的函式¶
- random.choice(seq)¶
從非空序列seq 回傳一個隨機元素。如果seq 為空,則引發
IndexError
。
- random.choices(population,weights=None,*,cum_weights=None,k=1)¶
回傳從population 中重置取樣出的一個大小為k 的元素 list。如果population 為空,則引發
IndexError
。如果指定了weights 序列,則根據相對權重進行選擇。另外,如果給定cum_weights 序列,則根據累積權重進行選擇(可能使用
itertools.accumulate()
計算)。例如,相對權重[10,5,30,5]
等同於累積權重[10,15,45,50]
。在內部,相對權重在進行選擇之前會轉換為累積權重,因此提供累積權重可以節省工作。如果既未指定weights 也未指定cum_weights,則以相等的機率進行選擇。如果提供了加權序列,則該序列的長度必須與population 序列的長度相同。它是一個
TypeError
來指定weights 和cum_weights。weights 或cum_weights 可以使用任何與
random()
所回傳的float
值(包括整數、float 和分數,但不包括小數)交互操作 (interoperates) 的數值類型。權重假定為非負數和有限的。如果所有權重均為零,則引發ValueError
。對於給定的種子,具有相等權重的
choices()
函式通常產生與重複呼叫choice()
不同的序列。choices()
使用的演算法使用浮點運算來實現內部一致性和速度。choice()
使用的演算法預設為整數運算和重複選擇,以避免捨入誤差產生的小偏差。在 3.6 版被加入.
在 3.9 版的變更:如果所有權重均為零,則引發
ValueError
。
- random.shuffle(x)¶
將序列x 原地 (in place) 隨機打亂位置。
要打亂一個不可變的序列並回傳一個新的被打亂的 list(串列),請使用
sample(x,k=len(x))
。請注意,即使對於較小的
len(x)
,x 的置換總數也會快速成長到大於大多數隨機數產生器的週期。這意味著長序列的大多數置換永遠無法產生。例如,長度為 2080 的序列是 Mersenne Twister 隨機數產生器週期內可以容納的最大序列。在 3.11 版的變更:移除可選參數random。
- random.sample(population,k,*,counts=None)¶
回傳從母體序列中選擇出的一個包含獨特元素、長度為k 的 list。用於不重置的隨機取樣。
回傳包含母體元素的新清單,同時保持原始母體不變。產生的清單按選擇順序排列,因此所有子切片也會是有效的隨機樣本。這允許抽獎獲獎者(樣本)分為大獎和第二名獲獎者(子切片)。
母體成員不必是hashable 或唯一的。如果母體包含重複項,則每次出現都是樣本中可能出現的一個選擇。
可以一次指定一個重複元素,也可以使用可選的僅關鍵字counts 參數指定重複元素。例如
sample(['red','blue'],counts=[4,2],k=5)
等同於sample(['red','red','red','red','blue','blue'],k=5)
。若要從整數範圍中選擇範例,請使用
range()
物件作為引數。這對於從大型母體中取樣特別快速且節省空間:sample(range(10000000),k=60)
。如果樣本大小大於母體大小,
ValueError
會被引發。在 3.9 版的變更:新增counts 參數。
在 3.11 版的變更:population 必須是一個序列。不再支援將 set 自動轉換為 list。
離散分布¶
以下函式產生離散分佈。
- random.binomialvariate(n=1,p=0.5)¶
二項分佈 (Binomial distribution)。回傳n 個獨立試驗的成功次數,每個試驗的成功機率為p:
數學上等價於:
sum(random()<pforiinrange(n))
試驗次數n 應為非負整數。成功的機率p 應在
0.0<=p<=1.0
之間。結果是0<=X<=n
範圍內的整數。在 3.12 版被加入.
實數分布¶
以下函式產生特定的實數分佈。函式參數以分佈方程中的對應變數命名,如常見的數學實踐所示;這些方程式中的大多數都可以在任意統計文本中找到。
- random.random()¶
回傳範圍
0.0<=X<1.0
中的下一個隨機浮點數
- random.uniform(a,b)¶
回傳一個隨機浮點數N,當
a<=b
時確保 N 為a<=N<=b
、b<a
時確保 N 為b<=N<=a
。終點值
b
可能包含在範圍內,也可能不包含在範圍內,取決於運算式a+(b-a)*random()
中的浮點捨入。
- random.triangular(low,high,mode)¶
回傳一個隨機浮點數N,使得
low<=N<=high
,並在這些邊界之間具有指定的mode。low 和high 邊界預設為零和一。mode 引數預設為邊界之間的中點,從而給出對稱分佈。
- random.betavariate(alpha,beta)¶
Beta(貝它)分布。參數的條件為
alpha>0
和beta>0
。回傳值的範圍介於 0 和 1 之間。
- random.expovariate(lambd=1.0)¶
指數分佈。lambd 為 1.0 除以所需的平均數。它應該不為零。(該參數將被稱為 "lambda",但這是 Python 中的保留字)如果lambd 為正,則回傳值的範圍從 0 到正無窮大;如果lambd 為負,則回傳值的範圍從負無窮大到 0。
在 3.12 版的變更:新增
lambd
的預設值。
- random.gammavariate(alpha,beta)¶
Gamma(伽瑪)分佈。(不是 Gamma 函式!)。形狀 (shape) 和比例 (scale) 參數alpha 和beta 必須具有正值。(根據呼叫習慣不同,部分來源會將 'beta' 定義為比例的倒數)。
Probability distribution function(機率密度函式)是:
x**(alpha-1)*math.exp(-x/beta)pdf(x)=--------------------------------------math.gamma(alpha)*beta**alpha
- random.gauss(mu=0.0,sigma=1.0)¶
常態分佈,也稱為高斯分佈。mu 是平均數,sigma 是標準差。這比下面定義的
normalvariate()
函式快一點。多執行緒須注意:當兩個執行緒同時呼叫此函式時,它們可能會收到相同的傳回值。這可以透過三種方式避免。1)讓每個執行緒使用隨機數產生器的不同實例。2)在所有呼叫周圍加鎖。3)使用較慢但執行緒安全的
normalvariate()
函式代替。在 3.11 版的變更:mu 和sigma 現在有預設引數。
- random.lognormvariate(mu,sigma)¶
對數常態分佈。如果你取此分佈的自然對數,你將獲得一個具有平均數mu 和標準差sigma 的常態分佈。mu 可以為任何值,並且sigma 必須大於零。
- random.normalvariate(mu=0.0,sigma=1.0)¶
常態分佈。mu 是平均數,sigma 是標準差。
在 3.11 版的變更:mu 和sigma 現在有預設引數。
- random.vonmisesvariate(mu,kappa)¶
mu 是平均角度,以 0 到 2*pi 之間的弧度表示,kappa 是濃度參數,必須大於或等於零。如果kappa 等於零,則此分佈在 0 到 2*pi 的範圍內將減小為均勻的隨機角度。
- random.paretovariate(alpha)¶
Pareto distribution(柏拉圖分佈)。alpha 是形狀參數。
- random.weibullvariate(alpha,beta)¶
Weibull distribution(韋伯分佈)。alpha 是比例參數,beta 是形狀參數。
替代產生器¶
- classrandom.Random([seed])¶
實現
random
模組使用的預設偽隨機數產生器的 class。如果
Random
的子類別希望使用不同的基礎產生器,則應該覆寫以下方法:- getstate()¶
在子類別中覆寫此方法以自訂
Random
實例的getstate()
行為。
- setstate(state)¶
在子類別中覆寫此方法以自訂
Random
實例的setstate()
行為。
或者,自訂產生器子類別還可以提供以下方法:
- getrandbits(k)¶
在子類別中覆寫此方法以自訂
Random
實例的getrandbits()
行為。
- classrandom.SystemRandom([seed])¶
使用
os.urandom()
函式從作業系統提供的來源產生隨機數的 Class。並非在所有系統上都可用。不依賴於軟體狀態,並且序列不可復現。因此seed()
方法沒有效果且被忽略。如果呼叫getstate()
和setstate()
方法會引發NotImplementedError
。
關於 Reproducibility(復現性)的注意事項¶
有時,能夠重現偽隨機數產生器給出的序列很有用。只要多執行緒未運行,透過重複使用種子值,同一序列就應該可以被復現。
大多數隨機 module 的演算法和 seed 設定函式在 Python 版本中可能會發生變化,但可以保證兩個方面不會改變:
如果增加了新的 seed 設定函式,則將提供向後相容的播種器 (seeder)。
當相容的播種器被賦予相同的種子時,產生器的
random()
方法將持續產生相同的序列。
範例¶
基礎範例:
>>>random()# 隨機浮點數:0.0 <= x < 1.00.37444887175646646>>>uniform(2.5,10.0)# 隨機浮點數:2.5 <= x <= 10.03.1800146073117523>>>expovariate(1/5)# 到達間隔平均為 5 秒5.148957571865031>>>randrange(10)# 0 到 9(含)的整數7\n>>>randrange(0,101,2)# 0 到 100(含)的偶數整數26\n>>>choice(['win','lose','draw'])# 從序列中隨機選取單一元素'draw'>>>deck='ace two three four'.split()>>>shuffle(deck)# 打亂串列>>>deck['four', 'two', 'ace', 'three']>>>sample([10,20,30,40,50],k=4)# 四個無重置的樣本[40, 10, 50, 30]
模擬:
>>># 六次輪盤旋轉(有重置的加權抽樣)>>>choices(['red','black','green'],[18,18,2],k=6)['red', 'green', 'black', 'black', 'red', 'black']>>># 從一副 52 張撲克牌中無重置發 20 張牌>>># 並確定具有十點值的牌的比例:十、傑克、王后或國王>>>deal=sample(['tens','low cards'],counts=[16,36],k=20)>>>deal.count('tens')/200.15>>># 估計從 7 次旋轉中得到 5 次或更多正面的概率>>># 使用一枚 60% 概率為正面的偏幣>>>sum(binomialvariate(n=7,p=0.6)>=5foriinrange(10_000))/10_0000.4169>>># 計算 5 個樣本的中位數位於中間兩個四分位數的概率>>>deftrial():...return2_500<=sorted(choices(range(10_000),k=5))[2]<7_500...>>>sum(trial()foriinrange(10_000))/10_0000.7958
統計 bootstrapping(自助法)的範例,使用有重置的重新取樣來估計樣本平均數的信賴區間:
# https://www.thoughtco.com/example-of-bootstrapping-3126155fromstatisticsimportfmeanasmeanfromrandomimportchoicesdata=[41,50,29,37,81,30,73,63,20,35,68,22,60,31,95]means=sorted(mean(choices(data,k=len(data)))foriinrange(100))print(f'The sample mean of{mean(data):.1f} has a 90% confidence 'f'interval from{means[5]:.1f} to{means[94]:.1f}')
重新取樣排列測試的範例,來確定觀察到的藥物與安慰劑之間差異的統計學意義或p 值:
# 範例來自 Dennis Shasha 和 Manda Wilson 的 "Statistics is Easy"fromstatisticsimportfmeanasmeanfromrandomimportshuffledrug=[54,73,53,70,73,68,52,65,65]placebo=[54,51,58,44,55,52,42,47,58,46]observed_diff=mean(drug)-mean(placebo)n=10_000count=0combined=drug+placeboforiinrange(n):shuffle(combined)new_diff=mean(combined[:len(drug)])-mean(combined[len(drug):])count+=(new_diff>=observed_diff)print(f'{n} 次標籤重新洗牌僅產生{count} 個差異實例')print(f'至少與觀察到的差異{observed_diff:.1f} 一樣極端。')print(f'單邊 p 值{count/n:.4f} 使我們拒絕無效假設')print(f'即藥物與安慰劑之間沒有差異。')
模擬多伺服器佇列 (queue) 的到達時間與服務交付:
fromheapqimportheapify,heapreplacefromrandomimportexpovariate,gaussfromstatisticsimportmean,quantilesaverage_arrival_interval=5.6average_service_time=15.0stdev_service_time=3.5num_servers=3waits=[]arrival_time=0.0servers=[0.0]*num_servers# 每個伺服器可用的時間heapify(servers)foriinrange(1_000_000):arrival_time+=expovariate(1.0/average_arrival_interval)next_server_available=servers[0]wait=max(0.0,next_server_available-arrival_time)waits.append(wait)service_duration=max(0.0,gauss(average_service_time,stdev_service_time))service_completed=arrival_time+wait+service_durationheapreplace(servers,service_completed)print(f'平均等待時間:{mean(waits):.1f} 最大等待時間:{max(waits):.1f}')print('四分位數:',[round(q,1)forqinquantiles(waits)])
也參考
Statistics for Hackers 是由Jake Vanderplas 製作的教學影片,僅使用幾個基本概念(包括模擬、取樣、洗牌、交叉驗證)進行統計分析。
Economics Simulation是由Peter Norvig 對市場進行的模擬,顯示了該模組提供的許多工具和分佈(高斯、均勻、樣本、 beta 變數、選擇,三角形、隨機數)的有效使用。
機率的具體介紹(使用Python)為Peter Norvig 的教學課程,涵蓋了機率理論的基礎知識與如何模擬以及使用 Python 執行數據分析。
使用方案¶
這些使用方案展示了如何有效地從itertools
模組的組合疊代器 (combinatoric iterators) 中進行隨機選擇:
defrandom_product(*args,repeat=1):"從 itertools.product(*args, **kwds) 中隨機選擇"pools=[tuple(pool)forpoolinargs]*repeatreturntuple(map(random.choice,pools))defrandom_permutation(iterable,r=None):"從 itertools.permutations(iterable, r) 中隨機選擇"pool=tuple(iterable)r=len(pool)ifrisNoneelserreturntuple(random.sample(pool,r))defrandom_combination(iterable,r):"從 itertools.combinations(iterable, r) 中隨機選擇"pool=tuple(iterable)n=len(pool)indices=sorted(random.sample(range(n),r))returntuple(pool[i]foriinindices)defrandom_combination_with_replacement(iterable,r):"有重置地選擇 r 個元素。結果按可疊代物件排序。"# 結果將在 set(itertools.combinations_with_replacement(iterable, r)) 中pool=tuple(iterable)n=len(pool)indices=sorted(random.choices(range(n),k=r))returntuple(pool[i]foriinindices)
預設的random()
回傳0.0 ≤ x < 1.0 範圍內 2⁻⁵³ 的倍數。所有數字都是均勻分佈的,並且可以完全表示為 Python float。但是,該間隔中的許多其他可表示的 float 不是可能的選擇。 例如0.05954861408025609
不是 2⁻⁵³ 的整數倍。
以下範例採用不同的方法。間隔中的所有 float 都是可能的選擇。尾數來自2⁵² ≤ 尾數 < 2⁵³ 範圍內的整數均勻分佈。指數來自幾何分佈,其中小於-53 的指數的出現頻率是下一個較大指數的一半。
fromrandomimportRandomfrommathimportldexpclassFullRandom(Random):defrandom(self):mantissa=0x10_0000_0000_0000|self.getrandbits(52)exponent=-53x=0whilenotx:x=self.getrandbits(32)exponent+=x.bit_length()-32returnldexp(mantissa,exponent)
Class 中的所有實數分佈都將使用新方法:
>>>fr=FullRandom()>>>fr.random()0.05954861408025609>>>fr.expovariate(0.25)8.87925541791544
該範例在概念上等效於一種演算法,該演算法從0.0 ≤ x < 1.0 範圍內 2⁻¹⁰⁷⁴ 的所有倍數中進行選擇。這些數字都是均勻分佈的,但大多數必須向下捨入到最接近的可表示的 Python float。(2⁻¹⁰⁷⁴ 是最小為正的非正規化 float,等於math.ulp(0.0)
)
命令列用法¶
在 3.13 版被加入.
random
模組可以從命令列執行。
python-mrandom[-h][-cCHOICE[CHOICE...]|-iN|-fN][input...]
接受以下選項:
- -h,--help¶
顯示幫助訊息並退出。
如果未給定選項,則輸出取決於輸入:
命令列範例¶
以下是random
命令列介面的一些範例:
$# 隨機選擇一個$python-mrandomeggbaconsausagespam"Lobster Thermidor aux crevettes with a Mornay sauce"Lobster Thermidor aux crevettes with a Mornay sauce$# 隨機整數$python-mrandom66$# 隨機浮點數$python-mrandom1.81.7080016272295635$# 使用明確的引數$python-mrandom--choiceeggbaconsausagespam"Lobster Thermidor aux crevettes with a Mornay sauce"egg$python-mrandom--integer63$python-mrandom--float1.81.5666339105010318$python-mrandom--integer65$python-mrandom--float63.1942323316565915