이 답변 : TF2 대 TF1 트레인 루프, 입력 데이터 프로세서 및 Eager 대 그래프 모드 실행을 포함하여 문제에 대한 자세한 그래프 / 하드웨어 수준 설명을 제공하는 것을 목표로합니다. 문제 요약 및 해결 지침은 다른 답변을 참조하십시오.
성능 검증 : 구성에 따라 때로는 하나가 더 빠르거나 때로는 다른 것이 더 빠릅니다. TF2와 TF1의 차이는 평균적으로 비슷하지만 중요한 구성 기반 차이가 존재하며 TF1이 TF2보다 더 자주 TF2보다 우선합니다. 아래의 "벤치마킹"을 참조하십시오.
EAGER VS. GRAPH : 일부에 대한이 전체 답변의 고기 : 내 테스트에 따르면 TF2의 열망은 TF1보다 느립니다 . 자세한 내용은 아래를 참조하십시오.
이 둘의 근본적인 차이점은 다음과 같습니다. Graph는 사전에 계산 네트워크를 설정하고 '말할 때'실행하지만 Eager는 생성시 모든 것을 실행합니다. 그러나 이야기는 여기서 시작됩니다.
Eager는 Graph가 없으며 실제로 예상과 달리 대부분 Graph 일 수 있습니다 . 그것은 크게 실행됩니다. Graph- 여기에는 그래프 의 많은 부분을 포함하는 모델 및 최적화 가중치가 포함됩니다.
Eager는 실행시 자체 그래프의 일부를 재구성합니다 . Graph가 완전히 구축되지 않은 직접적인 결과-프로파일 러 결과 참조. 여기에는 계산 오버 헤드가 있습니다.
Numpy 입력으로 느리게 느림 ; 당 이 망할 놈의 주석 및 코드, 열망에서 NumPy와 입력은 CPU에서 GPU로 텐서를 복사하는 오버 헤드 비용을 포함한다. 소스 코드를 단계별로 살펴보면 데이터 처리 차이점이 분명합니다. Eager는 Numpy를 직접 전달하고 Graph는 텐서를 통과 한 다음 Numpy로 평가합니다. 정확한 프로세스는 확실하지 않지만 후자는 GPU 수준 최적화를 포함해야합니다.
TF2 Eager가 TF1 Eager보다 느리다 -이것은 예상치 못한 것이다. 아래 벤치마킹 결과를 참조하십시오. 차이는 무시할 수있는 수준에서 의미있는 수준까지 다양하지만 일관성이 있습니다. 왜 그런지 잘 모르겠습니다. TF 개발자가 설명하면 답변을 업데이트합니다.
TF2 대 TF1 하십시오 TF dev에의, Q. 스콧 Zhu의의의 관련 부분을 인용 응답 - w / 내 중점 및 새로운 표현의 비트를 :
간절히 런타임은 ops를 실행하고 모든 파이썬 코드 라인의 숫자 값을 반환해야합니다. 단일 단계 실행 의 특성상 속도가 느려집니다 .
TF2에서 Keras는 tf.function을 활용하여 훈련, 평가 및 예측을위한 그래프를 작성합니다. 이를 모델의 "실행 기능"이라고합니다. TF1에서 "실행 기능"은 FuncGraph로, 일부 공통 구성 요소를 TF 기능으로 공유하지만 구현 방식이 다릅니다.
프로세스 중에 train_on_batch (), test_on_batch () 및 predict_on_batch ()에 대해 잘못된 구현이 남았습니다 . 여전히 수치 적으로 정확 하지만 x_on_batch의 실행 함수는 tf.function 래핑 된 파이썬 함수가 아닌 순수한 파이썬 함수입니다. 이됩니다 속도 저하의 원인이
TF2에서는 모든 입력 데이터를 tf.data.Dataset으로 변환하여 단일 유형의 입력을 처리하기 위해 실행 함수를 통합 할 수 있습니다. 데이터 세트 변환에 약간의 오버 헤드 가있을 수 있으며 이것이 일괄 처리 비용이 아닌 일회성 오버 헤드라고 생각합니다.
위의 마지막 단락의 마지막 문장과 아래 단락의 마지막 문장으로 :
열망 모드에서 속도 저하를 극복하기 위해 @ tf.function을 사용하여 파이썬 함수를 그래프로 변환합니다. np 배열과 같은 숫자 값을 피드하면 tf.function의 본문이 정적 그래프로 변환되어 최적화되고 최종 값을 반환합니다.이 값은 빠르고 TF1 그래프 모드와 유사한 성능을 가져야합니다.
프로파일 링 결과에 동의하지 않습니다.이 결과는 Eager의 입력 데이터 처리가 그래프보다 상당히 느리다는 것을 보여줍니다. 또한 확실 tf.data.Dataset
하지 않지만 Eager는 동일한 데이터 변환 방법을 여러 번 반복해서 호출합니다 (프로파일 러 참조).
마지막으로, dev의 링크 된 커밋 : Keras v2 루프를 지원하기위한 많은 변경 .
기차 루프 : (1) 열망 대 그래프; (2) 입력 데이터 형식, 훈련은 다음 중 하나 인 TF2 _select_training_loop()
, training.py 에서 독특한 기차 루프로 진행됩니다 .
training_v2.Loop()
training_distributed.DistributionMultiWorkerTrainingLoop(
training_v2.Loop()) # multi-worker mode
# Case 1: distribution strategy
training_distributed.DistributionMultiWorkerTrainingLoop(
training_distributed.DistributionSingleWorkerTrainingLoop())
# Case 2: generator-like. Input is Python generator, or Sequence object,
# or a non-distributed Dataset or iterator in eager execution.
training_generator.GeneratorOrSequenceTrainingLoop()
training_generator.EagerDatasetOrIteratorTrainingLoop()
# Case 3: Symbolic tensors or Numpy array-like. This includes Datasets and iterators
# in graph mode (since they generate symbolic tensors).
training_generator.GeneratorLikeTrainingLoop() # Eager
training_arrays.ArrayLikeTrainingLoop() # Graph
각각 리소스 할당을 다르게 처리하고 성능 및 기능에 영향을 미칩니다.
Train Loops : fit
vs train_on_batch
, keras
vs.tf.keras
: 네 가지 각각은 서로 다른 기차 루프를 사용하지만 모든 가능한 조합이 아닐 수도 있습니다. keras
' fit
예를 들어, 사용의 형태 fit_loop
예를 training_arrays.fit_loop()
, 그은 train_on_batch
사용할 수있다 K.function()
. tf.keras
이전 섹션에서 부분적으로 설명 된보다 정교한 계층 구조가 있습니다.
Train Loops : documentation- 몇 가지 다른 실행 방법에 대한 관련 소스 docstring :
다른 TensorFlow 작업과 달리 Python 숫자 입력을 텐서로 변환하지 않습니다. 또한, 각각의 개별 파이썬 숫자 값에 대해 새로운 그래프가 생성됩니다.
function
모든 고유 한 입력 모양 및 데이터 유형 집합에 대해 별도의 그래프를 인스턴스화합니다 .
단일 tf.function 객체는 후드 아래의 여러 계산 그래프에 매핑해야 할 수도 있습니다. 이것은 성능으로 만 볼 수 있어야합니다 (추적 그래프는 0이 아닌 계산 및 메모리 비용을 가짐 )
입력 데이터 프로세서 : 위와 유사하게 프로세서는 런타임 구성 (실행 모드, 데이터 형식, 배포 전략)에 따라 설정된 내부 플래그에 따라 사례별로 선택됩니다. Numpy 어레이와 직접 작동하는 Eager의 가장 간단한 사례입니다. 구체적인 예는 이 답변을 참조하십시오 .
모델 크기, 데이터 크기 :
- 결정적입니다. 모든 모델 및 데이터 크기 위에 단일 구성이 적용되지 않았습니다.
- 모델 크기에 상대적인 데이터 크기 가 중요합니다. 작은 데이터 및 모델의 경우 데이터 전송 (예 : CPU에서 GPU로) 오버 헤드가 지배적입니다. 마찬가지로, 작은 오버 헤드 프로세서는 데이터 변환 시간에 비해 큰 데이터에서 느리게 실행될 수 있습니다 (
convert_to_tensor
"프로파일 러" 참조 ).
- 열차 루프 및 입력 데이터 프로세서의 다양한 리소스 처리 수단에 따라 속도가 다릅니다.
벤치 마크 : 갈은 고기. - 워드 문서 - 엑셀 스프레드 시트
용어 :
- % 이하의 숫자는 모두 초입니다
- %는 다음과 같이 계산됩니다
(1 - longer_time / shorter_time)*100
. 이론적 근거 : 우리는 어떤 요소 가 다른 요소 보다 빠른지에 관심 이 있습니다 . shorter / longer
실제로 비선형 관계이며 직접 비교에 유용하지 않습니다.
- % 부호 결정 :
- TF2 vs TF1 :
+
TF2가 더 빠른 경우
- GvE (Graph vs. Eager) :
+
그래프가 더 빠른 경우
- TF2 = 텐서 플로우 2.0.0 + 케 라스 2.3.1; TF1 = TensorFlow 1.14.0 + Keras 2.2.5
프로파일 러 :
프로파일 러 -설명 : Spyder 3.3.6 IDE 프로파일 러.
테스트 환경 :
- 최소한의 백그라운드 작업을 실행하면서 맨 아래에서 실행 된 코드
- 이 게시물 에서 제안한 것처럼 GPU는 타이밍 반복 전에 몇 번의 반복으로 "워밍업" 되었습니다.
- 소스에서 빌드 된 CUDA 10.0.130, cuDNN 7.6.0, TensorFlow 1.14.0 및 TensorFlow 2.0.0 및 Anaconda
- Python 3.7.4, Spyder 3.3.6 IDE
- GTX 1070, Windows 10, 24GB DDR4 2.4MHz RAM, i7-7700HQ 2.8GHz CPU
방법론 :
- 벤치 마크 '작은', '중간'및 '대형'모델 및 데이터 크기
- 입력 데이터 크기와 무관하게 각 모델 크기에 대한 매개 변수 수 수정
- "큰"모델에는 더 많은 매개 변수와 레이어가 있습니다
- "큰"데이터가 더 이상 순서를 가지고 있지만, 같은
batch_size
과num_channels
- 모델만을 사용
Conv1D
, Dense
'학습 가능'층; RNN은 TF- 버전 임 렘당 피했다. 차이점
- 모델 및 옵티 마이저 그래프 작성을 생략하기 위해 벤치마킹 루프 외부에서 항상 하나의 트레인을 실행했습니다.
- 스파 스 데이터 (예
layers.Embedding()
:) 또는 스파 스 대상 (예 :SparseCategoricalCrossEntropy()
제한 사항 : "완전한"답변은 가능한 모든 열차 루프 및 반복자를 설명하지만, 그것은 확실히 내 시간 능력, 존재하지 않는 월급 또는 일반적인 필요성을 넘어서는 것입니다. 결과는 방법론만큼이나 우수합니다. 열린 마음으로 해석하십시오.
코드 :
import numpy as np
import tensorflow as tf
import random
from termcolor import cprint
from time import time
from tensorflow.keras.layers import Input, Dense, Conv1D
from tensorflow.keras.layers import Dropout, GlobalAveragePooling1D
from tensorflow.keras.models import Model
from tensorflow.keras.optimizers import Adam
import tensorflow.keras.backend as K
#from keras.layers import Input, Dense, Conv1D
#from keras.layers import Dropout, GlobalAveragePooling1D
#from keras.models import Model
#from keras.optimizers import Adam
#import keras.backend as K
#tf.compat.v1.disable_eager_execution()
#tf.enable_eager_execution()
def reset_seeds(reset_graph_with_backend=None, verbose=1):
if reset_graph_with_backend is not None:
K = reset_graph_with_backend
K.clear_session()
tf.compat.v1.reset_default_graph()
if verbose:
print("KERAS AND TENSORFLOW GRAPHS RESET")
np.random.seed(1)
random.seed(2)
if tf.__version__[0] == '2':
tf.random.set_seed(3)
else:
tf.set_random_seed(3)
if verbose:
print("RANDOM SEEDS RESET")
print("TF version: {}".format(tf.__version__))
reset_seeds()
def timeit(func, iterations, *args, _verbose=0, **kwargs):
t0 = time()
for _ in range(iterations):
func(*args, **kwargs)
print(end='.'*int(_verbose))
print("Time/iter: %.4f sec" % ((time() - t0) / iterations))
def make_model_small(batch_shape):
ipt = Input(batch_shape=batch_shape)
x = Conv1D(128, 40, strides=4, padding='same')(ipt)
x = GlobalAveragePooling1D()(x)
x = Dropout(0.5)(x)
x = Dense(64, activation='relu')(x)
out = Dense(1, activation='sigmoid')(x)
model = Model(ipt, out)
model.compile(Adam(lr=1e-4), 'binary_crossentropy')
return model
def make_model_medium(batch_shape):
ipt = Input(batch_shape=batch_shape)
x = ipt
for filters in [64, 128, 256, 256, 128, 64]:
x = Conv1D(filters, 20, strides=1, padding='valid')(x)
x = GlobalAveragePooling1D()(x)
x = Dense(256, activation='relu')(x)
x = Dropout(0.5)(x)
x = Dense(128, activation='relu')(x)
x = Dense(64, activation='relu')(x)
out = Dense(1, activation='sigmoid')(x)
model = Model(ipt, out)
model.compile(Adam(lr=1e-4), 'binary_crossentropy')
return model
def make_model_large(batch_shape):
ipt = Input(batch_shape=batch_shape)
x = Conv1D(64, 400, strides=4, padding='valid')(ipt)
x = Conv1D(128, 200, strides=1, padding='valid')(x)
for _ in range(40):
x = Conv1D(256, 12, strides=1, padding='same')(x)
x = Conv1D(512, 20, strides=2, padding='valid')(x)
x = Conv1D(1028, 10, strides=2, padding='valid')(x)
x = Conv1D(256, 1, strides=1, padding='valid')(x)
x = GlobalAveragePooling1D()(x)
x = Dense(256, activation='relu')(x)
x = Dropout(0.5)(x)
x = Dense(128, activation='relu')(x)
x = Dense(64, activation='relu')(x)
out = Dense(1, activation='sigmoid')(x)
model = Model(ipt, out)
model.compile(Adam(lr=1e-4), 'binary_crossentropy')
return model
def make_data(batch_shape):
return np.random.randn(*batch_shape), \
np.random.randint(0, 2, (batch_shape[0], 1))
def make_data_tf(batch_shape, n_batches, iters):
data = np.random.randn(n_batches, *batch_shape),
trgt = np.random.randint(0, 2, (n_batches, batch_shape[0], 1))
return tf.data.Dataset.from_tensor_slices((data, trgt))#.repeat(iters)
batch_shape_small = (32, 140, 30)
batch_shape_medium = (32, 1400, 30)
batch_shape_large = (32, 14000, 30)
batch_shapes = batch_shape_small, batch_shape_medium, batch_shape_large
make_model_fns = make_model_small, make_model_medium, make_model_large
iterations = [200, 100, 50]
shape_names = ["Small data", "Medium data", "Large data"]
model_names = ["Small model", "Medium model", "Large model"]
def test_all(fit=False, tf_dataset=False):
for model_fn, model_name, iters in zip(make_model_fns, model_names, iterations):
for batch_shape, shape_name in zip(batch_shapes, shape_names):
if (model_fn is make_model_large) and (batch_shape is batch_shape_small):
continue
reset_seeds(reset_graph_with_backend=K)
if tf_dataset:
data = make_data_tf(batch_shape, iters, iters)
else:
data = make_data(batch_shape)
model = model_fn(batch_shape)
if fit:
if tf_dataset:
model.train_on_batch(data.take(1))
t0 = time()
model.fit(data, steps_per_epoch=iters)
print("Time/iter: %.4f sec" % ((time() - t0) / iters))
else:
model.train_on_batch(*data)
timeit(model.fit, iters, *data, _verbose=1, verbose=0)
else:
model.train_on_batch(*data)
timeit(model.train_on_batch, iters, *data, _verbose=1)
cprint(">> {}, {} done <<\n".format(model_name, shape_name), 'blue')
del model
test_all(fit=True, tf_dataset=False)