1. Sebelum memulai
Dalam codelab ini, Anda akan mempelajari cara menjalankan inferensi deteksi objek dari aplikasi Android menggunakan TensorFlow Serving dengan REST dan gRPC.
Prasyarat
- Pengetahuan dasar tentang pengembangan Android dengan Java
- Pengetahuan dasar tentang machine learning dengan TensorFlow, seperti pelatihan dan deployment
- Pengetahuan dasar tentang terminal dan Docker
Yang akan Anda pelajari
- Cara menemukan model deteksi objek terlatih di TensorFlow Hub.
- Cara mem-build aplikasi Android sederhana dan membuat prediksi dengan model deteksi objek yang didownload melalui TensorFlow Serving (REST dan gRPC).
- Cara merender hasil deteksi di UI.
Yang Anda butuhkan
- Versi terbaru Android Studio
- Docker
- Bash
2. Memulai persiapan
Untuk mendownload kode codelab ini:
- Buka repositori GitHub untuk codelab ini.
- Klik Code > Download zip guna mendownload semua kode untuk codelab ini.
- Ekstrak file zip yang didownload untuk mengekstrak folder root
codelabs
dengan semua resource yang Anda butuhkan.
Untuk codelab ini, Anda hanya memerlukan file dalam subdirektori TFServing/ObjectDetectionAndroid
di repositori, yang berisi dua folder:
- Folder
starter
berisi kode awal yang Anda buat untuk codelab ini. - Folder
finished
berisi kode yang sudah selesai untuk aplikasi contoh yang telah selesai.
3. Tambahkan dependensi ke project
Mengimpor aplikasi awal ke Android Studio
- Di Android Studio, klik File > New > Import project, lalu pilih folder
starter
dari kode sumber yang Anda download sebelumnya.
Tambahkan dependensi untuk OkHttp dan gRPC
- Di file
app/build.gradle
project Anda, konfirmasi keberadaan dependensi.
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'
}
Sinkronkan project Anda dengan file Gradle
- Pilih
Sync Project with Gradle Files dari menu navigasi.
4. Menjalankan aplikasi awal
- Mulai Android Emulator, lalu klik
Run 'app' di menu navigasi.
Menjalankan dan menjelajahi aplikasi
Aplikasi akan diluncurkan di perangkat Android Anda. UI-nya cukup sederhana: ada gambar kucing yang ingin Anda deteksi objeknya dan pengguna dapat memilih cara mengirim data ke backend, dengan REST atau gRPC. Backend melakukan deteksi objek pada gambar dan menampilkan hasil deteksi ke aplikasi klien, yang merender UI lagi.
Saat ini, jika Anda mengklik Run inference, tidak akan ada yang terjadi. Hal ini karena komunikasi dengan backend belum dapat dilakukan.
5. Men-deploy model deteksi objek dengan TensorFlow Serving
Deteksi objek adalah tugas ML yang sangat umum dan tujuannya adalah mendeteksi objek dalam gambar, yaitu memprediksi kemungkinan kategori objek dan kotak pembatas di sekitarnya. Berikut adalah contoh hasil deteksi:
Google telah memublikasikan sejumlah model terlatih di TensorFlow Hub. Untuk melihat daftar lengkapnya, buka halaman object_detection. Anda menggunakan model SSD MobileNet V2 FPNLite 320x320 yang relatif ringan untuk codelab ini sehingga Anda tidak perlu menggunakan GPU untuk menjalankannya.
Untuk men-deploy model deteksi objek dengan TensorFlow Serving:
- Download file model.
- Buka kompresi file
.tar.gz
yang didownload dengan alat dekompresi, seperti 7-Zip. - Buat folder
ssd_mobilenet_v2_2_320
, lalu buat subfolder123
di dalamnya. - Masukkan folder
variables
yang diekstrak dan filesaved_model.pb
ke dalam subfolder123
.
Anda dapat merujuk ke folder ssd_mobilenet_v2_2_320
sebagai folder SavedModel
. 123
adalah contoh nomor versi. Jika mau, Anda dapat memilih nomor lain.
Struktur folder akan terlihat seperti gambar ini:
Memulai TensorFlow Serving
- Di terminal Anda, mulai TensorFlow Serving dengan Docker, tetapi ganti placeholder
PATH/TO/SAVEDMODEL
dengan jalur absolut folderssd_mobilenet_v2_2_320
di komputer Anda.
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 akan otomatis mendownload image TensorFlow Serving terlebih dahulu, yang memerlukan waktu sebentar. Setelah itu, TensorFlow Serving akan dimulai. Log akan terlihat seperti cuplikan kode berikut:
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. Menghubungkan aplikasi Android dengan TensorFlow Serving melalui REST
Backend sudah siap, sehingga Anda dapat mengirim permintaan klien ke TensorFlow Serving untuk mendeteksi objek dalam gambar. Ada dua cara untuk mengirim permintaan ke TensorFlow Serving:
- REST
- gRPC
Mengirim permintaan dan menerima respons melalui REST
Ada tiga langkah sederhana:
- Buat permintaan REST.
- Kirim permintaan REST ke TensorFlow Serving.
- Ekstrak hasil prediksi dari respons REST dan render UI-nya.
Anda akan mencapainya dalam MainActivity.java.
Buat permintaan REST
Saat ini, ada fungsi createRESTRequest()
kosong dalam file MainActivity.java
. Anda mengimplementasikan fungsi ini untuk membuat permintaan REST.
private Request createRESTRequest() {
}
TensorFlow Serving mengharapkan permintaan POST yang berisi tensor gambar untuk model SSD MobileNet yang Anda gunakan, jadi Anda perlu mengekstrak nilai RGB dari setiap piksel gambar ke dalam array, lalu membungkus array dalam JSON, yang merupakan payload permintaan.
- Tambahkan kode ini ke fungsi
createRESTRequest()
:
//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;
Mengirim permintaan REST ke TensorFlow Serving
Aplikasi ini memungkinkan pengguna memilih REST atau gRPC untuk berkomunikasi dengan TensorFlow Serving, sehingga ada dua cabang di listener onClick(View view)
.
predictButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
if (requestRadioGroup.getCheckedRadioButtonId() == R.id.rest) {
// TODO: REST request
}
else {
}
}
}
)
- Tambahkan kode ini ke cabang REST dari pemroses
onClick(View view)
untuk menggunakan OkHttp guna mengirim permintaan ke TensorFlow Serving:
// 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;
}
Memproses respons REST dari TensorFlow Serving
Model SSD MobileNet menampilkan sejumlah hasil, yang mencakup:
num_detections
: jumlah deteksidetection_scores
: skor deteksidetection_classes
: indeks kelas deteksidetection_boxes
: koordinat kotak pembatas
Anda menerapkan fungsi postprocessRESTResponse()
untuk menangani respons.
private void postprocessRESTResponse(Predict.PredictResponse response) {
}
- Tambahkan kode ini ke fungsi
postprocessRESTResponse()
:
// 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);
Sekarang, fungsi pascapemrosesan mengekstrak nilai prediksi dari respons, mengidentifikasi kategori objek yang paling mungkin dan koordinat verteks kotak pembatas, dan terakhir merender kotak pembatas deteksi di UI.
Menjalankan aplikasi
- Klik
Run 'app' di menu navigasi, lalu tunggu aplikasi dimuat.
- Pilih REST > Run inference.
Aplikasi memerlukan waktu beberapa detik sebelum merender kotak pembatas kucing dan menampilkan 17
sebagai kategori objek, yang dipetakan ke objek cat
dalam set data COCO.
7. Menghubungkan aplikasi Android dengan TensorFlow Serving melalui gRPC
Selain REST, TensorFlow Serving juga mendukung gRPC.
gRPC adalah framework Remote Procedure Call (RPC) modern, open source, dan berperforma tinggi yang dapat berjalan di lingkungan apa pun. Framework ini dapat menghubungkan layanan di dalam dan di seluruh pusat data secara efisien dengan dukungan yang dapat dicocokkan untuk load balancing, tracing, health checking, dan autentikasi. Berdasarkan pengamatan dalam praktiknya, performa gRPC lebih tinggi daripada REST.
Mengirim permintaan dan menerima respons dengan gRPC
Ada empat langkah sederhana:
- [Opsional] Buat kode stub klien gRPC.
- Buat permintaan gRPC.
- Kirim permintaan gRPC ke TensorFlow Serving.
- Ekstrak hasil yang diprediksi dari respons gRPC dan render UI-nya.
Anda akan mencapainya dalam MainActivity.java.
Opsional: Membuat kode stub klien gRPC
Untuk menggunakan gRPC dengan TensorFlow Serving, Anda harus mengikuti alur kerja gRPC. Untuk mempelajari detailnya lebih lanjut, baca dokumentasi gRPC.
TensorFlow Serving dan TensorFlow menentukan file .proto
untuk Anda. Mulai dari TensorFlow dan TensorFlow Serve 2.8, file .proto
berikut adalah file yang diperlukan:
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
- Untuk membuat stub, tambahkan kode ini ke file
app/build.gradle
.
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' }
}
}
}
}
Membuat permintaan gRPC
Serupa dengan permintaan REST, Anda membuat permintaan gRPC di fungsi createGRPCRequest()
.
private Request createGRPCRequest() {
}
- Tambahkan kode ini ke fungsi
createGRPCRequest()
:
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();
Mengirim permintaan gRPC ke TensorFlow Serving
Sekarang Anda dapat menyelesaikan pemroses onClick(View view)
.
predictButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
if (requestRadioGroup.getCheckedRadioButtonId() == R.id.rest) {
}
else {
// TODO: gRPC request
}
}
}
)
- Tambahkan kode ini ke cabang gRPC:
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;
}
Memproses respons gRPC dari TensorFlow Serving
Mirip dengan gRPC, Anda menerapkan fungsi postprocessGRPCResponse()
untuk menangani respons.
private void postprocessGRPCResponse(Predict.PredictResponse response) {
}
- Tambahkan kode ini ke fungsi
postprocessGRPCResponse()
:
// 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);
Sekarang, fungsi pascapemrosesan dapat mengekstrak nilai prediksi dari respons dan merender kotak pembatas deteksi di UI.
Menjalankan aplikasi
- Klik
Run 'app' di menu navigasi, lalu tunggu aplikasi dimuat.
- Pilih gRPC > Run inference.
Aplikasi memerlukan waktu beberapa detik sebelum merender kotak pembatas kucing dan menampilkan 17
sebagai kategori objek, yang dipetakan ke kategori cat
dalam set data COCO.
8. Selamat
Anda telah menggunakan TensorFlow Serving untuk menambahkan kemampuan deteksi objek ke aplikasi Anda.