Tempo di previsione incoerente di Keras


17

Ho cercato di ottenere una stima del tempo di previsione del mio modello di keras e ho realizzato qualcosa di strano. Oltre ad essere abbastanza veloce normalmente, ogni tanto il modello ha bisogno di molto tempo per elaborare una previsione. E non solo, quei tempi aumentano anche più a lungo il modello funziona. Ho aggiunto un esempio minimo di lavoro per riprodurre l'errore.

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

Il tempo non dipende dal campione (viene scelto casualmente). Se il test viene ripetuto, gli indici nel ciclo for in cui la previsione impiega più tempo saranno (quasi) nuovamente gli stessi.

inserisci qui la descrizione dell'immagine

Sto usando:

tensorflow 2.0.0
python 3.7.4

Per la mia applicazione devo garantire l'esecuzione entro un certo tempo. Questo è tuttavia impossibile considerando quel comportamento. Cosa non va? È un bug in Keras o un bug nel backend tensorflow?

EDIT: predict_on_batchmostra lo stesso comportamento, tuttavia, più scarso: inserisci qui la descrizione dell'immagine

y_pred = model(sample, training=False).numpy() mostra anche alcuni valori anomali pesanti, tuttavia non aumentano. inserisci qui la descrizione dell'immagine

EDIT 2: ho eseguito il downgrade all'ultima versione di tensorflow 1 (1.15). Non solo il problema non esiste più, ma anche il tempo di previsione "normale" è notevolmente migliorato! Non vedo i due picchi come problematici, in quanto non sono apparsi quando ho ripetuto il test (almeno non agli stessi indici e aumentando linearmente) e sono percentuali non grandi come nel primo grafico. inserisci qui la descrizione dell'immagine

Possiamo quindi concludere che questo sembra essere un problema inerente a tensorflow 2.0, che mostra un comportamento simile in altre situazioni come menziona @OverLordGoldDragon.


Quel comportamento sembra prevedibile ... l'aumento è una specie di lineare. Se includi questo comportamento nel tuo calcolo del tempo, non andrà? --- Non so cosa sta succedendo lì .... ma cosa succede se ci provi predict_on_batchinvece?
Daniel Möller,

Un altro tentativo, cosa succede con y_pred = model(sample).numpy()e con y_pred = model(sample, training=False).numpy()?
Daniel Möller,

Ho aggiunto i miei risultati. Le versioni intorpidite non sembrano mostrare il comportamento.
ga97dil,

Ma predict_classesè ancora il più veloce .... sembra. Che dire solo predict?
Daniel Möller,

1
Presumo che questo potrebbe essere un qualche tipo di pulizia della memoria ....
Daniel Möller,

Risposte:


10

TF2 generalmente mostra una gestione della memoria scadente e simile a un bug in diversi casi che ho riscontrato - breve descrizione qui e qui . Con la previsione in particolare, il metodo di alimentazione più efficace è model(x)direttamente tramite - vedi qui e le discussioni collegate.

In poche parole: model(x)agisce tramite il suo proprio __call__metodo (che eredita da base_layer.Layer), considerando che predict(), predict_classes()ecc comportare una funzione ciclo dedicato via _select_training_loop(); ognuno utilizza diversi metodi di pre-elaborazione e post-elaborazione dei dati adatti a diversi casi d'uso, e model(x)in 2.1 è stato progettato specificamente per offrire prestazioni più rapide di piccoli modelli / piccoli lotti (e forse di qualsiasi dimensione) (e comunque più veloci in 2.0).

Citando uno sviluppatore TensorFlow da discussioni collegate:

È possibile prevedere l'output utilizzando la chiamata del modello, non la previsione del modello, ovvero la chiamata model(x)renderebbe questo molto più veloce perché non esiste una parte "conversione in set di dati" e inoltre chiama direttamente una cache tf.function.

Nota : questo dovrebbe essere un problema minore in 2.1, e specialmente in 2.2 - ma testare comunque ogni metodo. Inoltre mi rendo conto che questo non risponde direttamente alla tua domanda sui picchi di tempo; Ho il sospetto che sia correlato ai meccanismi di memorizzazione nella cache Eager, ma il modo più sicuro per determinare è via TF Profiler, che è rotto in 2.1.


Aggiornamento : per quanto riguarda l' aumento dei picchi, possibile limitazione della GPU; hai fatto ~ 1000 iter, prova invece 10.000 - alla fine, l'aumento dovrebbe fermarsi. Come hai notato nei tuoi commenti, questo non si verifica con model(x); ha senso poiché è coinvolto un passaggio in meno di GPU ("conversione in set di dati").

Aggiornamento2 : potresti bug qui gli sviluppatori se dovessi affrontare questo problema; principalmente sono io a cantare lì


Questa è una buona risposta al motivo per cui un metodo è più lento, ma non spiega l'aumento del tempo di esecuzione su più corse.
LLSv2.0

1
@ LLSv2.0 Non sono del tutto sicuro di me stesso, ma ho aggiornato la risposta - sto ancora aspettando una risposta dagli sviluppatori quando ho sollevato questo problema da solo qui
OverLordGoldDragon

1
@ ga97dil Sì, allora non ho spiegazioni - prova a chiedere a Github, anche se potresti dover affrontare lunghi tempi di risposta.
OverLordGoldDragon,

1
@ ga97dil In effetti, TF1 può essere molto più veloce di TF2, anche se vale la pena provare TF 2.1 per piccoli modelli e set di dati, in quanto è il più veloce nell'allenamento nei miei benchmark (non ha fatto previsioni). Ancora più importante, se mai usi TF2, ti consiglio vivamente di testare la riproducibilità in Graph vs. Eager; i risultati possono differire enormemente in TF 2.1.
OverLordGoldDragon,

1
Ho aggiunto il tuo post al thread Git e il mio post TF2 vs. TF1. Grazie per avermi informato che il problema scompare in TF 1.
OverLordGoldDragon

2

Anche se non posso spiegare le incoerenze nei tempi di esecuzione, posso consigliare di provare a convertire il modello in TensorFlow Lite per accelerare le previsioni su singoli record di dati o piccoli lotti.

Ho eseguito un benchmark su questo modello:

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

I tempi di previsione per i singoli record erano:

  1. model.predict(input): 18ms
  2. model(input): 1,3 ms
  3. Modello convertito in TensorFlow Lite: 43us

Il tempo per convertire il modello è stato di 2 secondi.

La classe seguente mostra come convertire e utilizzare il modello e fornisce un predictmetodo come il modello Keras. Si noti che dovrebbe essere modificato per l'uso con modelli che non hanno solo un singolo ingresso 1-D e un singolo uscita 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]

Il codice di riferimento completo e una trama sono disponibili qui: https://medium.com/@micwurm/using-tensorflow-lite-to-speed-up-predictions-a3954886eb98


Fantastico, non l'ho mai provato prima, ma forse varrebbe la pena provare. Grazie per il suggerimento!
ga97dil
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.