python - numpy 和 matlab 之间的性能差异

标签 python performance matlab numpy backpropagation

我正在计算 backpropagation稀疏自编码器的算法。我已经使用 numpy 在 python 中实现了它并在 matlab .代码几乎相同,但性能却大不相同。 matlab 完成任务所需的时间为 0.252454 秒,而 numpy 为 0.973672151566,几乎是四倍。稍后我将在最小化问题中多次调用此代码,因此这种差异会导致实现之间出现几分钟的延迟。这是正常行为吗?我怎样才能提高 numpy 的性能?

Numpy 实现:

Sparse.rho 是一个调整参数,sparse.nodes 是隐藏层的节点数(25),sparse.input(64)是输入层的节点数,theta1 和 theta2 是第一个和第二层分别具有尺寸25x64和64x25,m等于10000,rhoest的尺寸为(25,),x的尺寸为10000x64,a3 10000x64和a2 10000x25。
UPDATE : 我在响应的一些想法之后对代码进行了更改。性能现在是 numpy: 0.65 vs matlab: 0.25。

partial_j1 = np.zeros(sparse.theta1.shape)
partial_j2 = np.zeros(sparse.theta2.shape)
partial_b1 = np.zeros(sparse.b1.shape)
partial_b2 = np.zeros(sparse.b2.shape)
t = time.time()

delta3t = (-(x-a3)*a3*(1-a3)).T

for i in range(m):

    delta3 = delta3t[:,i:(i+1)]
    sum1 =  np.dot(sparse.theta2.T,delta3)
    delta2 = ( sum1 + sum2 ) * a2[i:(i+1),:].T* (1 - a2[i:(i+1),:].T)
    partial_j1 += np.dot(delta2, a1[i:(i+1),:])
    partial_j2 += np.dot(delta3, a2[i:(i+1),:])
    partial_b1 += delta2
    partial_b2 += delta3

print "Backprop time:", time.time() -t

MATLAB 实现:
tic
for i = 1:m

    delta3 = -(data(i,:)-a3(i,:)).*a3(i,:).*(1 - a3(i,:));
    delta3 = delta3.';
    sum1 =  W2.'*delta3;
    sum2 = beta*(-sparsityParam./rhoest + (1 - sparsityParam) ./ (1.0 - rhoest) );
    delta2 = ( sum1 + sum2 ) .* a2(i,:).' .* (1 - a2(i,:).');
    W1grad = W1grad + delta2* a1(i,:);
    W2grad = W2grad + delta3* a2(i,:);
    b1grad = b1grad + delta2;
    b2grad = b2grad + delta3;
end
toc

最佳答案

说“Matlab 总是比 NumPy 快”或副
反之。通常,它们的性能具有可比性。使用 NumPy 时,要搞定
性能你必须记住,NumPy 的速度来自于调用
用 C/C++/Fortran 编写的底层函数。当你申请时它表现良好
这些函数到整个数组。通常,在 Python 循环中对较小数组或标量调用这些 NumPy 函数时,性能会降低。

你问的 Python 循环有什么问题?通过 Python 循环的每次迭代都是
调用 next方法。每次使用[]索引是调用__getitem__方法。每+=是调用 __iadd__ .每个虚线属性
查找(例如在 np.dot 中)涉及函数调用。那些函数调用
加起来对速度有很大的阻碍。这些钩子(Hook)给 Python
表达能力——字符串索引与索引不同
以字典为例。相同的语法,不同的含义。魔法是通过赋予不同的对象来实现的 __getitem__方法。

但这种表现力是以速度为代价的。所以当你不需要所有
那种动态的表现力,为了获得更好的表现,试着把自己限制在
NumPy 函数调用整个数组。

因此,删除 for 循环;尽可能使用“矢量化”方程。例如,代替

for i in range(m):
    delta3 = -(x[i,:]-a3[i,:])*a3[i,:]* (1 - a3[i,:])    

你可以计算delta3每个i一次全部:
delta3 = -(x-a3)*a3*(1-a3)

而在 for-loop delta3是一个向量,使用向量化方程 delta3是一个矩阵。
for-loop 中的一些计算不依赖 i因此应该被提升到循环之外。例如,sum2看起来像一个常数:
sum2 = sparse.beta*(-float(sparse.rho)/rhoest + float(1.0 - sparse.rho) / (1.0 - rhoest) )

这是一个可运行示例,其中包含代码 ( alt ) 的替代实现 ( orig )。

我的 timeit 基准显示 速度提高 6.8 倍 :
In [52]: %timeit orig()
1 loops, best of 3: 495 ms per loop

In [53]: %timeit alt()
10 loops, best of 3: 72.6 ms per loop
import numpy as np


class Bunch(object):
    """ http://code.activestate.com/recipes/52308 """
    def __init__(self, **kwds):
        self.__dict__.update(kwds)

m, n, p = 10 ** 4, 64, 25

sparse = Bunch(
    theta1=np.random.random((p, n)),
    theta2=np.random.random((n, p)),
    b1=np.random.random((p, 1)),
    b2=np.random.random((n, 1)),
)

x = np.random.random((m, n))
a3 = np.random.random((m, n))
a2 = np.random.random((m, p))
a1 = np.random.random((m, n))
sum2 = np.random.random((p, ))
sum2 = sum2[:, np.newaxis]

def orig():
    partial_j1 = np.zeros(sparse.theta1.shape)
    partial_j2 = np.zeros(sparse.theta2.shape)
    partial_b1 = np.zeros(sparse.b1.shape)
    partial_b2 = np.zeros(sparse.b2.shape)
    delta3t = (-(x - a3) * a3 * (1 - a3)).T
    for i in range(m):
        delta3 = delta3t[:, i:(i + 1)]
        sum1 = np.dot(sparse.theta2.T, delta3)
        delta2 = (sum1 + sum2) * a2[i:(i + 1), :].T * (1 - a2[i:(i + 1), :].T)
        partial_j1 += np.dot(delta2, a1[i:(i + 1), :])
        partial_j2 += np.dot(delta3, a2[i:(i + 1), :])
        partial_b1 += delta2
        partial_b2 += delta3
        # delta3: (64, 1)
        # sum1: (25, 1)
        # delta2: (25, 1)
        # a1[i:(i+1),:]: (1, 64)
        # partial_j1: (25, 64)
        # partial_j2: (64, 25)
        # partial_b1: (25, 1)
        # partial_b2: (64, 1)
        # a2[i:(i+1),:]: (1, 25)
    return partial_j1, partial_j2, partial_b1, partial_b2


def alt():
    delta3 = (-(x - a3) * a3 * (1 - a3)).T
    sum1 = np.dot(sparse.theta2.T, delta3)
    delta2 = (sum1 + sum2) * a2.T * (1 - a2.T)
    # delta3: (64, 10000)
    # sum1: (25, 10000)
    # delta2: (25, 10000)
    # a1: (10000, 64)
    # a2: (10000, 25)
    partial_j1 = np.dot(delta2, a1)
    partial_j2 = np.dot(delta3, a2)
    partial_b1 = delta2.sum(axis=1)
    partial_b2 = delta3.sum(axis=1)
    return partial_j1, partial_j2, partial_b1, partial_b2

answer = orig()
result = alt()
for a, r in zip(answer, result):
    try:
        assert np.allclose(np.squeeze(a), r)
    except AssertionError:
        print(a.shape)
        print(r.shape)
        raise

提示:请注意,我在注释中留下了所有中间数组的形状。了解数组的形状有助于我了解您的代码在做什么。数组的形状可以帮助指导您使用正确的 NumPy 函数。或者至少,注意形状可以帮助您了解操作是否合理。例如,当您计算
np.dot(A, B)

A.shape = (n, m)B.shape = (m, p) ,然后 np.dot(A, B)将是一个形状数组 (n, p) .

它可以帮助以 C_CONTIGUOUS 顺序构建数组(至少,如果使用 np.dot )。这样做可能会提高 3 倍的速度:

下面,xxf 相同除了 x是 C_CONTIGUOUS 和xf是 F_CONTIGUOUS -- 与 y 的关系相同和 yf .
import numpy as np

m, n, p = 10 ** 4, 64, 25
x = np.random.random((n, m))
xf = np.asarray(x, order='F')

y = np.random.random((m, n))
yf = np.asarray(y, order='F')

assert np.allclose(x, xf)
assert np.allclose(y, yf)
assert np.allclose(np.dot(x, y), np.dot(xf, y))
assert np.allclose(np.dot(x, y), np.dot(xf, yf))
%timeit基准测试显示速度的差异:
In [50]: %timeit np.dot(x, y)
100 loops, best of 3: 12.9 ms per loop

In [51]: %timeit np.dot(xf, y)
10 loops, best of 3: 27.7 ms per loop

In [56]: %timeit np.dot(x, yf)
10 loops, best of 3: 21.8 ms per loop

In [53]: %timeit np.dot(xf, yf)
10 loops, best of 3: 33.3 ms per loop

关于 Python 中的基准测试:

It can be misleading使用成对的差异time.time()调用以对 Python 中的代码速度进行基准测试。
您需要多次重复测量。最好禁用自动垃圾收集器。测量大的时间跨度(例如至少 10 秒的重复)以避免由于时钟计时器分辨率差而导致的错误并降低 time.time 的重要性也很重要。调用开销。 Python 为您提供了 timeit module,而不是自己编写所有代码。 .我基本上是用它来为代码段计时,只是我通过 IPython terminal 调用它。为了方便。

我不确定这是否会影响您的基准测试,但请注意它可能会有所作为。在 question I linked to ,根据 time.time两段代码相差 1.7 倍,而基准测试使用 timeit显示代码段在基本相同的时间内运行。

关于python - numpy 和 matlab 之间的性能差异,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18516605/

相关文章:

python - 从 pandas DataFrame 中的日期时间列中提取月份

performance - 字符串连接检查算法的运行时

ruby-on-rails - 分析 ruby​​/ruby on rails 应用程序

python - NumPy:矩阵点积与 MATLAB 不兼容

c - mex 文件中的段错误

python - Python 编辑器中的单元格模式

python - 你将如何在 python 中实现 ant-style patternsets 来选择文件组?

matlab - MATLAB 中是否有修正线性函数 max(0,x) 的优化矢量化函数?

python - 使用 pip 将 Python 包安装到不同的目录中?

java - C 中变量赋值需要多少个时钟周期?