AKTUALIZACJA - 15.01.2020 : prąd najlepsze praktyki dla małych seriach powinno karmić wejść do modelu bezpośrednio - tj preds = model(x)
, a jeśli warstwy zachowują się inaczej w pociągu / wnioskowania model(x, training=False)
. Według ostatniego zatwierdzenia jest to teraz udokumentowane .
Nie przeprowadziłem testów porównawczych, ale na dyskusję na Git warto też spróbować predict_on_batch()
- zwłaszcza z ulepszeniami w TF 2.1.
ULTIMATE winowajcą : self._experimental_run_tf_function = True
. To jest eksperymentalne . Ale tak naprawdę nie jest źle.
Do każdego czytnika TensorFlow: wyczyść swój kod . To bałagan. I narusza ważne praktyki kodowania, takie jak jedna funkcja robi jedną rzecz ; _process_inputs
robi o wiele więcej niż „dane wejściowe procesu”, to samo dla _standardize_user_data
. „Ja nie zapłacił za mało” - ale zrobić wynagrodzenia, w doliczonym czasie spędzonym zrozumienia własne rzeczy, aw użytkowników wypełniających swoją stronę problemy z błędami łatwiej rozwiązane z jaśniejszym kodu.
PODSUMOWANIE : jest tylko trochę wolniejszy compile()
.
compile()
ustawia flagę wewnętrzną, która przypisuje inną funkcję predykcji predict
. Ta funkcja tworzy nowy wykres przy każdym wywołaniu, spowalniając go względem nieskompilowanego. Różnica jest jednak wyraźna tylko wtedy, gdy czas pociągu jest znacznie krótszy niż czas przetwarzania danych . Jeśli zwiększymy rozmiar modelu do co najmniej średniej, oba staną się równe. Zobacz kod na dole.
Ten niewielki wzrost czasu przetwarzania danych jest z nadwyżką kompensowany przez zwiększoną wydajność wykresu. Ponieważ utrzymywanie tylko jednego wykresu modelu jest bardziej wydajne, jeden przed kompilacją jest odrzucany. Niemniej jednak : jeśli twój model jest mały w stosunku do danych, lepiej jest bez compile()
wnioskowania o model. Zobacz moją drugą odpowiedź w celu obejścia tego problemu.
CO POWINIENEM ZROBIĆ?
Porównaj wydajność modelu skompilowaną z nieskompilowaną, jak mam w kodzie na dole.
- Kompilacja jest szybsza : uruchom
predict
na skompilowanym modelu.
- Kompilacja jest wolniejsza : uruchom
predict
na nieskompilowanym modelu.
Tak, oba są możliwe i będą zależeć od (1) wielkości danych; (2) rozmiar modelu; (3) sprzęt. Kod u dołu faktycznie pokazuje, że skompilowany model jest szybszy, ale 10 iteracji to mała próbka. Zobacz „obejścia” w mojej innej odpowiedzi na „instrukcje”.
SZCZEGÓŁY :
Debugowanie zajęło trochę czasu, ale było zabawne. Poniżej opisuję kluczowych sprawców, których odkryłem, przytaczam odpowiednią dokumentację i pokazuję wyniki profilowania, które doprowadziły do ostatecznego wąskiego gardła.
( FLAG == self.experimental_run_tf_function
dla zwięzłości)
Model
domyślnie tworzy się z FLAG=False
. compile()
ustawia to True
.
predict()
obejmuje nabycie funkcji przewidywania, func = self._select_training_loop(x)
- Bez żadnych specjalnych kwargów przekazywanych do
predict
i compile
, wszystkie inne flagi są takie, że:
- (A)
FLAG==True
->func = training_v2.Loop()
- (B)
FLAG==False
->func = training_arrays.ArrayLikeTrainingLoop()
- Od kodu źródłowego docstring , (A) jest silnie zależny wykres, wykorzystuje bardziej strategii dystrybucyjnej i PO są podatne na tworzenie i niszczenie elementów wykresu, „może” (nie) udarność.
Prawdziwy winowajca : _process_inputs()
, co stanowi 81% starcie . Jego główny składnik? _create_graph_function()
, 72% czasu działania . Ta metoda nawet nie istnieje dla (B) . Jednak użycie modelu średniej wielkości _process_inputs
stanowi mniej niż 1% czasu działania . Kod u dołu i wyniki profilowania.
PROCESORZY DANYCH :
(A) :, <class 'tensorflow.python.keras.engine.data_adapter.TensorLikeDataAdapter'>
używane w _process_inputs()
. Odpowiedni kod źródłowy
(B) : numpy.ndarray
, zwrócony przez convert_eager_tensors_to_numpy
. Odpowiedni kod źródłowy i tutaj
FUNKCJA WYKONANIA MODELU (np. Przewidywanie)
(A) : funkcja dystrybucji i tutaj
(B) : funkcja dystrybucji (inna) i tutaj
PROFILER : wyniki dla kodu w mojej drugiej odpowiedzi „malutki model”, a w tej odpowiedzi „średni model”:
Mały model : 1000 iteracji,compile()
Mały model : 1000 iteracji, nie compile()
Model średni : 10 iteracji
DOKUMENTACJA (pośrednio) na temat wpływu compile()
: źródła
W przeciwieństwie do innych operacji TensorFlow, nie przekształcamy danych liczbowych pytona na tensory. Ponadto generowany jest nowy wykres dla każdej odrębnej wartości liczbowej pytona , na przykład wywołania g(2)
i g(3)
wygeneruje dwa nowe wykresy
function
tworzy osobny wykres dla każdego unikalnego zestawu kształtów wejściowych i typów danych . Na przykład następujący fragment kodu spowoduje prześledzenie trzech różnych wykresów, ponieważ każde wejście ma inny kształt
Pojedynczy obiekt tf.function może wymagać odwzorowania na wiele wykresów obliczeniowych pod maską. Powinno to być widoczne tylko jako wydajność (wykresy śledzenia mają niezerowe koszty obliczeniowe i pamięci ), ale nie powinny wpływać na poprawność programu
PRZYKŁAD :
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)
Wyjścia :
34.8542 sec
34.7435 sec