Temps de prédiction Keras incohérent

17

J'ai essayé d'obtenir une estimation du temps de prédiction de mon modèle de kéros et j'ai réalisé quelque chose d'étrange. En plus d'être assez rapide normalement, le modèle a besoin de temps en temps pour établir une prédiction. Et non seulement cela, ces temps augmentent également plus le modèle fonctionne. J'ai ajouté un exemple de travail minimal pour reproduire l'erreur.

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))

Le temps ne dépend pas de l'échantillon (il est prélevé au hasard). Si le test est répété, les indices de la boucle for où la prédiction prend plus de temps seront à nouveau (presque) les mêmes.

entrez la description de l'image ici

J'utilise:

tensorflow 2.0.0
python 3.7.4

Pour ma candidature, je dois garantir l'exécution dans un certain temps. Ceci est cependant impossible compte tenu de ce comportement. Qu'est-ce qui ne va pas? Est-ce un bug dans Keras ou un bug dans le backend tensorflow?

EDIT: predict_on_batchmontre le même comportement, cependant, plus clairsemé: entrez la description de l'image ici

y_pred = model(sample, training=False).numpy() montre également de fortes valeurs aberrantes, mais elles n'augmentent pas. entrez la description de l'image ici

EDIT 2: J'ai rétrogradé à la dernière version de tensorflow 1 (1.15). Non seulement le problème n'existe plus, mais le temps de prédiction "normal" s'est considérablement amélioré! Je ne vois pas les deux pointes comme problématiques, car elles ne sont pas apparues lorsque j'ai répété le test (du moins pas aux mêmes indices et en augmentant linéairement) et sont en pourcentage pas aussi grandes que dans le premier graphique. entrez la description de l'image ici

Nous pouvons donc conclure que cela semble être un problème inhérent à tensorflow 2.0, qui montre un comportement similaire dans d'autres situations comme le mentionne @OverLordGoldDragon.

ga97dil
la source
Ce comportement semble prévisible ... l'augmentation est plutôt linéaire. Si vous incluez ce comportement dans votre calcul de temps, cela n'ira-t-il pas? --- Je ne sais pas ce qui se passe là-bas .... mais que se passe-t-il si vous essayez à la predict_on_batchplace?
Daniel Möller
Une autre tentative, que se passe-t-il avec y_pred = model(sample).numpy()et avec y_pred = model(sample, training=False).numpy()?
Daniel Möller
J'ai ajouté mes conclusions. Les versions numpy ne semblent pas montrer le comportement.
ga97dil
Mais predict_classesc'est toujours le plus rapide ... semble-t-il. Et juste predict?
Daniel Möller
1
Je suppose que cela pourrait être une sorte de nettoyage de la mémoire ....
Daniel Möller

Réponses:

10

TF2 présente généralement une gestion de la mémoire médiocre et semblable à un bogue dans plusieurs cas que j'ai rencontrés - brève description ici et ici . Avec la prédiction en particulier, la méthode d'alimentation la plus performante est via model(x)directement - voir ici , et ses discussions liées.

En bref: model(x)agit par son son __call__procédé (qui hérite de base_layer.Layer), tandis que predict(), predict_classes()etc. associer une fonction de boucle via dédié _select_training_loop(); chacun utilise différentes méthodes de pré et post-traitement de données adaptées à différents cas d'utilisation, et model(x)en 2.1 a été conçu spécifiquement pour fournir des performances plus rapides pour les petits modèles / petits lots (et peut-être n'importe quelle taille) (et toujours les plus rapides en 2.0).

Citant un développeur TensorFlow à partir de discussions liées:

Vous pouvez prédire la sortie à l'aide d'un appel de modèle, et non d'une prédiction de modèle, c'est-à-dire que l'appel model(x)rendrait cela beaucoup plus rapide car il n'y a pas de partie "conversion en ensemble de données", et il appelle également directement un cache tf.function.

Remarque : cela devrait être moins un problème en 2.1, et en particulier en 2.2 - mais testez quand même chaque méthode. Je me rends également compte que cela ne répond pas directement à votre question sur les pics de temps; Je soupçonne que cela est lié aux mécanismes de mise en cache Désireux, mais le moyen le plus sûr de le déterminer est via TF Profiler, qui est cassé en 2.1.


Mise à jour : concernant l' augmentation des pics, limitation possible du GPU; vous avez fait environ 1 000 itérations, essayez 10 000 à la place - finalement, l'augmentation devrait cesser. Comme vous l'avez noté dans vos commentaires, cela ne se produit pas avec model(x); est logique car une étape GPU en moins est impliquée ("conversion en ensemble de données").

Mise à jour2 : vous pouvez ici bugner les développeurs si vous rencontrez ce problème; c'est surtout moi qui chante là-bas

OverLordGoldDragon
la source
C'est une bonne réponse pour expliquer pourquoi une méthode est plus lente, mais cela n'explique pas l'augmentation du temps d'exécution sur plusieurs exécutions.
LLSv2.0
1
@ LLSv2.0 Pas tout à fait sûr moi-même, mais réponse mise à jour - J'attends toujours une réponse des développeurs lorsque j'ai soulevé ce problème moi-même ici
OverLordGoldDragon
1
@ ga97dil Ouais, alors je n'ai plus d'explications - essayez de demander sur Github, bien que vous puissiez faire face à de longs temps de réponse.
OverLordGoldDragon
1
@ ga97dil En effet, TF1 peut être beaucoup plus rapide que TF2 - bien que TF 2.1 vaille la peine d'essayer pour les petits modèles et ensembles de données, car c'est le plus rapide dans la formation dans mes benchmarks (n'a pas fait de prédiction). Plus important encore, si vous utilisez TF2, je vous suggère fortement de tester la reproductibilité dans Graph vs. Eager; les résultats peuvent différer très en TF 2.1.
OverLordGoldDragon
1
J'ai ajouté votre message au fil de discussion Git et mon article TF2 vs TF1. Merci de m'avoir informé que le problème disparaît dans TF 1.
OverLordGoldDragon
2

Bien que je ne puisse pas expliquer les incohérences dans le temps d'exécution, je peux vous recommander d'essayer de convertir votre modèle en TensorFlow Lite pour accélérer les prédictions sur des enregistrements de données uniques ou de petits lots.

J'ai réalisé un benchmark sur ce modèle:

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')
])

Les temps de prédiction pour les enregistrements uniques étaient les suivants:

  1. model.predict(input): 18ms
  2. model(input): 1,3 ms
  3. Modèle converti en TensorFlow Lite: 43us

Le temps de conversion du modèle était de 2 secondes.

La classe ci-dessous montre comment convertir et utiliser le modèle et fournit une predictméthode comme le modèle Keras. Notez qu'il devrait être modifié pour être utilisé avec des modèles qui n'ont pas seulement une seule entrée 1D et une seule sortie 1D.

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]

Le code de référence complet et un graphique peuvent être trouvés ici: https://medium.com/@micwurm/using-tensorflow-lite-to-speed-up-predictions-a3954886eb98

Michael
la source
Cool, je ne l'ai jamais essayé auparavant mais peut-être que ça vaudrait le coup. Merci pour l'astuce!
ga97dil