TensorFlow का उपयोग करते हुए ढाल मूल एक मूल पायथन कार्यान्वयन की तुलना में बहुत धीमा है, क्यों?
मैं एक मशीन लर्निंग कोर्स का अनुसरण कर रहा हूं। मुझे TensorFlow की आदत डालने में मदद करने के लिए एक सरल रैखिक प्रतिगमन (LR) समस्या है। एलआर समस्या मापदंडों को खोजने के लिए है a
और b
इस तरह Y = a*X + b
एक (x, y)
बिंदु बादल का अनुमान लगाता है (जो मैंने खुद को सरलता के लिए उत्पन्न किया था)।
मैं 'निश्चित चरण आकार ढाल वंश (FSSGD)' का उपयोग करके इस LR समस्या को हल कर रहा हूं। मैंने इसे TensorFlow का उपयोग करके कार्यान्वित किया और यह काम करता है लेकिन मैंने देखा कि यह वास्तव में GPU और CPU दोनों पर धीमा है। क्योंकि मैं उत्सुक था कि मैंने पायथन / न्यूमपी में खुद एफएसएसजीडी को लागू किया और उम्मीद के मुताबिक यह बहुत तेजी से चलता है, के बारे में:
- TF @ CPU से 10 गुना तेज
- टीएफ @ जीपीयू से 20 गुना तेज
यदि TensorFlow यह धीमा है, तो मैं सोच भी नहीं सकता कि इतने सारे लोग इस ढांचे का उपयोग कर रहे हैं। इसलिए मुझे कुछ गलत करना चाहिए। क्या कोई मेरी मदद कर सकता है ताकि मैं अपने TensorFlow कार्यान्वयन को गति दे सकूं।
मुझे सीपीयू और जीपीयू के प्रदर्शन के बीच अंतर में कोई दिलचस्पी नहीं है। दोनों प्रदर्शन संकेतक केवल पूर्णता और चित्रण के लिए प्रदान किए जाते हैं। मुझे इस बात में दिलचस्पी है कि मेरा टेनसॉरफ्लो कार्यान्वयन एक कच्चे अजगर / न्यूमपी कार्यान्वयन की तुलना में इतना धीमा क्यों है।
संदर्भ के रूप में, मैं नीचे अपना कोड जोड़ता हूं।
- एक न्यूनतम (लेकिन पूरी तरह से काम करने वाला) उदाहरण के लिए स्ट्रिप किया गया।
- का उपयोग कर
Python v3.7.9 x64
। tensorflow-gpu==1.15
अभी के लिए उपयोग किया जाता है (क्योंकि कोर्स TensorFlow v1 का उपयोग करता है)- दोनों स्पाइडर और PyCharm में चलाने के लिए परीक्षण किया गया।
TensorFlow (40 सेकंड @CPU के बारे में 80 सेकंड @GPU पर निष्पादन समय) का उपयोग करके मेरा FSSGD कार्यान्वयन:
#%% 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('')
मेरे अपने अजगर FSSGD कार्यान्वयन (निष्पादन के बारे में 4 सेकंड):
#%% 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('')
जवाब
मुझे लगता है कि यह बड़ी पुनरावृत्ति संख्या का परिणाम है। मैं से यात्रा संख्या बदल दिया है 1e5
करने के लिए 1e3
है और यह भी से एक्स बदल x_data_input = np.arange(100, step=0.1)
करने के लिए x_data_input = np.arange(100, step=0.0001)
। इस तरह मैंने पुनरावृति संख्या को कम कर दिया है लेकिन गणना को 10 गुना बढ़ा दिया है। Np के साथ यह 22 सेकंड में किया जाता है और टेंसरफ्लो में यह 25 सेकंड में किया जाता है ।
मेरा अनुमान है कि प्रत्येक पुनरावृत्ति में टेनोरफ़्लो की अधिकता है (हमें ऐसा ढांचा देने के लिए जो बहुत कुछ कर सकता है) लेकिन आगे पास और पिछड़े पास की गति ठीक है।
मेरे प्रश्न का वास्तविक उत्तर विभिन्न टिप्पणियों में छिपा है। भविष्य के पाठकों के लिए, मैं इस उत्तर में इन निष्कर्षों को संक्षेप में बताऊंगा।
TensorFlow और एक कच्चे अजगर / NumPy कार्यान्वयन के बीच गति अंतर के बारे में
जवाब का यह हिस्सा वास्तव में काफी तार्किक है।
प्रत्येक पुनरावृत्ति (= प्रत्येक कॉल Session.run()
) TensorFlow संगणना करती है। TensorFlow में प्रत्येक गणना शुरू करने के लिए एक बड़ा उपरि है। GPU पर, यह ओवरहेड CPU पर से भी बदतर है। हालांकि, TensorFlow उपरोक्त कच्चे पायथन / NumPy कार्यान्वयन की तुलना में वास्तविक गणनाओं को बहुत कुशल और अधिक कुशलतापूर्वक निष्पादित करता है।
इसलिए, जब डेटा बिंदुओं की संख्या बढ़ जाती है, और इसलिए प्रति पुनरावृत्ति की गणना की संख्या आप देखेंगे कि TensorFlow और Python / NumPy के बीच सापेक्षिक प्रदर्शन TensorFlow के लाभ में बदल जाता है। उल्टा भी सही है।
प्रश्न में वर्णित समस्या बहुत कम अर्थ है कि गणना की संख्या बहुत कम है जबकि पुनरावृत्तियों की संख्या बहुत बड़ी है। यही कारण है कि TensorFlow इतना बुरा प्रदर्शन करता है। इस प्रकार की छोटी समस्याएं ठेठ उपयोग का मामला नहीं है जिसके लिए टेंसोरफ्लो को डिजाइन किया गया था।
निष्पादन समय को कम करने के लिए
फिर भी TensorFlow स्क्रिप्ट का निष्पादन समय बहुत कम हो सकता है! निष्पादन समय को कम करने के लिए पुनरावृत्तियों की संख्या कम होनी चाहिए (समस्या का आकार कोई भी हो, यह वैसे भी एक अच्छा उद्देश्य है)।
जैसा कि @ अमीन ने बताया है, यह इनपुट डेटा को स्केल करके हासिल किया जाता है। यह काम क्यों करता है इसकी एक बहुत ही संक्षिप्त व्याख्या: जिन मूल्यों को प्राप्त किया जाना है, उनके लिए ढाल और चर अपडेट का आकार अधिक संतुलित है। इसलिए, कम चरणों (= पुनरावृत्तियों) की आवश्यकता होती है।
अनुवर्ती @ अमीन की सलाह, मैं अंत में अपने एक्स-डेटा को स्केल करके समाप्त हुआ (कुछ कोड को नए कोड की स्थिति स्पष्ट करने के लिए दोहराया गया है):
# 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")
एक कारक 1000 द्वारा अभिसरण की गति को बढ़ाता है। इसके बजाय 1e5 iterations
, 1e2 iterations
आवश्यक है। यह आंशिक रूप से है क्योंकि एक के step size of 1e-1
बजाय एक अधिकतम उपयोग किया जा सकता है step size of 1e-4
।
कृपया ध्यान दें कि पाया गया वजन और पूर्वाग्रह अलग-अलग हैं और आपको अभी से स्केल किए गए डेटा को खिलाना होगा।
वैकल्पिक रूप से, आप पाया गया वज़न और पूर्वाग्रह को अनसुना करने का विकल्प चुन सकते हैं ताकि आप बिना सोचे डेटा को खिला सकें। इस कोड (कोड के अंत में कहीं डाल) का उपयोग करके अनस्कलिंग किया जाता है:
#%% Unscaling
W_val_unscaled = W_val[0,0]/x_std
b_val_unscaled = b_val[0]-x_mean*W_val[0,0]/x_std