Informationen zu diesem Codelab
1. Hinweis
In diesem Codelab lernen Sie, wie Sie mit TensorFlow Serving mit REST und gRPC eine Inferenz einer Objekterkennung in einer Android-App ausführen.
Vorbereitung
- Grundkenntnisse in der Android-Entwicklung mit Java
- Grundkenntnisse in Machine Learning mit TensorFlow, z. B. für Training und Bereitstellung
- Grundkenntnisse zu Terminals und Docker
Lerninhalte
- Vortrainierte Objekterkennungsmodelle in TensorFlow Hub finden
- Hier erfahren Sie, wie Sie mit TensorFlow Serving (REST und gRPC) eine einfache Android-App erstellen und Vorhersagen mit dem heruntergeladenen Modell zur Objekterkennung treffen.
- Rendering des Erkennungsergebnisses in der UI
Voraussetzungen
- Die aktuelle Version von Android Studio
- Docker
- Bash
2. Einrichten
So laden Sie den Code für dieses Codelab herunter:
- Rufen Sie das GitHub-Repository für dieses Codelab auf.
- Klicken Sie auf Code gt; ZIP herunterladen, um den gesamten Code für dieses Codelab herunterzuladen.
- Entpacken Sie die heruntergeladene ZIP-Datei, um den Root-Ordner
codelabs
mit allen benötigten Ressourcen zu entpacken.
Für dieses Codelab benötigen Sie nur die Dateien im Unterverzeichnis TFServing/ObjectDetectionAndroid
des Repositorys mit zwei Ordnern:
- Der Ordner
starter
enthält den Startcode, den Sie für dieses Codelab erstellen. - Der Ordner
finished
enthält den abgeschlossenen Code für die fertige Beispiel-App.
3. Abhängigkeiten zum Projekt hinzufügen
Starter-App in Android Studio importieren
- Klicken Sie in Android Studio auf File > New > Import Project und wählen Sie im Quellcode, den Sie zuvor heruntergeladen haben, den Ordner
starter
aus.
Abhängigkeiten für OkHttp und gRPC hinzufügen
- Prüfen Sie in der Datei
app/build.gradle
Ihres Projekts, ob die Abhängigkeiten vorhanden sind.
dependencies {
// ...
implementation 'com.squareup.okhttp3:okhttp:4.9.0'
implementation 'javax.annotation:javax.annotation-api:1.3.2'
implementation 'io.grpc:grpc-okhttp:1.29.0'
implementation 'io.grpc:grpc-protobuf-lite:1.29.0'
implementation 'io.grpc:grpc-stub:1.29.0'
}
Projekt mit Gradle-Dateien synchronisieren
- Wählen Sie im Navigationsmenü die Option
Projekt mit Gradle-Dateien synchronisieren aus.
4. Start-App ausführen
- Starten Sie den Android Emulator und klicken Sie dann im Navigationsmenü auf
App ausführen.
App ausführen und entdecken
Die App sollte auf Ihrem Android-Gerät gestartet werden. Die Benutzeroberfläche ist ziemlich einfach: Es gibt ein Katzenbild, in dem Sie Objekte erkennen möchten und der Nutzer auswählen kann, wie die Daten mit REST oder gRPC an das Back-End gesendet werden sollen. Das Back-End führt die Objekterkennung für das Bild aus und gibt die Erkennungsergebnisse an die Client-App zurück, mit der die Benutzeroberfläche noch einmal gerendert wird.
Wenn Sie auf Inferenz ausführen klicken, passiert im Moment nichts. Das liegt daran, dass noch keine Kommunikation mit dem Back-End möglich ist.
5. Objekterkennungsmodell mit TensorFlow Serving bereitstellen
Die Objekterkennung ist eine sehr gängige ML-Aufgabe und besteht darin, Objekte in Bildern zu erkennen, um mögliche Kategorien der Objekte und Begrenzungsrahmen um sich herum vorherzusagen. Hier ein Beispiel für ein Erkennungsergebnis:
Google hat eine Reihe vortrainierter Modelle auf TensorFlow Hub veröffentlicht. Die vollständige Liste finden Sie auf der Seite object_detection. Für dieses Codelab verwenden Sie das relativ einfache Modell SSD MobileNet V2 FPNLite 320x320, sodass Sie nicht unbedingt eine GPU dafür verwenden müssen.
So stellen Sie das Objekterkennungsmodell mit TensorFlow Serving bereit:
- Laden Sie die Modelldatei herunter.
- Entpacken Sie die heruntergeladene
.tar.gz
-Datei mit einem Dekomprimierungstool, z. B. 7-Zip. - Erstellen Sie einen Ordner
ssd_mobilenet_v2_2_320
und erstellen Sie darin einen Unterordner123
. - Verschieben Sie den extrahierten Ordner
variables
und die Dateisaved_model.pb
in den Unterordner123
.
Du kannst den Ordner ssd_mobilenet_v2_2_320
als Ordner SavedModel
verwenden. 123
ist eine Beispielversionsnummer. Wenn du möchtest, kannst du eine andere Nummer auswählen.
Die Ordnerstruktur sollte so aussehen:
TensorFlow-Bereitstellung starten
- Starten Sie in Ihrem Terminal TensorFlow Serving mit Docker, aber ersetzen Sie den Platzhalter
PATH/TO/SAVEDMODEL
durch den absoluten Pfad des Ordnersssd_mobilenet_v2_2_320
auf Ihrem Computer.
docker pull tensorflow/serving docker run -it --rm -p 8500:8500 -p 8501:8501 -v "PATH/TO/SAVEDMODEL:/models/ssd_mobilenet_v2_2" -e MODEL_NAME=ssd_mobilenet_v2_2 tensorflow/serving
Docker lädt automatisch das TensorFlow Serving-Image herunter. Das dauert eine Minute. Danach sollte die TensorFlow-Bereitstellung beginnen. Das Protokoll sollte so aussehen:
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/ssd_mobilenet_v2_2/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/ssd_mobilenet_v2_2/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: ssd_mobilenet_v2_2 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. Android-App mit TensorFlow über REST verbinden
Das Back-End ist jetzt bereit, sodass Sie Clientanfragen an TensorFlow Serving senden können, um Objekte in Bildern zu erkennen. Es gibt zwei Möglichkeiten, Anfragen an die TensorFlow-Bereitstellung zu senden:
- REST
- gRPC
Anfragen senden und Antworten über REST erhalten
Es sind drei einfache Schritte:
- Erstellen Sie die REST-Anfrage.
- Senden Sie die REST-Anfrage an TensorFlow Serving.
- Extrahieren Sie das vorhergesagte Ergebnis aus der REST-Antwort und rendern Sie die UI.
Dies erreichen Sie in MainActivity.java.
REST-Anfrage erstellen
Im Moment gibt es eine leere createRESTRequest()
-Funktion in der MainActivity.java
-Datei. Diese Funktion implementieren Sie, um eine REST-Anfrage zu erstellen.
private Request createRESTRequest() {
}
Für TensorFlow Serving wird eine POST-Anfrage mit dem Bildtensor für das von Ihnen verwendete SSD MobileNet-Modell erwartet. Daher müssen Sie die RGB-Werte aus jedem Pixel des Bilds in einem Array extrahieren und das Array dann in einer JSON-Datei zusammenfassen (die Nutzlast der Anfrage).
- Füge der Funktion
createRESTRequest()
diesen Code hinzu:
//Create the REST request.
int[] inputImg = new int[INPUT_IMG_HEIGHT * INPUT_IMG_WIDTH];
int[][][][] inputImgRGB = new int[1][INPUT_IMG_HEIGHT][INPUT_IMG_WIDTH][3];
inputImgBitmap.getPixels(inputImg, 0, INPUT_IMG_WIDTH, 0, 0, INPUT_IMG_WIDTH, INPUT_IMG_HEIGHT);
int pixel;
for (int i = 0; i < INPUT_IMG_HEIGHT; i++) {
for (int j = 0; j < INPUT_IMG_WIDTH; j++) {
// Extract RBG values from each pixel; alpha is ignored
pixel = inputImg[i * INPUT_IMG_WIDTH + j];
inputImgRGB[0][i][j][0] = ((pixel >> 16) & 0xff);
inputImgRGB[0][i][j][1] = ((pixel >> 8) & 0xff);
inputImgRGB[0][i][j][2] = ((pixel) & 0xff);
}
}
RequestBody requestBody =
RequestBody.create("{\"instances\": " + Arrays.deepToString(inputImgRGB) + "}", JSON);
Request request =
new Request.Builder()
.url("http://" + SERVER + ":" + REST_PORT + "/v1/models/" + MODEL_NAME + ":predict")
.post(requestBody)
.build();
return request;
REST-Anfrage an TensorFlow Serving senden
In der Anwendung kann der Nutzer REST oder gRPC für die Kommunikation mit TensorFlow Serving auswählen. Es gibt also zwei Zweige im onClick(View view)
-Listener.
predictButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
if (requestRadioGroup.getCheckedRadioButtonId() == R.id.rest) {
// TODO: REST request
}
else {
}
}
}
)
- Füge diesen Code dem REST-Branch des
onClick(View view)
-Listeners hinzu, um mithilfe von OkHttp die Anfrage an TensorFlow Serving zu senden:
// Send the REST request.
Request request = createRESTRequest();
try {
client =
new OkHttpClient.Builder()
.connectTimeout(20, TimeUnit.SECONDS)
.writeTimeout(20, TimeUnit.SECONDS)
.readTimeout(20, TimeUnit.SECONDS)
.callTimeout(20, TimeUnit.SECONDS)
.build();
Response response = client.newCall(request).execute();
JSONObject responseObject = new JSONObject(response.body().string());
postprocessRESTResponse(responseObject);
} catch (IOException | JSONException e) {
Log.e(TAG, e.getMessage());
responseTextView.setText(e.getMessage());
return;
}
REST-Antwort von TensorFlow Serving verarbeiten
Das SSD MobileNet-Modell gibt eine Reihe von Ergebnissen zurück:
num_detections
: Anzahl der Erkennungendetection_scores
: Erkennungswertedetection_classes
: der Index der Erkennungsklassedetection_boxes
: die Begrenzungsrahmen-Koordinaten
Sie implementieren die Funktion postprocessRESTResponse()
, um die Antwort zu verarbeiten.
private void postprocessRESTResponse(Predict.PredictResponse response) {
}
- Füge der Funktion
postprocessRESTResponse()
diesen Code hinzu:
// Process the REST response.
JSONArray predictionsArray = responseObject.getJSONArray("predictions");
//You only send one image, so you directly extract the first element.
JSONObject predictions = predictionsArray.getJSONObject(0);
// Argmax
int maxIndex = 0;
JSONArray detectionScores = predictions.getJSONArray("detection_scores");
for (int j = 0; j < predictions.getInt("num_detections"); j++) {
maxIndex =
detectionScores.getDouble(j) > detectionScores.getDouble(maxIndex + 1) ? j : maxIndex;
}
int detectionClass = predictions.getJSONArray("detection_classes").getInt(maxIndex);
JSONArray boundingBox = predictions.getJSONArray("detection_boxes").getJSONArray(maxIndex);
double ymin = boundingBox.getDouble(0);
double xmin = boundingBox.getDouble(1);
double ymax = boundingBox.getDouble(2);
double xmax = boundingBox.getDouble(3);
displayResult(detectionClass, (float) ymin, (float) xmin, (float) ymax, (float) xmax);
Die Nachbearbeitungsfunktion extrahiert prognostizierte Werte aus der Antwort, identifiziert die wahrscheinlichste Kategorie des Objekts und die Koordinaten der Eckpunkte des Begrenzungsrahmens und rendert schließlich den Begrenzungsrahmen zur Erkennung auf der Benutzeroberfläche.
Ausführen
- Klicken Sie im Navigationsmenü auf
Ausführen 'App' und warten Sie, bis die App geladen ist.
- Wählen Sie REST > RunInferenz aus.
Es dauert einige Sekunden, bis die App den Begrenzungsrahmen der Katze rendert und 17
als Kategorie des Objekts anzeigt. Diese Kategorie ist dem Objekt cat
im COCO-Dataset zugeordnet.
7. Android-App mit TensorFlow über gRPC verbinden
Zusätzlich zur REST-Unterstützung unterstützt TensorFlow Serving auch BeyondCorp.
gRPC ist ein modernes, leistungsstarkes Open-Source-RPC-Framework (Remote Procedure Call), das in jeder Umgebung ausgeführt werden kann. Dank der flexiblen Unterstützung für Load-Balancing, Tracing, Systemdiagnose und Authentifizierung lassen sich Dienste effizient in und zwischen Rechenzentren verbinden. Es wurde festgestellt, dass gRPC in der Praxis leistungsstärker ist als REST.
Anfragen mit gRPC senden und Antworten erhalten
Es sind vier einfache Schritte:
- [Optional] Generieren Sie den gRPC-Client-Stub-Code.
- Erstellen Sie die gRPC-Anfrage.
- Senden Sie die gRPC-Anfrage an TensorFlow Serving.
- Extrahieren Sie das vorhergesagte Ergebnis aus der gRPC-Antwort und rendern Sie die UI.
Dies erreichen Sie in MainActivity.java.
Optional: gRPC-Client-Stub-Code generieren
Wenn Sie gRPC mit TensorFlow Serving verwenden möchten, müssen Sie dem gRPC-Workflow folgen. Weitere Informationen finden Sie in der BeyondCorp-Dokumentation.
Die Bereitstellung von .proto
-Dateien wird durch TensorFlow Serving und TensorFlow definiert. Ab TensorFlow und TensorFlow Serving 2.8 sind diese .proto
-Dateien erforderlich:
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
- Fügen Sie der Datei
app/build.gradle
diesen Code hinzu, um die Stub-Datei zu generieren.
apply plugin: 'com.google.protobuf'
protobuf {
protoc { artifact = 'com.google.protobuf:protoc:3.11.0' }
plugins {
grpc { artifact = 'io.grpc:protoc-gen-grpc-java:1.29.0'
}
}
generateProtoTasks {
all().each { task ->
task.builtins {
java { option 'lite' }
}
task.plugins {
grpc { option 'lite' }
}
}
}
}
gRPC-Anfrage erstellen
Ähnlich wie bei der REST-Anfrage erstellen Sie die gRPC-Anfrage in der Funktion createGRPCRequest()
.
private Request createGRPCRequest() {
}
- Füge diesen Code der Funktion
createGRPCRequest()
hinzu:
if (stub == null) {
channel = ManagedChannelBuilder.forAddress(SERVER, GRPC_PORT).usePlaintext().build();
stub = PredictionServiceGrpc.newBlockingStub(channel);
}
Model.ModelSpec.Builder modelSpecBuilder = Model.ModelSpec.newBuilder();
modelSpecBuilder.setName(MODEL_NAME);
modelSpecBuilder.setVersion(Int64Value.of(MODEL_VERSION));
modelSpecBuilder.setSignatureName(SIGNATURE_NAME);
Predict.PredictRequest.Builder builder = Predict.PredictRequest.newBuilder();
builder.setModelSpec(modelSpecBuilder);
TensorProto.Builder tensorProtoBuilder = TensorProto.newBuilder();
tensorProtoBuilder.setDtype(DataType.DT_UINT8);
TensorShapeProto.Builder tensorShapeBuilder = TensorShapeProto.newBuilder();
tensorShapeBuilder.addDim(TensorShapeProto.Dim.newBuilder().setSize(1));
tensorShapeBuilder.addDim(TensorShapeProto.Dim.newBuilder().setSize(INPUT_IMG_HEIGHT));
tensorShapeBuilder.addDim(TensorShapeProto.Dim.newBuilder().setSize(INPUT_IMG_WIDTH));
tensorShapeBuilder.addDim(TensorShapeProto.Dim.newBuilder().setSize(3));
tensorProtoBuilder.setTensorShape(tensorShapeBuilder.build());
int[] inputImg = new int[INPUT_IMG_HEIGHT * INPUT_IMG_WIDTH];
inputImgBitmap.getPixels(inputImg, 0, INPUT_IMG_WIDTH, 0, 0, INPUT_IMG_WIDTH, INPUT_IMG_HEIGHT);
int pixel;
for (int i = 0; i < INPUT_IMG_HEIGHT; i++) {
for (int j = 0; j < INPUT_IMG_WIDTH; j++) {
// Extract RBG values from each pixel; alpha is ignored.
pixel = inputImg[i * INPUT_IMG_WIDTH + j];
tensorProtoBuilder.addIntVal((pixel >> 16) & 0xff);
tensorProtoBuilder.addIntVal((pixel >> 8) & 0xff);
tensorProtoBuilder.addIntVal((pixel) & 0xff);
}
}
TensorProto tensorProto = tensorProtoBuilder.build();
builder.putInputs("input_tensor", tensorProto);
builder.addOutputFilter("num_detections");
builder.addOutputFilter("detection_boxes");
builder.addOutputFilter("detection_classes");
builder.addOutputFilter("detection_scores");
return builder.build();
gRPC-Anfrage an TensorFlow Serving senden
Sie können jetzt den Listener onClick(View view)
abschließen.
predictButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
if (requestRadioGroup.getCheckedRadioButtonId() == R.id.rest) {
}
else {
// TODO: gRPC request
}
}
}
)
- Fügen Sie diesen Code dem gRPC-Branch hinzu:
try {
Predict.PredictRequest request = createGRPCRequest();
Predict.PredictResponse response = stub.predict(request);
postprocessGRPCResponse(response);
} catch (Exception e) {
Log.e(TAG, e.getMessage());
responseTextView.setText(e.getMessage());
return;
}
gRPC-Antwort von TensorFlow Serving verarbeiten
Ähnlich wie in gRPC implementieren Sie die Funktion postprocessGRPCResponse()
, um die Antwort zu verarbeiten.
private void postprocessGRPCResponse(Predict.PredictResponse response) {
}
- Füge der Funktion
postprocessGRPCResponse()
diesen Code hinzu:
// Process the response.
float numDetections = response.getOutputsMap().get("num_detections").getFloatValList().get(0);
List<Float> detectionScores = response.getOutputsMap().get("detection_scores").getFloatValList();
int maxIndex = 0;
for (int j = 0; j < numDetections; j++) {
maxIndex = detectionScores.get(j) > detectionScores.get(maxIndex + 1) ? j : maxIndex;
}
Float detectionClass = response.getOutputsMap().get("detection_classes").getFloatValList().get(maxIndex);
List<Float> boundingBoxValues = response.getOutputsMap().get("detection_boxes").getFloatValList();
float ymin = boundingBoxValues.get(maxIndex * 4);
float xmin = boundingBoxValues.get(maxIndex * 4 + 1);
float ymax = boundingBoxValues.get(maxIndex * 4 + 2);
float xmax = boundingBoxValues.get(maxIndex * 4 + 3);
displayResult(detectionClass.intValue(), ymin, xmin, ymax, xmax);
Mit der Nachbearbeitungsfunktion können jetzt vorhergesagte Werte aus der Antwort extrahiert und der Erkennungs-Begrenzungsrahmen in der UI gerendert werden.
Ausführen
- Klicken Sie im Navigationsmenü auf
Ausführen 'App' und warten Sie, bis die App geladen ist.
- Wählen Sie CIDR > Ausführung ausführen aus.
Es dauert einige Sekunden, bis die App den Begrenzungsrahmen der Katze rendert und 17
als Kategorie des Objekts anzeigt. Das ist der Kategorie cat
im COCO-Dataset zugeordnet.
8. Glückwunsch
Sie haben TensorFlow Serving verwendet, um Ihrer App Objekterkennungsfunktionen hinzuzufügen.