Tworzenie prostej witryny, która klasyfikuje obrazy

1. Zanim zaczniesz

Z tych ćwiczeń z programowania dowiesz się, jak uruchomić wnioskowanie klasyfikacji obrazów z witryny za pomocą TensorFlow Serving z REST i gRPC.

Wymagania wstępne

  • podstawowa wiedza z zakresu tworzenia stron internetowych, np. HTML i JavaScript;
  • Podstawowa wiedza o uczeniu maszynowym z TensorFlow, np. trenowanie i wdrażanie
  • Podstawowa znajomość terminali i Dockera

Czego się nauczysz

  • Jak znaleźć wstępnie wytrenowane modele klasyfikacji obrazów w TensorFlow Hub.
  • Jak utworzyć prostą witrynę i generować prognozy za pomocą pobranego modelu klasyfikacji obrazów za pomocą TensorFlow Serving (REST i gRPC).
  • Jak renderować wynik wykrywania w interfejsie.

Czego potrzebujesz

2. Konfiguracja

Aby pobrać kod do tego ćwiczenia:

  1. Otwórz to repozytorium GitHub.
  2. Aby pobrać cały kod do tych ćwiczeń z programowania, kliknij Code > Download zip (Kod > Pobierz plik ZIP).

a72f2bb4caa9a96.png

  1. Rozpakuj pobrany plik ZIP, aby wyodrębnić folder główny codelabs ze wszystkimi potrzebnymi zasobami.

W tym ćwiczeniu potrzebne są tylko pliki z podkatalogu TFServing/ImageClassificationWeb w repozytorium, który zawiera 2 foldery:

  • Folder starter zawiera kod początkowy, na którym będziesz pracować w tym laboratorium.
  • Folder finished zawiera gotowy kod ukończonej przykładowej aplikacji.

3. Instalowanie zależności

Aby zainstalować zależności:

  • W terminalu przejdź do folderu starter, a następnie zainstaluj wymagane pakiety NPM:
npm install

4. Uruchamianie witryny startowej

Użyj Web Server for Chrome, aby wczytać plik TFServing/ImageClassificationWeb/starter/dist/index.html:

  1. Wpisz Chrome://apps/ na pasku adresu Chrome, a następnie znajdź Web Server for Chrome na liście aplikacji.
  2. Uruchom Web Server for Chrome, a następnie wybierz folder TFServing/ImageClassificationWeb/starter/dist/.
  3. Kliknij przełącznik Serwer WWW, aby go włączyć, a następnie otwórz w przeglądarce adres http://localhost:8887/.

f7b43cd44ebf1f1b.png

Uruchamianie i przeglądanie witryny

Powinna być teraz widoczna witryna. Interfejs jest dość prosty: jest w nim obraz kota, który chcesz sklasyfikować, a użytkownik może wysłać dane do backendu za pomocą REST lub gRPC. Backend przeprowadza klasyfikację obrazu i zwraca wynik klasyfikacji do witryny, która go wyświetla.

837d97a27c59a0b3.png

Jeśli klikniesz Classify (Klasyfikuj), nic się nie stanie, ponieważ nie można jeszcze nawiązać komunikacji z backendem.

5. Wdrażanie modelu klasyfikacji obrazów za pomocą TensorFlow Serving

Klasyfikacja obrazów to bardzo popularne zadanie uczenia maszynowego, które polega na przypisywaniu obrazów do wstępnie zdefiniowanych kategorii na podstawie ich głównej zawartości. Oto przykład klasyfikacji kwiatów:

a6da16b4a7665db0.png

Na TensorFlow Hub znajdziesz wiele wstępnie wytrenowanych modeli klasyfikacji obrazów. W tym ćwiczeniu z programowania użyjesz popularnego modelu Inception v3.

Aby wdrożyć model klasyfikacji obrazów za pomocą TensorFlow Serving:

  1. Pobierz plik modelu Inception v3.
  2. Rozpakuj pobrany plik .tar.gz za pomocą narzędzia do dekompresji, np. 7-Zip.
  3. Utwórz folder inception_v3, a następnie utwórz w nim podfolder 123.
  4. Umieść wyodrębniony variables folder i saved_model.pb plik w podfolderze 123.

Folder inception_v3 możesz określać jako folder SavedModel. 123 to przykładowy numer wersji. Jeśli chcesz, możesz wybrać inny numer.

Struktura folderów powinna wyglądać jak na tym obrazie:

21a8675ac8d31907.png

Uruchamianie TensorFlow Serving

  • W terminalu uruchom TensorFlow Serving za pomocą Dockera, ale zastąp PATH/TO/SAVEDMODEL bezwzględną ścieżką do folderu inception_v3 na komputerze.
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 najpierw automatycznie pobierze obraz TensorFlow Serving, co zajmie minutę. Następnie powinna się uruchomić usługa TensorFlow Serving. Dziennik powinien wyglądać jak ten fragment kodu:

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. Konfigurowanie proxy Envoy

Obecnie TensorFlow Serving nie ustawia nagłówka Access-Control-Allow-Origin, więc przeglądarka blokuje żądanie z JavaScriptu interfejsu do TensorFlow Serving ze względów bezpieczeństwa. Aby to obejść, musisz użyć serwera proxy, np. Envoy, do przekazywania żądania z JavaScriptu do backendu TensorFlow Serving.

Uruchomienie Envoys

  • W terminalu pobierz obraz Envoy i uruchom Envoy za pomocą Dockera, ale zastąp obiekt zastępczy PATH/TO/ENVOY-CUSTOM.YAML bezwzględną ścieżką pliku envoy-custom.yaml w folderze 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 najpierw automatycznie pobierze obraz Envoy. Następnie powinien się uruchomić Envoy. Dziennik powinien wyglądać jak ten fragment kodu:

[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. Łączenie witryny z TensorFlow za pomocą interfejsu REST

Backend jest już gotowy, więc możesz wysyłać żądania klienta do TensorFlow Serving, aby klasyfikować obrazy. Żądania do TensorFlow Serving można wysyłać na 2 sposoby:

  • REST
  • gRPC

Wysyłanie żądań i otrzymywanie odpowiedzi za pomocą interfejsu REST

Aby wysyłać i odbierać żądania za pomocą interfejsu REST, wykonaj 3 proste kroki:

  1. Utwórz żądanie REST.
  2. Wyślij żądanie REST do TensorFlow Serving.
  3. Wyodrębnij przewidywany wynik z odpowiedzi REST i wyświetl go.

Wykonaj te czynności w pliku src/index.js.

Tworzenie żądania REST

Obecnie funkcja classify_img() nie wysyła żądania REST do TensorFlow Serving. Aby utworzyć żądanie REST, musisz najpierw wdrożyć tę gałąź REST:

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

} 

TensorFlow Serving oczekuje żądania POST, które zawiera tensor obrazu dla używanego modelu Inception v3. Musisz więc wyodrębnić wartości RGB z każdego piksela obrazu do tablicy, a następnie umieścić tablicę w pliku JSON, który jest ładunkiem żądania.

  • Dodaj ten kod do gałęzi 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');
}

Wysyłanie żądania REST do TensorFlow Serving

Teraz możesz wysłać prośbę.

  • W gałęzi REST dodaj ten kod bezpośrednio po kodzie powyżej:
// Send the REST request.
xhr.send(data);

Przetwarzanie odpowiedzi REST z TensorFlow Serving

Model Inception v3 zwraca tablicę prawdopodobieństw, że obraz należy do wstępnie zdefiniowanych kategorii. Jeśli prognoza się powiedzie, w interfejsie użytkownika powinna się wyświetlić najbardziej prawdopodobna kategoria.

Aby obsłużyć odpowiedź, zaimplementuj detektor onload().

xhr.onload = () => {

}
  • Dodaj ten kod do odbiornika onload():
// Process the REST response.
const response = JSON.parse(xhr.responseText);
const maxIndex = argmax(response['predictions'][0])
document.getElementById('category').textContent = 'Predicted category: ' + maxIndex;

Teraz odbiorca wyodrębnia z odpowiedzi przewidywane prawdopodobieństwa, identyfikuje najbardziej prawdopodobną kategorię obiektu i wyświetla wynik w interfejsie.

Uruchom

  1. W terminalu przejdź do folderu starter i użyj webpacka, aby połączyć wszystkie pliki JavaScript w jeden plik, który możesz umieścić w pliku dist/index.html:
npm install -g npx
npm install --save-dev webpack
npx webpack
  1. Odśwież adres http://localhost:8887/ w przeglądarce, a potem kliknij REST > Classify.

Witryna wyświetla 286 jako przewidywaną kategorię, która jest powiązana z etykietą Egyptian Catzbiorze danych ImageNet.

c865a93b9b58335d.png

8. Łączenie witryny z TensorFlow Serving za pomocą gRPC

Oprócz REST TensorFlow Serving obsługuje też gRPC.

b6f4449c2c850b0e.png

gRPC to nowoczesna, otwarta i wydajna platforma zdalnego wywoływania procedur (RPC), która może działać w dowolnym środowisku. Umożliwia wydajne łączenie usług w centrach danych i między nimi dzięki wtyczkom do równoważenia obciążenia, śledzenia, sprawdzania stanu i uwierzytelniania. Zaobserwowano, że w praktyce gRPC jest wydajniejszy niż REST.

Wysyłanie żądań i otrzymywanie odpowiedzi za pomocą gRPC

Wystarczą 4 proste kroki:

  1. Opcjonalnie: wygeneruj kod namiestnika klienta gRPC.
  2. Utwórz żądanie gRPC.
  3. Wysyłanie żądania gRPC do TensorFlow Serving.
  4. Wyodrębnij przewidywany wynik z odpowiedzi gRPC i wyświetl go w interfejsie.

Te czynności wykonuje się w pliku src/index.js.

Opcjonalnie: generowanie kodu namiestnika klienta gRPC

Aby używać gRPC z TensorFlow Serving, musisz postępować zgodnie z procesem gRPC. Więcej informacji znajdziesz w dokumentacji gRPC.

a9d0e5cb543467b4.png

TensorFlow Serving i TensorFlow definiują za Ciebie pliki .proto. W TensorFlow i TensorFlow Serving w wersji 2.8 wymagane są te .proto pliki:

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
  • W terminalu otwórz folder starter/src/proto/ i wygeneruj stub:
bash generate_grpc_stub_js.sh

Tworzenie żądania gRPC

Podobnie jak w przypadku żądania REST, żądanie gRPC tworzysz w gałęzi gRPC..

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

}
else {
    print("Using gRPC")
    // TODO: Add code to send a gRPC request to TensorFlow Serving.
    
}
  • Dodaj ten kod do gałęzi 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);

Wysyłanie żądania gRPC do TensorFlow Serving

Teraz możesz wysłać prośbę.

  • Dodaj ten kod bezpośrednio po kodzie w gałęzi gRPC w poprzednim fragmencie kodu:
// Send the gRPC request.
stub.predict(predictionServiceRequest, {}, function(err, response) {
    // TODO: Add code to process the response.
});

Przetwarzanie odpowiedzi gRPC z TensorFlow Serving

Na koniec zaimplementuj powyższą funkcję wywołania zwrotnego, aby obsłużyć odpowiedź.

  • Dodaj ten kod do treści funkcji w poprzednim fragmencie kodu:
// 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;
}

Teraz odbiorca wyodrębnia z odpowiedzi przewidywane prawdopodobieństwa, identyfikuje najbardziej prawdopodobną kategorię obiektu i wyświetla wynik w interfejsie.

Uruchom

  1. W terminalu użyj webpacka, aby połączyć wszystkie pliki JavaScript w jeden plik, który możesz umieścić w pliku index.html:
npx webpack
  1. Odśwież stronę http://localhost:8887/ w przeglądarce.
  2. Kliknij gRPC > Klasyfikuj.

Witryna wyświetla przewidywaną kategorię 286, która jest powiązana z etykietą Egyptian Catzbiorze danych ImageNet.

9. Gratulacje

Używasz TensorFlow Serving do dodawania funkcji klasyfikacji obrazów do swojej witryny.

Więcej informacji