יצירת אפליקציית Flutter לסיווג טקסטים

1. לפני שמתחילים

בשיעור ה-Codelab הזה תלמדו איך להריץ מסקנה של סיווג טקסט מאפליקציית Flutter באמצעות TensorFlow Serving דרך REST ו-gRPC.

דרישות מוקדמות

מה תלמדו

  • איך ליצור אפליקציית Flutter פשוטה ולסווג טקסטים באמצעות TensorFlow Serving ‏ (REST ו-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 הזה:

  1. עוברים אל מאגר GitHub של ה-codelab הזה.
  2. לוחצים על Code > Download zip (קוד > הורדת קובץ zip) כדי להוריד את כל הקוד של ה-codelab הזה.

2cd45599f51fb8a2.png

  1. מבטלים את הדחיסה של קובץ ה-ZIP שהורדתם כדי לחלץ תיקיית בסיס codelabs-main עם כל המשאבים שאתם צריכים.

ב-codelab הזה, צריך רק את הקבצים בספריית המשנה tfserving-flutter/codelab2 במאגר, שמכילה שתי תיקיות:

  • התיקייה starter מכילה את קוד ההתחלה שעליו תבנו את הפתרון במעבדת הקוד הזו.
  • התיקייה finished מכילה את הקוד המלא של האפליקציה לדוגמה.

4. הורדת התלות של הפרויקט

  1. ב-VS Code, לוחצים על File > Open folder (קובץ > פתיחת תיקייה) ואז בוחרים את התיקייה starter מקוד המקור שהורדתם קודם.
  2. אם מופיעה תיבת דו-שיח עם הנחיה להוריד את החבילות הנדרשות לאפליקציית המתחילים, לוחצים על Get packages (קבלת חבילות).
  3. אם תיבת הדו-שיח הזו לא מופיעה, פותחים את הטרמינל ומריצים את הפקודה flutter pub get בתיקייה starter.

7ada07c300f166a6.png

5. הפעלת האפליקציה למתחילים

  1. ב-VS Code, מוודאים שהאמולטור של Android או הסימולטור של iOS מוגדרים כראוי ומופיעים בסרגל המצב.

לדוגמה, כך נראה השימוש ב-Pixel 5 עם Android Emulator:

9767649231898791.png

כך נראה השימוש ב-iPhone 13 עם סימולטור iOS:

95529e3a682268b2.png

  1. לוחצים על a19a0c68bc4046e6.png התחלת ניפוי באגים.

הפעלת האפליקציה וסקירת התכונות שלה

האפליקציה אמורה להיפתח ב-Android Emulator או ב-iOS Simulator. ממשק המשתמש די פשוט. יש שדה טקסט שמאפשר למשתמש להקליד את הטקסט. המשתמש יכול לבחור אם לשלוח את הנתונים לקצה העורפי באמצעות REST או gRPC. הקצה העורפי משתמש במודל TensorFlow כדי לבצע סיווג טקסט בקלט שעבר עיבוד מראש, ומחזיר את תוצאת הסיווג לאפליקציית הלקוח, שמעדכנת את ממשק המשתמש בתורה.

b298f605d64dc132.png d3ef3ccd3c338108.png

אם לוחצים על סיווג, לא קורה כלום כי עדיין אין תקשורת עם העורף.

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]. המספרים הספציפיים יכולים להשתנות בהתאם למילון המילים.

  1. בקובץ 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]);
    }
  }
} 
  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:

  1. יוצרים את בקשת ה-REST.
  2. שולחים את בקשת ה-REST אל TensorFlow Serving.
  3. מחולצים את התוצאה החזויה מתגובת ה-REST ומציגים את ממשק המשתמש.

את השלבים האלה מבצעים בקובץ main.dart.

יצירה ושליחה של בקשת REST ל-TensorFlow Serving

  1. בשלב הזה, הפונקציה predict() לא שולחת את בקשת ה-REST ל-TensorFlow Serving. כדי ליצור בקשת REST, צריך להטמיע את הענף REST:
if (_connectionMode == ConnectionModeType.rest) {
  // TODO: Create and send the REST request.

}
  1. מוסיפים את הקוד הבא לענף 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');
}

קוד העיבוד שלאחר מכן מחלץ מהתשובה את ההסתברות שהמשפט שהוזן הוא הודעת ספאם ומציג את תוצאת הסיווג בממשק המשתמש.

הפעלה

  1. לוחצים על a19a0c68bc4046e6.png התחלת ניפוי באגים וממתינים לטעינת האפליקציה.
  2. מזינים טקסט ובוחרים באפשרות REST > Classify (סיווג).

8e21d795af36d07a.png e79a0367a03c2169.png

9. חיבור אפליקציית Flutter ל-TensorFlow Serving דרך gRPC

בנוסף ל-REST, ‏ TensorFlow Serving תומך גם ב-gRPC.

b6f4449c2c850b0e.png

‫gRPC היא מסגרת מודרנית בקוד פתוח לקריאה לשירות מרוחק (RPC) עם ביצועים גבוהים, שיכולה לפעול בכל סביבה. הוא יכול לחבר ביעילות שירותים במרכזי נתונים שונים, עם תמיכה בחיבורים לטעינת איזון, מעקב, בדיקת תקינות ואימות. בפועל, נראה ש-gRPC מניב ביצועים טובים יותר מ-REST.

שליחת בקשות וקבלת תגובות באמצעות gRPC

יש ארבעה שלבים פשוטים לשליחת בקשות ולקבלת תשובות באמצעות gRPC:

  1. אופציונלי: יוצרים את קוד ה-stub של לקוח gRPC.
  2. יוצרים את בקשת ה-gRPC.
  3. שולחים את בקשת gRPC אל TensorFlow Serving.
  4. מחלקים את התוצאה החזויה מהתגובה של gRPC ומציגים את ממשק המשתמש.

את השלבים האלה מבצעים בקובץ main.dart.

אופציונלי: יצירת קוד stub של לקוח gRPC

כדי להשתמש ב-gRPC עם TensorFlow Serving, צריך לפעול לפי תהליך העבודה של gRPC. פרטים נוספים זמינים במסמכי התיעוד של gRPC.

a9d0e5cb543467b4.png

‫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');
}

עכשיו קוד העיבוד שלאחר מכן מחלץ את תוצאת הסיווג מהתשובה ומציג אותה בממשק המשתמש.

הפעלה

  1. לוחצים על a19a0c68bc4046e6.png התחלת ניפוי באגים וממתינים לטעינת האפליקציה.
  2. מזינים טקסט ובוחרים באפשרות gRPC > Classify (סיווג).

e44e6e9a5bde2188.png 92644d723f61968c.png

10. מזל טוב

השתמשת ב-TensorFlow Serving כדי להוסיף לאפליקציה יכולות של סיווג טקסט.

ב-codelab הבא תשפרו את המודל כך שתוכלו לזהות הודעות ספאם ספציפיות שלא ניתן לזהות באמצעות האפליקציה הנוכחית.

מידע נוסף