【问题标题】:Matching speed of R apply in PythonR应用在Python中的匹配速度
【发布时间】:2017-10-22 21:19:03
【问题描述】:

我想有效地将复杂的函数应用于 Python 中的矩阵行(编辑:Python 3)。在 R 中,这是 apply 函数和它的近亲,它工作得很快。

在 Python 中,我知道这可以通过几种方式完成。列表理解、numpy.apply_along_axis、panas.dataframe.apply。

在我的编码中,这些 Python 方法非常慢。我应该使用另一种方法吗?或者也许我对这些 Python 方法的实现不正确?

这是一个例子。数学取自概率回归模型。要明确我的目标不是执行概率回归,我对一种有效的应用方法很感兴趣。

在 R 中:

> n = 100000
> p = 7
> x = matrix(rnorm(700000, 0 , 2), ncol = 7)
> beta = rep(1, p)

> start <- Sys.time()
> test <- apply(x, 1, function(t)(dnorm(sum(t*beta))*sum(t*beta)/pnorm(sum(t*beta))) )
> end <- Sys.time()
> print(end - start)
Time difference of 0.6112201 secs

在 Python 中通过理解:

import numpy as np
from scipy.stats import norm
import time

n = 100000
p = 7
x = norm.rvs(0, 2, n * p)
x = x.reshape( (n , p) )
beta = np.ones(p)

start = time.time()
test = [ 
norm.pdf(sum(x[i,]*beta))*sum(x[i,]*beta)/norm.cdf(sum(x[i,]*beta)) 
for i in range(100000) ]
end = time.time()
print (end - start)
23.316735982894897

在 Python 中通过 pandas.dataframe.apply:

frame = DataFrame(x)
f = lambda t: norm.pdf(sum(t))*sum(t)/norm.cdf(sum(t))
start = time.time()
test = frame.apply(f, axis = 1)
end = time.time()
print(end - start)
34.39404106140137

this 问题中,最受好评的回答指出 apply_along_axis 不是为了速度。所以我不包括这种方法。

同样,我对快速执行这些计算很感兴趣。非常感谢您的帮助!

【问题讨论】:

  • python 2 还是 python 3?在 python 2 中,避免使用range(100000),因为它必须分配列表,请改用xrange。在您的特殊情况下,避免两者都支持[norm.pdf(sum(x_*beta))*sum(x_*beta)/norm.cdf(sum(x_*beta)) for x_ in x.transpose()]。你真正想要的可能是用 numpy 构造一个 ufunc。
  • 你为什么要单点评估pdf、cdf?
  • 我想推荐cupy?所需的 CUDA 库和支持 CUDA 的 GPU...
  • @percusse 这个表达式是概率回归模型的可能性梯度的一部分。我知道库可以为我执行概率回归,但这个表达式说明了我想要做的计算类型。
  • 另请参阅my Python performance answer with quick benchmarks 了解相关问题。

标签: python r numpy list-comprehension apply


【解决方案1】:

列表推导使用效率非常低的 Python 级别循环。您应该修改代码以利用 numpy 矢量化。如果您更改 time.time() 调用之间的内容

xbeta = np.sum(x * beta, axis=1)
test = norm.pdf(xbeta) * xbeta / norm.cdf(xbeta)

您会看到巨大的差异。对于我的机器,它在 0.02 秒内完成。 为了让您安心,我已经根据您的列表理解对其进行了测试,它们都给出了相同的结果。

xbeta 是你在计算中浪费了很多次的东西。通过沿第二轴求和,我们将其折叠为一维数组,即 100000 行。现在所有的计算都处理一维数组,所以我们让 numpy 处理剩下的事情。

【讨论】:

  • 实际上,R 中的 apply 系列是底层循环,可以说没有矢量化:Is the “*apply” family really not vectorized?.
  • @Parfait 非常感谢您的评论,因为我不知道 R。我只是将它的行为比作 numpy。那么,我的理解是否正确,apply 使用 R 级循环,但它们恰好比纯 Python 更有效?
  • 见上面的链接,尤其是OP's own answer apply 是简单的隐藏循环,在 C 级运行循环但调用 R 级函数(R like Python 是写在C 和一些 Fortran 的顶部)。 Vectorzied 被松散地定义为仅机器级别的计算。
  • @Parfait 是的,我的意思是专门针对 apply,而不是 *apply 系列。
【解决方案2】:

这似乎是你想要的:

y = np.dot(x, beta)
test2 = norm.pdf(y) * y / norm.cdf(y)

# let's compare it to the expected output:
np.allclose(test2, np.array(test))
True

根据 ipython,这会计算“测试”列表中的所有 100000 个值(在数值容差范围内),但运行时间约为 11.5 毫秒:

%time y = np.dot(x, beta); test2 = norm.pdf(y) * y / norm.cdf(y)
CPU times: user 10 ms, sys: 0 ns, total: 10 ms
Wall time: 15.2 ms

这通过以下方式改进了您的版本:

  1. 消除常见的sum(x[i,]*beta) 表达式
  2. 将循环从 i=0 到 100000 向量化为点积。

在两者中,第二个更为重要。

我还注意到您上面的代码使用了sum,这是一个 python 内置函数,可以对任何迭代器求和。你几乎肯定应该使用np.sum,它是专门用于 numpy 数组的 numpy 矢量化版本。我不得不提一下这一点,因为一旦我以“批处理”形式重写了您的代码,sum 就隐含在点积中,因此np.sum 不会出现在最终版本中。

【讨论】:

  • 嗯...比较你和 OP 的 numpy 和 pandas,np.all() 返回 False
  • @Parfait,OP 以两种不同的方式解决了同样的问题,只是用 pandas 进行试验,看看它是否更快(事实并非如此。)根本没有理由使用 pandas,所以我们应该忽略它部分。我将我的代码计算的test2 与上面几行python 列表理解计算的第一个test 列表进行比较。
  • 正确。我比较了您的 test2 和 OP 的 testnp.all() 不返回 True
  • 是吗?它在我的机器上完全相等,但我想它可能对浮点舍入很敏感。 np.allclose(test2, np.array(test)) 在你的机器上说什么?或者不知道np.mean( np.abs(test2-np.array(test)) ) 有多大?
  • 现在我得到 Truenp.allclose() 的差别非常小:3.60353489474e-14。我在 ubuntu 64 位/python 3.5/numpy 1.12.1
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-07
  • 1970-01-01
  • 1970-01-01
  • 2014-12-04
相关资源
最近更新 更多