ЭТО ОТВЕТ : имеет целью предоставить подробное описание проблемы на уровне графического / аппаратного уровня, включая циклы 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;Точный процесс неизвестен, но последний должен включать оптимизацию на уровне графического процессора
TF2 Eager медленнее , чем TF1 Eager - это .. неожиданноСмотрите результаты сравнительного анализа ниже. Различия варьируются от незначительных до значительных, но они последовательны. Не уверен, почему это так - если разработчик TF уточнит, обновит ответ.
TF2 против TF1 : цитирование соответствующих частей разработчика TF,Q. Скотт Чжу, ответ - с моим акцентом и переписыванием:
В нетерпении, во время выполнения необходимо выполнить операции и вернуть числовое значение для каждой строкикод 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 в тензоры. Более того, создается новый граф для каждого отдельного числового значения Python
function
создает отдельный граф для каждого уникального набора входных форм и типов данных .
Один объект tf.function может потребоваться отобразить на несколько графов вычислений под капотом. Это должно быть видно только как производительность (трассировка графиков имеет ненулевые вычислительные и затраты памяти )
Процессоры ввода данных : как и выше, процессор выбирается индивидуально, в зависимости от внутренних флагов, установленных в соответствии с конфигурациями времени выполнения (режим выполнения, формат данных, стратегия распространения). Самый простой случай с Eager, который работает напрямую с массивами Numpy. Для некоторых конкретных примеров см. этот ответ .
РАЗМЕР МОДЕЛИ, РАЗМЕР ДАННЫХ:
- Решающий;на всех моделях и размерах данных не было единой конфигурации.
- Размер данных относительно важен размер модели;для небольших данных и модели могут преобладать издержки при передаче данных (например, с процессора на графический процессор). Аналогично, небольшие служебные процессоры могут работать медленнее при больших объемах данных в расчете на доминирующее время преобразования данных (см.
convert_to_tensor
в разделе «ПРОФИЛЬ») - Скорость отличается в зависимости от цикла передачи и разных обработчиков входных данных для обработки ресурсов.
ЭТАЛОН : измельченное мясо. - Word Document - Электронная таблица Excel
![image](https://i.stack.imgur.com/AJV1e.png)
![image](https://i.stack.imgur.com/7OYcM.png)
![image](https://i.stack.imgur.com/14KMd.png)
![image](https://i.stack.imgur.com/fINXs.png)
Терминология :
- % - все цифры меньше секунд
- % рассчитывается как
(1 - longer_time / shorter_time)*100
;обоснование: нас интересует по какому фактору один быстрее другого;shorter / longer
на самом деле является нелинейным отношением, бесполезным для прямого сравнения - Определение знака%:
- TF2 против TF1:
+
если TF2 быстрее - GvE(Graph против Eager):
+
, если Graph быстрее
- TF2 = TensorFlow 2.0.0 + Keras 2.3.1;TF1 = TensorFlow 1.14.0 + Keras 2.2.5
PROFILER :
![image](https://i.stack.imgur.com/RnSGK.png)
![image](https://i.stack.imgur.com/D3EDP.png)
![image](https://i.stack.imgur.com/4Ywcs.png)
![image](https://i.stack.imgur.com/33LXG.png)
PROFILER - Объяснение : Профилировщик IDE Spyder 3.3.6.
Некоторые функции повторяются в гнездах других;следовательно, трудно отследить точное разделение между функциями «обработки данных» и «обучения», поэтому будет некоторое совпадение - как это выражено в самом последнем результате.
% вычисленных значений по времени выполнения минус время сборки
- Время сборки вычисляется путем суммирования всех (уникальных) сред выполнения, которые были вызваны 1 или 2 раза
- Время поезда вычисляется путем суммирования всех (уникальных) сред выполнения, которые были названы таким же количеством раз, что и число итераций, и временем выполнения некоторых их гнезд
- Функции профилируются в соответствии с их оригиналом *К сожалению, 1257 * имен (то есть
_func = func
будет отображаться как func
), которые смешиваются во время сборки - следовательно, необходимо исключить его
ИСПЫТАТЕЛЬНАЯ СРЕДА :
- Выполненный код снизу с минимальным количеством фоновых задач
- Графический процессор был «прогрет» за несколько итераций до итерирования по времени, как предложено в в этом посте
- 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Процессор с частотой 0,8 ГГц
МЕТОДОЛОГИЯ :
- Эталонный тест для "маленькой", "средней" и "большой" модели и данныхразмеры
- Фикс # параметров для каждого размера модели, независимо от размера входных данных
- В модели "большего размера" больше параметров и слоев
- В данных "большего размера" последовательность длиннее, но одинаковые
batch_size
и num_channels
- В моделях используются только
Conv1D
, Dense
обучаемые слои;RNN, которых избегают для каждой реализации TF. различия - Всегда выполнял одну посадку поезда за пределами цикла бенчмаркинга, чтобы исключить построение модели и графика оптимизатора
- Не использовать разреженные данные (например,
layers.Embedding()
) или разреженные цели (например, SparseCategoricalCrossEntropy()
* 1306)*
ОГРАНИЧЕНИЯ : «полный» ответ объяснил бы каждый возможный цикл поезда и итератор, но это, безусловно, выходит за рамки моих временных возможностей, несуществующей зарплаты или общей необходимости. только так хорошо, как методология - интерпретировать с открытым разумом.
CODE :
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)