Cómo implementar modelos de TensorFlow en producción con TF Serving

Introducción

La puesta en producción de modelos de aprendizaje automático (ML) se ha convertido en un tema recurrente y popular. Muchas empresas y marcos ofrecen diferentes soluciones que tienen como objetivo abordar este problema.

Para abordar esta preocupación, Google lanzó TensorFlow (TF) Serving con la esperanza de resolver el problema de implementar modelos de AA en producción.

Esta pieza ofrece un tutorial práctico sobre cómo servir una red de segmentación semántica convolucional previamente entrenada. Al final de este artículo, podrá utilizar TF Serving para implementar y realizar solicitudes a una CNN profunda entrenada en TF. Además, presentaré una descripción general de los principales bloques de TF Serving, y discutiré sus API y cómo funciona todo.

Una cosa que notará de inmediato es que requiere muy poco código para servir realmente un modelo TF. Si desea seguir el tutorial y ejecutar el ejemplo en su máquina, sígalo como está. Pero, si solo desea saber sobre TensorFlow Serving, puede concentrarse en las dos primeras secciones.

Esta pieza enfatiza parte del trabajo que estamos haciendo aquí en Daitan Group.

Bibliotecas de publicación de TensorFlow: descripción general

Dediquemos un tiempo a comprender cómo TF Serving maneja el ciclo de vida completo de los modelos de ML. Aquí, repasaremos (en un nivel alto) cada uno de los componentes principales de TF Serving. El objetivo de esta sección es proporcionar una introducción sencilla a las API de TF Serving. Para obtener una descripción detallada, diríjase a la página de documentación de TF Serving.

TensorFlow Serving se compone de algunas abstracciones. Estas abstracciones implementan API para diferentes tareas. Los más importantes son Servable, Loader, Source y Manager. Repasemos cómo interactúan.

En pocas palabras, el ciclo de vida del servicio comienza cuando TF Serving identifica un modelo en el disco. El componente Fuente se encarga de eso. Se encarga de identificar nuevos modelos que se deben cargar. En la práctica, vigila el sistema de archivos para identificar cuándo llega una nueva versión del modelo al disco. Cuando ve una nueva versión, procede a crear un cargador para esa versión específica del modelo.

En resumen, Loader sabe casi todo sobre el modelo. Incluye cómo cargarlo y cómo estimar los recursos requeridos del modelo, como la RAM solicitada y la memoria de la GPU. El cargador tiene un puntero al modelo en el disco junto con todos los metadatos necesarios para cargarlo. Pero hay un problema aquí: el cargador no puede cargar el modelo todavía.

Después de crear el cargador, la fuente lo envía al administrador como una versión de aspiración.

Al recibir la versión aspirada del modelo, el gerente continúa con el proceso de publicación. Aquí hay dos posibilidades. Una es que la primera versión del modelo está lista para su implementación. En esta situación, el Gerente se asegurará de que los recursos necesarios estén disponibles. Una vez que lo estén, el administrador le da permiso al cargador para cargar el modelo.

La segunda es que estamos impulsando una nueva versión de un modelo existente. En este caso, el administrador debe consultar el complemento Política de versiones antes de continuar. La Política de versiones determina cómo se lleva a cabo el proceso de carga de una nueva versión del modelo.

En concreto, al cargar una nueva versión de un modelo, podemos elegir entre preservar (1) disponibilidad o (2) recursos. En el primer caso, nos interesa asegurarnos de que nuestro sistema esté siempre disponible para las solicitudes de los clientes entrantes. Sabemos que Manager permite que Loader instancia el nuevo gráfico con los nuevos pesos.

En este punto, tenemos dos versiones de modelo cargadas al mismo tiempo. Pero el Administrador descarga la versión anterior solo después de que se completa la carga y es seguro cambiar entre modelos.

Por otro lado, si queremos ahorrar recursos al no tener el búfer extra (para la nueva versión), podemos optar por preservar los recursos. Puede ser útil para modelos muy pesados ​​tener un pequeño espacio en la disponibilidad, a cambio de ahorrar memoria.

Al final, cuando un cliente solicita un identificador para el modelo, el administrador devuelve un identificador al Servable.

Con esta descripción general, estamos listos para sumergirnos en una aplicación del mundo real. En las siguientes secciones, describimos cómo prestar servicio a una red neuronal convolucional (CNN) mediante TF Serving.

Exportar un modelo para servir

El primer paso para entregar un modelo de AA integrado en TensorFlow es asegurarse de que tenga el formato correcto. Para hacer eso, TensorFlow proporciona la clase SavedModel.

SavedModel es el formato de serialización universal para los modelos de TensorFlow. Si está familiarizado con TF, probablemente haya usado TensorFlow Saver para conservar las variables de su modelo.

TensorFlow Saver proporciona funcionalidades para guardar / restaurar los archivos de punto de control del modelo en / desde el disco. De hecho, SavedModel incluye TensorFlow Saver y está destinado a ser la forma estándar de exportar modelos TF para la publicación.

El objeto SavedModel tiene algunas características interesantes.

Primero, le permite guardar más de un metagrama en un solo objeto de modelo guardado. En otras palabras, nos permite tener diferentes gráficos para diferentes tareas.

Por ejemplo, suponga que acaba de terminar de entrenar su modelo. En la mayoría de las situaciones, para realizar inferencias, su gráfico no necesita algunas operaciones específicas de entrenamiento. Estas operaciones pueden incluir las variables del optimizador, tensores de programación de la tasa de aprendizaje, operaciones adicionales de preprocesamiento, etc.

Además, es posible que desee ofrecer una versión cuantificada de un gráfico para la implementación móvil.

En este contexto, SavedModel le permite guardar gráficos con diferentes configuraciones. En nuestro ejemplo, tendríamos tres gráficos diferentes con las etiquetas correspondientes, como "entrenamiento", "inferencia" y "móvil". Además, estos tres gráficos compartirían el mismo conjunto de variables, lo que enfatiza la eficiencia de la memoria.

No hace mucho tiempo, cuando queríamos implementar modelos TF en dispositivos móviles, necesitábamos saber los nombres de los tensores de entrada y salida para alimentar y obtener datos hacia / desde el modelo. Esta necesidad obligó a los programadores a buscar el tensor que necesitaban entre todos los tensores del gráfico. Si los tensores no se nombran correctamente, la tarea puede resultar muy tediosa.

Para facilitar las cosas, SavedModel ofrece soporte para SignatureDefs. En resumen, SignatureDefs define la firma de un cálculo compatible con TensorFlow. Determina los tensores de entrada y salida adecuados para un gráfico computacional. En pocas palabras, con estas firmas puede especificar los nodos exactos que se utilizarán para la entrada y la salida.

Para utilizar sus API de servicio integradas, TF Serving requiere que los modelos incluyan una o más SignatureDefs.

Para crear dichas firmas, necesitamos proporcionar definiciones para entradas, salidas y el nombre del método deseado. Las entradas y salidas representan un mapeo de la cadena a los objetos TensorInfo (más sobre este último). Aquí, definimos los tensores predeterminados para alimentar y recibir datos desde y hacia un gráfico. El parámetro method_name se dirige a una de las API de servicio de alto nivel de TF.

Actualmente, hay tres API de servicio: clasificación, predicción y regresión. Cada definición de firma coincide con una API de RPC específica. Classification SegnatureDef se utiliza para Classify RPC API. Predict SegnatureDef se utiliza para la API de Predict RPC, y así sucesivamente.

Para la firma de clasificación, debe haber un tensor de entradas (para recibir datos) y al menos uno de los dos posibles tensores de salida: clases y / o puntuaciones. La Regression SignatureDef requiere exactamente un tensor para la entrada y otro para la salida. Por último, la firma Predict permite un número dinámico de tensores de entrada y salida.

Además, SavedModel admite el almacenamiento de activos para los casos en los que la inicialización de operaciones depende de archivos externos. Además, tiene mecanismos para borrar los dispositivos antes de crear el modelo guardado.

Ahora, veamos cómo podemos hacerlo en la práctica.

Configurando el medio ambiente

Antes de comenzar, clone esta implementación de TensorFlow DeepLab-v3 desde Github.

DeepLab es la mejor ConvNet de segmentación semántica de Google. Básicamente, la red toma una imagen como entrada y genera una imagen similar a una máscara que separa ciertos objetos del fondo.

Esta versión se entrenó en el conjunto de datos de segmentación de VOC de Pascal. Así, puede segmentar y reconocer hasta 20 clases. Si desea saber más sobre la segmentación semántica y DeepLab-v3, eche un vistazo a Diving into Deep Convolutional Semantic Segmentation Networks y Deeplab_V3.

Todos los archivos relacionados con el servicio residen en: ./deeplab_v3/serving/. Allí, encontrará dos archivos importantes: deeplab_saved_model.py y deeplab_client.ipynb

Antes de continuar, asegúrese de descargar el modelo previamente entrenado de Deeplab-v3. Dirígete al repositorio de GitHub de arriba, haz clic en el enlace de los puntos de control y descarga la carpeta llamada 16645 / .

Al final, debería tener una carpeta llamada tboard_logs / con la carpeta 16645 / dentro de ella.

Ahora, necesitamos crear dos entornos virtuales Python. Uno para Python 3 y otro para Python 2. Para cada env, asegúrese de instalar las dependencias necesarias. Puede encontrarlos en los archivos serve_requirements.txt y client_requirements.txt.

Necesitamos dos envs de Python porque nuestro modelo, DeepLab-v3, se desarrolló bajo Python 3. Sin embargo, la API de Python de TensorFlow Serving solo se publica para Python 2. Por lo tanto, para exportar el modelo y ejecutar TF serve, usamos Python 3 env . Para ejecutar el código del cliente usando la API de Python TF Serving, usamos el paquete PIP (solo disponible para Python 2).

Tenga en cuenta que puede renunciar al entorno de Python 2 utilizando las API de servicio de bazel. Consulte la Instalación de servicio de TF para obtener más detalles.

Con ese paso completo, comencemos con lo que realmente importa.

Cómo hacerlo

Para usar SavedModel, TensorFlow proporciona una clase de utilidad de alto nivel fácil de usar llamada SavedModelBuilder. La clase SavedModelBuilder proporciona funcionalidades para guardar múltiples meta gráficos, variables asociadas y activos.

Veamos un ejemplo en ejecución de cómo exportar un modelo CNN de segmentación profunda para servir.

Como se mencionó anteriormente, para exportar el modelo, usamos la clase SavedModelBuilder. Generará un archivo de búfer de protocolo SavedModel junto con las variables y activos del modelo (si es necesario).

Analicemos el código.

El SavedModelBuilder recibe (como entrada) el directorio donde guardar los datos del modelo. Aquí, la variable export_path es la concatenación de export_path_base y model_version . Como resultado, las diferentes versiones del modelo se guardarán en directorios separados dentro de la carpeta export_path_base .

Digamos que tenemos una versión de referencia de nuestro modelo en producción, pero queremos implementar una nueva versión. Hemos mejorado la precisión de nuestro modelo y queremos ofrecer esta nueva versión a nuestros clientes.

Para exportar una versión diferente del mismo gráfico, podemos simplemente establecer FLAGS.model_version en un valor entero más alto. Luego, se creará una carpeta diferente (que contiene la nueva versión de nuestro modelo) dentro de la carpeta export_path_base .

Ahora, necesitamos especificar los tensores de entrada y salida de nuestro modelo. Para hacer eso, usamos SignatureDefs. Las firmas definen qué tipo de modelo queremos exportar. Proporciona un mapeo de cadenas (nombres lógicos de Tensor) a objetos TensorInfo. La idea es que, en lugar de hacer referencia a los nombres de tensor reales para entrada / salida, los clientes pueden hacer referencia a los nombres lógicos definidos por las firmas.

Para servir una CNN de segmentación semántica, vamos a crear una firma de predicción . Tenga en cuenta que la función build_signature_def () toma la asignación de tensores de entrada y salida, así como la API deseada.

Un SignatureDef requiere la especificación de: entradas, salidas y nombre del método. Tenga en cuenta que esperamos tres valores para las entradas: una imagen y dos tensores más que especifican sus dimensiones (alto y ancho). Para los productos , definimos solo un resultado: la máscara de resultados de segmentación.

Tenga en cuenta que las cadenas 'image', 'height', 'width' y 'segmentation_map' no son tensores. En cambio, son nombres lógicos que se refieren a los tensores reales input_tensor , image_height_tensor e image_width_tensor . Por lo tanto, pueden ser cualquier cadena única que desee.

Además, las asignaciones en SignatureDefs se relacionan con los objetos protobuf de TensorInfo, no con los tensores reales. Para crear objetos TensorInfo, usamos la función de utilidad: tf.saved_model.utils.build_tensor_info (tensor) .

Eso es. Ahora llamamos a la función add_meta_graph_and_variables () para construir el objeto de búfer del protocolo SavedModel. Luego ejecutamos el método save () y conservará una instantánea de nuestro modelo en el disco que contiene las variables y los activos del modelo.

Ahora podemos ejecutar deeplab_saved_model.py para exportar nuestro modelo.

Si todo salió bien, verá la carpeta ./serving/versions/1 . Tenga en cuenta que el '1' representa la versión actual del modelo. Dentro de cada subdirectorio de versión, verá los siguientes archivos:

  • Saved_model.pb o Saved_model.pbtxt. Este es el archivo de modelo guardado serializado. Incluye una o más definiciones de gráficos del modelo, así como las definiciones de firmas.
  • Variables. Esta carpeta contiene las variables serializadas de los gráficos.

Ahora, estamos listos para lanzar nuestro servidor modelo. Para hacer eso, ejecute:

$ tensorflow_model_server --port=9000 --model_name=deeplab --model_base_path=

El model_base_path refiere a donde se guardó el modelo exportado. Además, no especificamos la carpeta de la versión en la ruta. El control de versiones del modelo lo maneja TF Serving.

Generación de solicitudes de clientes

El código del cliente es muy sencillo. Échale un vistazo en: deeplab_client.ipynb.

Primero, leemos la imagen que queremos enviar al servidor y la convertimos al formato correcto.

A continuación, creamos un código auxiliar de gRPC. El stub nos permite llamar a los métodos del servidor remoto. Para hacer eso, instanciamos la clase beta_create_PredictionService_stub del módulo prediction_service_pb2 . En este punto, el stub contiene la lógica necesaria para llamar a procedimientos remotos (desde el servidor) como si fueran locales.

Ahora, necesitamos crear y configurar el objeto de solicitud. Dado que nuestro servidor implementa la API de predicción de TensorFlow, necesitamos analizar una solicitud de predicción. Para emitir una petición Predecir, en primer lugar, creamos una instancia del PredictRequest clase desde el predict_pb2 módulo. También necesitamos especificar los parámetros model_spec.name y model_spec.signature_name . El nombre param es el argumento 'model_name' que definimos cuando lanzamos el servidor. Y el signature_name se refiere al nombre lógico asignado al parámetro signature_def_map () de la rutina add_meta_graph ().

A continuación, debemos proporcionar los datos de entrada definidos en la firma del servidor. Recuerde que, en el servidor, definimos una API Predict para esperar una imagen, así como dos escalares (el alto y el ancho de la imagen). Para alimentar los datos de entrada en el objeto de solicitud, TensorFlow proporciona la utilidad tf.make_tensor_proto () . Este método crea un objeto TensorProto a partir de un objeto numpy / Python. Podemos usarlo para alimentar la imagen y sus dimensiones al objeto de solicitud.

Parece que estamos listos para llamar al servidor. Para hacer eso, llamamos al método Predict () (usando el stub) y pasamos el objeto de solicitud como argumento.

Para las solicitudes que devuelven una única respuesta, gRPC admite tanto llamadas síncronas como asincrónicas. Por lo tanto, si desea realizar algún trabajo mientras se procesa la solicitud, podríamos llamar a Predict.future () en lugar de Predict () .

Ahora podemos buscar y disfrutar de los resultados.

Espero que les haya gustado este artículo. ¡Gracias por leer!

Si quieres más, echa un vistazo a:

Cómo entrenar su propio FaceID ConvNet usando la ejecución de TensorFlow Eager

Los rostros están en todas partes, desde fotos y videos en sitios web de redes sociales, hasta aplicaciones de seguridad para el consumidor como ... medium.freecodecamp.org Buceo en redes de segmentación semántica convolucional profunda y Deeplab_V3

Las redes neuronales convolucionales profundas (DCNN) han logrado un éxito notable en varias aplicaciones de visión artificial ... medium.freecodecamp.org