لمحة عن هذا الدرس التطبيقي حول الترميز
1. قبل البدء
في هذا الدرس التطبيقي حول الترميز، ستتعرّف على كيفية تنفيذ استنتاج رصد الكائنات من أحد تطبيقات Android باستخدام TensorFlow serving مع REST وgRPC.
المتطلّبات الأساسية
- معرفة أساسية بتطوير تطبيقات Android باستخدام لغة البرمجة Java
- معرفة أساسية بتعلُّم الآلة مع TensorFlow، مثل التدريب والنشر
- معرفة أساسية حول محطات الدفع وقاعدة الإرساء
ما ستتعرَّف عليه
- كيفية العثور على نماذج رصد الكائنات المعدّة مسبقًا في TensorFlow Hub
- كيفية إنشاء تطبيق بسيط يعمل بنظام التشغيل Android وتوقّع التوقّعات باستخدام نموذج اكتشاف العنصر الذي تم تنزيله من خلال TensorFlow العرض (REST وgRPC).
- كيفية عرض نتيجة الاكتشاف في واجهة المستخدم.
الأشياء التي تحتاج إليها
- أحدث إصدار من استوديو Android
- الدوكر
- البش
2. الإعداد
لتنزيل الرمز في هذا الدرس التطبيقي حول الترميز:
- انتقِل إلى مستودع GitHub لهذا الدرس التطبيقي حول الترميز.
- انقر على رمز > تنزيل ملف zip لتنزيل كل رموز هذا الدرس التطبيقي حول الترميز.
- يمكنك فك ضغط ملف ZIP الذي تم تنزيله لفك ضغط مجلد الجذر الذي يتضمّن
codelabs
مع جميع الموارد التي تحتاجها.
بالنسبة إلى هذا الدرس التطبيقي حول الترميز، تحتاج فقط إلى الملفات في دليل TFServing/ObjectDetectionAndroid
الفرعي في المستودع، الذي يحتوي على مجلدَين:
- يتضمّن المجلد
starter
رمز التفعيل الذي تستند إليه في هذا الدرس التطبيقي حول الترميز. - يحتوي مجلد
finished
على الرمز المكتمل لنموذج التطبيق المكتمل.
3. إضافة تبعيات إلى المشروع
استيراد تطبيق إجراء التفعيل إلى "استوديو Android"
- في "استوديو Android"، انقر على File > جديد > استيراد المشروع ثم اختَر المجلد
starter
من رمز المصدر الذي نزّلته سابقًا.
إضافة تبعيات لكل من OkHttp وgRPC
- تأكّد من وجود تبعيات في ملف
app/build.gradle
في مشروعك.
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'
}
مزامنة مشروعك مع ملفات Gradle
- اختَر
مزامنة المشروع مع ملفات Gradle من قائمة التنقل.
4. تشغيل تطبيق إجراء التفعيل
- ابدأ محاكي Android، ثم انقر على
تشغيل "app'" في قائمة التنقل.
تشغيل التطبيق واستكشافه
من المفترض أن يتم تشغيل التطبيق على جهاز Android. واجهة المستخدم سهلة الاستخدام، فهي توفّر صورة للقطط تريد من خلالها اكتشاف الأشياء ويمكن للمستخدم اختيار الطريقة لإرسال البيانات إلى الخلفية، وذلك باستخدام REST أو gRPC. وتُجري الواجهة الخلفية اكتشاف العنصر على الصورة وتعرض نتائج الاكتشاف إلى تطبيق العميل، والتي تعرض واجهة المستخدم مرة أخرى.
الآن، إذا نقرت على تشغيل الاستنتاج، لن يحدث أي شيء. ويرجع هذا إلى عدم إمكانية التواصل مع الخلفية.
5. نشر نموذج اكتشاف العنصر باستخدام TensorFlow serving
اكتشاف الكائنات هو مهمة شائعة جدًا في تعلِّم الآلة وهدفها هو اكتشاف الكائنات داخل الصور، وتحديدًا للتنبؤ بالفئات المحتملة من العناصر والمربّعات المحيطة بها. في ما يلي مثال على نتيجة اكتشاف:
نشرت Google عددًا من النماذج المدرَّبة مسبقًا على TensorFlow Hub. للاطّلاع على القائمة الكاملة، انتقِل إلى الصفحة object_detection. تستخدم النموذج SSD MobileNet V2 FPNLite 320x320 خفيف الوزن لهذا الدرس التطبيقي لكي لا تحتاج بالضرورة إلى استخدام وحدة معالجة رسومات لتشغيله.
لنشر نموذج اكتشاف العنصر باستخدام TensorFlow تنفيذ:
- نزِّل ملف النموذج.
- يمكنك فك ضغط ملف
.tar.gz
الذي تم تنزيله باستخدام أداة فك ضغط، مثل 7-Zip. - يمكنك إنشاء مجلد
ssd_mobilenet_v2_2_320
ثم إنشاء مجلد فرعي فيه123
. - ضع المجلد
variables
المستخرج والملفsaved_model.pb
في المجلد الفرعي123
.
يمكنك الإشارة إلى المجلد ssd_mobilenet_v2_2_320
باعتباره المجلد SavedModel
. 123
هو مثال على رقم الإصدار. يمكنك اختيار رقم آخر، إذا أردت ذلك.
يجب أن تظهر بنية المجلد على النحو التالي:
بدء عرض TensorFlow
- في الوحدة الطرفية، ابدأ تشغيل TensorFlow للعرض باستخدام docker، ولكن استبدِل العنصر النائب
PATH/TO/SAVEDMODEL
بالمسار المطلق للمجلدssd_mobilenet_v2_2_320
على جهاز الكمبيوتر.
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 تلقائيًا صورة العرض TensorFlow أولاً، الأمر الذي يستغرق دقيقة واحدة. بعد ذلك، من المفترض أن يبدأ عرض TensorFlow. يجب أن يظهر السجل مثل مقتطف الرمز هذا:
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 بخدمة TensorFlow العرض من خلال REST
أصبحت الخلفية جاهزة الآن، لذا يمكنك إرسال طلبات العميل إلى TensorFlow العرض لاكتشاف العناصر داخل الصور. تتوفّر طريقتان لإرسال الطلبات إلى TensorFlow Driving:
- استراحة
- gRPC
إرسال طلبات وتلقّي ردود من خلال REST
هناك ثلاث خطوات بسيطة:
- إنشاء طلب REST.
- يُرجى إرسال طلب REST إلى TensorFlow serving.
- استخرِج النتيجة المتوقعة من استجابة REST واعرض واجهة المستخدم.
ستعمل على تحقيق ذلك في MainActivity.java.
إنشاء طلب REST
هناك الآن دالة createRESTRequest()
فارغة في الملف MainActivity.java
. نفّذ هذه الدالة لإنشاء طلب REST.
private Request createRESTRequest() {
}
تتوقع خدمة TensorFlow عرض طلب POST يحتوي على موتّر الصورة لنموذج SSD للأجهزة الجوّالة الذي تستخدمه، لذا تحتاج إلى استخراج قيم RGB من كل وحدة بكسل للصورة إلى مصفوفة ثم لف المصفوفة في JSON، وهو حمولة الطلب.
- أضِف هذا الرمز إلى الدالة
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;
إرسال طلب REST إلى TensorFlow للعرض
يتيح التطبيق للمستخدِم اختيار REST أو gRPC للتواصل مع TensorFlow صينة، لذلك هناك فرعان في أداة معالجة onClick(View view)
.
predictButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
if (requestRadioGroup.getCheckedRadioButtonId() == R.id.rest) {
// TODO: REST request
}
else {
}
}
}
)
- أضِف هذا الرمز إلى فرع REST للمستمع من
onClick(View view)
لاستخدام OkHttp لإرسال الطلب إلى TensorFlow العرض:
// 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 من TensorFlow العرض
يعرض نموذج SDS للأجهزة الجوّالة عددًا من النتائج، ومن بينها:
num_detections
: عدد عمليات الرصدdetection_scores
: نتائج الاكتشافdetection_classes
: فهرس فئة الاكتشافdetection_boxes
: إحداثيات مربع الإحاطة
يجب تنفيذ الدالة postprocessRESTResponse()
لمعالجة الاستجابة.
private void postprocessRESTResponse(Predict.PredictResponse response) {
}
- أضِف هذا الرمز إلى الدالة
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);
والآن، تستخرج وظيفة ما بعد المعالجة القيم المتوقعة من الاستجابة، وتعرّف على الفئة الأكثر احتمالاً للعنصر وإحداثيات الخطوط الحدّية، وتعرض في النهاية مربع وضع الرصد على واجهة المستخدم.
التشغيل
- انقر على
تشغيل التطبيق'؛ في قائمة التنقل ثم انتظر حتى يتم تحميل التطبيق.
- اختَر REST > Run theاستنتاج.
يستغرق الأمر بضع ثوانٍ قبل أن يعرض التطبيق المربّع المحاذي للقطّ ويعرض 17
كفئة الكائن الذي يتم ربطه بالكائن cat
في مجموعة بيانات COCO.
7. ربط تطبيق Android بخدمة TensorFlow من خلال خدمة gRPC
بالإضافة إلى REST، تتوافق خدمة TensorFlow أيضًا مع gRPC.
gRPC هو إطار عمل حديث ومفتوح المصدر وعالي الأداء للإجراءات عن بُعد يمكن تشغيله في أي بيئة. وتستطيع هذه الخدمة توصيل الخدمات بكفاءة في مراكز البيانات وفي مناطق أخرى مع دعم قابل للتوصيل لموازنة الحِمل وتتبُّع الحالة الصحية والمصادقة. لقد لاحظنا أن gRPC هو أكثر أداءً من REST من الناحية العملية.
إرسال الطلبات وتلقّي الردود باستخدام gRPC
هناك أربع خطوات بسيطة:
- [اختياري] أنشئ رمز كائن gRPC للعميل.
- إنشاء طلب gRPC.
- أرسِل طلب gRPC إلى TensorFlow العرض.
- استخرِج النتيجة المتوقعة من استجابة gRPC وعرض واجهة المستخدم.
ستعمل على تحقيق ذلك في MainActivity.java.
اختياري: إنشاء رمز كائن gRPC للعميل
لاستخدام gRPC مع عرض TensorFlow، عليك اتّباع طريقة عمل gRPC. لمعرفة المزيد من التفاصيل، يُرجى الاطّلاع على وثائق gRPC.
يشكّل TensorFlow للعرض وTensorFlow ملفات .proto
بالنيابة عنك. اعتبارًا من TensorFlow وTensorFlow العرض 2.8، يجب توفير الملفات التالية .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
- لإنشاء التنويه الموجز، أضِف هذا الرمز إلى ملف
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' }
}
}
}
}
إنشاء طلب gRPC
على غرار طلب REST، يمكنك إنشاء طلب gRPC في الدالة createGRPCRequest()
.
private Request createGRPCRequest() {
}
- أضِف هذا الرمز إلى دالة
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();
إرسال طلب gRPC إلى TensorFlow العرض
يمكنك الآن إنهاء أداة معالجة onClick(View view)
.
predictButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View view) {
if (requestRadioGroup.getCheckedRadioButtonId() == R.id.rest) {
}
else {
// TODO: gRPC request
}
}
}
)
- إضافة هذا الرمز إلى فرع 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;
}
معالجة استجابة gRPC من TensorFlow
على غرار gRPC، يمكنك تنفيذ الدالة postprocessGRPCResponse()
لمعالجة الاستجابة.
private void postprocessGRPCResponse(Predict.PredictResponse response) {
}
- أضِف هذا الرمز إلى الدالة
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);
يمكن لدالة ما بعد المعالجة استخراج القيم المتوقعة من الاستجابة، وعرض مربع تحديد الرصد في واجهة المستخدم.
التشغيل
- انقر على
تشغيل التطبيق'؛ في قائمة التنقل ثم انتظر حتى يتم تحميل التطبيق.
- اختَر gRPC > لتشغيل الاستنتاج.
يستغرق الأمر بضع ثوانٍ قبل أن يعرض التطبيق المربع المحيط بالقط ويعرض 17
كفئة الكائن، والتي يتم ربطها بالفئة cat
في مجموعة بيانات COCO.
8. تهانينا
لقد استخدمت منصة TensorFlow للعرض لإضافة إمكانات رصد العناصر إلى تطبيقك.