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
- Docker
- Google Chrome
- Serwer WWW Chrome
- Node.js i NPM
- Bash
- Kompilator bufora protokołu (wymagany tylko w przypadku, gdy chcesz samodzielnie wygenerować wycinek z gRPC)
- Wtyczka gRPC-web code generator (wymagana tylko wtedy, gdy chcesz samodzielnie wygenerować kod wewnętrzny gRPC)
2. Konfiguracja
Aby pobrać kod tych ćwiczeń z programowania:
- Otwórz to repozytorium GitHub.
- Kliknij Kod > Pobierz plik ZIP, aby pobrać cały kod do tych ćwiczeń z programowania.
- 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
:
- Wpisz
Chrome://apps/
na pasku adresu w Chrome, a następnie na liście aplikacji znajdź Serwer WWW dla Chrome. - Uruchom serwer WWW dla Chrome, a następnie wybierz folder
TFServing/ImageClassificationWeb/starter/dist/
. - Kliknij przełącznik Serwer WWW, aby włączyć tę funkcję, a następnie otwórz w przeglądarce adres http://localhost:8887/.
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.
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:
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:
- Pobierz plik modelu Inception v3.
- Rozpakuj pobrany plik
.tar.gz
za pomocą narzędzia do dekompresji, takiego jak 7-Zip. - Utwórz folder
inception_v3
, a następnie utwórz w nim podfolder123
. - Umieść wyodrębniony folder
variables
i pliksaved_model.pb
w podfolderze123
.
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:
Rozpocznij udostępnianie TensorFlow
- W terminalu uruchom udostępnianie TensorFlow z Dockerem, ale zastąp
PATH/TO/SAVEDMODEL
ścieżką bezwzględną folderuinception_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ą plikuenvoy-custom.yaml
w folderzestarter
.
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:
- Utwórz żądanie REST.
- Wyślij żądanie REST do serwera TensorFlow Serving.
- 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
- 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 plikudist/index.html
:
npm install -g npx npm install --save-dev webpack npx webpack
- 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.
8. Łączenie witryny z udostępnianiem danych przez TensorFlow za pomocą gRPC
Oprócz REST, TensorFlow Serving obsługuje też gRPC.
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:
- Opcjonalnie: wygeneruj kod śledzenia klienta gRPC.
- Utwórz żądanie gRPC.
- Wyślij żądanie gRPC do wyświetlania przez TensorFlow.
- 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.
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
- 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
- Odśwież http://localhost:8887/ w przeglądarce.
- 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.