1. לפני שמתחילים
בשיעור ה-Codelab הזה תלמדו איך להריץ מסקנה של סיווג טקסט מאפליקציית Flutter באמצעות TensorFlow Serving דרך REST ו-gRPC.
דרישות מוקדמות
- ידע בסיסי בפיתוח Flutter עם Dart
- ידע בסיסי בלמידת מכונה עם TensorFlow, כמו אימון לעומת פריסה
- ידע בסיסי בטרמינלים וב-Docker
- אימון מודל לזיהוי ספאם בתגובות באמצעות סדנת קוד של TensorFlow Lite Model Maker
מה תלמדו
- איך ליצור אפליקציית Flutter פשוטה ולסווג טקסטים באמצעות TensorFlow Serving (REST ו-gRPC).
- איך להציג את התוצאות בממשק המשתמש.
מה נדרש
- Flutter SDK
- הגדרה של Android או iOS ב-Flutter
- הגדרה של Visual Studio Code (VS Code) ל-Flutter ול-Dart
- Docker
- Bash
- קומפיילר של פרוטוקול buffer ותוסף gRPC Dart לקומפיילר של פרוטוקול (נדרש רק אם רוצים ליצור מחדש את ה-stub של gRPC באופן עצמאי)
2. הגדרת סביבת הפיתוח של Flutter
כדי לפתח ב-Flutter, צריך שני רכיבי תוכנה כדי להשלים את שיעור ה-Lab הזה – Flutter SDK ועורך.
אפשר להריץ את ה-codelab באמצעות כל אחד מהמכשירים הבאים:
- סימולטור iOS (נדרשת התקנה של כלי Xcode).
- Android Emulator (נדרשת הגדרה ב-Android Studio).
- דפדפן (חובה להשתמש ב-Chrome לצורך ניפוי באגים).
- כאפליקציה למחשב Windows, Linux או macOS. אתם צריכים לפתח בפלטפורמה שבה אתם מתכננים לבצע פריסה. לכן, אם רוצים לפתח אפליקציה למחשב שולחני עם Windows, צריך לפתח אותה ב-Windows כדי לגשת לשרשרת הבנייה המתאימה. יש דרישות ספציפיות למערכות הפעלה שמוסברות בפירוט במאמר docs.flutter.dev/desktop.
3. להגדרה
כדי להוריד את הקוד של ה-Codelab הזה:
- עוברים אל מאגר GitHub של ה-codelab הזה.
- לוחצים על Code > Download zip (קוד > הורדת קובץ zip) כדי להוריד את כל הקוד של ה-codelab הזה.
- מבטלים את הדחיסה של קובץ ה-ZIP שהורדתם כדי לחלץ תיקיית בסיס
codelabs-main
עם כל המשאבים שאתם צריכים.
ב-codelab הזה, צריך רק את הקבצים בספריית המשנה tfserving-flutter/codelab2
במאגר, שמכילה שתי תיקיות:
- התיקייה
starter
מכילה את קוד ההתחלה שעליו תבנו את הפתרון במעבדת הקוד הזו. - התיקייה
finished
מכילה את הקוד המלא של האפליקציה לדוגמה.
4. הורדת התלות של הפרויקט
- ב-VS Code, לוחצים על File > Open folder (קובץ > פתיחת תיקייה) ואז בוחרים את התיקייה
starter
מקוד המקור שהורדתם קודם. - אם מופיעה תיבת דו-שיח עם הנחיה להוריד את החבילות הנדרשות לאפליקציית המתחילים, לוחצים על Get packages (קבלת חבילות).
- אם תיבת הדו-שיח הזו לא מופיעה, פותחים את הטרמינל ומריצים את הפקודה
flutter pub get
בתיקייהstarter
.
5. הפעלת האפליקציה למתחילים
- ב-VS Code, מוודאים שהאמולטור של Android או הסימולטור של iOS מוגדרים כראוי ומופיעים בסרגל המצב.
לדוגמה, כך נראה השימוש ב-Pixel 5 עם Android Emulator:
כך נראה השימוש ב-iPhone 13 עם סימולטור iOS:
- לוחצים על
התחלת ניפוי באגים.
הפעלת האפליקציה וסקירת התכונות שלה
האפליקציה אמורה להיפתח ב-Android Emulator או ב-iOS Simulator. ממשק המשתמש די פשוט. יש שדה טקסט שמאפשר למשתמש להקליד את הטקסט. המשתמש יכול לבחור אם לשלוח את הנתונים לקצה העורפי באמצעות REST או gRPC. הקצה העורפי משתמש במודל TensorFlow כדי לבצע סיווג טקסט בקלט שעבר עיבוד מראש, ומחזיר את תוצאת הסיווג לאפליקציית הלקוח, שמעדכנת את ממשק המשתמש בתורה.
אם לוחצים על סיווג, לא קורה כלום כי עדיין אין תקשורת עם העורף.
6. פריסת מודל לסיווג טקסט באמצעות TensorFlow Serving
סיווג טקסט הוא משימה נפוצה מאוד בלמידת מכונה, שבה מסווגים טקסטים לקטגוריות מוגדרות מראש. ב-codelab הזה תפרסו את המודל שאומן מראש מתוך Train a comment-spam detection model with TensorFlow Lite Model Maker codelab באמצעות TensorFlow Serving, ותקראו ל-backend מה-frontend של Flutter כדי לסווג את טקסט הקלט כספאם או כלא ספאם.
הפעלת TensorFlow Serving
- בטרמינל, מפעילים את TensorFlow Serving עם Docker, אבל מחליפים את placeholder
PATH/TO/SAVEDMODEL
בנתיב המוחלט של התיקייהmm_spam_savedmodel
במחשב.
docker pull tensorflow/serving docker run -it --rm -p 8500:8500 -p 8501:8501 -v "PATH/TO/SAVEDMODEL:/models/spam-detection" -e MODEL_NAME=spam-detection tensorflow/serving
מערכת Docker מורידה אוטומטית את תמונת TensorFlow Serving קודם, וזה לוקח דקה. לאחר מכן, TensorFlow Serving אמור להתחיל לפעול. היומן צריך להיראות כמו קטע הקוד הבא:
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: spam-detection 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 ...
7. חלוקת משפט הקלט לטוקנים
הקצה העורפי מוכן עכשיו, אז כמעט מוכנים לשלוח בקשות לקוח ל-TensorFlow Serving, אבל קודם צריך לבצע טוקניזציה של משפט הקלט. אם בודקים את טנסור הקלט של המודל, אפשר לראות שהוא מצפה לרשימה של 20 מספרים שלמים במקום למחרוזות גולמיות. טוקניזציה היא מיפוי של המילים שאתם מקלידים באפליקציה לרשימה של מספרים שלמים על סמך מילון אוצר מילים, לפני שאתם שולחים אותן לקצה העורפי לצורך סיווג. לדוגמה, אם מקלידים buy book online to learn more
, תהליך הטוקניזציה ממפה אותו ל-[32, 79, 183, 10, 224, 631, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
. המספרים הספציפיים יכולים להשתנות בהתאם למילון המילים.
- בקובץ
lib/main.dart
, מוסיפים את הקוד הזה לשיטהpredict()
כדי ליצור את מילון המילים_vocabMap
.
// Build _vocabMap if empty.
if (_vocabMap.isEmpty) {
final vocabFileString = await rootBundle.loadString(vocabFile);
final lines = vocabFileString.split('\n');
for (final l in lines) {
if (l != "") {
var wordAndIndex = l.split(' ');
(_vocabMap)[wordAndIndex[0]] = int.parse(wordAndIndex[1]);
}
}
}
- מיד אחרי קטע הקוד הקודם, מוסיפים את הקוד הזה כדי להטמיע טוקניזציה:
// Tokenize the input sentence.
final inputWords = _inputSentenceController.text
.toLowerCase()
.replaceAll(RegExp('[^a-z ]'), '')
.split(' ');
// Initialize with padding token.
_tokenIndices = List.filled(maxSentenceLength, 0);
var i = 0;
for (final w in inputWords) {
if ((_vocabMap).containsKey(w)) {
_tokenIndices[i] = (_vocabMap)[w]!;
i++;
}
// Truncate the string if longer than maxSentenceLength.
if (i >= maxSentenceLength - 1) {
break;
}
}
הקוד הזה ממיר את מחרוזת המשפט לאותיות קטנות, מסיר תווים שאינם אלפביתיים וממפה את המילים ל-20 אינדקסים של מספרים שלמים על סמך טבלת אוצר המילים.
8. חיבור אפליקציית Flutter ל-TensorFlow Serving באמצעות REST
יש שתי דרכים לשלוח בקשות ל-TensorFlow Serving:
- REST
- gRPC
שליחת בקשות וקבלת תשובות באמצעות REST
יש שלושה שלבים פשוטים לשליחת בקשות ולקבלת תשובות באמצעות REST:
- יוצרים את בקשת ה-REST.
- שולחים את בקשת ה-REST אל TensorFlow Serving.
- מחולצים את התוצאה החזויה מתגובת ה-REST ומציגים את ממשק המשתמש.
את השלבים האלה מבצעים בקובץ main.dart
.
יצירה ושליחה של בקשת REST ל-TensorFlow Serving
- בשלב הזה, הפונקציה
predict()
לא שולחת את בקשת ה-REST ל-TensorFlow Serving. כדי ליצור בקשת REST, צריך להטמיע את הענף REST:
if (_connectionMode == ConnectionModeType.rest) {
// TODO: Create and send the REST request.
}
- מוסיפים את הקוד הבא לענף REST:
//Create the REST request.
final response = await http.post(
Uri.parse('http://' +
_server +
':' +
restPort.toString() +
'/v1/models/' +
modelName +
':predict'),
body: jsonEncode(<String, List<List<int>>>{
'instances': [_tokenIndices],
}),
);
עיבוד התגובה של REST מ-TensorFlow Serving
- מוסיפים את הקוד הזה מיד אחרי קטע הקוד הקודם כדי לטפל בתגובת ה-REST:
// Process the REST response.
if (response.statusCode == 200) {
Map<String, dynamic> result = jsonDecode(response.body);
if (result['predictions']![0][1] >= classificationThreshold) {
return 'This sentence is spam. Spam score is ' +
result['predictions']![0][1].toString();
}
return 'This sentence is not spam. Spam score is ' +
result['predictions']![0][1].toString();
} else {
throw Exception('Error response');
}
קוד העיבוד שלאחר מכן מחלץ מהתשובה את ההסתברות שהמשפט שהוזן הוא הודעת ספאם ומציג את תוצאת הסיווג בממשק המשתמש.
הפעלה
- לוחצים על
התחלת ניפוי באגים וממתינים לטעינת האפליקציה.
- מזינים טקסט ובוחרים באפשרות REST > Classify (סיווג).
9. חיבור אפליקציית Flutter ל-TensorFlow Serving דרך gRPC
בנוסף ל-REST, TensorFlow Serving תומך גם ב-gRPC.
gRPC היא מסגרת מודרנית בקוד פתוח לקריאה לשירות מרוחק (RPC) עם ביצועים גבוהים, שיכולה לפעול בכל סביבה. הוא יכול לחבר ביעילות שירותים במרכזי נתונים שונים, עם תמיכה בחיבורים לטעינת איזון, מעקב, בדיקת תקינות ואימות. בפועל, נראה ש-gRPC מניב ביצועים טובים יותר מ-REST.
שליחת בקשות וקבלת תגובות באמצעות gRPC
יש ארבעה שלבים פשוטים לשליחת בקשות ולקבלת תשובות באמצעות gRPC:
- אופציונלי: יוצרים את קוד ה-stub של לקוח gRPC.
- יוצרים את בקשת ה-gRPC.
- שולחים את בקשת gRPC אל TensorFlow Serving.
- מחלקים את התוצאה החזויה מהתגובה של gRPC ומציגים את ממשק המשתמש.
את השלבים האלה מבצעים בקובץ main.dart
.
אופציונלי: יצירת קוד stub של לקוח gRPC
כדי להשתמש ב-gRPC עם TensorFlow Serving, צריך לפעול לפי תהליך העבודה של gRPC. פרטים נוספים זמינים במסמכי התיעוד של gRPC.
TensorFlow Serving ו-TensorFlow מגדירים את קובצי ה-.proto
בשבילכם. החל מגרסה 2.8 של TensorFlow ו-TensorFlow Serving, אלה הקבצים שנדרשים:.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
google/protobuf/any.proto
google/protobuf/wrappers.proto
- בטרמינל, עוברים לתיקייה
starter/lib/proto/
ומייצרים את ה-stub:
bash generate_grpc_stub_dart.sh
יצירת בקשת gRPC
בדומה לבקשת REST, יוצרים את בקשת gRPC בענף gRPC.
if (_connectionMode == ConnectionModeType.rest) {
} else {
// TODO: Create and send the gRPC request.
}
- מוסיפים את הקוד הזה כדי ליצור את בקשת ה-gRPC:
//Create the gRPC request.
final channel = ClientChannel(_server,
port: grpcPort,
options:
const ChannelOptions(credentials: ChannelCredentials.insecure()));
_stub = PredictionServiceClient(channel,
options: CallOptions(timeout: const Duration(seconds: 10)));
ModelSpec modelSpec = ModelSpec(
name: 'spam-detection',
signatureName: 'serving_default',
);
TensorShapeProto_Dim batchDim = TensorShapeProto_Dim(size: Int64(1));
TensorShapeProto_Dim inputDim =
TensorShapeProto_Dim(size: Int64(maxSentenceLength));
TensorShapeProto inputTensorShape =
TensorShapeProto(dim: [batchDim, inputDim]);
TensorProto inputTensor = TensorProto(
dtype: DataType.DT_INT32,
tensorShape: inputTensorShape,
intVal: _tokenIndices);
// If you train your own model, update the input and output tensor names.
const inputTensorName = 'input_3';
const outputTensorName = 'dense_5';
PredictRequest request = PredictRequest(
modelSpec: modelSpec, inputs: {inputTensorName: inputTensor});
הערה: שמות הטנסורים של הקלט והפלט יכולים להיות שונים ממודל למודל, גם אם ארכיטקטורות המודלים זהות. אם מאמנים מודל משלכם, חשוב לעדכן אותם.
שליחת בקשת gRPC אל TensorFlow Serving
- מוסיפים את הקוד הזה אחרי קטע הקוד הקודם כדי לשלוח את בקשת ה-gRPC אל TensorFlow Serving:
// Send the gRPC request.
PredictResponse response = await _stub.predict(request);
עיבוד התגובה של gRPC מ-TensorFlow Serving
- מוסיפים את הקוד הזה אחרי קטע הקוד הקודם כדי להטמיע את פונקציות הקריאה החוזרת לטיפול בתגובה:
// Process the response.
if (response.outputs.containsKey(outputTensorName)) {
if (response.outputs[outputTensorName]!.floatVal[1] >
classificationThreshold) {
return 'This sentence is spam. Spam score is ' +
response.outputs[outputTensorName]!.floatVal[1].toString();
} else {
return 'This sentence is not spam. Spam score is ' +
response.outputs[outputTensorName]!.floatVal[1].toString();
}
} else {
throw Exception('Error response');
}
עכשיו קוד העיבוד שלאחר מכן מחלץ את תוצאת הסיווג מהתשובה ומציג אותה בממשק המשתמש.
הפעלה
- לוחצים על
התחלת ניפוי באגים וממתינים לטעינת האפליקציה.
- מזינים טקסט ובוחרים באפשרות gRPC > Classify (סיווג).
10. מזל טוב
השתמשת ב-TensorFlow Serving כדי להוסיף לאפליקציה יכולות של סיווג טקסט.
ב-codelab הבא תשפרו את המודל כך שתוכלו לזהות הודעות ספאם ספציפיות שלא ניתן לזהות באמצעות האפליקציה הנוכחית.