Python中的隨機取樣和概率分佈(一)

orion發表於2021-12-05

Python(包括其包Numpy)中包含了了許多概率演算法,包括基礎的隨機取樣以及許多經典的概率分佈生成。我們這個系列介紹幾個在機器學習中常用的概率函式。先來看最基礎的功能——隨機取樣。

1. random.choice

如果我們只需要從序列裡採一個樣本(所有樣本等概率被採),只需要使用random.choice即可:

import random
res1 = random.choice([0, 1, 2, 3, 4])
print(res1) # 3

2. random.choices(有放回)

當然,很多時候我們不只需要採一個數,而且我們需要設定序列中每一項被採的概率不同。此時我們可以採用random.random.choices函式, 該函式用於有放回的(即一個資料項可以被重複採多次)對一個序列進行取樣。其函式原型如下:


random.choices(population, weights=None, *, cum_weights=None, k=1)

population: 欲取樣的序列
weights: 每個樣本被賦予的權重(又稱相對權重),決定每個樣本被採的概率,如[10, 0, 30, 60, 0]
cum_weights: 累積權重,相對權重[10, 0, 30, 60, 0]相當於累積權重[10, 10, 40, 100, 100]


我們從[0, 1, 2, 3, 4]中按照相對權重取樣3個樣本如下:

res2 = random.choices([0, 1, 2, 3, 4], weights=[10, 0, 30, 60, 0], k=3)
# 注意population不是關鍵字引數,在函式呼叫時不能寫成population=[0,1,2,3,4]來傳參
# 關於關鍵字引數和位置引數,可以參看我的部落格《Python技法2:函式引數的進階用法》https://www.cnblogs.com/orion-orion/p/15647408.html
print(res2) # [3, 3, 2]

[0, 1, 2, 3, 4]中按照累積權重取樣3和樣本如下:

res3 = random.choices([0, 1, 2, 3, 4], cum_weights=[10, 10, 40, 100, 100], k=3)
print(res3) # [0, 3, 3]

注意,相對權重weights和累計權重cum_weights不能同時傳入,否則會報TypeError異常'Cannot specify both weights and cumulative weights'

3. numpy.sample(無放回)

random.sample是無放回,如果我們需要無放回取樣(即每一項只能採一次),那我們需要使用random.sample。需要注意的是,如果使用該函式,將無法定義樣本權重。該函式原型如下:


random.sample(population, k, *, counts=None)¶

population: 欲取樣的序列
k: 取樣元素個數
counts: 用於population是可重複集合的情況,定義集合元素的重複次數。sample(['red', 'blue'], counts=[4, 2], k=5)等價於sample(['red', 'red', 'red', 'red', 'blue', 'blue'], k=5)


我們無放回地對序列[0, 1, 2, 3, 4]取樣3次如下:

res3 = random.sample([0, 1, 2, 3, 4], k=3)
print(res3) # [3, 2, 1]

無放回地對可重複集合[0, 1, 1, 2, 2, 3, 3, 4]取樣3次如下:

res4 = random.sample([0, 1, 2, 3, 4], k=3, counts=[1, 2, 2, 2, 1])
print(res4) # [3, 2, 2]

如果counts長度和population序列長度不一致,會丟擲異常ValueError:"The number of counts does not match the population"

4.rng.choicesrng.sample

還有一種有放回取樣實現方法是我在論文[1]的程式碼[2]中學習到的。即先定義一個隨機數生成器,再呼叫隨機數生成器的choices方法或sample方法,其使用方法和random.choice/random.sample函式相同。

rng_seed = 1234
rng = random.Random(rng_seed)
res5 = rng.choices(
     population=[0,1,2,3,4],
     weights=[0.1, 0, 0.3, 0.6, 0],
     k=3,
)
print(res5) # [3, 3, 0]

res6 = rng.sample(
     population=[0, 1, 2, 3, 4],
     k=3,
)
print(res6) # [4, 0, 2]

這兩個函式在論文[1]的實現程式碼[2]中用來隨機選擇任務節點client

 def sample_clients(self):
        """
        sample a list of clients without repetition

        """
        rng_seed = (seed if (seed is not None and seed >= 0) else int(time.time()))
        self.rng = random.Random(rng_seed)

        if self.sample_with_replacement:
            self.sampled_clients = \
                self.rng.choices(
                    population=self.clients,
                    weights=self.clients_weights,
                    k=self.n_clients_per_round,
                )
        else:
            self.sampled_clients = self.rng.sample(self.clients, k=self.n_clients_per_round)

5. numpy.random.choices

從序列中按照權重分佈取樣也可以採用numpy.random.choice實現。其函式原型如下:

random.choice(a, size=None, replace=True, p=None)

a: 1-D array-like or int   如果是1-D array-like,那麼樣本會從其元素中抽取。如果是int,那麼樣本會從np.arange(a)中抽取;
size: int or tuple of ints, optional   為輸出形狀大小,如果給定形狀為\((m, n, k)\),那麼\(m\times n\times k\)的樣本會從中抽取。預設為None,即返回一個單一標量。
replace: boolean, optional   表示取樣是又放回的還是無放回的。若replace=True,則為又放回取樣(一個值可以被採多次),否則是無放回的(一個值只能被採一次)。
p: 1-D array-like, optional   表示a中每一項被採的概率。如果沒有給定,則我們假定a中各項被採的概率服從均勻分佈(即每一項被採的概率相同)。

[0,1,2,3,4,5]中重複/不重複取樣3次如下:

import numpy as np
res1 = np.random.choice(5, 3, replace=True)
print(res1) # [1 1 4]

res2 = np.random.choice(5, 3, replace=False)
print(res2) # [2 1 4]

同樣是[0,1,2,3,4,5]中重複/不重複取樣3次,現在來看我們為每個樣本設定不同概率的情況:

res3 = np.random.choice(5, 3, p=[0.1, 0, 0.3, 0.6, 0])
print(res3)  # [2 3 3]

res4 = np.random.choice(5, 3, replace=False, p=[0.1, 0, 0.3, 0.6, 0])
print(res4) # [3 2 0]

參考文獻

相關文章