1. Antes de comenzar
En este codelab, aprenderás a ejecutar una inferencia de clasificación de imágenes de un sitio web con TensorFlow Serving con REST y gRPC.
Requisitos previos
- Conocimientos básicos del desarrollo web, como HTML y JavaScript
- Conocimientos básicos del aprendizaje automático con TensorFlow, como la implementación y el entrenamiento
- 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 simple y realizar predicciones con el modelo de clasificación de imágenes descargado mediante TensorFlow Serving (REST y gRPC).
- Cómo renderizar el resultado de la detección en la IU
Requisitos
- Docker
- Google Chrome
- Servidor web para 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 web de gRPC (solo es necesario si deseas volver a generar el stub de gRPC por tu cuenta)
2. Prepárate
Para descargar el código de este codelab, haz lo siguiente:
- Navega a este repositorio de GitHub.
- Haz clic en Código > Descargar ZIP para descargar todo el código de este codelab.
- Descomprime el archivo ZIP descargado para descomprimir 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 completo de la app de muestra finalizada.
3. Instala las dependencias
Para instalar las dependencias, sigue estos pasos:
- En tu terminal, navega a la carpeta
starter
y, luego, instala los paquetes de NPM obligatorios:
npm install
4. Cómo ejecutar el sitio web inicial
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 Servidor web para Chrome en la lista de la app. - Inicia el servidor web para Chrome y elige la carpeta
TFServing/ImageClassificationWeb/starter/dist/
. - Haga clic en el botón de activación del Servidor web para habilitarlo y, luego, navegue a http://localhost:8887/ en su navegador.
Ejecutar y explorar el sitio web
Deberías ver el sitio web ahora. La IU es bastante simple: hay una imagen de gato en la que deseas clasificarla 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 muestra el resultado de la clasificación al sitio web, que muestra el resultado.
Si hace clic en Clasificar, 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 su contenido principal. El siguiente es un ejemplo de clasificación de flores:
Hay varios modelos de clasificación de imágenes previamente entrenados en TensorFlow Hub. En este codelab, usas un modelo popular de Inception v3.
Para implementar el modelo de clasificación de imágenes con TensorFlow Serving, sigue estos pasos:
- Descarga el archivo de 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, una subcarpeta123
dentro de ella. - Coloca la carpeta
variables
extraída y el archivosaved_model.pb
en la subcarpeta123
.
Puedes hacer referencia a la carpeta inception_v3
como la carpeta SavedModel
. 123
es un número de versión de ejemplo. Si quieres, puedes elegir otro número.
La estructura de carpetas debe verse de la siguiente manera:
Inicia TensorFlow Serving
- En tu terminal, inicia TensorFlow Serving con Docker, pero reemplaza
PATH/TO/SAVEDMODEL
con 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 comenzar. 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 frontend de JavaScript a TensorFlow Serving por razones de seguridad. A fin de solucionar esto, debes usar un proxy, como Envoy, para enviar la solicitud de JavaScript al backend de TensorFlow Serving.
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. Luego, debería comenzar Envoy. 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 mediante REST
El backend está listo para que puedas enviar solicitudes de clientes a TensorFlow Serving para clasificar imágenes. Hay dos maneras 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 de REST a TensorFlow Serving.
- Extrae el resultado previsto de la respuesta de REST y muestra el resultado.
Debes seguir 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. Debes implementar esta rama de REST para crear una solicitud de REST primero:
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 contiene el tensor de imagen para el modelo de Inception v3 que usas, por lo que debes extraer los valores RGB de cada píxel de la imagen en un array y luego unirlo 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 justo después del código anterior en la rama de REST:
// Send the REST request.
xhr.send(data);
Cómo procesar la respuesta de REST de TensorFlow Serving
El modelo Inception v3 muestra un arreglo de probabilidades de que la imagen pertenezca a categorías predefinidas. Cuando la predicción se realiza correctamente, deberías mostrar la categoría más probable en la IU.
Implementarás 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 previstas de la respuesta, identifica la categoría más probable del objeto y muestra el resultado en la IU.
Ejecuta la app
- 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
- Actualice http://localhost:8887/ en su navegador y, luego, haga clic en REST > Clasificar.
El sitio web muestra 286
como la categoría prevista, 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 es compatible con gRPC.
gRPC es un framework moderno de código abierto, de alto rendimiento y llamada de procedimiento remoto (RPC), que se puede ejecutar en cualquier entorno. Puede conectar servicios en centros de datos, y entre ellos, con compatibilidad conectable para 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 gRPC a TensorFlow Serving.
- Extrae el resultado previsto de la respuesta de gRPC y muéstralo 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. Para 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, estos archivos .proto
son los necesarios:
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, puedes 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 en 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.
});
Cómo procesar 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 previstas de la respuesta, identifica la categoría más probable del objeto y muestra el resultado en la IU.
Ejecuta la app
- En tu terminal, usa webpack para empaquetar 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.
- Haga clic en gRPC > Clasificar.
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.