ЭТОТ ОТВЕТ : стремится предоставить подробное, графическое / аппаратное описание проблемы - включая циклы поезда TF2 против TF1, процессоры ввода данных и выполнения в режиме Eager vs. Graph. Сводка проблемы и рекомендации по ее решению см. В моем другом ответе.
РЕЗУЛЬТАТ ПРОИЗВОДИТЕЛЬНОСТИ : иногда один быстрее, иногда другой, в зависимости от конфигурации. Что касается TF2 против TF1, то в среднем они примерно на одном уровне, но существенные различия на основе конфигурации существуют, и TF1 превосходит TF2 чаще, чем наоборот. Смотрите «СРАВНЕНИЕ» ниже.
EAGER VS. GRAPH : смысл всего этого ответа для некоторых: согласно моему тестированию, TF2 медленнее, чем TF1. Подробности дальше вниз.
Принципиальное различие между ними заключается в следующем: Graph активно настраивает вычислительную сеть и выполняет ее, когда «ей говорят», тогда как Eager выполняет все при создании. Но история только начинается здесь:
Eager НЕ лишен Graph , и на самом деле он может быть главным образом Graph, вопреки ожиданиям. То, чем он в значительной степени является, является выполненным графом - это включает в себя веса модели и оптимизатора, составляющие большую часть графика.
Eager перестраивает часть собственного графа при исполнении ; Непосредственное следствие того, что Graph не полностью построен - смотрите результаты профилировщика. Это накладные расходы.
Eager медленнее с Numpy входами ; согласно этому Git-комментарию и коду, входные данные Numpy в Eager включают накладные расходы на копирование тензоров из CPU в GPU. Проходя через исходный код, различия в обработке данных очевидны; Eager напрямую передает Numpy, а Graph передает тензоры, которые затем оцениваются Numpy; не уверен в точном процессе, но последний должен включать оптимизацию на уровне GPU
TF2 Eager медленнее, чем TF1 Eager - это ... неожиданно. Смотрите результаты сравнительного анализа ниже. Различия варьируются от незначительных до значительных, но они последовательны. Не уверен, почему это так - если разработчик TF уточнит, обновит ответ.
TF2 против TF1 : цитирование соответствующих частей разработчика TF, Q. Scott Zhu's, ответ - с моим акцентом и переписыванием:
В нетерпении, среда выполнения должна выполнить операции и вернуть числовое значение для каждой строки кода Python. Характер выполнения одного шага приводит к его замедлению .
В TF2 Keras использует функцию tf.function для построения своего графика для обучения, оценки и прогнозирования. Мы называем их «функцией исполнения» для модели. В TF1 «функцией выполнения» был FuncGraph, который разделял некоторый общий компонент как функцию TF, но имел другую реализацию.
Во время этого процесса мы как-то оставили неправильную реализацию для train_on_batch (), test_on_batch () и Forext_on_batch () . Они по-прежнему численно корректны , но функция выполнения для x_on_batch - это чисто функция Python, а не функция Python, заключенная в tf.function. Это вызовет медлительность
В TF2 мы конвертируем все входные данные в набор tf.data.Dataset, с помощью которого мы можем объединить нашу функцию выполнения для обработки одного типа входных данных. При преобразовании набора данных могут быть некоторые накладные расходы , и я думаю, что это единовременные накладные расходы, а не цена за пакет
С последним предложением последнего абзаца выше и последним пунктом нижеследующего абзаца:
Чтобы преодолеть медлительность в активном режиме, у нас есть функция @ tf.function, которая превратит функцию python в граф. При подаче числового значения, такого как массив np, тело функции tf.f конвертируется в статический график, оптимизируется и возвращает конечное значение, которое является быстрым и должно иметь производительность, аналогичную режиму графика TF1.
Я не согласен - согласно моим результатам профилирования, которые показывают, что обработка входных данных в Eager значительно медленнее, чем в Graph. Кроме того, не уверены, tf.data.Dataset
в частности, но Eager неоднократно вызывает несколько одинаковых методов преобразования данных - см. Профилировщик.
И, наконец, связанный коммит dev: значительное количество изменений для поддержки циклов Keras v2 .
Петли поезда : в зависимости от (1) Eager vs. Graph; (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
Каждый из них обрабатывает распределение ресурсов по-разному и влияет на производительность и возможности.
Петли поезда: fit
против train_on_batch
, keras
противtf.keras
: каждая из четырех использует разные петли поезда, хотя, возможно, не во всех возможных комбинациях. keras
' fit
, например, использует форму fit_loop
, например training_arrays.fit_loop()
, и ее train_on_batch
можно использовать K.function()
. tf.keras
имеет более сложную иерархию, частично описанную в предыдущем разделе.
Обучающие циклы: документация - соответствующая исходная документация по некоторым различным методам выполнения:
В отличие от других операций TensorFlow, мы не преобразуем числовые входы Python в тензоры. Кроме того, новый график генерируется для каждого отдельного числового значения питона
function
создает отдельный график для каждого уникального набора входных форм и типов данных .
Один объект tf.function может потребоваться отобразить на несколько графов вычислений под капотом. Это должно быть видно только как производительность (трассировка графиков имеет ненулевые вычислительные затраты и стоимость памяти )
Процессоры ввода данных : аналогично вышеописанному, процессор выбирается индивидуально в зависимости от внутренних флагов, установленных в соответствии с конфигурациями времени выполнения (режим выполнения, формат данных, стратегия распределения). Самый простой случай с Eager, который работает напрямую с массивами Numpy. Для некоторых конкретных примеров, смотрите этот ответ .
РАЗМЕР МОДЕЛИ, РАЗМЕР ДАННЫХ:
- Является решающим; ни одна конфигурация не увенчалась всеми моделями и размерами данных.
- Размер данных относительно размера модели важен; для небольших данных и модели могут преобладать издержки при передаче данных (например, с процессора на графический процессор). Аналогичным образом, небольшие служебные процессоры могут работать медленнее для больших данных
convert_to_tensor
в зависимости от времени преобразования данных (см. «ПРОФИЛЬЕР»).
- Скорость различается в зависимости от цикла обработки данных и обработки входных данных разными способами.
ЭТАЛОНЫ : измельченное мясо. - Документ Word - Электронная таблица Excel
Терминология :
- числа без% - все секунды
- % рассчитывается как
(1 - longer_time / shorter_time)*100
; обоснование: нас интересует , какой фактор один быстрее другого; shorter / longer
на самом деле нелинейное отношение, не полезно для прямого сравнения
- Определение знака%:
- TF2 против TF1:
+
если TF2 быстрее
- GvE (Graph против Eager):
+
если график быстрее
- TF2 = TensorFlow 2.0.0 + Keras 2.3.1; TF1 = TensorFlow 1.14.0 + Keras 2.2.5
PROFILER :
PROFILER - Объяснение : Spyder 3.3.6 IDE профилировщик.
Некоторые функции повторяются в гнездах других; следовательно, трудно отследить точное разделение между функциями «обработки данных» и «обучения», поэтому будет некоторое совпадение - как это выражено в самом последнем результате.
% вычисленных значений во время выполнения минус время сборки
- Время сборки вычисляется путем суммирования всех (уникальных) сред выполнения, которые были вызваны 1 или 2 раза
- Время поезда вычисляется путем суммирования всех (уникальных) сред выполнения, которые были названы таким же количеством раз, что и количество итераций, и временем выполнения некоторых их гнезд
- Функции , к сожалению , профилируются в соответствии с их оригинальными именами (то есть
_func = func
будут профилироваться как func
), которые смешиваются во время сборки - следовательно, необходимо исключить это
ИСПЫТАТЕЛЬНАЯ СРЕДА :
- Выполненный код внизу с минимальным количеством фоновых задач
- 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, 24 ГБ оперативной памяти DDR4 2,4 МГц, процессор i7-7700HQ 2,8 ГГц
МЕТОДОЛОГИЯ :
- Ориентир «маленькая», «средняя» и «большая» модель и размеры данных
- Исправлено количество параметров для каждого размера модели, независимо от размера входных данных.
- «Большая» модель имеет больше параметров и слоев
- «Большие» данные имеют более длинную последовательность, но такие же
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)