快速蒙特卡洛模拟与 numpy?



我正在学习R和Python中的"做贝叶斯数据分析"中的练习。

我想找到一种使用恒定空间进行蒙特卡洛模拟的快速方法

下面的问题微不足道,但可以作为不同方法的良好测试:

例 4.3

确定从洗牌的皮诺奇尔牌组中抽出 10 的确切概率。(在皮诺奇尔牌组中,有48张牌。有六个值:9、10、杰克、女王、国王、王牌。在标准的四种花色中,每个值都有两份副本:心形、钻石、棍棒、黑桃。

(A)得到10分的概率是多少?

答案当然是1/6。

我能找到的最快的解决方案(与 R 的速度相当)是使用np.random.choice生成大量抽卡数组,然后应用Counter。我不喜欢不必要地创建数组的想法,所以我尝试使用字典和 for 循环,一次绘制一张卡片并增加该类型卡片的计数。令我惊讶的是,它的速度要慢得多!

我测试的 3 种方法的完整代码如下。 _Is有一种方法可以做到这一点,它的性能与method1()一样,但使用常量空间?

Python代码:(Google Colab链接)

deck = [c for c in ['9','10','Jack','Queen','King','Ace'] for _ in range(8)]
num_draws = 1000000
def method1():
draws = np.random.choice(deck, size=num_draws, replace=True)
df = pd.DataFrame([Counter(draws)])/num_draws
print(df)

def method2():
card_counts = defaultdict(int)
for _ in range(num_draws):
card_counts[np.random.choice(deck, replace=True)] += 1
df = pd.DataFrame([card_counts])/num_draws
print(df)

def method3():
card_counts = defaultdict(int)
for _ in range(num_draws):
card_counts[deck[random.randint(0, len(deck)-1)]] += 1
df = pd.DataFrame([card_counts])/num_draws
print(df)

Python timeit() 结果:

方法1:1.2997

方法2:23.0626

方法3: 5.5859

R 代码:

card = sample(deck, numDraws, replace=TRUE)
print(as.data.frame(table(card)/numDraws))

这是一个带有np.unique+np.bincount-

def unique():    
unq,ids = np.unique(deck, return_inverse=True)
all_ids = np.random.choice(ids, size=num_draws, replace=True)
ar = np.bincount(all_ids)/num_draws
return pd.DataFrame(ar[None], columns=unq)

NumPy在这里如何提供帮助?

这里有两个主要的改进可以帮助我们:

  1. 我们将字符串数据转换为数字。NumPy可以很好地处理此类数据。为此,我们正在使用np.unique.

  2. 我们使用np.bincount来代替计数步骤。同样,它适用于数值数据,我们确实从此方法开始时完成的数值转换中获得了它。

  3. NumPy通常适用于大数据,这里就是这种情况。


给定样本数据集与最快method1进行比较的时序 -

In [177]: %timeit method1()
328 ms ± 16.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
In [178]: %timeit unique()
12.4 ms ± 265 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Numpy 通过在其数字引擎中运行 C 代码来实现效率。Python很方便,但它比C慢几个数量级。

在 Numpy 和其他高性能 Python 库中,Python 代码主要由胶水代码组成,准备要调度的任务。由于存在开销,因此一次绘制大量样本要快得多。

请记住,为 Numpy 提供 100 万个元素的缓冲区仍然是恒定空间。然后,您可以通过循环采样 10 亿次。

这种额外的内存分配通常不是问题。如果您必须不惜一切代价避免使用内存,同时仍然从 Numpy 中获得性能优势,您可以尝试使用 Numba 或 Cython 来加速它。

from numba import jit
@jit(nopython=True)
def method4():
card_counts = np.zeros(6)
for _ in range(num_draws):
card_counts[np.random.randint(0, 6)] += 1
return card_counts/num_draws

最新更新