Keras tiempo de predicción inconsistente

17

Traté de obtener una estimación del tiempo de predicción de mi modelo de keras y me di cuenta de algo extraño. Además de ser bastante rápido normalmente, de vez en cuando el modelo necesita bastante tiempo para llegar a una predicción. Y no solo eso, esos tiempos también aumentan cuanto más tiempo se ejecuta el modelo. Agregué un ejemplo de trabajo mínimo para reproducir el error.

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

El tiempo no depende de la muestra (se selecciona al azar). Si se repite la prueba, los índices en el bucle for donde la predicción lleva más tiempo serán (casi) los mismos nuevamente.

ingrese la descripción de la imagen aquí

Estoy usando:

tensorflow 2.0.0
python 3.7.4

Para mi aplicación, necesito garantizar la ejecución en un momento determinado. Sin embargo, esto es imposible teniendo en cuenta ese comportamiento. ¿Qué va mal? ¿Es un error en Keras o un error en el backend de Tensorflow?

EDITAR: predict_on_batchmuestra el mismo comportamiento, sin embargo, más escaso: ingrese la descripción de la imagen aquí

y_pred = model(sample, training=False).numpy() muestra algunos valores atípicos pesados ​​también, sin embargo, no están aumentando. ingrese la descripción de la imagen aquí

EDIT 2: bajé a la última versión de tensorflow 1 (1.15). No solo el problema ya no existe, sino que el tiempo de predicción "normal" mejoró significativamente. No veo los dos picos como problemáticos, ya que no aparecieron cuando repetí la prueba (al menos no con los mismos índices y aumentando linealmente) y son porcentuales no tan grandes como en la primera gráfica. ingrese la descripción de la imagen aquí

Por lo tanto, podemos concluir que esto parece ser un problema inherente al tensorflow 2.0, que muestra un comportamiento similar en otras situaciones como menciona @OverLordGoldDragon.

ga97dil
fuente
Ese comportamiento suena predecible ... el aumento es algo lineal. Si incluye este comportamiento en su cálculo de tiempo, ¿no desaparecerá? --- No sé lo que está sucediendo allí ... pero ¿qué sucede si lo intentas predict_on_batch?
Daniel Möller
Otro intento, ¿qué pasa con y_pred = model(sample).numpy()y con y_pred = model(sample, training=False).numpy()?
Daniel Möller
Agregué mis hallazgos. Las versiones numpy no parecen mostrar el comportamiento.
ga97dil
Pero predict_classessigue siendo el más rápido ... parece. ¿Qué tal solo predict?
Daniel Möller
1
Supongo que esto podría ser algún tipo de limpieza de memoria ...
Daniel Möller

Respuestas:

10

TF2 generalmente exhibe una administración de memoria pobre y similar a un error en varias instancias que he encontrado: breve descripción aquí y aquí . Con la predicción en particular, el método de alimentación más eficaz es model(x)directamente, ver aquí , y sus discusiones vinculadas.

En pocas palabras: model(x)actúa a través de su su __call__método (que se hereda de base_layer.Layer), mientras que predict(), predict_classes(), etc. implicar una función de bucle dedicado a través de _select_training_loop(); cada uno utiliza diferentes métodos de procesamiento previo y posterior de datos adecuados para diferentes casos de uso, y model(x)en 2.1 fue diseñado específicamente para obtener el rendimiento más rápido de modelo pequeño / lote pequeño (y tal vez cualquier tamaño) (y aún más rápido en 2.0).

Citando a un desarrollador de TensorFlow de las discusiones vinculadas:

Puede predecir la salida utilizando la llamada del modelo, no la predicción del modelo, es decir, llamar model(x)haría esto mucho más rápido porque no hay una parte de "conversión a conjunto de datos", y también está llamando directamente a un caché tf.function.

Nota : esto debería ser un problema menor en 2.1, y especialmente 2.2, pero pruebe cada método de todos modos. También me doy cuenta de que esto no responde directamente a su pregunta sobre los picos de tiempo; Sospecho que está relacionado con los mecanismos de almacenamiento en caché Eager, pero la forma más segura de determinarlo es a través de TF Profiler, que se divide en 2.1.


Actualización : respecto al aumento de picos, posible aceleración de GPU; has hecho ~ 1000 iters, prueba 10,000 en su lugar; eventualmente, el aumento debería detenerse. Como notó en sus comentarios, esto no ocurre con model(x); tiene sentido ya que se trata de un paso menos de GPU ("conversión a conjunto de datos").

Actualización 2 : podría molestar a los desarrolladores aquí si enfrenta este problema; es sobre todo yo cantando allí

OverLordGoldDragon
fuente
Esta es una buena respuesta a por qué un método es más lento, pero no explica el aumento del tiempo de ejecución en varias ejecuciones.
LLSv2.0
1
@ LLSv2.0 No estoy completamente seguro de mí mismo, pero tengo una respuesta actualizada: todavía estoy esperando una respuesta de los desarrolladores cuando planteé este problema aquí
OverLordGoldDragon
1
@ ga97dil Sí, entonces no tengo explicaciones, intente preguntar en Github, aunque puede enfrentar largos tiempos de respuesta.
OverLordGoldDragon
1
@ ga97dil De hecho, TF1 puede ser mucho más rápido que TF2, aunque vale la pena probar TF 2.1 para modelos pequeños y conjuntos de datos, ya que es el entrenamiento más rápido en mis puntos de referencia (no hice predicciones). Más importante aún, si alguna vez usa TF2, le sugiero que pruebe la reproducibilidad en Graph vs. Eager; Los resultados pueden diferir extremadamente en TF 2.1.
OverLordGoldDragon
1
Agregué tu publicación al hilo de Git y mi publicación TF2 vs. TF1. Gracias por informarme que el problema desaparece en TF 1.
OverLordGoldDragon
2

Si bien no puedo explicar las inconsistencias en el tiempo de ejecución, puedo recomendar que intente convertir su modelo a TensorFlow Lite para acelerar las predicciones en registros de datos únicos o lotes pequeños.

Ejecuté un punto de referencia en este modelo:

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

Los tiempos de predicción para registros individuales fueron:

  1. model.predict(input): 18 ms
  2. model(input): 1.3ms
  3. Modelo convertido a TensorFlow Lite: 43us

El tiempo para convertir el modelo fue de 2 segundos.

La siguiente clase muestra cómo convertir y usar el modelo y proporciona un predictmétodo como el modelo Keras. Tenga en cuenta que debería modificarse para su uso con modelos que no solo tienen una sola entrada 1-D y una sola salida 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]

El código de referencia completo y un diagrama se pueden encontrar aquí: https://medium.com/@micwurm/using-tensorflow-lite-to-speed-up-predictions-a3954886eb98

Miguel
fuente
Genial, nunca lo probé antes, pero tal vez valdría la pena intentarlo. ¡Gracias por la pista!
ga97dil