TensorFlow를 사용하는 경사 하강 법은 기본 Python 구현보다 훨씬 느립니다. 왜 그런가요?

Dec 29 2020

기계 학습 과정을 따르고 있습니다. TensorFlow에 익숙해지는 데 도움이되는 간단한 선형 회귀 (LR) 문제가 있습니다. 하여 LR 문제는 매개 변수를 찾을 수 있습니다 ab같은 Y = a*X + b에 근접 (x, y)(아주 간단하게하기 위해 자신을 생성하는) 포인트 클라우드를.

나는 'FSSGD (fixed step size gradient descent)'를 사용하여이 LR 문제를 해결하고 있습니다. TensorFlow를 사용하여 구현했는데 작동하지만 GPU와 CPU 모두에서 정말 느립니다. 궁금했기 때문에 FSSGD를 Python / NumPy로 직접 구현했으며 예상대로 다음과 같이 훨씬 빠르게 실행됩니다.

  • TF @ CPU보다 10 배 빠름
  • TF @ GPU보다 20 배 빠름

TensorFlow가 이렇게 느리면 많은 사람들이이 프레임 워크를 사용하고 있다고 상상할 수 없습니다. 그래서 내가 뭔가 잘못하고있는 게 틀림 없어. 누구든지 TensorFlow 구현 속도를 높일 수 있도록 나를 도울 수 있습니까?

CPU와 GPU 성능의 차이에는 관심이 없습니다. 두 성과 지표는 모두 완전성과 설명을 위해 제공됩니다. 내 TensorFlow 구현이 원시 Python / NumPy 구현보다 훨씬 느린 이유에 관심이 있습니다.

참고로 아래 코드를 추가합니다.

  • 최소한의 (그러나 완전히 작동하는) 예제를 제외했습니다.
  • 사용 Python v3.7.9 x64.
  • tensorflow-gpu==1.15현재 사용됨 (강좌에서 TensorFlow v1을 사용하기 때문에)
  • Spyder와 PyCharm 모두에서 실행되도록 테스트되었습니다.

TensorFlow를 사용한 내 FSSGD 구현 (실행 시간 약 40 초 @CPU ~ 80 초 @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('')

내 파이썬 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('')

답변

1 amin Dec 29 2020 at 21:12

큰 반복 횟수의 결과라고 생각합니다. 반복 번호를에서 1e51e3변경하고 x도에서 x_data_input = np.arange(100, step=0.1)으로 변경 했습니다 x_data_input = np.arange(100, step=0.0001). 이런 식으로 반복 횟수를 줄 였지만 계산을 10 배 늘 렸습니다. np를 사용하면 22 초 안에 완료되고 tensorflow에서는 25 초 안에 완료됩니다 .

내 생각 엔 : tensorflow는 각 반복에서 많은 오버 헤드를 가지고 있지만 (많은 일을 할 수있는 프레임 워크를 제공하기 위해) 순방향 패스와 역방향 패스 속도는 괜찮습니다.

Stefan Dec 31 2020 at 17:35

내 질문에 대한 실제 답변은 다양한 댓글에 숨겨져 있습니다. 미래의 독자를 위해이 답변에 이러한 결과를 요약하겠습니다.

TensorFlow와 원시 Python / NumPy 구현 간의 속도 차이 정보

대답의이 부분은 실제로 매우 논리적입니다.

각 반복 (= 각 호출 Session.run()) TensorFlow는 계산을 수행합니다. TensorFlow에는 각 계산을 시작하는 데 큰 오버 헤드가 있습니다. GPU에서이 오버 헤드는 CPU보다 더 나쁩니다. 그러나 TensorFlow는 위의 원시 Python / NumPy 구현보다 실제 계산을 매우 효율적이고 효율적으로 실행합니다.

따라서 데이터 포인트 수가 증가하여 반복 당 계산 수가 증가하면 TensorFlow와 Python / NumPy 간의 상대적 성능이 TensorFlow의 이점으로 이동한다는 것을 알 수 있습니다. 그 반대도 마찬가지입니다.

질문에 설명 된 문제는 계산 횟수가 매우 적고 반복 횟수가 매우 크다는 것을 의미하는 매우 작습니다. 이것이 TensorFlow가 그렇게 나쁘게 수행되는 이유입니다. 이러한 유형의 작은 문제는 TensorFlow가 설계된 일반적인 사용 사례가 아닙니다.

실행 시간을 줄이려면

그래도 TensorFlow 스크립트의 실행 시간은 많이 줄일 수 있습니다! 실행 시간을 줄이려면 반복 횟수를 줄여야합니다 (문제의 크기에 관계없이 어쨌든 좋은 목표입니다).

@amin이 지적했듯이 이것은 입력 데이터를 확장하여 달성됩니다. 이것이 작동하는 이유를 매우 간략하게 설명합니다. 그래디언트 및 변수 업데이트의 크기는 값을 찾을 절대 값에 비해 균형이 더 잘 맞습니다. 따라서 더 적은 단계 (= 반복)가 필요합니다.

@amin의 조언에 따라 마침내 다음과 같이 내 x- 데이터 크기를 조정했습니다 (새 코드의 위치를 ​​명확하게하기 위해 일부 코드가 반복됨).

# 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