Gradient descent sử dụng TensorFlow chậm hơn nhiều so với triển khai Python cơ bản, tại sao?
Tôi đang theo một khóa học máy học. Tôi có một bài toán hồi quy tuyến tính (LR) đơn giản để giúp tôi làm quen với TensorFlow. Vấn đề của LR là tìm các tham số avà các tham số bđó Y = a*X + bgần đúng với một (x, y)đám mây điểm (do chính tôi tạo ra vì mục đích đơn giản).
Tôi đang giải quyết vấn đề LR này bằng cách sử dụng 'gốc chuyển màu kích thước bước cố định (FSSGD)'. Tôi đã triển khai nó bằng cách sử dụng TensorFlow và nó hoạt động nhưng tôi nhận thấy rằng nó thực sự chậm cả trên GPU và CPU. Bởi vì tôi tò mò, tôi đã tự triển khai FSSGD bằng Python / NumPy và như mong đợi điều này chạy nhanh hơn nhiều, về:
- Nhanh hơn 10 lần so với TF @ CPU
- Nhanh hơn 20 lần so với TF @ GPU
Nếu TensorFlow chậm như vậy, tôi không thể tưởng tượng rằng có rất nhiều người đang sử dụng khung này. Vì vậy, tôi phải làm điều gì đó sai. Bất cứ ai có thể giúp tôi để tôi có thể tăng tốc độ triển khai TensorFlow của mình.
Tôi KHÔNG quan tâm đến sự khác biệt giữa hiệu suất CPU và GPU. Cả hai chỉ số hoạt động chỉ được cung cấp đầy đủ và minh họa. Tôi quan tâm đến lý do tại sao triển khai TensorFlow của tôi lại chậm hơn nhiều so với triển khai Python / NumPy thô.
Để tham khảo, tôi thêm mã của mình vào bên dưới.
- Được rút gọn thành một ví dụ tối thiểu (nhưng hoạt động đầy đủ).
- Đang sử dụng
Python v3.7.9 x64. - Được sử dụng
tensorflow-gpu==1.15ngay bây giờ (vì khóa học sử dụng TensorFlow v1) - Đã thử nghiệm để chạy trong cả Spyder và PyCharm.
Triển khai FSSGD của tôi bằng TensorFlow (thời gian thực thi khoảng 40 giây @CPU đến 80 giây @GPU):
#%% General imports
import numpy as np
import timeit
import tensorflow.compat.v1 as tf
#%% Get input data
# Generate simulated input data
x_data_input = np.arange(100, step=0.1)
y_data_input = x_data_input + 20 * np.sin(x_data_input/10) + 15
#%% Define tensorflow model
# Define data size
n_samples = x_data_input.shape[0]
# Tensorflow is finicky about shapes, so resize
x_data = np.reshape(x_data_input, (n_samples, 1))
y_data = np.reshape(y_data_input, (n_samples, 1))
# Define placeholders for input
X = tf.placeholder(tf.float32, shape=(n_samples, 1), name="tf_x_data")
Y = tf.placeholder(tf.float32, shape=(n_samples, 1), name="tf_y_data")
# Define variables to be learned
with tf.variable_scope("linear-regression", reuse=tf.AUTO_REUSE): #reuse= True | False | tf.AUTO_REUSE
W = tf.get_variable("weights", (1, 1), initializer=tf.constant_initializer(0.0))
b = tf.get_variable("bias", (1,), initializer=tf.constant_initializer(0.0))
# Define loss function
Y_pred = tf.matmul(X, W) + b
loss = tf.reduce_sum((Y - Y_pred) ** 2 / n_samples) # Quadratic loss function
# %% Solve tensorflow model
#Define algorithm parameters
total_iterations = 1e5 # Defines total training iterations
#Construct TensorFlow optimizer
with tf.variable_scope("linear-regression", reuse=tf.AUTO_REUSE): #reuse= True | False | tf.AUTO_REUSE
opt = tf.train.GradientDescentOptimizer(learning_rate = 1e-4)
opt_operation = opt.minimize(loss, name="GDO")
#To measure execution time
time_start = timeit.default_timer()
with tf.Session() as sess:
#Initialize variables
sess.run(tf.global_variables_initializer())
#Train variables
for index in range(int(total_iterations)):
_, loss_val_tmp = sess.run([opt_operation, loss], feed_dict={X: x_data, Y: y_data})
#Get final values of variables
W_val, b_val, loss_val = sess.run([W, b, loss], feed_dict={X: x_data, Y: y_data})
#Print execution time
time_end = timeit.default_timer()
print('')
print("Time to execute code: {0:0.9f} sec.".format(time_end - time_start))
print('')
# %% Print results
print('')
print('Iteration = {0:0.3f}'.format(total_iterations))
print('W_val = {0:0.3f}'.format(W_val[0,0]))
print('b_val = {0:0.3f}'.format(b_val[0]))
print('')
Triển khai FSSGD python của riêng tôi (thời gian thực thi khoảng 4 giây):
#%% General imports
import numpy as np
import timeit
#%% Get input data
# Define input data
x_data_input = np.arange(100, step=0.1)
y_data_input = x_data_input + 20 * np.sin(x_data_input/10) + 15
#%% Define Gradient Descent (GD) model
# Define data size
n_samples = x_data_input.shape[0]
#Initialize data
W = 0.0 # Initial condition
b = 0.0 # Initial condition
# Compute initial loss
y_gd_approx = W*x_data_input+b
loss = np.sum((y_data_input - y_gd_approx)**2)/n_samples # Quadratic loss function
#%% Execute Gradient Descent algorithm
#Define algorithm parameters
total_iterations = 1e5 # Defines total training iterations
GD_stepsize = 1e-4 # Gradient Descent fixed step size
#To measure execution time
time_start = timeit.default_timer()
for index in range(int(total_iterations)):
#Compute gradient (derived manually for the quadratic cost function)
loss_gradient_W = 2.0/n_samples*np.sum(-x_data_input*(y_data_input - y_gd_approx))
loss_gradient_b = 2.0/n_samples*np.sum(-1*(y_data_input - y_gd_approx))
#Update trainable variables using fixed step size gradient descent
W = W - GD_stepsize * loss_gradient_W
b = b - GD_stepsize * loss_gradient_b
#Compute loss
y_gd_approx = W*x_data_input+b
loss = np.sum((y_data_input - y_gd_approx)**2)/x_data_input.shape[0]
#Print execution time
time_end = timeit.default_timer()
print('')
print("Time to execute code: {0:0.9f} sec.".format(time_end - time_start))
print('')
# %% Print results
print('')
print('Iteration = {0:0.3f}'.format(total_iterations))
print('W_val = {0:0.3f}'.format(W))
print('b_val = {0:0.3f}'.format(b))
print('')
Trả lời
Tôi nghĩ đó là kết quả của số lần lặp lại lớn. Tôi đã thay đổi số lặp từ 1e5thành 1e3và cũng thay đổi x từ x_data_input = np.arange(100, step=0.1)thành x_data_input = np.arange(100, step=0.0001). Bằng cách này, tôi đã giảm số lần lặp lại nhưng tăng tính toán lên 10 lần. Với np, nó được thực hiện trong 22 giây và trong tensorflow, nó được thực hiện trong 25 giây .
Dự đoán của tôi: tensorflow có rất nhiều chi phí trong mỗi lần lặp (để cung cấp cho chúng tôi một khuôn khổ có thể làm được nhiều điều) nhưng tốc độ chuyền về phía trước và tốc độ chuyền lùi đều ổn.
Câu trả lời thực sự cho câu hỏi của tôi được ẩn trong các bình luận khác nhau. Đối với những độc giả trong tương lai, tôi sẽ tóm tắt những phát hiện này trong câu trả lời này.
Về sự khác biệt tốc độ giữa TensorFlow và triển khai Python / NumPy thô
Phần này của câu trả lời thực sự khá logic.
Mỗi lần lặp (= mỗi lần gọi Session.run()) TensorFlow thực hiện các phép tính. TensorFlow có chi phí lớn để bắt đầu mỗi lần tính toán. Trên GPU, chi phí này thậm chí còn tồi tệ hơn trên CPU. Tuy nhiên, TensorFlow thực thi các tính toán thực tế rất hiệu quả và hiệu quả hơn so với triển khai Python / NumPy thô ở trên.
Vì vậy, khi số lượng điểm dữ liệu được tăng lên và do đó số lượng tính toán trên mỗi lần lặp, bạn sẽ thấy rằng hiệu suất tương đối giữa TensorFlow và Python / NumPy thay đổi theo lợi thế của TensorFlow. Điều ngược lại cũng đúng.
Vấn đề được mô tả trong câu hỏi là rất nhỏ nghĩa là số lần tính toán rất ít trong khi số lần lặp lại rất lớn. Đó là lý do tại sao TensorFlow hoạt động rất tệ. Loại vấn đề nhỏ này không phải là trường hợp sử dụng điển hình mà TensorFlow được thiết kế.
Để giảm thời gian thực hiện
Tuy nhiên, thời gian thực thi của tập lệnh TensorFlow có thể giảm đi rất nhiều! Để giảm thời gian thực hiện, số lần lặp phải giảm (bất kể kích thước của vấn đề, dù sao thì đây cũng là một mục đích tốt).
Như @ amin's đã chỉ ra, điều này đạt được bằng cách mở rộng dữ liệu đầu vào. Giải thích rất ngắn gọn tại sao điều này hoạt động: kích thước của các bản cập nhật gradient và biến được cân bằng hơn so với giá trị tuyệt đối mà các giá trị sẽ được tìm thấy. Do đó, cần ít bước hơn (= lặp lại).
Theo lời khuyên của @ amin, cuối cùng tôi đã kết thúc bằng cách mở rộng dữ liệu x của mình như sau (một số mã được lặp lại để làm rõ vị trí của mã mới):
# Tensorflow is finicky about shapes, so resize
x_data = np.reshape(x_data_input, (n_samples, 1))
y_data = np.reshape(y_data_input, (n_samples, 1))
### START NEW CODE ###
# Scale x_data
x_mean = np.mean(x_data)
x_std = np.std(x_data)
x_data = (x_data - x_mean) / x_std
### END NEW CODE ###
# Define placeholders for input
X = tf.placeholder(tf.float32, shape=(n_samples, 1), name="tf_x_data")
Y = tf.placeholder(tf.float32, shape=(n_samples, 1), name="tf_y_data")
Việc mở rộng quy mô tăng tốc độ hội tụ lên một hệ số 1000. Thay vào đó 1e5 iterations, 1e2 iterationslà cần thiết. Điều này một phần là do giá trị tối đa step size of 1e-1có thể được sử dụng thay vì a step size of 1e-4.
Xin lưu ý rằng trọng lượng và độ lệch tìm thấy là khác nhau và bạn phải cung cấp dữ liệu tỷ lệ từ bây giờ.
Theo tùy chọn, bạn có thể chọn bỏ tỷ lệ trọng lượng và độ lệch tìm thấy để bạn có thể cung cấp dữ liệu chưa được chia tỷ lệ. Việc hủy quy mô được thực hiện bằng cách sử dụng mã này (đặt ở đâu đó ở cuối mã):
#%% Unscaling
W_val_unscaled = W_val[0,0]/x_std
b_val_unscaled = b_val[0]-x_mean*W_val[0,0]/x_std