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
- Docker
- Google Chrome
- Web Server for Chrome
- Node.js i NPM
- Bash
- Kompilator bufora protokołu (potrzebny tylko wtedy, gdy chcesz samodzielnie ponownie wygenerować stub gRPC)
- Wtyczka generatora kodu gRPC-web (potrzebna tylko wtedy, gdy chcesz samodzielnie ponownie wygenerować stub gRPC)
2. Konfiguracja
Aby pobrać kod do tego ćwiczenia:
- Otwórz to repozytorium GitHub.
- Aby pobrać cały kod do tych ćwiczeń z programowania, kliknij Code > Download zip (Kod > Pobierz plik ZIP).
- 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
:
- Wpisz
Chrome://apps/
na pasku adresu Chrome, a następnie znajdź Web Server for Chrome na liście aplikacji. - Uruchom Web Server for Chrome, a następnie wybierz folder
TFServing/ImageClassificationWeb/starter/dist/
. - Kliknij przełącznik Serwer WWW, aby go włączyć, a następnie otwórz w przeglądarce adres http://localhost:8887/.
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.
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:
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:
- Pobierz plik modelu Inception v3.
- Rozpakuj pobrany plik
.tar.gz
za pomocą narzędzia do dekompresji, np. 7-Zip. - Utwórz folder
inception_v3
, a następnie utwórz w nim podfolder123
. - Umieść wyodrębniony
variables
folder isaved_model.pb
plik w podfolderze123
.
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:
Uruchamianie TensorFlow Serving
- W terminalu uruchom TensorFlow Serving za pomocą Dockera, ale zastąp
PATH/TO/SAVEDMODEL
bezwzględną ścieżką do 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 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ą 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 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:
- Utwórz żądanie REST.
- Wyślij żądanie REST do TensorFlow Serving.
- 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
- 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 plikudist/index.html
:
npm install -g npx npm install --save-dev webpack npx webpack
- 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 Cat
w zbiorze danych ImageNet.
8. Łączenie witryny z TensorFlow Serving za pomocą gRPC
Oprócz REST TensorFlow Serving obsługuje też gRPC.
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:
- Opcjonalnie: wygeneruj kod namiestnika klienta gRPC.
- Utwórz żądanie gRPC.
- Wysyłanie żądania gRPC do TensorFlow Serving.
- 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.
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
- 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
- Odśwież stronę http://localhost:8887/ w przeglądarce.
- Kliknij gRPC > Klasyfikuj.
Witryna wyświetla przewidywaną kategorię 286
, która jest powiązana z etykietą Egyptian Cat
w zbiorze danych ImageNet.
9. Gratulacje
Używasz TensorFlow Serving do dodawania funkcji klasyfikacji obrazów do swojej witryny.