Почему TensorFlow 2 намного медленнее, чем TensorFlow 1? - PullRequest
69 голосов
/ 18 октября 2019

Многие пользователи указали, что это причина для перехода на Pytorch, но я пока не нашел оправдания / объяснения для того, чтобы пожертвовать самым важным практическим качеством, скоростью и стремлением к исполнению.

Нижепроизводительность бенчмаркинга кода, TF1 против TF2 - с TF1, работающим в любом месте от 47% до 276% быстрее .

Мой вопрос таков: Что на уровне графического или аппаратного обеспечения приводит к такому существенному замедлению?


Ищете подробный ответ - яуже знаком с широкими понятиями. Соответствующий Git

Спецификации : CUDA 10.0.130, cuDNN 7.4.2, Python 3.7.4, Windows 10, GTX 1070


Результаты тестов :

image


ОБНОВЛЕНИЕ : отключение выполнения Eager согласно приведенному ниже коду делает не Помогите. Поведение, однако, противоречиво: иногда работа в графическом режиме значительно помогает, в других случаях она работает на медленнее относительно Eager.

Поскольку разработчики TF нигде не появляются, я будусам исследую этот вопрос - могу следить за прогрессом в связанной проблеме Github.

ОБНОВЛЕНИЕ 2 : тонны экспериментальных результатов, которыми можно поделиться, вместе с объяснениями;должно быть сделано сегодня.


Код теста :

# use tensorflow.keras... to benchmark tf.keras; used GPU for all above benchmarks
from keras.layers import Input, Dense, LSTM, Bidirectional, Conv1D
from keras.layers import Flatten, Dropout
from keras.models import Model
from keras.optimizers import Adam
import keras.backend as K
import numpy as np
from time import time

batch_shape = (32, 400, 16)
X, y = make_data(batch_shape)

model_small = make_small_model(batch_shape)
model_small.train_on_batch(X, y)  # skip first iteration which builds graph
timeit(model_small.train_on_batch, 200, X, y)

K.clear_session()  # in my testing, kernel was restarted instead

model_medium = make_medium_model(batch_shape)
model_medium.train_on_batch(X, y)  # skip first iteration which builds graph
timeit(model_medium.train_on_batch, 10, X, y)

Используемые функции :

def timeit(func, iterations, *args):
    t0 = time()
    for _ in range(iterations):
        func(*args)
    print("Time/iter: %.4f sec" % ((time() - t0) / iterations))

def make_small_model(batch_shape):
    ipt   = Input(batch_shape=batch_shape)
    x     = Conv1D(128, 400, strides=4, padding='same')(ipt)
    x     = Flatten()(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_medium_model(batch_shape):
    ipt   = Input(batch_shape=batch_shape)
    x     = Bidirectional(LSTM(512, activation='relu', return_sequences=True))(ipt)
    x     = LSTM(512, activation='relu', return_sequences=True)(x)
    x     = Conv1D(128, 400, strides=4, padding='same')(x)
    x     = Flatten()(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))

Ответы [ 2 ]

25 голосов
/ 01 ноября 2019

VERDICT : это не , ЕСЛИ вы знаете, что делаете. Но если вы не , это может стоить вам очень дорого - в среднем на несколько обновлений графического процессора и в худшем случае на несколько графических процессоров.


ЭТО ОТВЕТ: цель состоит в том, чтобы предоставить общее описание проблемы, а также рекомендации по выбору конфигурации обучения, соответствующей вашим потребностям. Подробное низкоуровневое описание, которое включает в себя все результаты бенчмаркинга + используемый код, см. В моем другом ответе.

Я буду обновлять свои ответы с дополнительной информацией, если узнаю, - могу добавить в закладки/ "пометьте звездой" этот вопрос для справки.


РЕЗЮМЕ ВЫПУСКА : как подтверждено разработчиком TensorFlow, Q. Скоттом Чжу, TF2-ориентированная разработка на Eagerвыполнение и тесная интеграция с Keras, что повлекло за собой быстрые изменения в источнике TF, в том числе на уровне графов. Преимущества: значительно расширенные возможности обработки, распространения, отладки и развертывания. Однако стоимость некоторых из них - скорость.

Однако дело обстоит гораздо сложнее. Это не только TF1 против TF2 - факторы, приводящие к значительным различиям в скорости поезда, включают:

  1. TF2 против TF1
  2. Режим «Стремление против графика»
  3. keras против tf.keras
  4. numpy против tf.data.Dataset против ...
  5. train_on_batch() против fit()
  6. GPU и CPU

К сожалению, почти ничего из вышеперечисленного не зависит от другого, и каждый может по крайней мере удвоить время выполнения относительно другого. К счастью, вы можете определить, что будет работать лучше всего систематически и с помощью нескольких ярлыков - как я покажу.


ЧТО СЛЕДУЕТ ДЕЛАТЬ? В настоящее время единственный способis - эксперимент для вашей конкретной модели, данных и оборудования. Ни одна отдельная конфигурация не всегда будет работать лучше, но есть , которые делают и не делают, чтобы упростить ваш поиск:

>> DO:

  • train_on_batch() + numpy + tf.keras + TF1 + Eager / Graph
  • train_on_batch() + numpy + tf.keras + TF2 + Graph
  • fit() +numpy + tf.keras + TF1 / TF2 + График + большая модель и данные

>> НЕ:

  • fit() + numpy + keras для малых и средних моделей и данных
  • fit() + numpy + tf.keras + TF1 / TF2 + Eager
  • train_on_batch() + numpy + keras + TF1 + Eager

  • [Major] tf.python.keras;он может работать в 10-100 раз медленнее и с большим количеством ошибок; больше информации

    • Включает layers, models, optimizers и связанный с ними "готовый" импорт использования;ops, utils и связанные с ними «частные» импорты хороши, но чтобы быть уверенным, проверьте alts, и используются ли они в tf.keras

См. кодвнизу моего другого ответа для примера настройки бенчмаркинга. Приведенный выше список основан главным образом на таблицах «ЭТАЛОНОВ» в другом ответе.


ОГРАНИЧЕНИЯ из вышеупомянутых DO & DON'T's:

  • Этот вопрос называется «Почему TF2 намного медленнее, чем TF1?», И хотя его тело явно относится к тренировкам, вопрос не ограничивается этим; логический вывод также подвержен существенным различиям в скорости, даже в пределах одной и той же версии TF, импорта, формата данных и т. Д. - см. этот ответ .
  • RNN, вероятно, заметно изменят сетку данных в другом ответе, так как они были улучшены в TF2
  • Модели, используемые в основном Conv1D и Dense - без RNN, редкие данные / цели, 4 / 5D входы и другие конфиги
  • Входные данные ограничены numpy и tf.data.Dataset, в то время как существует много других форматов;см. другой ответ
  • Использован графический процессор;результаты будут отличаться на процессоре. Фактически, когда я задал вопрос, моя CUDA не была должным образом сконфигурирована, и некоторые результаты были основаны на CPU.

Почему TF2 жертвовал самым практичным качеством, скорость, для нетерпеливого выполнения? Это не имеет, ясно - граф все еще доступен. Но если вопрос «зачем вообще стремиться»:

  • Превосходная отладка : вы, вероятно, сталкивались с множеством вопросов, спрашивающих «как получить выходные данные промежуточного уровня» или «как проверить вес»;с нетерпением, это (почти) так же просто, как .__dict__. Граф, напротив, требует знакомства со специальными внутренними функциями, что значительно усложняет весь процесс отладки и самоанализа.
  • Более быстрое создание прототипов : согласно идеям, аналогичным приведенным выше;более быстрое понимание = больше времени осталось для фактического DL.

КАК ВКЛЮЧИТЬ / ОТКЛЮЧИТЬ EAGER?

tf.enable_eager_execution()  # TF1; must be done before any model/tensor creation
tf.compat.v1.disable_eager_execution() # TF2; above holds

ДОПОЛНИТЕЛЬНАЯ ИНФОРМАЦИЯ :

  • Осторожно с _on_batch() методами в TF2;согласно разработчикам TF, они все еще используют более медленную реализацию, но не намеренно - то есть это должно быть исправлено. Подробности см. В другом ответе.

ЗАПРОСЫ НА TENSORFLOW DEVS :

  1. Пожалуйста, исправьте train_on_batch() и аспект производительности вызоваfit() итеративно;пользовательские петли поезда важны для многих, особенно для меня.
  2. Добавьте документацию / документальное упоминание об этих различиях в производительности для сведения пользователей.
  3. Увеличьте общую скорость выполнения, чтобы не допускать скачки писков в Pytorch.

Благодарности : Благодаря


ОБНОВЛЕНИЯ :

  • 11/14/19 - нашел модель (в моем реальном приложении)это работает медленнее на TF2 для всех * конфигураций с входными данными Numpy. Различия составляли 13-19%, в среднем 17%. Однако различия между keras и tf.keras были более значительными: 18-40% , средн. 32% (оба TF1 и 2). (* - за исключением Eager, для которого TF2 OOM'd)
20 голосов
/ 01 ноября 2019

ЭТО ОТВЕТ : имеет целью предоставить подробное описание проблемы на уровне графического / аппаратного уровня, включая циклы 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

image

image

image


Терминология :

  • % - все цифры меньше секунд
  • % рассчитывается как (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

image

image

image


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)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...