用numpy快速进行蒙特卡洛仿真?

时间:2019-06-08 16:47:01

标签: python pandas numpy

我正在遵循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))

2 个答案:

答案 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在这里有什么帮助?

有两项重大改进正在帮助我们:

  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)

答案 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