Tensorflow v1.10 + ¿por qué se necesita una función de receptor de entrada cuando los puntos de control se realizan sin ella?

Estoy en el proceso de adaptar mi modelo a la API del estimador de TensorFlow.

Hace poco hice una pregunta con respecto a la detención temprana basada en los datos de validación donde, además de la detención temprana, se debe exportar el mejor modelo en este punto.

Parece que mi comprensión de qué es una exportación de modelo y qué es un punto de control no está completa.

Los puntos de control se hacen automáticamente. A mi entender, los puntos de control son suficientes para que el estimador comience a “calentar”, ya sea utilizando pesos por entrenamiento o pesos previos a un error (por ejemplo, si sufrió un corte de energía).

Lo bueno de los puntos de control es que no tengo que escribir ningún código además de lo que es necesario para un estimador personalizado (a saber, input_fn y model_fn ).

Mientras que, dado un estimador inicializado, uno puede simplemente llamar a su método de tren para entrenar el modelo, en la práctica este método es bastante deslustrado. A menudo uno quisiera hacer varias cosas:

  1. compare la red periódicamente con un conjunto de datos de validación para asegurarse de que no está sobrecargando
  2. detener el entrenamiento temprano si ocurre un ajuste excesivo
  3. guarde el mejor modelo cada vez que la red termine (ya sea golpeando la cantidad especificada de pasos de entrenamiento o por los criterios de detención temprana).

Para alguien nuevo en el API de estimador de “alto nivel”, se requiere mucha experiencia de bajo nivel (por ejemplo, para input_fn ), ya que la forma en que se puede lograr que el estimador haga esto no es sencilla.

Con un poco de código de código se puede volver a trabajar # 1 utilizando tf.estimator.TrainSpec y tf.estimator.EvalSpec con tf.estimator.train_and_evaluate .

En la pregunta anterior, el usuario @GPhilo aclara cómo se puede lograr el # 2 utilizando una función tf.contrib del tf.contrib :

 tf.contrib.estimator.stop_if_no_decrease_hook(my_estimator,'my_metric_to_monitor', 10000) 

(no intuitivo como “la detención temprana no se activa según el número de evaluaciones que no mejoran, sino la cantidad de evals que no mejoran en un cierto rango de pasos”).

@GPhilo – señalando que no está relacionado con el # 2 – también respondió cómo hacerlo # 3 (como se solicitó en la publicación original). Sin embargo, no entiendo qué es input_serving_fn , por qué se necesita o cómo hacerlo.

Esto es aún más confuso para mí, ya que no se necesita tal función para hacer puntos de control, o para que el estimador comience a “calentarse” desde el punto de control.

Así que mis preguntas son:

  • ¿Cuál es la diferencia entre un punto de control y un mejor modelo exportado?
  • ¿Qué es exactamente una función de recepción de entrada de servicio y cómo escribir una? (He pasado un poco de tiempo leyendo los documentos de tensorflow y no encuentro suficiente para entender cómo debo escribir uno, y por qué tengo que hacerlo).
  • ¿Cómo puedo entrenar mi estimador, guardar el mejor modelo y luego cargarlo?

Para ayudar a responder mi pregunta, estoy proporcionando este documento de Colab .

Este cuaderno autónomo produce algunos datos ficticios, los guarda en TF Records, tiene un estimador personalizado muy simple a través de model_fn y entrena a este modelo con un input_fn que usa los archivos de TF Record. Por lo tanto, debería ser suficiente para que alguien me explique qué marcadores de posición debo hacer para la función del receptor de entrada de datos y cómo puedo lograr el número 3 .

Actualizar

@GPhilo, ante todo, no puedo subestimar mi aprecio por su cuidadosa consideración y cuidado al ayudarme (y, con suerte, a otros) a entender este asunto.

Mi “objective” (que me motiva a hacer esta pregunta) es intentar crear un marco reutilizable para redes de capacitación para poder pasar un build_fn and go diferente (además de tener las características de calidad de vida del modelo exportado, la detención temprana, etc.) .

Puede encontrar un Colab actualizado (basado en sus respuestas) aquí .

Después de varias lecturas de su respuesta, he encontrado ahora algo más de confusión:

1.

La forma en que proporciona información al modelo de inferencia es diferente a la que usa para el entrenamiento.

¿Por qué? A mi entender, la canalización de entrada de datos no es:

 load raw —> process —> feed to model 

Sino más bien

 Load raw —> pre process —> store (perhaps as tf records) # data processing has nothing to do with feeding data to the model? Load processed —> feed to model 

En otras palabras, entiendo (quizás de manera incorrecta) que el objective de tf Example / SequenceExample es almacenar una entidad de dato singular completa lista para funcionar, no es necesario otro procesamiento que no sea la lectura del archivo TFRecord .

Por lo tanto, puede haber una diferencia entre el input_fn entrenamiento / evaluación y el de inferencia (p. Ej., La lectura de un archivo frente a una evaluación impaciente / interactiva de la memoria), pero el formato de los datos es el mismo (a excepción de la inferencia, es posible que desee incluir solo 1 ejemplo) en lugar de un lote …)

Estoy de acuerdo en que ” la canalización de entrada no es parte del modelo en sí “. Sin embargo, en mi mente, y aparentemente me equivoco al pensar que sí, con el estimador debería poder alimentarlo con un lote para entrenamiento y un solo ejemplo (o lote) para inferir.

Un lado: “ Al evaluar, no necesita los gradientes y necesita una función de entrada diferente. “, La única diferencia (al menos en mi caso) son los archivos de los que está leyendo.

  1. Estoy familiarizado con esa Guía de TF, pero no la encuentro útil porque no me queda claro qué marcadores de posición debo agregar y qué operaciones adicionales se deben agregar para convertir los datos.

¿Qué pasa si entreno mi modelo con registros y quiero inferir solo con los tensores densos?

De manera tangencial, encuentro el ejemplo en la guía de enlaces, dado que la interfaz de registro de tf requiere que el usuario defina múltiples veces cómo escribir / extraer características de un archivo de registro de tf en diferentes contextos. Además, dado que el equipo de TF ha declarado explícitamente que tienen poco interés en documentar los registros de TF, para mí, cualquier documentación que se construya sobre ellos es igualmente esclarecedor.

  1. Con respecto a tf.estimator.export.build_raw_serving_input_receiver_fn . ¿Cómo se llama el marcador de posición? ¿Entrada? ¿Podría quizás mostrar el análogo de tf.estimator.export.build_raw_serving_input_receiver_fn escribiendo el equivalente serving_input_receiver_fn

  2. Con respecto a su ejemplo serving_input_receiver_fn con las imágenes de entrada. ¿Cómo sabe si debe llamar a las características ‘imágenes’ y al tensor del receptor ‘input_data’? ¿Es ese (el último) estándar?

  3. Cómo nombrar una exportación con signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY .

¿Cuál es la diferencia entre un punto de control y un mejor modelo exportado?

Un punto de control es, como mínimo, un archivo que contiene los valores de todas las variables de un gráfico específico tomadas en un punto de tiempo específico . Por gráfico específico quiero decir que al volver a cargar su punto de control, lo que hace TensorFlow es recorrer todas las variables definidas en su gráfico (la de la session que está ejecutando) y buscar una variable en el archivo de punto de control que tenga el mismo nombre como el de la gráfica . Para reanudar el entrenamiento, esto es ideal porque su gráfica siempre tendrá el mismo aspecto entre reinicios.

Un modelo exportado tiene un propósito diferente. La idea de un modelo exportado es que, una vez que haya terminado el entrenamiento, desee obtener algo que pueda usar para la inferencia que no contenga todas las partes (pesadas) que son específicas del entrenamiento (algunos ejemplos: cálculo de gradiente, global paso variable, entrada de tubería, …). Además, y este es el punto clave, por lo general, la forma en que proporciona información al modelo de inferencia es diferente a la que usa para la capacitación. Para la capacitación, tiene un canal de entrada que carga, preprocesa y alimenta datos a su red. Esta canalización de entrada no es parte del modelo en sí y debe modificarse para inferencia. Este es un punto clave cuando se opera con Estimator s.

¿Por qué necesito una función de receptor de entrada de servicio?

Para responder a esto voy a dar un primer paso atrás. ¿Por qué necesitamos funciones de entrada en todos los anuncios que son? Los Estimator de TF, aunque quizás no sean tan intuitivos como otras formas de modelar redes, tienen una gran ventaja: se diferencian claramente entre lógica de modelo y lógica de procesamiento de entrada por medio de funciones de entrada y funciones de modelo.

Un modelo vive en 3 fases diferentes: Capacitación, Evaluación e Inferencia. Para los casos de uso más comunes (o al menos, todo lo que puedo pensar en este momento), el gráfico que se ejecuta en TF será diferente en todas estas fases. El gráfico es la combinación de preprocesamiento de entrada, modelo y toda la maquinaria necesaria para ejecutar el modelo en la fase actual.

Algunos ejemplos para aclarar aún más: Cuando entrene, necesita gradientes para actualizar los pesos, un optimizador que ejecute el paso de capacitación, métricas de todo tipo para monitorear cómo van las cosas, un canal de entrada que recostack datos del conjunto de capacitación, etc. . Al evaluar, no necesita los gradientes y necesita una función de entrada diferente. Cuando tf.data.* inferencia, todo lo que necesita es la parte delantera del modelo y, de nuevo, la función de entrada será diferente (no tf.data.* Cosas, pero generalmente solo un marcador de posición).

Cada una de estas fases en Estimator s tiene su propia función de entrada. Está familiarizado con la capacitación y la evaluación, la inferencia es simplemente la función de serving input receiver . En la jerga de TF, “servir” es el proceso de empaquetar un modelo entrenado y usarlo para inferencia (hay todo un sistema de servicio TensorFlow para operaciones a gran escala, pero eso está más allá de esta cuestión y es muy probable que no lo necesite).

Es hora de citar una guía de TF sobre el tema :

Durante el entrenamiento, un input_fn () ingiere datos y los prepara para que los utilice el modelo. En el momento de la serving_input_receiver_fn() , de manera similar, un serving_input_receiver_fn() acepta solicitudes de inferencia y las prepara para el modelo. Esta función tiene los siguientes propósitos:

  • Para agregar marcadores de posición al gráfico que el sistema de servicio alimentará con solicitudes de inferencia.
  • Para agregar cualquier operación adicional necesaria para convertir los datos del formato de entrada a la función Tensores esperada por el modelo.

Ahora, la especificación de la función de entrada de servicio depende de cómo planea enviar la entrada a su gráfico.

Si va a empaquetar los datos en un tf.Example (serializado) tf.Example (Que es similar a uno de los registros en sus archivos TFRecord), su función de entrada de servicio tendrá un marcador de posición de cadena (que es para los bytes serializados para el ejemplo). ) y necesitará una especificación de cómo interpretar el ejemplo para extraer sus datos. Si esta es la forma en que desea ir, lo invito a que eche un vistazo al ejemplo de la guía que se encuentra más arriba. En esencia, muestra cómo configura la especificación de cómo interpretar el ejemplo y lo analiza para obtener los datos de entrada.

Si, en cambio, está planeando incorporar directamente la entrada a la primera capa de su red , aún necesita definir una función de entrada de servicio, pero esta vez solo contendrá un marcador de posición que se conectará directamente a la red. TF ofrece una función que hace precisamente eso: tf.estimator.export.build_raw_serving_input_receiver_fn .

Entonces, ¿realmente necesitas escribir tu propia función de entrada? SI todo lo que necesitas es un marcador de posición, no. Simplemente use build_raw_serving_input_receiver_fn con los parámetros apropiados. Si necesita un preprocesamiento más sofisticado, entonces sí, puede que necesite escribir el suyo propio. En ese caso, se vería algo así:

 def serving_input_receiver_fn(): """For the sake of the example, let's assume your input to the network will be a 28x28 grayscale image that you'll then preprocess as needed""" input_images = tf.placeholder(dtype=tf.uint8, shape=[None, 28, 28, 1], name='input_images') # here you do all the operations you need on the images before they can be fed to the net (eg, normalizing, reshaping, etc). Let's assume "images" is the resulting tensor. features = {'input_data' : images} # this is the dict that is then passed as "features" parameter to your model_fn receiver_tensors = {'input_data': input_images} # As far as I understand this is needed to map the input to a name you can retrieve later return tf.estimator.export.ServingInputReceiver(features, receiver_tensors) 

¿Cómo puedo entrenar mi estimador, guardar el mejor modelo y luego cargarlo?

Su model_fn toma el parámetro de modo para que pueda construir condicionalmente el modelo. En tu colab, siempre tienes un optimizador, por ejemplo. Esto es incorrecto, ya que solo debería estar allí para mode == tf.estimator.ModeKeys.TRAIN .

En segundo lugar, su build_fn tiene un parámetro de “salidas” que no tiene sentido. Esta función debe representar su gráfico de inferencia, tome como entrada solo los tensores que le suministrará en la inferencia y devuelva los logits / predicciones. build_fn los parámetros de outputs no están allí, ya que la firma build_fn debería ser def build_fn(inputs, params) .

Además, usted define su model_fn para tomar features como un tensor. Si bien esto se puede hacer, lo limita a tener exactamente una entrada y complica las cosas para serve_fn (no puede usar build_raw_... enlatado, pero necesita escribir el suyo propio y devolver un TensorServingInputReceiver en TensorServingInputReceiver lugar). model_fn solución más genérica y asumiré que model_fn es el siguiente ( model_fn el scope de la variable por brevedad, lo agrego según sea necesario):

 def model_fn(features, labels, mode, params): my_input = features["input_data"] my_input.set_shape(I_SHAPE(params['batch_size'])) # output of the network onet = build_fn(features, params) predicted_labels = tf.nn.sigmoid(onet) predictions = {'labels': predicted_labels, 'logits': onet} export_outputs = { # see EstimatorSpec's docs to understand what this is and why it's necessary. 'labels': tf.estimator.export.PredictOutput(predicted_labels), 'logits': tf.estimator.export.PredictOutput(onet) } # NOTE: export_outputs can also be used to save models as "SavedModel"s during evaluation. # HERE is where the common part of the graph between training, inference and evaluation stops. if mode == tf.estimator.ModeKeys.PREDICT: # return early and avoid adding the rest of the graph that has nothing to do with inference. return tf.estimator.EstimatorSpec(mode=mode, predictions=predictions, export_outputs=export_outputs) labels.set_shape(O_SHAPE(params['batch_size'])) # calculate loss loss = loss_fn(onet, labels) # add optimizer only if we're training if mode == tf.estimator.ModeKeys.TRAIN: optimizer = tf.train.AdagradOptimizer(learning_rate=params['learning_rate']) # some metrics used both in training and eval mae = tf.metrics.mean_absolute_error(labels=labels, predictions=predicted_labels, name='mea_op') mse = tf.metrics.mean_squared_error(labels=labels, predictions=predicted_labels, name='mse_op') metrics = {'mae': mae, 'mse': mse} tf.summary.scalar('mae', mae[1]) tf.summary.scalar('mse', mse[1]) if mode == tf.estimator.ModeKeys.EVAL: return tf.estimator.EstimatorSpec(mode, loss=loss, eval_metric_ops=metrics, predictions=predictions, export_outputs=export_outputs) if mode == tf.estimator.ModeKeys.TRAIN: train_op = optimizer.minimize(loss, global_step=tf.train.get_global_step()) return tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op, eval_metric_ops=metrics, predictions=predictions, export_outputs=export_outputs) 

Ahora, para configurar la parte de exportación, una vez finalizada la llamada a train_and_evaluate :

1) Defina su función de entrada de servicio:

 serving_fn = tf.estimator.export.build_raw_serving_input_receiver_fn( {'input_data':tf.placeholder(tf.float32, [None,#YOUR_INPUT_SHAPE_HERE (without batch size)#])}) 

2) Exportar el modelo a alguna carpeta.

 est.export_savedmodel('my_directory_for_saved_models', serving_fn) 

Esto guardará el estado actual del estimador en donde lo especificó. Si desea un punto de control export_savedmodel , cárguelo antes de llamar a export_savedmodel . Esto guardará en “my_directory_for_saved_models” un gráfico de predicción con los parámetros entrenados que tenía el estimador cuando llamó a la función de exportación.

Finalmente, es posible que desee congelar el gráfico (busque freeze_graph.py ) y optimizarlo para la inferencia (busque freeze_graph.py y / o transform_graph ) obteniendo un archivo *.pb congelado que luego puede cargar y usar para la inferencia que desee. .


Editar: Añadiendo respuestas a las nuevas preguntas en la actualización.

Nota al margen:

Mi “objective” (que me motiva a hacer esta pregunta) es intentar crear un marco reutilizable para redes de capacitación para poder pasar un build_fn and go diferente (además de tener las características de calidad de vida del modelo exportado, la detención temprana, etc.) .

De todos modos, si lo gestionas, publícalo en GitHub en algún lugar y vincúlalo. He estado tratando de poner en marcha lo mismo durante un tiempo y los resultados no son tan buenos como me gustaría.

Pregunta 1:

En otras palabras, entiendo (quizás de manera incorrecta) que el objective de tf Example / SequenceExample es almacenar una entidad de dato singular completa lista para funcionar, no es necesario otro procesamiento que no sea la lectura del archivo TFRecord.

En realidad, este no suele ser el caso (aunque, en teoría , su forma también está perfectamente bien). Puede ver TFRecords como una forma (muy documentada) de almacenar un conjunto de datos de forma compacta. Por ejemplo, para los conjuntos de datos de imágenes, un registro normalmente contiene los datos de imagen comprimidos (como en los bytes que componen un archivo jpeg / png), su etiqueta y alguna información meta. Luego, el canal de entrada lee un registro, lo decodifica, lo procesa según sea necesario y lo alimenta a la red. Por supuesto, puede mover la deencoding y el preprocesamiento antes de la generación del conjunto de datos de TFRecord y almacenar en los ejemplos los datos listos para alimentar, pero el aumento de tamaño de su conjunto de datos será enorme.

La tubería de preprocesamiento específica es un ejemplo de lo que cambia entre fases (por ejemplo, puede tener aumento de datos en la línea de capacitación, pero no en las demás). Por supuesto, hay casos en que estas tuberías son iguales, pero en general esto no es cierto.

Sobre el lado:

“Al evaluar, no necesita los gradientes y necesita una función de entrada diferente. “, La única diferencia (al menos en mi caso) son los archivos de los que está leyendo.

En tu caso eso puede ser. Pero, una vez más, supongamos que está usando el aumento de datos: debe deshabilitarlo (o, mejor, no tenerlo) durante la evaluación y esto altera su canalización.

Pregunta 2: ¿Qué pasa si entreno mi modelo con registros y quiero inferir solo con los tensores densos?

Esta es precisamente la razón por la que separa la tubería del modelo. El modelo toma como entrada un tensor y opera sobre él. Si ese tensor es un marcador de posición o es el resultado de un subgrafo que lo convierte de un Ejemplo a un tensor, ese es un detalle que pertenece al marco, no al modelo en sí.

El punto de división es la entrada del modelo. El modelo espera un tensor (o, en el caso más genérico, un dict de name:tensor elementos del name:tensor ) como entrada y lo utiliza para construir su gráfico de cálculo. Las funciones de entrada deciden de dónde proviene esa entrada, pero mientras la salida de todas las funciones de entrada tenga la misma interfaz, se pueden intercambiar las entradas según sea necesario y el modelo simplemente tomará lo que sea necesario y lo usará.

Entonces, para resumir, asumiendo que entrena / eval con ejemplos y predice con tensores densos, su tren y las funciones de entrada eval establecerán una tubería que leerá ejemplos de algún lugar, los decodificará en tensores y los devolverá al modelo para usarlos como entradas. Por otro lado, la función de entrada de predicción, simplemente configura un marcador de posición por entrada de su modelo y los devuelve al modelo, porque se supone que usted colocará en los marcadores de posición los datos listos para ser alimentados a la red.

Pregunta 3:

build_raw_serving_input_receiver_fn el marcador de posición como un parámetro de build_raw_serving_input_receiver_fn , por lo que eliges su nombre:

 tf.estimator.export.build_raw_serving_input_receiver_fn( {'images':tf.placeholder(tf.float32, [None,28,28,1], name='input_images')}) 

Pregunta 4:

Hubo un error en el código (había mezclado dos líneas), la clave del input_data debería haber sido input_data ( input_data el código anterior). La clave en el dictado tiene que ser la clave que usa para recuperar el tensor de las features en su model_fn . En model_fn la primera línea es:

 my_input = features["input_data"] 

por lo tanto la clave es 'input_data' . Según la clave en receiver_tensor , todavía no estoy muy seguro de cuál es el rol que uno tiene, por lo que mi sugerencia es que intente establecer un nombre diferente al de la clave en las features y verifique dónde aparece el nombre.

Pregunta 5:

No estoy seguro de entender, editaré esto después de algunas aclaraciones.