Crea un sitio web simple que clasifique imágenes

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

2. Prepárate

Para descargar el código de este codelab, haz lo siguiente:

  1. Navega a este repositorio de GitHub.
  2. Haz clic en Código > Descargar ZIP para descargar todo el código de este codelab.

a72f2bb4caa9a96.png

  1. 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:

  1. Ingresa Chrome://apps/ en la barra de direcciones de Chrome y, luego, busca Servidor web para Chrome en la lista de la app.
  2. Inicia el servidor web para Chrome y elige la carpeta TFServing/ImageClassificationWeb/starter/dist/.
  3. Haga clic en el botón de activación del Servidor web para habilitarlo y, luego, navegue a http://localhost:8887/ en su navegador.

f7b43cd44ebf1f1b.png

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.

837d97a27c59a0b3.png

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:

a6da16b4a7665db0.png

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:

  1. Descarga el archivo de modelo Inception v3.
  2. Descomprime el archivo .tar.gz descargado con una herramienta de descompresión, como 7-Zip.
  3. Crea una carpeta inception_v3 y, luego, una subcarpeta 123 dentro de ella.
  4. Coloca la carpeta variables extraída y el archivo saved_model.pb en la subcarpeta 123.

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:

21a8675ac8d31907.png

Inicia TensorFlow Serving

  • En tu terminal, inicia TensorFlow Serving con Docker, pero reemplaza PATH/TO/SAVEDMODEL con la ruta de acceso absoluta de la carpeta inception_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 archivo envoy-custom.yaml en la carpeta starter.
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:

  1. Crea la solicitud REST.
  2. Envía la solicitud de REST a TensorFlow Serving.
  3. 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

  1. 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 archivo dist/index.html:
npm install -g npx
npm install --save-dev webpack
npx webpack
  1. 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.

c865a92b9b58335d.png

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.

b6f4449c2c850b0e.png

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:

  1. Genera el código de stub del cliente de gRPC (opcional).
  2. Crea la solicitud de gRPC.
  3. Envía la solicitud gRPC a TensorFlow Serving.
  4. 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.

a9d0e5cb543467b4.png

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

  1. 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
  1. Actualiza http://localhost:8887/ en tu navegador.
  2. 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.

Más información