2016-02-02 18 views
6

Tôi hiện đang làm việc trên một dự án học máy nơi - được cung cấp ma trận dữ liệu Z và vector rho - Tôi phải tính toán giá trị và độ dốc của số logistic loss function tại số rho. Tính toán liên quan đến phép nhân ma trận-vector cơ bản và các hoạt động log/exp, với một thủ thuật để tránh tràn số (được mô tả trong số previous post) này.Tăng tốc phép nhân-vector và lũy thừa trong Python, có thể bằng cách gọi C/C++

Tôi hiện đang thực hiện việc này bằng Python bằng cách sử dụng NumPy như được hiển thị bên dưới (dưới dạng tham chiếu, mã này chạy bằng 0,2 giây). Mặc dù điều này hoạt động tốt, tôi muốn tăng tốc độ nó kể từ khi tôi gọi hàm nhiều lần trong mã của tôi (và nó đại diện cho hơn 90% tính toán liên quan đến dự án của tôi).

Tôi đang tìm bất kỳ cách nào để cải thiện thời gian chạy của mã này mà không cần song song (nghĩa là chỉ có 1 CPU). Tôi rất vui khi sử dụng bất kỳ gói công khai nào có sẵn trong Python, hoặc gọi C hoặc C++ (vì tôi đã nghe nói rằng điều này cải thiện thời gian hoạt động theo thứ tự độ lớn). Tiền xử lý ma trận dữ liệu Z cũng sẽ ổn. Một số điều có thể được khai thác để tính toán tốt hơn là rằng vector rho thường là thưa thớt (với khoảng 50% các mục = 0) và thường có xa hơn hàng hơn cột (trong hầu hết trường hợp 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

Tại sao bạn loại trừ nhiều hơn 1 CPU? Mặc dù Python VM về cơ bản là một luồng đơn, bạn có thể gọi các chủ đề POSIX từ bên trong một phần mở rộng C sau khi bạn sao chép dữ liệu vào một cấu trúc dữ liệu thân thiện với luồng hơn.Có thể có các lý do khác không sử dụng nhiều CPU, nhưng bạn không bị hạn chế bởi giới hạn đó nếu bạn thoát khỏi C. – rts1

+1

@rts Câu hỏi hay. Trong trường hợp này, tôi cần giới hạn nó thành 1 CPU vì mã gọi 'compute_logistic_loss_function' thực sự được song song ... Vì vậy, chỉ có 1 CPU sẽ có sẵn khi hàm được gọi. –

+1

Đối với 'n' lớn, thời gian chạy dường như bị chi phối bởi' loss_slope = Z * (phi_slope - 1.0) ', nó phát ra cùng kích thước với' Z'. Vì bạn đang sử dụng các hàng trung bình, bạn có thể viết lại nó dưới dạng sản phẩm dấu chấm bằng cách sử dụng 'ZTdot (phi_slope) .T/Z.shape [0]', mang lại hệ số 4 tốc độ trên máy móc. –

Trả lời

1

Thư viện của gia đình BLAS đã được điều chỉnh để có hiệu suất tốt nhất. Vì vậy, không có nỗ lực để liên kết với một số mã C/C++ có khả năng cung cấp cho bạn bất kỳ lợi ích. Tuy nhiên, bạn có thể thử các triển khai BLAS khác nhau, vì có khá nhiều trong số chúng xung quanh, bao gồm một số được điều chỉnh đặc biệt cho một số CPU.

Điều khác mà tôi nghĩ là sử dụng thư viện như theano (hoặc tensorflow) của Google có thể đại diện cho toàn bộ đồ thị tính toán (tất cả các hoạt động trong hàm của bạn ở trên) và áp dụng tối ưu hóa toàn cầu cho nó. Sau đó, nó có thể tạo mã CPU từ biểu đồ đó thông qua C++ (và bằng cách lật công tắc đơn giản cũng là mã GPU). Nó cũng có thể tự động tính toán các dẫn xuất tượng trưng cho bạn. Tôi đã sử dụng theano cho vấn đề học máy và đó là một thư viện thực sự tuyệt vời cho điều đó, mặc dù không phải là dễ nhất để học.

(Tôi gửi bài này như một câu trả lời vì nó quá dài cho một chú thích)

Edit:

tôi thực sự đã có một đi vào này trong theano, nhưng kết quả thực sự là khoảng 2x chậm hơn trên CPU, xem dưới đây tại sao. Tôi sẽ đăng nó ở đây anyway, có lẽ đó là một điểm khởi đầu cho người khác để làm điều gì đó tốt hơn: (đây chỉ là một phần mã, hoàn thành với mã từ bài gốc)

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 là khá tối ưu. Điều tốt nhất bạn có thể làm là thử các thư viện khác với dữ liệu có cùng kích thước được khởi tạo ngẫu nhiên (không được khởi tạo đến 0) và làm điểm chuẩn của riêng bạn.

Nếu bạn muốn thử, bạn tất nhiên có thể thử BLAS. Bạn cũng nên thử xem eigen, cá nhân tôi tìm thấy nó nhanh hơn trên một trong các ứng dụng của tôi.

Các vấn đề liên quan