pab*_*edo 22 python performance matlab numpy backpropagation
我正在计算backpropagation稀疏自动编码器的算法.我已经在python中使用numpy和实现了它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:我已经根据响应的一些想法引入了代码中的更改.现在表现不佳:0.65对比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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
unu*_*tbu 45
说"Matlab总是比NumPy快"反之亦然,反之亦然.他们的表现往往具有可比性 使用NumPy时,为了获得良好的性能,您必须记住NumPy的速度来自于调用用C/C++/Fortran编写的底层函数.将这些函数应用于整个数组时,它表现良好.通常,当您在Python循环中的较小数组或标量上调用NumPy函数时,性能会较差.
你问的Python循环有什么问题?通过Python循环的每次迭代都是对next方法的调用.[]索引的每次使用都是对__getitem__方法的调用
.每一个+=都是打电话给__iadd__.每个虚线属性查找(例如in np.dot)都涉及函数调用.这些函数调用加剧了速度的显着阻碍.这些钩子赋予Python表达能力 - 字符串的索引意味着不同于例如索引的索引.相同的语法,不同的含义.通过给对象提供不同的__getitem__方法来实现魔术.
但这种富有表现力的力量需要付出代价.因此,当您不需要所有动态表达性时,为了获得更好的性能,请尝试将自己限制为对整个数组的NumPy函数调用.
所以,删除for循环; 尽可能使用"矢量化"方程.例如,而不是
for i in range(m):
delta3 = -(x[i,:]-a3[i,:])*a3[i,:]* (1 - a3[i,:])
Run Code Online (Sandbox Code Playgroud)
你可以一次计算delta3每一个i:
delta3 = -(x-a3)*a3*(1-a3)
Run Code Online (Sandbox Code Playgroud)
而在for-loop delta3矢量中,使用矢量化方程delta3是矩阵.
其中的一些计算for-loop不依赖于i因此应该在循环之外被提升.例如,sum2看起来像一个常数:
sum2 = sparse.beta*(-float(sparse.rho)/rhoest + float(1.0 - sparse.rho) / (1.0 - rhoest) )
Run Code Online (Sandbox Code Playgroud)
这是一个可运行的示例,其中包含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
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
提示:请注意,我在评论中留下了所有中间数组的形状.知道数组的形状有助于我理解你的代码在做什么.数组的形状可以帮助指导您使用正确的NumPy函数.或者至少,注意形状可以帮助您了解操作是否合理.例如,当你计算时
np.dot(A, B)
Run Code Online (Sandbox Code Playgroud)
和A.shape = (n, m)和B.shape = (m, p),然后np.dot(A, B)将形状的阵列(n, p).
它可以帮助以C_CONTIGUOUS顺序构建数组(至少,如果使用的话np.dot).这样做可能会加快3倍的速度:
下面,x是一样的xf,除了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))
Run Code Online (Sandbox Code Playgroud)
%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
Run Code Online (Sandbox Code Playgroud)
关于Python中的基准测试:
使用time.time()调用对中的差异来对Python中的代码速度进行基准测试可能会产生误导.您需要多次重复测量.最好禁用自动垃圾收集器.测量大跨度时间(例如至少10秒重复)也很重要,以避免由于时钟定时器中的分辨率差而导致的错误并降低time.time呼叫开销的重要性.Python不是自己编写所有代码,而是为您提供timeit模块.我基本上用它来计算代码片段,除了我为了方便我通过IPython终端调用它.
我不确定这是否会影响您的基准测试,但请注意它可能会有所作为.在我链接的问题中,根据time.time两段代码的不同,因为使用timeit的基准测试表明,这些代码在基本相同的时间内运行.
| 归档时间: |
|
| 查看次数: |
22644 次 |
| 最近记录: |