Utwórz prostą witrynę, która klasyfikuje obrazy

1. Zanim zaczniesz

Z tego ćwiczenia dowiesz się, jak przeprowadzać wnioskowanie z klasyfikacji obrazów na stronie przy użyciu TensorFlow Serving with REST i gRPC.

Wymagania wstępne

  • Podstawowa znajomość programowania, np. HTML i JavaScript
  • Podstawowa wiedza dotycząca systemów uczących się z TensorFlow, np. szkolenia i wdrażanie
  • Podstawowe informacje o terminalach i Dockerze

Czego się nauczysz

  • Jak znaleźć wytrenowane modele klasyfikacji obrazów w TensorFlow Hub.
  • Jak utworzyć prostą witrynę i prognozować przy użyciu pobranego modelu klasyfikacji obrazów za pomocą wyświetlania przez TensorFlow (REST i gRPC).
  • Jak renderować wynik wykrywania w interfejsie.

Czego potrzebujesz

2. Konfiguracja

Aby pobrać kod tych ćwiczeń z programowania:

  1. Otwórz to repozytorium GitHub.
  2. Kliknij Kod > Pobierz plik ZIP, aby pobrać cały kod do tych ćwiczeń z programowania.

a72f2bb4caa9a96.png

  1. Rozpakuj pobrany plik ZIP, by rozpakować folder główny codelabs zawierający wszystkie potrzebne zasoby.

Na potrzeby tego ćwiczenia z programu potrzebujesz tylko plików z podkatalogu TFServing/ImageClassificationWeb repozytorium, które zawiera 2 foldery:

  • Folder starter zawiera kod początkowy, który wykorzystasz w tym ćwiczeniu z programowania.
  • Folder finished zawiera pełny kod gotowej 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. Uruchom stronę startową

Użyj serwera WWW dla Chrome, aby wczytać plik TFServing/ImageClassificationWeb/starter/dist/index.html:

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

f7b43cd44ebf1f1b.png

Uruchamianie i przeglądanie strony internetowej

Witryna powinna być już widoczna. Interfejs jest prosty: dostępny jest obraz kota, który chcesz sklasyfikować, a użytkownik może wysłać dane do backendu z użyciem REST lub gRPC. Backend wykonuje klasyfikację obrazu na obrazie i zwraca wynik klasyfikacji do witryny internetowej, gdzie jest on wyświetlany.

837d97a27c59a0b3.png

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

5. Wdrażanie modelu klasyfikacji obrazów za pomocą wyświetlania danych w TensorFlow

Klasyfikacja obrazów to bardzo częste zadanie ML, które klasyfikuje obraz do wstępnie zdefiniowanych kategorii na podstawie jego podstawowej treści. Oto przykład klasyfikowania kwiatów:

A6da16b4a7665db0.png

W TensorFlow Hub jest wiele wytrenowanych modeli klasyfikacji obrazów. Do tego ćwiczenia używasz popularnego modelu Inception v3.

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

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

Folder inception_v3 można nazywać folderem SavedModel. 123 to przykładowy numer wersji. Jeśli chcesz, możesz wybrać inny numer.

Struktura folderów powinna wyglądać tak:

21a8675ac8d31907.png

Rozpocznij udostępnianie TensorFlow

  • W terminalu uruchom udostępnianie TensorFlow z Dockerem, ale zastąp PATH/TO/SAVEDMODEL ścieżką bezwzględną 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 automatycznie pobiera najpierw obraz związany z wyświetlaniem TensorFlow, co zajmuje minutę. Po tym czasie powinno rozpocząć się udostępnianie TensorFlow. 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. Skonfiguruj serwer proxy Envoy

Obecnie serwer TensorFlow nie obsługuje nagłówka Access-Control-Allow-Origin, więc ze względów bezpieczeństwa przeglądarka blokuje żądanie z JavaScriptu frontendu. Aby obejść ten problem, musisz skorzystać z serwera proxy, takiego jak Envoy, aby przesłać żądanie z JavaScriptu do backendu TensorFlow TensorFlow.

Uruchom usługę Envoy

  • W terminalu pobierz obraz Envoy i rozpocznij Envoya w dockerze, ale zastąp zmienną 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 automatycznie pobierze najpierw obraz Envoy. Potem powinna rozpocząć się 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 przez REST

Backend jest już gotowy, więc możesz wysyłać żądania klienta do wyświetlania w TensorFlow, aby klasyfikować obrazy. Istnieją dwa sposoby wysyłania żądań do obsługi TensorFlow:

  • REST
  • gRPC

Wysyłanie żądań i odbieranie odpowiedzi przez REST

Aby wysyłać i odbierać żądania w REST, musisz wykonać trzy proste kroki:

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

Te kroki możesz wykonać w pliku src/index.js.

Utwórz żądanie REST

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

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

} 

Udostępnianie w TensorFlow oczekuje na żądanie POST zawierające 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 pakować ją w formacie 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');
}

Wyślij żądanie REST do serwera TensorFlow

Możesz wysłać prośbę.

  • Dodaj ten kod bezpośrednio po powyższym kodzie w oddziale REST:
// Send the REST request.
xhr.send(data);

Przetwórz odpowiedź REST z wyświetlania w TensorFlow

Model Inception v3 zwraca tablicę prawdopodobieństwa, że obraz należy do wstępnie zdefiniowanych kategorii. Gdy prognoza się powiedzie, musisz podać najbardziej prawdopodobną kategorię w interfejsie.

Musisz zaimplementować detektor onload() do obsługi odpowiedzi.

xhr.onload = () => {

}
  • Dodaj ten kod do detektora 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 detektor wyodrębnia prognozowane prawdopodobieństwa z odpowiedzi, identyfikuje najbardziej prawdopodobną kategorię obiektu i wyświetla wynik w interfejsie użytkownika.

Uruchom

  1. W terminalu przejdź do folderu starter i użyj pakietu Webpack do połączenia wszystkich plików 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ż http://localhost:8887/w przeglądarce, a następnie kliknij REST > Classify.

Witryna wyświetla 286 jako kategorię prognozowaną, która jest zmapowana na etykietę Egyptian Cat w zbiorze danych ImageNet.

C865a93b9b58335d.png

8. Łączenie witryny z udostępnianiem danych przez TensorFlow za pomocą gRPC

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

B6F4449C2C850b0e.png

gRPC to nowoczesna platforma RPC o wysokiej wydajności, która może działać w dowolnym środowisku. Może skutecznie łączyć usługi w centrach danych i z innych, a także korzystać z wtyczek z obsługą równoważenia obciążenia, śledzenia, kontroli stanu i uwierzytelniania. Zaobserwowano, że gRPC jest w praktyce bardziej wydajny niż REST.

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

Wystarczą 4 proste kroki:

  1. Opcjonalnie: wygeneruj kod śledzenia klienta gRPC.
  2. Utwórz żądanie gRPC.
  3. Wyślij żądanie gRPC do wyświetlania przez TensorFlow.
  4. Wyodrębnij przewidywany wynik z odpowiedzi gRPC i wyświetl go w interfejsie użytkownika.

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

Opcjonalnie: wygeneruj kod wewnętrzny klienta gRPC

Aby używać gRPC z udostępnianiem za pomocą TensorFlow, musisz wykonać przepływ pracy gRPC. Więcej informacji znajdziesz w dokumentacji gRPC.

a9d0e5cb543467b4.png

Pliki TensorFlow i TensorFlow definiują pliki .proto. Od TensorFlow 2.8 i TensorFlow 2.8 potrzebne są te pliki .proto:

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 przejdź do folderu starter/src/proto/ i wygeneruj atrapę:
bash generate_grpc_stub_js.sh

Utwórz żądanie gRPC

Podobnie jak w przypadku żądania REST, tworzysz żądanie gRPC 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);

Wyślij żądanie gRPC do udostępniania w TensorFlow

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.
});

Przetwórz odpowiedź gRPC z udostępniania w TensorFlow

Na koniec zaimplementuj 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 detektor wyodrębnia prognozowane prawdopodobieństwa z odpowiedzi, identyfikuje najbardziej prawdopodobną kategorię obiektu i wyświetla wynik w interfejsie użytkownika.

Uruchom

  1. W terminalu użyj pakietu internetowego, aby umieścić wszystkie pliki JavaScript w jednym pliku, który możesz umieścić w pliku index.html:
npx webpack
  1. Odśwież http://localhost:8887/ w przeglądarce.
  2. Kliknij gRPC > Classify.

Witryna wyświetla przewidywaną kategorię wartości 286, która jest zmapowana na etykietę Egyptian Cat w zbiorze danych ImageNet.

9. Gratulacje

Dzięki zastosowaniu TensorFlow udostępniono w witrynie funkcje klasyfikacji obrazów.

Więcej informacji