我正在遵循R和Python中“进行贝叶斯数据分析”中的练习。
我想找到一种使用恒定空间的快速蒙特卡洛模拟方法。
以下问题很小,但是可以很好地测试不同方法:
ex 4.3
确定从乱序的Pinoch牌中抽出10的确切概率。 (在Pinochle牌组中,有48张牌。共有6个值:9、10,Jack,Queen,King和Ace。在标准的四套西装中,每个值都有两个副本:红心,钻石,球杆,黑桃)
(A)获得10的概率是多少?
当然,答案是1/6。
我能找到的最快解决方案(与R的速度相当)是使用np.random.choice
生成大量的纸牌抽奖,然后应用Counter
。我不喜欢不必要地创建数组的想法,因此我尝试使用字典和for循环,一次绘制一张卡,并增加该卡类型的数量。令我惊讶的是,它慢得多!
下面是我测试的3种方法的完整代码。 _是否有一种方法可以像method1()一样执行,但是使用恒定空间?
Python代码:(Google Colab link)
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))
答案 0 :(得分:2)
这里是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可以很好地处理此类数据。为此,我们使用np.unique
。
我们使用np.bincount
代替计数步骤。再次,它可以很好地与数字数据配合使用,而在此方法开始时,我们确实通过数字转换获得了它。
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)
答案 1 :(得分:0)
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