Crea un semplice sito web che classifica le immagini

1. Prima di iniziare

In questo codelab imparerai a eseguire un'inferenza di classificazione delle immagini da un sito web utilizzando TensorFlow Serving con REST e gRPC.

Prerequisiti

  • Conoscenza di base dello sviluppo web, ad esempio HTML e JavaScript
  • Conoscenza di base del machine learning con TensorFlow, ad esempio addestramento e deployment
  • Conoscenza di base di terminali e Docker

Obiettivi didattici

  • Come trovare modelli di classificazione delle immagini preaddestrati su TensorFlow Hub.
  • Come creare un semplice sito web ed effettuare previsioni con il modello di classificazione delle immagini scaricato tramite TensorFlow Serving (REST e gRPC).
  • Come visualizzare il risultato del rilevamento nell'UI.

Che cosa ti serve

2. Configurazione

Per scaricare il codice per questo codelab:

  1. Vai a questo repository GitHub.
  2. Fai clic su Code > Download zip per scaricare tutto il codice di questo codelab.

a72f2bb4caa9a96.png

  1. Decomprimi il file ZIP scaricato per estrarre una cartella principale codelabs con tutte le risorse necessarie.

Per questo codelab, ti servono solo i file nella sottodirectory TFServing/ImageClassificationWeb del repository, che contiene due cartelle:

  • La cartella starter contiene il codice iniziale su cui si basa questo codelab.
  • La cartella finished contiene il codice completato per l'app di esempio finita.

3. Installare le dipendenze

Per installare le dipendenze:

  • Nel terminale, vai alla cartella starter e installa i pacchetti NPM richiesti:
npm install

4. Esegui il sito web iniziale

Utilizza Web Server for Chrome per caricare il file TFServing/ImageClassificationWeb/starter/dist/index.html:

  1. Inserisci Chrome://apps/ nella barra degli indirizzi di Chrome, quindi trova Web Server for Chrome nell'elenco delle app.
  2. Avvia Web Server for Chrome e scegli la cartella TFServing/ImageClassificationWeb/starter/dist/.
  3. Fai clic sul pulsante di attivazione/disattivazione Web Server per attivarlo, quindi vai a http://localhost:8887/ nel browser.

f7b43cd44ebf1f1b.png

Esegui ed esplora il sito web

Ora dovresti vedere il sito web. L'interfaccia utente è piuttosto semplice: c'è un'immagine di un gatto che vuoi classificare e l'utente può inviare i dati al backend con REST o gRPC. Il backend esegue la classificazione dell'immagine e restituisce il risultato al sito web, che lo visualizza.

837d97a27c59a0b3.png

Se fai clic su Classifica, non succede nulla perché non può ancora comunicare con il backend.

5. Deployment di un modello di classificazione delle immagini con TensorFlow Serving

La classificazione delle immagini è un'attività di machine learning molto comune che classifica un'immagine in categorie predefinite in base al contenuto principale dell'immagine. Ecco un esempio di classificazione dei fiori:

a6da16b4a7665db0.png

In TensorFlow Hub sono disponibili diversi modelli di classificazione delle immagini preaddestrati. Per questo codelab viene utilizzato un modello Inception v3 molto diffuso.

Per eseguire il deployment del modello di classificazione delle immagini con TensorFlow Serving:

  1. Scarica il file del modello Inception v3.
  2. Decomprimi il file .tar.gz scaricato con uno strumento di decompressione, ad esempio 7-Zip.
  3. Crea una cartella inception_v3 e poi una sottocartella 123 al suo interno.
  4. Inserisci la cartella variables e il file saved_model.pb estratti nella sottocartella 123.

Puoi fare riferimento alla cartella inception_v3 come cartella SavedModel. 123 è un numero di versione di esempio. Se vuoi, puoi scegliere un altro numero.

La struttura delle cartelle dovrebbe essere simile a quella nell'immagine:

21a8675ac8d31907.png

Avvia TensorFlow Serving

  • Nel terminale, avvia TensorFlow Serving con Docker, ma sostituisci PATH/TO/SAVEDMODEL con il percorso assoluto della cartella inception_v3 sul tuo computer.
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 scarica automaticamente l'immagine TensorFlow Serving, il che richiede un minuto. Dopodiché, TensorFlow Serving dovrebbe avviarsi. Il log dovrebbe avere l'aspetto seguente:

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. Configurare il proxy Envoy

Al momento TensorFlow Serving non imposta l'intestazione Access-Control-Allow-Origin, quindi il browser blocca la richiesta da JavaScript frontend a TensorFlow Serving per motivi di sicurezza. Per risolvere questo problema, devi utilizzare un proxy, ad esempio Envoy, per eseguire il proxy della richiesta da JavaScript al backend di TensorFlow Serving.

Avvia Envoy

  • Nel terminale, scarica l'immagine Envoy e avvia Envoy con Docker, ma sostituisci il segnaposto PATH/TO/ENVOY-CUSTOM.YAML con il percorso assoluto del file envoy-custom.yaml nella cartella 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 scarica automaticamente prima l'immagine Envoy. Dopodiché, Envoy dovrebbe avviarsi. Il log dovrebbe avere l'aspetto seguente:

[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. Collega il sito web a TensorFlow tramite REST

Il backend è ora pronto, quindi puoi inviare richieste client a TensorFlow Serving per classificare le immagini. Esistono due modi per inviare richieste a TensorFlow Serving:

  • REST
  • gRPC

Inviare richieste e ricevere risposte tramite REST

Esistono tre semplici passaggi per inviare e ricevere richieste tramite REST:

  1. Crea la richiesta REST.
  2. Invia la richiesta REST a TensorFlow Serving.
  3. Estrai il risultato previsto dalla risposta REST e visualizzalo.

Questi passaggi vengono eseguiti nel file src/index.js.

Crea la richiesta REST

Al momento, la funzione classify_img() non invia la richiesta REST a TensorFlow Serving. Devi implementare questo ramo REST per creare prima una richiesta REST:

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

} 

TensorFlow Serving prevede una richiesta POST che contenga il tensore dell'immagine per il modello Inception v3 che utilizzi, quindi devi estrarre i valori RGB da ogni pixel dell'immagine in un array e poi racchiudere l'array in un JSON, che è il payload della richiesta.

  • Aggiungi questo codice al ramo 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');
}

Invia la richiesta REST a TensorFlow Serving

Ora puoi inviare la richiesta.

  • Aggiungi questo codice subito dopo il codice precedente nel ramo REST:
// Send the REST request.
xhr.send(data);

Elaborare la risposta REST di TensorFlow Serving

Il modello Inception v3 restituisce un array di probabilità che l'immagine appartenga a categorie predefinite. Se la previsione ha esito positivo, devi restituire la categoria più probabile nell'interfaccia utente.

Implementi il listener onload() per gestire la risposta.

xhr.onload = () => {

}
  • Aggiungi questo codice al listener onload():
// Process the REST response.
const response = JSON.parse(xhr.responseText);
const maxIndex = argmax(response['predictions'][0])
document.getElementById('category').textContent = 'Predicted category: ' + maxIndex;

Ora il listener estrae le probabilità previste dalla risposta, identifica la categoria più probabile dell'oggetto e mostra il risultato nell'interfaccia utente.

Esegui

  1. Nel terminale, vai alla cartella starter e utilizza webpack per raggruppare tutti i file JavaScript in un unico file che puoi incorporare nel file dist/index.html:
npm install -g npx
npm install --save-dev webpack
npx webpack
  1. Aggiorna http://localhost:8887/ nel browser e poi fai clic su REST > Classify.

Il sito web mostra 286 come categoria prevista, che corrisponde all'etichetta Egyptian Cat nel set di dati ImageNet.

c865a93b9b58335d.png

8. Collega il sito web a TensorFlow Serving tramite gRPC

Oltre a REST, TensorFlow Serving supporta anche gRPC.

b6f4449c2c850b0e.png

gRPC è un framework RPC (Remote Procedure Call) moderno, open source e ad alte prestazioni che può essere eseguito in qualsiasi ambiente. Può connettere in modo efficiente i servizi all'interno e tra i data center con supporto plug-in per bilanciamento del carico, tracciamento, controllo di integrità e autenticazione. È stato osservato che gRPC è più performante di REST nella pratica.

Inviare richieste e ricevere risposte con gRPC

Esistono quattro semplici passaggi:

  1. (Facoltativo) Genera il codice stub del client gRPC.
  2. Crea la richiesta gRPC.
  3. Invia la richiesta gRPC a TensorFlow Serving.
  4. Estrai il risultato previsto dalla risposta gRPC e visualizzalo nella UI.

Completa questi passaggi nel file src/index.js.

(Facoltativo) Genera il codice dello stub client gRPC

Per utilizzare gRPC con TensorFlow Serving, devi seguire il flusso di lavoro gRPC. Per saperne di più, consulta la documentazione di gRPC.

a9d0e5cb543467b4.png

TensorFlow Serving e TensorFlow definiscono i file .proto per te. A partire da TensorFlow e TensorFlow Serving 2.8, questi sono i file .proto necessari:

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
  • Nel terminale, vai alla cartella starter/src/proto/ e genera lo stub:
bash generate_grpc_stub_js.sh

Crea la richiesta gRPC

Analogamente alla richiesta REST, crei la richiesta gRPC nel ramo gRPC.

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

}
else {
    print("Using gRPC")
    // TODO: Add code to send a gRPC request to TensorFlow Serving.
    
}
  • Aggiungi questo codice al ramo 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);

Invia la richiesta gRPC a TensorFlow Serving

Ora puoi inviare la richiesta.

  • Aggiungi questo codice immediatamente dopo il codice nel ramo gRPC nello snippet di codice precedente:
// Send the gRPC request.
stub.predict(predictionServiceRequest, {}, function(err, response) {
    // TODO: Add code to process the response.
});

Elaborare la risposta gRPC da TensorFlow Serving

Infine, implementa la funzione di callback sopra per gestire la risposta.

  • Aggiungi questo codice al corpo della funzione nello snippet di codice precedente:
// 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;
}

Ora il listener estrae le probabilità previste dalla risposta, identifica la categoria più probabile dell'oggetto e mostra il risultato nell'interfaccia utente.

Esegui

  1. Nel terminale, utilizza webpack per raggruppare tutti i file JavaScript in un unico file che puoi incorporare nel file index.html:
npx webpack
  1. Aggiorna http://localhost:8887/ nel browser.
  2. Fai clic su gRPC > Classifica.

Il sito web mostra la categoria prevista di 286, che corrisponde all'etichetta Egyptian Cat nel set di dati ImageNet.

9. Complimenti

Hai utilizzato TensorFlow Serving per aggiungere funzionalità di classificazione delle immagini al tuo sito web.

Scopri di più