2016-02-02 14 views
6

我目前正在研究机器学习项目,其中 - 给定数据矩阵Z和向量​​rho - 我必须计算值和斜率logistic loss functionrho。计算涉及基本的矩阵向量乘法和对数/对数运算,避免数值溢出的技巧(在这个previous post中描述)。加速Python中的矩阵向​​量乘法和指数运算,可能通过调用C/C++

我目前正在使用NumPy在Python中执行此操作,如下所示(作为参考,此代码以0.2s运行)。虽然这很好,但我想加快速度,因为我在代码中多次调用该函数(它代表了我的项目中涉及的计算的90%以上)。

我正在寻找任何方法来改善此代码的运行时间而无需并行化(即只有1个CPU)。我很高兴使用Python中的任何公开包,或者调用C或C++(因为我听说这可以将运行时间提高一个数量级)。预处理数据矩阵Z也是可以的。可能更好的计算被利用有些东西是矢量rho通常是稀疏的(含50%左右的条目= 0),并通常有更多的行比列(在大多数情况下n_cols <= 100


import time 
import numpy as np 

np.__config__.show() #make sure BLAS/LAPACK is being used 
np.random.seed(seed = 0) 

#initialize data matrix X and label vector Y 
n_rows, n_cols = 1e6, 100 
X = np.random.random(size=(n_rows, n_cols)) 
Y = np.random.randint(low=0, high=2, size=(n_rows, 1)) 
Y[Y==0] = -1 
Z = X*Y # all operations are carried out on Z 

def compute_logistic_loss_value_and_slope(rho, Z): 
    #compute the value and slope of the logistic loss function in a way that is numerically stable 
    #loss_value: (1 x 1) scalar = 1/n_rows * sum(log(1 .+ exp(-Z*rho)) 
    #loss_slope: (n_cols x 1) vector = 1/n_rows * sum(-Z*rho ./ (1+exp(-Z*rho)) 
    #see also: https://stackoverflow.com/questions/20085768/ 

    scores = Z.dot(rho) 
    pos_idx = scores > 0 
    exp_scores_pos = np.exp(-scores[pos_idx]) 
    exp_scores_neg = np.exp(scores[~pos_idx]) 

    #compute loss value 
    loss_value = np.empty_like(scores) 
    loss_value[pos_idx] = np.log(1.0 + exp_scores_pos) 
    loss_value[~pos_idx] = -scores[~pos_idx] + np.log(1.0 + exp_scores_neg) 
    loss_value = loss_value.mean() 

    #compute loss slope 
    phi_slope = np.empty_like(scores) 
    phi_slope[pos_idx] = 1.0/(1.0 + exp_scores_pos) 
    phi_slope[~pos_idx] = exp_scores_neg/(1.0 + exp_scores_neg) 
    loss_slope = Z.T.dot(phi_slope - 1.0)/Z.shape[0] 

    return loss_value, loss_slope 


#initialize a vector of integers where more than half of the entries = 0 
rho_test = np.random.randint(low=-10, high=10, size=(n_cols, 1)) 
set_to_zero = np.random.choice(range(0,n_cols), size =(np.floor(n_cols/2), 1), replace=False) 
rho_test[set_to_zero] = 0.0 

start_time = time.time() 
loss_value, loss_slope = compute_logistic_loss_value_and_slope(rho_test, Z) 
print "total runtime = %1.5f seconds" % (time.time() - start_time) 
+3

为什么你排除超过1个CPU?尽管Python VM基本上是单线程的,但是在将数据复制到更适合线程的数据结构之后,可以从C扩展中调用POSIX线程。可能有其他原因不使用多个CPU,但如果您转到C,则不受限制。 – rts1

+1

@rts好问题。在这种情况下,我需要将它限制为1个CPU,因为调用'compute_logistic_loss_function'的代码实际上是并行化的......所以当函数被调用时只有1个CPU可用。 –

+1

对于较大的'n',运行时似乎被'loss_slope = Z *(phi_slope - 1.0)'控制,它与'Z'广播出相同的大小。由于你在平均值以上,你可以使用'ZTdot(phi_slope).T/Z.shape [0]'将它重新写成一个点积,这会使我的速度提高4倍机。 –

回答

1

BLAS系列的图书馆已经进行了高度调整,以获得最佳性能。因此,无法链接到某些C/C++代码可能会给您带来任何好处。然而,你可以尝试各种BLAS实现,因为它们中有相当多的实现,其中包括一些特别针对某些CPU的调整。

我想到的另一件事是使用像theano(或Google的tensorflow)这样的库,它能够表示整个计算图(上述函数中的所有操作)并对其应用全局优化。然后它可以通过C++从该图生成CPU代码(并通过翻转一个简单的开关也可以生成GPU代码)。它也可以为你自动计算符号衍生物。我已经使用theano进行机器学习问题,并且它是一个非常棒的库,尽管不是最容易学的。

(我张贴此作为一个答案,因为它太长的评论)

编辑:

我居然在theano在这了个去,但结果实际上大约为2x在CPU上更慢,请参阅下面的原因。我会在这里反正张贴,也许这是为别人做一些更好的起点:(这只是部分代码,完成从原来的职位代码)

import theano 

def make_graph(rho, Z): 
    scores = theano.tensor.dot(Z, rho) 

    # this is very inefficient... it calculates everything twice and 
    # then picks one of them depending on scores being positive or not. 
    # not sure how to express this in theano in a more efficient way 
    pos = theano.tensor.log(1 + theano.tensor.exp(-scores)) 
    neg = theano.tensor.log(scores + theano.tensor.exp(scores)) 
    loss_value = theano.tensor.switch(scores > 0, pos, neg) 
    loss_value = loss_value.mean() 

    # however computing the derivative is a real joy now: 
    loss_slope = theano.tensor.grad(loss_value, rho) 

    return loss_value, loss_slope 

sym_rho = theano.tensor.col('rho') 
sym_Z = theano.tensor.matrix('Z') 
sym_loss_value, sym_loss_slope = make_graph(sym_rho, sym_Z) 

compute_logistic_loss_value_and_slope = theano.function(
     inputs=[sym_rho, sym_Z], 
     outputs=[sym_loss_value, sym_loss_slope] 
     ) 

# use function compute_logistic_loss_value_and_slope() as in original code 
0

numpy的是相当优化。你可以做的最好的办法是尝试其他库的初始化为随机(不初始化为0)的大小相同的数据,并做你自己的基准。

如果你想尝试,你当然可以尝试BLAS。你也应该尝试eigen,我个人发现它在我的一个应用程序中速度更快。