Penurunan gradien menggunakan TensorFlow jauh lebih lambat daripada implementasi Python dasar, mengapa?

Dec 29 2020

Saya mengikuti kursus pembelajaran mesin. Saya memiliki masalah regresi linier sederhana (LR) untuk membantu saya terbiasa dengan TensorFlow. Masalah LR adalah menemukan parameter adan bsemacamnya yang Y = a*X + bmendekati (x, y)titik cloud (yang saya buat sendiri demi kesederhanaan).

Saya memecahkan masalah LR ini menggunakan 'penurunan gradien ukuran langkah tetap (FSSGD)'. Saya mengimplementasikannya menggunakan TensorFlow dan berhasil, tetapi saya perhatikan bahwa ini sangat lambat baik pada GPU maupun CPU. Karena saya penasaran saya menerapkan FSSGD sendiri dengan Python / NumPy dan seperti yang diharapkan ini berjalan lebih cepat, tentang:

  • 10x lebih cepat dari TF @ CPU
  • 20x lebih cepat dari TF @ GPU

Jika TensorFlow selambat ini, saya tidak dapat membayangkan begitu banyak orang yang menggunakan framework ini. Jadi saya pasti melakukan sesuatu yang salah. Adakah yang bisa membantu saya sehingga saya bisa mempercepat implementasi TensorFlow saya.

Saya TIDAK tertarik dengan perbedaan antara kinerja CPU dan GPU. Kedua indikator kinerja tersebut hanya disediakan untuk kelengkapan dan ilustrasi. Saya tertarik dengan mengapa implementasi TensorFlow saya jauh lebih lambat daripada implementasi Python / NumPy mentah.

Sebagai referensi, saya menambahkan kode saya di bawah ini.

  • Dilucuti ke contoh minimal (tetapi berfungsi penuh).
  • Menggunakan Python v3.7.9 x64.
  • Digunakan tensorflow-gpu==1.15untuk saat ini (karena kursus menggunakan TensorFlow v1)
  • Diuji untuk berjalan di Spyder dan PyCharm.

Implementasi FSSGD saya menggunakan TensorFlow (waktu eksekusi sekitar 40 detik @CPU hingga 80 detik @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('')

Implementasi FSSGD python saya sendiri (waktu eksekusi sekitar 4 detik):

#%% 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('')

Jawaban

1 amin Dec 29 2020 at 21:12

Saya pikir itu adalah hasil dari angka iterasi yang besar. Saya telah mengubah nomor iterasi dari 1e5menjadi 1e3dan juga mengubah x dari x_data_input = np.arange(100, step=0.1)menjadi x_data_input = np.arange(100, step=0.0001). Dengan cara ini saya telah mengurangi angka iterasi tetapi meningkatkan komputasi sebesar 10x. Dengan np selesai dalam 22 detik dan dalam tensorflow selesai dalam 25 detik .

Dugaan saya: tensorflow memiliki banyak overhead di setiap iterasi (untuk memberi kami kerangka kerja yang dapat melakukan banyak hal) tetapi kecepatan operan maju dan kecepatan mundur tidak apa-apa.

Stefan Dec 31 2020 at 17:35

Jawaban sebenarnya atas pertanyaan saya tersembunyi di berbagai komentar. Untuk pembaca selanjutnya, saya akan merangkum temuan tersebut dalam jawaban ini.

Tentang perbedaan kecepatan antara TensorFlow dan implementasi Python / NumPy mentah

Bagian jawaban ini sebenarnya cukup logis.

Setiap iterasi (= setiap panggilan Session.run()) TensorFlow melakukan komputasi. TensorFlow memiliki overhead yang besar untuk memulai setiap komputasi. Di GPU, overhead ini bahkan lebih buruk daripada di CPU. Namun, TensorFlow mengeksekusi komputasi sebenarnya dengan sangat efisien dan lebih efisien daripada implementasi Python / NumPy mentah di atas.

Jadi, ketika jumlah poin data ditingkatkan, dan oleh karena itu jumlah komputasi per iterasi, Anda akan melihat bahwa performa relatif antara TensorFlow dan Python / NumPy bergeser dalam keuntungan TensorFlow. Kebalikannya juga benar.

Masalah yang dijelaskan dalam soal sangat kecil artinya jumlah komputasi sangat sedikit sedangkan jumlah iterasinya sangat banyak. Itulah mengapa TensorFlow berkinerja sangat buruk. Jenis masalah kecil ini bukanlah kasus penggunaan umum yang dirancang TensorFlow.

Untuk mengurangi waktu eksekusi

Tetap saja waktu eksekusi skrip TensorFlow dapat banyak dikurangi! Untuk mengurangi waktu eksekusi, jumlah iterasi harus dikurangi (tidak peduli seberapa besar masalahnya, ini adalah tujuan yang baik).

Seperti yang ditunjukkan @ amin, ini dicapai dengan menskalakan data input. Penjelasan yang sangat singkat mengapa ini berfungsi: ukuran gradien dan pembaruan variabel lebih seimbang dibandingkan dengan nilai absolut yang nilainya dapat ditemukan. Oleh karena itu, diperlukan lebih sedikit langkah (= iterasi).

Mengikuti saran @ amin, saya akhirnya menskalakan x-data saya sebagai berikut (beberapa kode diulang untuk membuat posisi kode baru jelas):

# 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")

Penskalaan mempercepat konvergensi dengan faktor 1000. Sebaliknya 1e5 iterations, 1e2 iterationsdibutuhkan. Ini sebagian karena maksimum step size of 1e-1dapat digunakan daripada a step size of 1e-4.

Harap perhatikan bahwa bobot dan bias yang ditemukan berbeda dan Anda harus memasukkan data berskala mulai sekarang.

Secara opsional, Anda dapat memilih untuk mengurangi skala bobot dan bias yang ditemukan sehingga Anda dapat memasukkan data yang tidak diskalakan. Pembongkaran skala dilakukan menggunakan kode ini (diletakkan di suatu tempat di akhir kode):

#%% Unscaling
W_val_unscaled = W_val[0,0]/x_std
b_val_unscaled = b_val[0]-x_mean*W_val[0,0]/x_std