Créer un site Web simple qui classe des images

1. Avant de commencer

Dans cet atelier de programmation, vous allez apprendre à exécuter une inférence de classification d'images à partir d'un site Web à l'aide de TensorFlow Serving avec REST et gRPC.

Prérequis

  • Connaissances de base en développement Web, comme HTML et JavaScript
  • Connaissances de base du machine learning avec TensorFlow, telles que l'entraînement et le déploiement
  • Connaissances de base des terminaux et de Docker

Points abordés

  • Découvrez comment trouver des modèles de classification d'images pré-entraînés sur TensorFlow Hub.
  • Créer un site Web simple et effectuer des prédictions avec le modèle de classification d'images téléchargé via TensorFlow Serving (REST et gRPC)
  • Afficher le résultat de la détection dans l'UI

Prérequis

2. Configuration

Pour télécharger le code de cet atelier de programmation, procédez comme suit :

  1. Accédez à ce dépôt GitHub.
  2. Cliquez sur Code > Download ZIP (Code > Télécharger le fichier ZIP) afin de télécharger l'ensemble du code pour cet atelier de programmation.

a72f2bb4caa9a96.png

  1. Décompressez le fichier ZIP téléchargé pour accéder au dossier racine codelabs contenant toutes les ressources nécessaires.

Pour cet atelier de programmation, vous n'avez besoin que des fichiers du sous-répertoire TFServing/ImageClassificationWeb dans le dépôt, qui contient deux dossiers :

  • Le dossier starter contient le code de démarrage sur lequel s'appuie cet atelier de programmation.
  • Le dossier finished contient le code final de l'application exemple.

3. Installer les dépendances

Pour installer les dépendances :

  • Dans votre terminal, accédez au dossier starter, puis installez les packages NPM requis :
npm install

4. Exécuter le site Web de démarrage

Utilisez Web Server for Chrome pour charger le fichier TFServing/ImageClassificationWeb/starter/dist/index.html :

  1. Saisissez Chrome://apps/ dans la barre d'adresse de Chrome, puis recherchez Web Server for Chrome dans la liste des applications.
  2. Lancez le serveur Web pour Chrome, puis sélectionnez le dossier TFServing/ImageClassificationWeb/starter/dist/.
  3. Cliquez sur le bouton bascule Web Server (Serveur Web) pour l'activer, puis accédez à http://localhost:8887/ dans votre navigateur.

f7b43cd44ebf1f1b.png

Exécuter et explorer le site Web

Le site Web devrait maintenant s'afficher. L'UI est assez simple : elle contient une image de chat que vous souhaitez classer. L'utilisateur peut envoyer les données au backend avec REST ou gRPC. Le backend effectue la classification de l'image et renvoie le résultat de la classification au site Web, qui l'affiche.

837d97a27c59a0b3.png

Si vous cliquez sur Classify (Classer), rien ne se passe, car la communication avec le backend n'est pas encore établie.

5. Déployer un modèle de classification d'images avec TensorFlow Serving

La classification d'images est une tâche de ML très courante qui consiste à classer une image dans des catégories prédéfinies en fonction du contenu principal de l'image. Voici un exemple de classification de fleurs :

a6da16b4a7665db0.png

TensorFlow Hub propose un certain nombre de modèles de classification d'images pré-entraînés. Pour cet atelier de programmation, vous utiliserez un modèle Inception v3 populaire.

Pour déployer le modèle de classification d'images avec TensorFlow Serving :

  1. Téléchargez le fichier du modèle Inception v3.
  2. Décompressez le fichier .tar.gz téléchargé à l'aide d'un outil de décompression tel que 7-Zip.
  3. Créez un dossier inception_v3, puis créez un sous-dossier 123 à l'intérieur.
  4. Placez le dossier variables et le fichier saved_model.pb extraits dans le sous-dossier 123.

Vous pouvez faire référence au dossier inception_v3 en tant que dossier SavedModel. 123 est un exemple de numéro de version. Si vous le souhaitez, vous pouvez choisir un autre nombre.

La structure des dossiers doit ressembler à celle de cette image :

21a8675ac8d31907.png

Démarrer TensorFlow Serving

  • Dans votre terminal, démarrez TensorFlow Serving avec Docker, en remplaçant PATH/TO/SAVEDMODEL par le chemin absolu du dossier inception_v3 sur votre ordinateur.
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 commence par télécharger automatiquement l'image TensorFlow Serving, ce qui prend une minute. Le service TensorFlow Serving devrait alors démarrer. Le journal doit se présenter comme cet extrait de code :

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. Configurer le proxy Envoy

Actuellement, TensorFlow Serving ne définit pas l'en-tête Access-Control-Allow-Origin. Pour des raisons de sécurité, le navigateur bloque donc la requête du JavaScript frontend vers TensorFlow Serving. Pour contourner ce problème, vous devez utiliser un proxy, tel que Envoy, afin de transférer la requête de JavaScript vers le backend TensorFlow Serving.

Démarrer Envoy

  • Dans votre terminal, téléchargez l'image Envoy et démarrez Envoy avec Docker, en remplaçant l'espace réservé PATH/TO/ENVOY-CUSTOM.YAML par le chemin absolu du fichier envoy-custom.yaml dans le dossier 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 commence par télécharger automatiquement l'image Envoy. Envoy devrait alors démarrer. Le journal doit se présenter comme cet extrait de code :

[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. Connecter le site Web à TensorFlow via REST

Le backend est prêt pour que vous puissiez envoyer des requêtes client à TensorFlow Serving afin de classer des images. Il existe deux façons d'envoyer des requêtes à TensorFlow Serving :

  • REST
  • gRPC

Envoyer des requêtes et recevoir des réponses via REST

La procédure pour envoyer et recevoir des requêtes via REST comporte trois étapes simples :

  1. Créez la requête REST.
  2. Envoyez la requête REST à TensorFlow Serving.
  3. Extrayez le résultat prédit de la réponse REST et affichez-le.

Vous devez effectuer ces étapes dans le fichier src/index.js.

Créer la requête REST

Pour le moment, la fonction classify_img() n'envoie pas la requête REST à TensorFlow Serving. Vous devez d'abord implémenter cette branche REST pour créer une requête REST :

if (radioButtons[0].checked) {
    console.log('Using REST');
    // TODO: Add code to send a REST request to TensorFlow Serving.

} 

TensorFlow Serving s'attend à recevoir une requête POST contenant le Tensor d'image pour le modèle Inception v3 que vous utilisez. Vous devez donc extraire les valeurs RVB de chaque pixel de l'image dans un tableau, puis encapsuler le tableau dans un JSON, qui constitue la charge utile de la requête.

  • Ajoutez ce code à la branche 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');
}

Envoyer la requête REST à TensorFlow Serving

Vous pouvez maintenant envoyer la demande.

  • Ajoutez ce code immédiatement après le code ci-dessus dans la branche REST :
// Send the REST request.
xhr.send(data);

Traiter la réponse REST à partir de TensorFlow Serving

Le modèle Inception v3 renvoie un tableau de probabilités indiquant que l'image appartient à des catégories prédéfinies. Lorsque la prédiction est réussie, vous devez afficher la catégorie la plus probable dans l'UI.

Vous implémentez l'écouteur onload() pour gérer la réponse.

xhr.onload = () => {

}
  • Ajoutez ce code à l'écouteur onload() :
// Process the REST response.
const response = JSON.parse(xhr.responseText);
const maxIndex = argmax(response['predictions'][0])
document.getElementById('category').textContent = 'Predicted category: ' + maxIndex;

L'écouteur extrait alors les probabilités prédites de la réponse, identifie la catégorie la plus probable de l'objet et affiche le résultat dans l'UI.

Exécuter le code

  1. Dans votre terminal, accédez au dossier starter et utilisez webpack pour regrouper tous les fichiers JavaScript dans un seul fichier que vous pouvez intégrer au fichier dist/index.html :
npm install -g npx
npm install --save-dev webpack
npx webpack
  1. Actualisez http://localhost:8887/ dans votre navigateur, puis cliquez sur REST > Classify.

Le site Web affiche 286 comme catégorie prédite, ce qui correspond au libellé Egyptian Cat dans l'ensemble de données ImageNet.

c865a93b9b58335d.png

8. Connecter le site Web à TensorFlow Serving via gRPC

TensorFlow Serving est non seulement compatible avec REST, mais aussi avec gRPC.

b6f4449c2c850b0e.png

gRPC est un framework d'appel de procédure à distance (RPC) moderne, Open Source et haute performance qui peut être exécuté dans n'importe quel environnement. Il permet de connecter efficacement les services dans et entre les centres de données avec compatibilité par plug-in pour l'équilibrage de charge, le traçage, la vérification de l'état et l'authentification. Selon les observations, gRPC s'avère plus performant que REST dans la pratique.

Envoyer des requêtes et recevoir des réponses avec gRPC

Voici quatre étapes simples :

  1. (Facultatif) Générez le code du bouchon pour le client gRPC.
  2. Créez la requête gRPC.
  3. Envoyez la requête gRPC à TensorFlow Serving.
  4. Extrayez le résultat prédit de la réponse gRPC et affichez-le dans l'UI.

Vous devez effectuer ces étapes dans le fichier src/index.js.

(Facultatif) Générer le code du bouchon pour le client gRPC

Pour utiliser gRPC avec TensorFlow Serving, vous devez suivre le workflow gRPC. Pour en savoir plus, consultez la documentation gRPC.

a9d0e5cb543467b4.png

TensorFlow Serving et TensorFlow définissent les fichiers .proto automatiquement. Pour TensorFlow et TensorFlow Serving 2.8, vous avez besoin des fichiers .proto suivants :

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
  • Dans votre terminal, accédez au dossier starter/src/proto/ et générez le bouchon :
bash generate_grpc_stub_js.sh

Créer la requête gRPC

Comme pour la requête REST, vous devez créer la requête gRPC dans la branche gRPC.

if (connectionMode[picker.selectedRow(inComponent: 0)] == "REST") {

}
else {
    print("Using gRPC")
    // TODO: Add code to send a gRPC request to TensorFlow Serving.
    
}
  • Ajoutez ce code à la branche 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);

Envoyer la requête gRPC à TensorFlow Serving

Vous pouvez maintenant envoyer la demande.

  • Ajoutez ce code immédiatement après le code de la branche gRPC dans l'extrait de code précédent :
// Send the gRPC request.
stub.predict(predictionServiceRequest, {}, function(err, response) {
    // TODO: Add code to process the response.
});

Traiter la réponse gRPC de TensorFlow Serving

Enfin, vous implémentez la fonction de rappel ci-dessus pour gérer la réponse.

  • Ajoutez ce code au corps de la fonction dans l'extrait de code précédent :
// 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;
}

L'écouteur extrait alors les probabilités prédites de la réponse, identifie la catégorie la plus probable de l'objet et affiche le résultat dans l'UI.

Exécuter le code

  1. Dans votre terminal, utilisez webpack pour regrouper tous les fichiers JavaScript dans un seul fichier que vous pourrez intégrer au fichier index.html :
npx webpack
  1. Actualisez http://localhost:8887/ dans votre navigateur.
  2. Cliquez sur gRPC > Classify.

Le site Web affiche la catégorie prédite 286, qui correspond au libellé Egyptian Cat dans l'ensemble de données ImageNet.

9. Félicitations

Vous avez utilisé TensorFlow Serving pour ajouter des fonctionnalités de classification d'images à votre site Web.

En savoir plus