ACTUALIZACIÓN - 01/15/2020 : la mejor práctica actual para los pequeños tamaños de lote debe ser para alimentar las entradas al modelo directamente - es decir preds = model(x)
, y si las capas se comportan de manera diferente en tren / inferencia, model(x, training=False)
. Según la última confirmación, esto ahora está documentado .
No los he comparado, pero según la discusión de Git , también vale la pena intentarlo predict_on_batch()
, especialmente con las mejoras en TF 2.1.
ÚLTIMA CULPABLE : self._experimental_run_tf_function = True
. Es experimental . Pero en realidad no es malo.
Para cualquier desarrollador de TensorFlow que lea: limpie su código . Es un desastre. Y viola las prácticas de codificación importantes, como una función hace una cosa ; _process_inputs
hace mucho más que "entradas de proceso", lo mismo para _standardize_user_data
. "No se me paga lo suficiente", pero sí paga, en el tiempo extra dedicado a comprender sus propias cosas, y en los usuarios que llenan su página de Problemas con errores más fácilmente resueltos con un código más claro.
RESUMEN : es solo un poco más lento con compile()
.
compile()
establece una bandera interna que asigna una función de predicción diferente a predict
. Esta función construye un nuevo gráfico en cada llamada, ralentizándolo en relación a no compilado. Sin embargo, la diferencia solo se pronuncia cuando el tiempo del tren es mucho más corto que el tiempo de procesamiento de datos . Si aumentamos el tamaño del modelo al menos a un tamaño mediano, los dos se vuelven iguales. Ver código en la parte inferior.
Este ligero aumento en el tiempo de procesamiento de datos está más que compensado por la capacidad de gráficos amplificados. Como es más eficiente mantener solo un gráfico de modelo, se descarta el precompilado. No obstante : si su modelo es pequeño en relación con los datos, es mejor sin la compile()
inferencia del modelo. Vea mi otra respuesta para una solución alternativa.
¿QUÉ TENGO QUE HACER?
Compare el rendimiento del modelo compilado con el no compilado como lo he hecho en el código en la parte inferior.
- Compilado es más rápido : ejecutar
predict
en un modelo compilado.
- Compilado es más lento : ejecutar
predict
en un modelo sin compilar.
Sí, ambos son posibles y dependerá del (1) tamaño de los datos; (2) tamaño del modelo; (3) hardware. El código en la parte inferior en realidad muestra que el modelo compilado es más rápido, pero 10 iteraciones es una pequeña muestra. Ver "soluciones" en mi otra respuesta para el "cómo hacerlo".
DETALLES :
Esto tomó un tiempo para depurar, pero fue divertido. A continuación, describo a los principales culpables que descubrí, cito documentación relevante y muestro los resultados del generador de perfiles que condujeron al último cuello de botella.
( FLAG == self.experimental_run_tf_function
por brevedad)
Model
por defecto crea una instancia con FLAG=False
. compile()
ajustarlo en el True
.
predict()
implica adquirir la función de predicción, func = self._select_training_loop(x)
- Sin ningún kwargs especial pasado a
predict
y compile
, todas las demás banderas son tales que:
- (A)
FLAG==True
->func = training_v2.Loop()
- (B)
FLAG==False
->func = training_arrays.ArrayLikeTrainingLoop()
- Desde la cadena de documentación del código fuente , (A) depende en gran medida de los gráficos, utiliza más estrategias de distribución y las operaciones son propensas a crear y destruir elementos gráficos, lo que "puede" (afectar) al rendimiento.
Verdadero culpable : _process_inputs()
representa el 81% del tiempo de ejecución . ¿Su componente principal? _create_graph_function()
, 72% del tiempo de ejecución . Este método ni siquiera existe para (B) . Sin embargo, el uso de un modelo de tamaño medio _process_inputs
comprende menos del 1% del tiempo de ejecución . Código en la parte inferior, y siguen los resultados del perfil.
Procesadores de datos :
(A) : <class 'tensorflow.python.keras.engine.data_adapter.TensorLikeDataAdapter'>
utilizado en _process_inputs()
. Código fuente relevante
(B) : numpy.ndarray
devuelto por convert_eager_tensors_to_numpy
. Código fuente relevante , y aquí
MODELO DE FUNCIÓN DE EJECUCIÓN (por ejemplo, predecir)
(A) : función de distribución , y aquí
(B) : función de distribución (diferente) , y aquí
PERFIL : resultados para el código en mi otra respuesta, "modelo pequeño", y en esta respuesta, "modelo mediano":
Modelo minúsculo : 1000 iteraciones,compile()
Modelo minúsculo : 1000 iteraciones, no compile()
Modelo medio : 10 iteraciones
DOCUMENTACIÓN (indirectamente) sobre los efectos de compile()
: fuente
A diferencia de otras operaciones de TensorFlow, no convertimos entradas numéricas de python en tensores. Además, se genera un nuevo gráfico para cada valor numérico de Python distinto , por ejemplo, llamando g(2)
y g(3)
generará dos nuevos gráficos
function
crea una instancia de un gráfico separado para cada conjunto único de formas de entrada y tipos de datos . Por ejemplo, el siguiente fragmento de código dará como resultado el seguimiento de tres gráficos distintos, ya que cada entrada tiene una forma diferente
Un solo objeto tf.function podría necesitar mapearse a múltiples gráficos de cálculo debajo del capó. Esto debería ser visible solo como rendimiento (los gráficos de seguimiento tienen un costo computacional y de memoria distinto de cero ), pero no deberían afectar la corrección del programa
Contraejemplo :
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)
Salidas :
34.8542 sec
34.7435 sec