UPDATE - 1/15/2020 : текущая лучшая практика для небольших объемов партий должны кормить входы модели непосредственно - то есть preds = model(x)
, и если слои ведут себя по- разному на поезда / вывода, model(x, training=False)
. Для последнего коммита это теперь задокументировано .
Я не тестировал их, но в обсуждении Git также стоит попробовать predict_on_batch()
- особенно с улучшениями в TF 2.1.
ULTIMATE Виновник : self._experimental_run_tf_function = True
. Это экспериментально . Но это на самом деле не плохо.
Любой читатель TensorFlow, читающий: очистите свой код . Это беспорядок. И это нарушает важные методы кодирования, такие как одна функция выполняет одну вещь ; _process_inputs
делает намного больше, чем «входные данные процесса», то же самое для _standardize_user_data
. «Я не заплатил достаточно» , - но вы делаете оплату, в дополнительное время , потраченного понимание своего собственного материала, а также пользователи , заполняющих страницу Проблемы , связанные с ошибками проще решить с более ясным кодом.
РЕЗЮМЕ : это немного медленнее compile()
.
compile()
устанавливает внутренний флаг, который назначает другую функцию прогнозирования predict
. Эта функция создает новый граф при каждом вызове, замедляя его по сравнению с некомпилированным. Однако разница проявляется только тогда, когда время в поезде намного меньше времени обработки данных . Если мы увеличим размер модели по крайней мере до среднего, оба станут равными. Смотрите код внизу.
Это небольшое увеличение времени обработки данных более чем компенсируется усиленной графикой. Поскольку более эффективно хранить только один граф модели, один прекомпилятор отбрасывается. Тем не менее : если ваша модель мала по сравнению с данными, вам лучше не делать compile()
вывод из модели. Смотрите мой другой ответ для обходного пути.
ЧТО МНЕ ДЕЛАТЬ?
Сравните производительность модели скомпилированной и не скомпилированной, как в коде внизу.
- Скомпилировано быстрее : запустить
predict
на скомпилированной модели.
- Компилируется медленнее : запускается
predict
на некомпилированной модели.
Да, оба варианта возможны, и это будет зависеть от (1) размера данных; (2) размер модели; (3) аппаратное обеспечение. Код внизу на самом деле показывает, что скомпилированная модель работает быстрее, но 10 итераций - небольшой пример. См. "Обходные пути" в моем другом ответе для "с практическими рекомендациями".
ДЕТАЛИ :
Это заняло некоторое время для отладки, но было весело. Ниже я опишу ключевых преступников, которых я обнаружил, приведу соответствующую документацию и покажу результаты профилировщика, которые привели к окончательному узкому месту.
( FLAG == self.experimental_run_tf_function
для краткости)
Model
по умолчанию создает экземпляр с FLAG=False
. compile()
устанавливает его в True
.
predict()
включает в себя приобретение функции прогнозирования, func = self._select_training_loop(x)
- Без каких-либо специальных kwargs, переданных
predict
и compile
, все другие флаги таковы, что:
- (А)
FLAG==True
->func = training_v2.Loop()
- (B)
FLAG==False
->func = training_arrays.ArrayLikeTrainingLoop()
- Исходя из строки документации исходного кода , (A) сильно зависит от графов, использует больше стратегии распределения, а операции склонны к созданию и уничтожению элементов графа, которые «могут» (влияют) на производительность.
Истинный виновник : _process_inputs()
составляет 81% времени выполнения . Его основной компонент? _create_graph_function()
, 72% выполнения . Этот метод даже не существует для (B) . Однако использование модели среднего размера _process_inputs
составляет менее 1% времени выполнения . Код внизу и результаты профилирования следуют.
ПРОЦЕССОРЫ ДАННЫХ :
(A) :, <class 'tensorflow.python.keras.engine.data_adapter.TensorLikeDataAdapter'>
используется в _process_inputs()
. Соответствующий исходный код
(B) :, numpy.ndarray
возвращается convert_eager_tensors_to_numpy
. Соответствующий исходный код и здесь
ФУНКЦИЯ ИСПОЛНЕНИЯ МОДЕЛИ (например, прогноз)
(A) : функция распределения , а здесь
(Б) : функция распределения (разная) , а здесь
PROFILER : результаты для кода в моем другом ответе «маленькая модель» и в этом ответе «средняя модель»:
Крошечная модель : 1000 итераций,compile()
Крошечная модель : 1000 итераций, нет compile()
Средняя модель : 10 итераций
ДОКУМЕНТАЦИЯ (косвенно) о влиянии compile()
: источника
В отличие от других операций TensorFlow, мы не преобразуем числовые входы Python в тензоры. Кроме того, новый граф генерируется для каждого отдельного числового значения питона , например, вызывая g(2)
и g(3)
генерирует два новых графика
function
создает отдельный график для каждого уникального набора входных форм и типов данных . Например, следующий фрагмент кода приведет к трассировке трех отдельных графиков, поскольку каждый вход имеет различную форму
Один объект tf.function может потребоваться отобразить на несколько графов вычислений под капотом. Это должно быть видно только как производительность (трассировка графиков имеет ненулевые вычислительные затраты и стоимость памяти ), но не должна влиять на правильность программы
Контрпример :
from tensorflow.keras.layers import Input, Dense, LSTM, Bidirectional, Conv1D
from tensorflow.keras.layers import Flatten, Dropout
from tensorflow.keras.models import Model
import numpy as np
from time import time
def timeit(func, arg, iterations):
t0 = time()
for _ in range(iterations):
func(arg)
print("%.4f sec" % (time() - t0))
batch_size = 32
batch_shape = (batch_size, 400, 16)
ipt = Input(batch_shape=batch_shape)
x = Bidirectional(LSTM(512, activation='relu', return_sequences=True))(ipt)
x = LSTM(512, activation='relu', return_sequences=True)(ipt)
x = Conv1D(128, 400, 1, 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)
X = np.random.randn(*batch_shape)
timeit(model.predict, X, 10)
model.compile('adam', loss='binary_crossentropy')
timeit(model.predict, X, 10)
Выходы :
34.8542 sec
34.7435 sec