Keras niespójny czas przewidywania

17

Próbowałem oszacować czas prognozy mojego modelu Keras i zdałem sobie sprawę z czegoś dziwnego. Poza tym, że normalnie jest dość szybki, co jakiś czas model potrzebuje dość długo, aby wymyślić prognozę. Co więcej, czasy te również wydłużają się wraz z dłuższym działaniem modelu. Dodałem minimalny działający przykład, aby odtworzyć błąd.

import time
import numpy as np
from sklearn.datasets import make_classification
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Flatten

# Make a dummy classification problem
X, y = make_classification()

# Make a dummy model
model = Sequential()
model.add(Dense(10, activation='relu',name='input',input_shape=(X.shape[1],)))
model.add(Dense(2, activation='softmax',name='predictions'))
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])

model.fit(X, y, verbose=0, batch_size=20, epochs=100)

for i in range(1000):
    # Pick a random sample
    sample = np.expand_dims(X[np.random.randint(99), :], axis=0)
    # Record the prediction time 10x and then take the average
    start = time.time()
    for j in range(10):
        y_pred = model.predict_classes(sample)
    end = time.time()
    print('%d, %0.7f' % (i, (end-start)/10))

Czas nie zależy od próbki (jest ona wybierana losowo). Jeśli test się powtórzy, wskaźniki w pętli for, w przypadku których przewidywanie trwa dłużej, będą (prawie) takie same.

wprowadź opis zdjęcia tutaj

Używam:

tensorflow 2.0.0
python 3.7.4

W przypadku mojej aplikacji muszę zagwarantować wykonanie w określonym czasie. Jest to jednak niemożliwe, biorąc pod uwagę takie zachowanie. Co idzie nie tak? Czy to błąd w Kerasie czy błąd w backendzie tensorflow?

EDYCJA: predict_on_batchpokazuje to samo zachowanie, jednak bardziej rzadkie: wprowadź opis zdjęcia tutaj

y_pred = model(sample, training=False).numpy() pokazuje również pewne duże wartości odstające, jednak nie rosną. wprowadź opis zdjęcia tutaj

EDYCJA 2: Uaktualniłem do najnowszej wersji tensorflow 1 (1.15). Nie tylko problem już nie istnieje, ale znacznie poprawiono „normalny” czas przewidywania! Nie uważam tych dwóch skoków za problematyczne, ponieważ nie pojawiły się, gdy powtórzyłem test (przynajmniej nie przy tych samych wskaźnikach i liniowo rosną) i są procentowe nie tak duże jak na pierwszym wykresie. wprowadź opis zdjęcia tutaj

Możemy zatem stwierdzić, że wydaje się, że jest to problem nieodłączny od tensorflow 2.0, który wykazuje podobne zachowanie w innych sytuacjach, jak wspomniano w @OverLordGoldDragon.

ga97dil
źródło
To zachowanie brzmi przewidywalnie ... wzrost jest w pewnym sensie liniowy. Jeśli uwzględnisz to zachowanie w obliczeniach czasu, czy nie przejdzie? --- Nie wiem, co się tam dzieje ... ale co się stanie, jeśli spróbujesz predict_on_batchzamiast tego?
Daniel Möller
Kolejna próba, co dzieje się zi y_pred = model(sample).numpy()z y_pred = model(sample, training=False).numpy()?
Daniel Möller
Dodałem swoje ustalenia. Wersje numpy nie wydają się zachowywać.
ga97dil
Ale predict_classeswciąż jest najszybszy ... wydaje się. Co powiesz na tylko predict?
Daniel Möller
1
Zakładam, że może to być czyszczenie pamięci ...
Daniel Möller

Odpowiedzi:

10

TF2 ogólnie wykazuje słabe i podobne do błędów zarządzanie pamięcią w kilku przypadkach, które napotkałem - krótki opis tu i tutaj . W szczególności w przypadku prognoz najbardziej wydajną metodą karmienia jest metoda model(x)bezpośrednia - patrz tutaj i powiązane dyskusje.

W skrócie: model(x)działa za pośrednictwem jego ITS __call__metodą (które dziedziczy base_layer.Layer), a predict(), predict_classes()itp wiązać się specjalną funkcję pętli poprzez _select_training_loop(); każda z nich wykorzystuje inne metody przetwarzania i przetwarzania danych odpowiednie dla różnych przypadków użycia, a model(x)w 2.1 został zaprojektowany specjalnie w celu uzyskania najszybszej wydajności małego modelu / małej partii (i być może dowolnej wielkości) (i wciąż najszybszej w 2.0).

Cytując programistę TensorFlow z powiązanych dyskusji:

Możesz przewidzieć wynik za pomocą wywołania modelu, a nie przewidywania modelu, tzn. Wywołanie model(x)przyspieszyłoby to, ponieważ nie ma części „konwersja do zestawu danych”, a także bezpośrednio wywołuje buforowane tf.function.

Uwaga : nie powinno to stanowić problemu w 2.1, a zwłaszcza 2.2 - ale przetestuj każdą metodę. Zdaję sobie również sprawę, że nie odpowiada to bezpośrednio na twoje pytanie dotyczące skoków czasu; Podejrzewam, że ma to związek z mechanizmami buforowania Eager, ale najpewniejszym sposobem na określenie tego jest użycie TF Profiler, które jest zepsute w 2.1.


Aktualizacja : w związku z rosnącymi skokami, możliwym ograniczeniem GPU; wykonałeś ~ 1000 iterów, zamiast tego spróbuj 10 000 - w końcu wzrost powinien się skończyć. Jak zauważyłeś w swoich komentarzach, nie dzieje się tak z model(x); ma sens, gdy w grę wchodzi jeden krok GPU („konwersja do zestawu danych”).

Aktualizacja 2 : możesz napotkać tutaj błąd deweloperów , jeśli napotkasz ten problem; głównie śpiewam tam

OverLordGoldDragon
źródło
To dobra odpowiedź na pytanie, dlaczego jedna metoda jest wolniejsza, ale nie tłumaczy rosnącego czasu wykonywania w wielu testach.
LLSv2.0
1
@ LLSv2.0 Nie do końca pewny siebie, ale zaktualizowane odpowiedź - ja wciąż czeka na odpowiedź z deweloperów, kiedy podniósł tę kwestię sobie tutaj
OverLordGoldDragon
1
@ ga97dil Tak, więc nie mam wyjaśnień - spróbuj zapytać na Github, ale możesz napotkać długie czasy odpowiedzi.
OverLordGoldDragon
1
@ ga97dil Rzeczywiście, TF1 może być znacznie szybszy niż TF2 - chociaż TF 2.1 warto wypróbować dla małych modeli i zestawów danych, ponieważ jest to najszybszy trening w moich testach porównawczych (nie przewidywałem). Co ważniejsze, jeśli kiedykolwiek użyjesz TF2, zdecydowanie sugeruję przetestowanie odtwarzalności w programie Graph vs. Eager; wyniki mogą się bardzo różnić w TF 2.1.
OverLordGoldDragon
1
Dodałem twój post do wątku Git i mój post TF2 vs. TF1. Dzięki za poinformowanie mnie, że problem znika w TF 1.
OverLordGoldDragon
2

Chociaż nie potrafię wyjaśnić niespójności w czasie wykonywania, mogę zalecić, aby spróbować przekonwertować model na TensorFlow Lite, aby przyspieszyć przewidywania dotyczące pojedynczych rekordów danych lub małych partii.

Przeprowadziłem test porównawczy dla tego modelu:

model = tf.keras.models.Sequential([
    tf.keras.layers.Dense(384, activation='elu', input_shape=(256,)),
    tf.keras.layers.Dense(384, activation='elu'),
    tf.keras.layers.Dense(256, activation='elu'),
    tf.keras.layers.Dense(128, activation='elu'),
    tf.keras.layers.Dense(32, activation='tanh')
])

Czasy prognoz dla pojedynczych rekordów były następujące:

  1. model.predict(input): 18ms
  2. model(input): 1,3 ms
  3. Model przekonwertowany na TensorFlow Lite: 43us

Czas konwersji modelu wynosił 2 sekundy.

Poniższa klasa pokazuje, jak przekonwertować i korzystać z modelu oraz zapewnia predictmetodę podobną do modelu Keras. Należy pamiętać, że należy go zmodyfikować w celu użycia z modelami, które nie mają tylko jednego wejścia 1-D i jednego wyjścia 1-D.

class LiteModel:

    @classmethod
    def from_file(cls, model_path):
        return LiteModel(tf.lite.Interpreter(model_path=model_path))

    @classmethod
    def from_keras_model(cls, kmodel):
        converter = tf.lite.TFLiteConverter.from_keras_model(kmodel)
        tflite_model = converter.convert()
        return LiteModel(tf.lite.Interpreter(model_content=tflite_model))

    def __init__(self, interpreter):
        self.interpreter = interpreter
        self.interpreter.allocate_tensors()
        input_det = self.interpreter.get_input_details()[0]
        output_det = self.interpreter.get_output_details()[0]
        self.input_index = input_det["index"]
        self.output_index = output_det["index"]
        self.input_shape = input_det["shape"]
        self.output_shape = output_det["shape"]
        self.input_dtype = input_det["dtype"]
        self.output_dtype = output_det["dtype"]

    def predict(self, inp):
        inp = inp.astype(self.input_dtype)
        count = inp.shape[0]
        out = np.zeros((count, self.output_shape[1]), dtype=self.output_dtype)
        for i in range(count):
            self.interpreter.set_tensor(self.input_index, inp[i:i+1])
            self.interpreter.invoke()
            out[i] = self.interpreter.get_tensor(self.output_index)[0]
        return out

    def predict_single(self, inp):
        """ Like predict(), but only for a single record. The input data can be a Python list. """
        inp = np.array([inp], dtype=self.input_dtype)
        self.interpreter.set_tensor(self.input_index, inp)
        self.interpreter.invoke()
        out = self.interpreter.get_tensor(self.output_index)
        return out[0]

Pełny kod testu i wykres można znaleźć tutaj: https://medium.com/@micwurm/using-tensorflow-lite-to-speed-up-predictions-a3954886eb98

Michał
źródło
Fajnie, nigdy wcześniej tego nie próbowałem, ale może warto spróbować. Dzięki za podpowiedź!
ga97dil