1. Antes de comenzar
En este codelab, aprenderás a ejecutar una inferencia de clasificación de imágenes desde un sitio web con TensorFlow Serving a través de REST y gRPC.
Requisitos previos
- Conocimientos básicos de desarrollo web, como HTML y JavaScript
- Conocimientos básicos del aprendizaje automático con TensorFlow, como el entrenamiento y la implementación
- Conocimientos básicos de las terminales y Docker
Qué aprenderás
- Cómo encontrar modelos de clasificación de imágenes previamente entrenados en TensorFlow Hub
- Cómo crear un sitio web sencillo y hacer predicciones con el modelo de clasificación de imágenes descargado a través de TensorFlow Serving (REST y gRPC)
- Cómo renderizar el resultado de la detección en la IU
Requisitos
- Docker
- Google Chrome
- Web Server for Chrome
- Node.js y NPM
- Bash
- Compilador de búfer de protocolo (solo es necesario si quieres volver a generar el stub de gRPC por tu cuenta)
- Complemento del generador de código de gRPC-web (solo es necesario si quieres volver a generar el stub de gRPC por tu cuenta)
2. Prepárate
Para descargar el código de este codelab, haz lo siguiente:
- Dirígete a este repositorio de GitHub.
- Haz clic en Code > Download ZIP para descargar todo el código de este codelab.
- Descomprime el archivo ZIP descargado para desempaquetar una carpeta raíz
codelabs
con todos los recursos que necesitas.
Para este codelab, solo necesitas los archivos del subdirectorio TFServing/ImageClassificationWeb
en el repositorio, que contiene dos carpetas:
- La carpeta
starter
contiene el código de inicio en el que se basa este codelab. - La carpeta
finished
contiene el código completado de la app de ejemplo finalizada.
3. Instala las dependencias
Para instalar las dependencias, haz lo siguiente:
- En tu terminal, navega a la carpeta
starter
y, luego, instala los paquetes de NPM necesarios:
npm install
4. Ejecuta el sitio web de inicio
Usa Web Server for Chrome para cargar el archivo TFServing/ImageClassificationWeb/starter/dist/index.html
:
- Ingresa
Chrome://apps/
en la barra de direcciones de Chrome y, luego, busca Web Server for Chrome en la lista de apps. - Inicia Web Server for Chrome y, luego, elige la carpeta
TFServing/ImageClassificationWeb/starter/dist/
. - Haz clic en el botón de activación Servidor web para habilitarlo y, luego, navega a http://localhost:8887/ en tu navegador.
Ejecuta y explora el sitio web
Ahora deberías ver el sitio web. La IU es bastante sencilla: hay una imagen de un gato que deseas clasificar y el usuario puede enviar los datos al backend con REST o gRPC. El backend realiza la clasificación de imágenes en la imagen y devuelve el resultado de la clasificación al sitio web, que muestra el resultado.
Si haces clic en Classify, no sucede nada porque todavía no puede comunicarse con el backend.
5. Implementa un modelo de clasificación de imágenes con TensorFlow Serving
La clasificación de imágenes es una tarea de AA muy común que clasifica una imagen en categorías predefinidas según el contenido principal de la imagen. Este es un ejemplo de clasificación de flores:
En TensorFlow Hub, hay varios modelos de clasificación de imágenes previamente entrenados. En este codelab, usarás un modelo popular de Inception v3.
Para implementar el modelo de clasificación de imágenes con TensorFlow Serving, haz lo siguiente:
- Descarga el archivo del modelo Inception v3.
- Descomprime el archivo
.tar.gz
descargado con una herramienta de descompresión, como 7-Zip. - Crea una carpeta
inception_v3
y, luego, crea una subcarpeta123
dentro de ella. - Coloca la carpeta
variables
extraída y el archivosaved_model.pb
en la subcarpeta123
.
Puedes consultar la carpeta inception_v3
como la carpeta SavedModel
. 123
es un ejemplo de número de versión. Si lo deseas, puedes elegir otro número.
La estructura de carpetas debería verse como en la siguiente imagen:
Inicia TensorFlow Serving
- En tu terminal, inicia TensorFlow Serving con Docker, pero reemplaza
PATH/TO/SAVEDMODEL
por la ruta de acceso absoluta de la carpetainception_v3
en tu computadora.
docker pull tensorflow/serving docker run -it --rm -p 8500:8500 -p 8501:8501 -v "PATH/TO/SAVEDMODEL:/models/inception" -e MODEL_NAME=inception tensorflow/serving
Docker descarga automáticamente la imagen de TensorFlow Serving primero, lo cual tarda un minuto. Luego, TensorFlow Serving debería iniciarse. El registro debería verse como este fragmento de código:
2022-02-25 06:01:12.513231: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:206] Restoring SavedModel bundle. 2022-02-25 06:01:12.585012: I external/org_tensorflow/tensorflow/core/platform/profile_utils/cpu_utils.cc:114] CPU Frequency: 3000000000 Hz 2022-02-25 06:01:13.395083: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:190] Running initialization op on SavedModel bundle at path: /models/inception/123 2022-02-25 06:01:13.837562: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:277] SavedModel load for tags { serve }; Status: success: OK. Took 1928700 microseconds. 2022-02-25 06:01:13.877848: I tensorflow_serving/servables/tensorflow/saved_model_warmup_util.cc:59] No warmup data file found at /models/inception/123/assets.extra/tf_serving_warmup_requests 2022-02-25 06:01:13.929844: I tensorflow_serving/core/loader_harness.cc:87] Successfully loaded servable version {name: inception version: 123} 2022-02-25 06:01:13.985848: I tensorflow_serving/model_servers/server_core.cc:486] Finished adding/updating models 2022-02-25 06:01:13.985987: I tensorflow_serving/model_servers/server.cc:367] Profiler service is enabled 2022-02-25 06:01:13.988994: I tensorflow_serving/model_servers/server.cc:393] Running gRPC ModelServer at 0.0.0.0:8500 ... [warn] getaddrinfo: address family for nodename not supported 2022-02-25 06:01:14.033872: I tensorflow_serving/model_servers/server.cc:414] Exporting HTTP/REST API at:localhost:8501 ... [evhttp_server.cc : 245] NET_LOG: Entering the event loop ...
6. Configura el proxy de Envoy
Actualmente, TensorFlow Serving no establece el encabezado Access-Control-Allow-Origin
, por lo que el navegador bloquea la solicitud del código JavaScript del frontend a TensorFlow Serving por motivos de seguridad. Para solucionar este problema, debes usar un proxy, como Envoy, para transferir la solicitud de JavaScript al backend de TensorFlow Serving.
Cómo iniciar Envoy
- En tu terminal, descarga la imagen de Envoy y, luego, inicia Envoy con Docker, pero reemplaza el marcador de posición
PATH/TO/ENVOY-CUSTOM.YAML
por la ruta de acceso absoluta del archivoenvoy-custom.yaml
en la carpetastarter
.
docker pull envoyproxy/envoy-dev:fd3e8370ddb7a96634c192d1461516e6de1d1797 docker run --add-host host.docker.internal:host-gateway --rm -it -p 9901:9901 -p 8000:8000 -p 8080:8080 -v PATH/TO/ENVOY-CUSTOM.YAML:/envoy-custom.yaml envoyproxy/envoy-dev:fd3e8370ddb7a96634c192d1461516e6de1d1797 -c /envoy-custom.yaml
Docker descarga automáticamente la imagen de Envoy primero. Luego, Envoy debería iniciarse. El registro debería verse como este fragmento de código:
[2022-03-02 07:51:48.563][1][info][main] [source/server/server.cc:436] response trailer map: 152 bytes: grpc-message,grpc-status [2022-03-02 07:51:48.681][1][info][main] [source/server/server.cc:772] runtime: {} [2022-03-02 07:51:48.682][1][info][admin] [source/server/admin/admin.cc:134] admin address: 0.0.0.0:9901 [2022-03-02 07:51:48.683][1][info][config] [source/server/configuration_impl.cc:127] loading tracing configuration [2022-03-02 07:51:48.683][1][info][config] [source/server/configuration_impl.cc:87] loading 0 static secret(s) [2022-03-02 07:51:48.683][1][info][config] [source/server/configuration_impl.cc:93] loading 2 cluster(s) [2022-03-02 07:51:48.687][1][info][config] [source/server/configuration_impl.cc:97] loading 2 listener(s) [2022-03-02 07:51:48.694][1][info][config] [source/server/configuration_impl.cc:109] loading stats configuration [2022-03-02 07:51:48.696][1][info][main] [source/server/server.cc:868] starting main dispatch loop [2022-03-02 07:51:48.881][1][info][runtime] [source/common/runtime/runtime_impl.cc:446] RTDS has finished initialization [2022-03-02 07:51:48.881][1][info][upstream] [source/common/upstream/cluster_manager_impl.cc:207] cm init: all clusters initialized [2022-03-02 07:51:48.881][1][info][main] [source/server/server.cc:849] all clusters initialized. initializing init manager [2022-03-02 07:51:48.881][1][info][config] [source/server/listener_manager_impl.cc:784] all dependencies initialized. starting workers [2022-03-02 07:51:48.902][1][warning][main] [source/server/server.cc:747] there is no configured limit to the number of allowed active connections. Set a limit via the runtime key overload.global_downstream_max_connections
7. Conecta el sitio web con TensorFlow a través de REST
Ahora el backend está listo, por lo que puedes enviar solicitudes de clientes a TensorFlow Serving para clasificar imágenes. Existen dos métodos de enviar solicitudes a TensorFlow Serving:
- REST
- gRPC
Envía solicitudes y recibe respuestas a través de REST
Existen tres pasos sencillos para enviar y recibir solicitudes a través de REST:
- Crea la solicitud REST.
- Envía la solicitud REST a TensorFlow Serving.
- Extrae el resultado previsto de la respuesta de REST y muestra el resultado.
Completarás estos pasos en el archivo src/index.js
.
Crea la solicitud de REST
En este momento, la función classify_img()
no envía la solicitud de REST a TensorFlow Serving. Primero, debes implementar esta rama de REST para crear una solicitud de REST:
if (radioButtons[0].checked) {
console.log('Using REST');
// TODO: Add code to send a REST request to TensorFlow Serving.
}
TensorFlow Serving espera una solicitud POST que contenga el tensor de imagen para el modelo Inception v3 que usas, por lo que debes extraer los valores RGB de cada píxel de la imagen en un array y, luego, encapsular el array en un JSON, que es la carga útil de la solicitud.
- Agrega este código a la rama de REST:
//Create the REST request.
let imgTensor = new Array();
let pixelArray = new Array();
context.drawImage(img, 0, 0);
for(let i=0; i<inputImgHeight; i++) {
pixelArray[i] = new Array();
for (let j=0; j<inputImgWidth; j++) {
pixelArray[i][j] = new Array();
pixelArray[i][j].push(context.getImageData(i, j, 1, 1).data[0]/255);
pixelArray[i][j].push(context.getImageData(i, j, 1, 1).data[1]/255);
pixelArray[i][j].push(context.getImageData(i, j, 1, 1).data[2]/255);
}
}
imgTensor.push(pixelArray);
const RESTURL = 'http://localhost:8000/v1/models/inception:predict';
let xhr = new XMLHttpRequest();
xhr.open('POST', RESTURL);
xhr.setRequestHeader('Content-Type', 'application/json;charset=utf-8;');
let data = JSON.stringify({
instances: imgTensor
});
xhr.onload = () => {
}
xhr.onerror = () => {
console.log('REST request error');
}
Envía la solicitud de REST a TensorFlow Serving
Ahora puedes enviar la solicitud.
- Agrega este código inmediatamente después del código anterior en la rama de REST:
// Send the REST request.
xhr.send(data);
Procesa la respuesta de REST de TensorFlow Serving
El modelo Inception v3 devuelve un array de probabilidades de que la imagen pertenezca a categorías predefinidas. Cuando la predicción sea exitosa, debes mostrar la categoría más probable en la IU.
Implementas el objeto de escucha onload()
para controlar la respuesta.
xhr.onload = () => {
}
- Agrega este código al objeto de escucha
onload()
:
// Process the REST response.
const response = JSON.parse(xhr.responseText);
const maxIndex = argmax(response['predictions'][0])
document.getElementById('category').textContent = 'Predicted category: ' + maxIndex;
Ahora, el objeto de escucha extrae las probabilidades predichas de la respuesta, identifica la categoría más probable del objeto y muestra el resultado en la IU.
Ejecución
- En tu terminal, ve a la carpeta
starter
y usa webpack para empaquetar todos los archivos JavaScript en un solo archivo que puedas incorporar en el archivodist/index.html
:
npm install -g npx npm install --save-dev webpack npx webpack
- Actualiza http://localhost:8887/ en tu navegador y, luego, haz clic en REST > Classify.
El sitio web muestra 286
como la categoría predicha, que se asigna a la etiqueta Egyptian Cat
en el conjunto de datos de ImageNet.
8. Conecta el sitio web con TensorFlow Serving a través de gRPC
Además de REST, TensorFlow Serving también admite gRPC.
gRPC es un framework de llamada de procedimiento remoto (RPC) moderno de código abierto y alto rendimiento, que se puede ejecutar en cualquier entorno. Puede conectar de forma eficaz servicios en centros de datos (y entre ellos) y ofrece compatibilidad conectable con balanceo de cargas, seguimiento, verificación de estado y autenticación. Se observó que gRPC tiene un mejor rendimiento que REST en la práctica.
Envía solicitudes y recibe respuestas con gRPC
Hay cuatro pasos sencillos:
- Genera el código de stub del cliente de gRPC (opcional).
- Crea la solicitud de gRPC.
- Envía la solicitud de gRPC a TensorFlow Serving.
- Extrae el resultado previsto de la respuesta de gRPC y lo muestra en la IU.
Completa estos pasos en el archivo src/index.js
.
Genera el código de stub del cliente de gRPC (opcional)
Para usar gRPC con TensorFlow Serving, debes seguir el flujo de trabajo de gRPC. Si quieres obtener más información, consulta la documentación de gRPC.
TensorFlow Serving y TensorFlow definen los archivos .proto
por ti. A partir de TensorFlow y TensorFlow Serving 2.8, se necesitan estos archivos .proto
:
tensorflow/core/example/example.proto
tensorflow/core/example/feature.proto
tensorflow/core/protobuf/struct.proto
tensorflow/core/protobuf/saved_object_graph.proto
tensorflow/core/protobuf/saver.proto
tensorflow/core/protobuf/trackable_object_graph.proto
tensorflow/core/protobuf/meta_graph.proto
tensorflow/core/framework/node_def.proto
tensorflow/core/framework/attr_value.proto
tensorflow/core/framework/function.proto
tensorflow/core/framework/types.proto
tensorflow/core/framework/tensor_shape.proto
tensorflow/core/framework/full_type.proto
tensorflow/core/framework/versions.proto
tensorflow/core/framework/op_def.proto
tensorflow/core/framework/graph.proto
tensorflow/core/framework/tensor.proto
tensorflow/core/framework/resource_handle.proto
tensorflow/core/framework/variable.proto
tensorflow_serving/apis/inference.proto
tensorflow_serving/apis/classification.proto
tensorflow_serving/apis/predict.proto
tensorflow_serving/apis/regression.proto
tensorflow_serving/apis/get_model_metadata.proto
tensorflow_serving/apis/input.proto
tensorflow_serving/apis/prediction_service.proto
tensorflow_serving/apis/model.proto
- En tu terminal, navega a la carpeta
starter/src/proto/
y genera el stub:
bash generate_grpc_stub_js.sh
Crea la solicitud de gRPC
Al igual que con la solicitud de REST, debes crear la solicitud de gRPC en la rama de gRPC..
if (connectionMode[picker.selectedRow(inComponent: 0)] == "REST") {
}
else {
print("Using gRPC")
// TODO: Add code to send a gRPC request to TensorFlow Serving.
}
- Agrega este código a la rama de gRPC:
// Create the gRPC request.
const PredictModule = require('./proto/generated/tensorflow_serving/apis/predict_pb.js');
const PredictionServiceClientModule = require('./proto/generated/tensorflow_serving/apis/prediction_service_grpc_web_pb.js');
const ModelModule = require('./proto/generated/tensorflow_serving/apis/model_pb.js');
const TensorModule = require('./proto/generated/tensorflow/core/framework/tensor_pb.js');
const GPRCURL = 'http://localhost:8080';
const stub = new PredictionServiceClientModule.PredictionServiceClient(GPRCURL);
const modelSpec = new ModelModule.ModelSpec();
modelSpec.setName('inception');
const tensorProto = new TensorModule.TensorProto();
const tensorShapeProto = new TensorModule.TensorShapeProto();
const batchDim = (new TensorModule.TensorShapeProto.Dim()).setSize(1);
const heightDim = (new TensorModule.TensorShapeProto.Dim()).setSize(inputImgHeight);
const widthDim = (new TensorModule.TensorShapeProto.Dim()).setSize(inputImgWidth);
const channelDim = (new TensorModule.TensorShapeProto.Dim()).setSize(3);
tensorShapeProto.setDimList([batchDim, heightDim, widthDim, channelDim]);
tensorProto.setDtype(proto.tensorflow.DataType.DT_FLOAT);
tensorProto.setTensorShape(tensorShapeProto);
context.drawImage(img, 0, 0);
for(let i=0; i<inputImgHeight; i++) {
for (let j=0; j<inputImgWidth; j++) {
tensorProto.addFloatVal(context.getImageData(i, j, 1, 1).data[0]/255);
tensorProto.addFloatVal(context.getImageData(i, j, 1, 1).data[1]/255);
tensorProto.addFloatVal(context.getImageData(i, j, 1, 1).data[2]/255);
}
}
const predictionServiceRequest = new PredictModule.PredictRequest();
predictionServiceRequest.setModelSpec(modelSpec);
predictionServiceRequest.getInputsMap().set('inputs', tensorProto);
Envía la solicitud de gRPC a TensorFlow Serving
Ahora puedes enviar la solicitud.
- Agrega este código inmediatamente después del código de la rama de gRPC en el fragmento de código anterior:
// Send the gRPC request.
stub.predict(predictionServiceRequest, {}, function(err, response) {
// TODO: Add code to process the response.
});
Procesa la respuesta de gRPC de TensorFlow Serving
Por último, implementa la función de devolución de llamada anterior para controlar la respuesta.
- Agrega este código al cuerpo de la función en el fragmento de código anterior:
// Process the gRPC response.
if (err) {
console.log(err.code);
console.log(err.message);
}
else {
const maxIndex = argmax(response.getOutputsMap().get('logits').getFloatValList());
document.getElementById('category').textContent = 'Predicted category: ' + maxIndex;
}
Ahora, el objeto de escucha extrae las probabilidades predichas de la respuesta, identifica la categoría más probable del objeto y muestra el resultado en la IU.
Ejecución
- En tu terminal, usa webpack para agrupar todos los archivos JavaScript en un solo archivo que puedas incorporar en el archivo
index.html
:
npx webpack
- Actualiza http://localhost:8887/ en tu navegador.
- Haz clic en gRPC > Classify.
El sitio web muestra la categoría prevista de 286
, que se asigna a la etiqueta Egyptian Cat
en el conjunto de datos de ImageNet.
9. Felicitaciones
Usaste TensorFlow Serving para agregar capacidades de clasificación de imágenes a tu sitio web.