En teoría, la predicción debe ser constante ya que los pesos tienen un tamaño fijo. ¿Cómo recupero mi velocidad después de la compilación (sin la necesidad de eliminar el optimizador)?
Ver experimento asociado: https://nbviewer.jupyter.org/github/off99555/TensorFlowExperiments/blob/master/test-prediction-speed-after-compile.ipynb?flush_cache=true
python
performance
tensorflow
keras
jupyter-notebook
off99555
fuente
fuente
fit
sincompile
; El optimizador ni siquiera existe para actualizar ningún peso.predict
puede usarse sinfit
ocompile
como se describe en mi respuesta, pero la diferencia de rendimiento no debería ser tan dramática, de ahí el problema.Respuestas:
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 apredict
. 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.
predict
en un modelo compilado.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 conFLAG=False
.compile()
ajustarlo en elTrue
.predict()
implica adquirir la función de predicción,func = self._select_training_loop(x)
predict
ycompile
, todas las demás banderas son tales que:FLAG==True
->func = training_v2.Loop()
FLAG==False
->func = training_arrays.ArrayLikeTrainingLoop()
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 porconvert_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()
: fuenteContraejemplo :
Salidas :
fuente
compile()
?ACTUALIZACIÓN : vea la respuesta real publicada como una respuesta separada; esta publicación contiene información complementaria
.compile()
configura la mayoría del gráfico TF / Keras, incluidas las pérdidas, las métricas, los gradientes y, en parte, el optimizador y sus pesos, lo que garantiza una notable desaceleración.Que es inesperado es el grado de desaceleración - 10 veces en mi propio experimento, y para
predict()
que no se actualiza ningún peso. Mirando el código fuente de TF2, los elementos del gráfico aparecen estrechamente entrelazados, con recursos que no necesariamente se asignan "de manera justa".Posible pasar por alto por los desarrolladores sobre
predict
el rendimiento de un modelo sin compilar, ya que los modelos generalmente se usan compilados, pero en la práctica , esta es una diferencia inaceptable. También es posible que sea un "mal necesario", ya que existe una solución simple (ver más abajo).Esta no es una respuesta completa, y espero que alguien pueda proporcionarla aquí; de lo contrario, sugeriría abrir un problema de Github en TensorFlow. (OP tiene; aquí )
Solución alternativa : entrene un modelo, guarde sus pesos , reconstruya el modelo sin compilar, cargue los pesos. No , no guardar el modelo completo (por ejemplo
model.save()
), ya que va a cargar compilado - en lugar de utilizarmodel.save_weights()
emodel.load_weights()
.Solución 2 : arriba, pero use
load_model(path, compile=False)
; crédito de sugerencia: D. MöllerACTUALIZACIÓN : aclarar, optimizador está no totalmente instancia utilizando
compile
, incluidas susweights
yupdates
tensores - esto se hace cuando la primera llamada a una función de ajuste se hace (fit
,train_on_batch
, etc.), a través demodel._make_train_function()
.El comportamiento observado es, por lo tanto, aún más extraño. Peor aún, construir el optimizador no provoca más ralentizaciones (ver más abajo), lo que sugiere que el "tamaño del gráfico" no es la explicación principal aquí.
EDITAR : en algunos modelos, una desaceleración de 30x . TensorFlow, ¿qué has hecho? Ejemplo a continuación:
Salidas :
fuente
model.fit()
versus un bucle dinámico con ansiosa ejecución para ver si la pérdida de rendimiento es demasiado grande ...load_model(name, compile=False)
, es más simple que guardar / cargar pesas y recrear el modelo.