สร้างแอป Flutter เพื่อจัดประเภทข้อความ

1. ก่อนเริ่มต้น

ในโค้ดแล็บนี้ คุณจะได้เรียนรู้วิธีเรียกใช้การอนุมานการจัดประเภทข้อความจากแอป Flutter ด้วย TensorFlow Serving ผ่าน REST และ gRPC

ข้อกำหนดเบื้องต้น

สิ่งที่คุณจะได้เรียนรู้

  • วิธีสร้างแอป Flutter อย่างง่ายและจัดประเภทข้อความผ่าน TensorFlow Serving (REST และ gRPC)
  • วิธีแสดงผลลัพธ์ใน UI

สิ่งที่คุณต้องมี

2. ตั้งค่าสภาพแวดล้อมในการพัฒนา Flutter

สำหรับการพัฒนา Flutter คุณต้องมีซอฟต์แวร์ 2 อย่างเพื่อทำแล็บนี้ให้เสร็จสมบูรณ์ ได้แก่ Flutter SDK และโปรแกรมแก้ไข

คุณเรียกใช้ Codelab ได้โดยใช้อุปกรณ์ต่อไปนี้

  • โปรแกรมจำลอง iOS (ต้องติดตั้งเครื่องมือ Xcode)
  • โปรแกรมจำลอง Android (ต้องตั้งค่าใน Android Studio)
  • เบราว์เซอร์ (ต้องใช้ Chrome สำหรับการแก้ไขข้อบกพร่อง)
  • เป็นแอปพลิเคชันเดสก์ท็อป Windows, Linux หรือ macOS คุณต้องพัฒนาบนแพลตฟอร์มที่วางแผนจะใช้งาน ดังนั้น หากต้องการพัฒนาแอปเดสก์ท็อป Windows คุณต้องพัฒนาบน Windows เพื่อเข้าถึงห่วงโซ่การสร้างที่เหมาะสม มีข้อกำหนดเฉพาะของระบบปฏิบัติการที่อธิบายไว้โดยละเอียดใน docs.flutter.dev/desktop

3. ตั้งค่า

วิธีดาวน์โหลดโค้ดสำหรับ Codelab นี้

  1. ไปที่ที่เก็บ GitHub สำหรับ Codelab นี้
  2. คลิกโค้ด > ดาวน์โหลด ZIP เพื่อดาวน์โหลดโค้ดทั้งหมดสำหรับ Codelab นี้

2cd45599f51fb8a2.png

  1. แตกไฟล์ ZIP ที่ดาวน์โหลดเพื่อคลายโฟลเดอร์รูท codelabs-main ที่มีทรัพยากรทั้งหมดที่คุณต้องการ

สำหรับโค้ดแล็บนี้ คุณจะต้องใช้เฉพาะไฟล์ในไดเรกทอรีย่อย tfserving-flutter/codelab2 ในที่เก็บ ซึ่งมี 2 โฟลเดอร์ ได้แก่

  • โฟลเดอร์ starter มีโค้ดเริ่มต้นที่คุณจะใช้ต่อยอดสำหรับโค้ดแล็บนี้
  • โฟลเดอร์ finished มีโค้ดที่เสร็จสมบูรณ์สำหรับแอปตัวอย่างที่เสร็จแล้ว

4. ดาวน์โหลดทรัพยากร Dependency สำหรับโปรเจ็กต์

  1. ใน VS Code ให้คลิกไฟล์ > เปิดโฟลเดอร์ แล้วเลือกโฟลเดอร์ starter จากซอร์สโค้ดที่คุณดาวน์โหลดไว้ก่อนหน้านี้
  2. หากเห็นกล่องโต้ตอบปรากฏขึ้นซึ่งแจ้งให้คุณดาวน์โหลดแพ็กเกจที่จำเป็นสำหรับแอปเริ่มต้น ให้คลิกรับแพ็กเกจ
  3. หากไม่เห็นกล่องโต้ตอบนี้ ให้เปิดเทอร์มินัล แล้วเรียกใช้คำสั่ง flutter pub get ในโฟลเดอร์ starter

7ada07c300f166a6.png

5. เรียกใช้แอปเริ่มต้น

  1. ใน VS Code ตรวจสอบว่าได้ตั้งค่า Android Emulator หรือ iOS Simulator อย่างถูกต้องและปรากฏในแถบสถานะ

ตัวอย่างเช่น สิ่งที่คุณเห็นเมื่อใช้ Pixel 5 กับโปรแกรมจำลอง Android มีดังนี้

9767649231898791.png

สิ่งที่คุณเห็นเมื่อใช้ iPhone 13 กับ iOS Simulator มีดังนี้

95529e3a682268b2.png

  1. คลิก a19a0c68bc4046e6.png เริ่มแก้ไขข้อบกพร่อง

เรียกใช้และสำรวจแอป

แอปควรเปิดตัวใน Android Emulator หรือ iOS Simulator UI นั้นใช้งานง่าย มีช่องข้อความที่ให้ผู้ใช้พิมพ์ข้อความ ผู้ใช้เลือกได้ว่าจะส่งข้อมูลไปยังแบ็กเอนด์ด้วย REST หรือ gRPC แบ็กเอนด์ใช้โมเดล TensorFlow เพื่อทำการจัดประเภทข้อความในอินพุตที่ประมวลผลล่วงหน้า และส่งผลการจัดประเภทกลับไปยังแอปไคลเอ็นต์ ซึ่งจะอัปเดต UI ในทางกลับกัน

b298f605d64dc132.png d3ef3ccd3c338108.png

หากคลิกจัดประเภท จะไม่มีอะไรเกิดขึ้นเนื่องจากยังสื่อสารกับแบ็กเอนด์ไม่ได้

6. ติดตั้งใช้งานโมเดลการจัดประเภทข้อความด้วย TensorFlow Serving

การจัดประเภทข้อความคืองานแมชชีนเลิร์นนิงที่พบบ่อยมาก ซึ่งจะจัดประเภทข้อความเป็นหมวดหมู่ที่กำหนดไว้ล่วงหน้า ในโค้ดแล็บนี้ คุณจะได้ติดตั้งใช้งานโมเดลที่ผ่านการฝึกมาก่อนจากโค้ดแล็บการฝึกโมเดลตรวจจับสแปมในความคิดเห็นด้วย TensorFlow Lite Model Maker ด้วย TensorFlow Serving และเรียกใช้แบ็กเอนด์จากฟรอนต์เอนด์ Flutter เพื่อจัดประเภทข้อความที่ป้อนเป็นสแปมหรือไม่ใช่สแปม

เริ่ม TensorFlow Serving

  • ในเทอร์มินัล ให้เริ่ม TensorFlow Serving ด้วย Docker แต่แทนที่ตัวยึดตำแหน่ง 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 โดยอัตโนมัติก่อน ซึ่งจะใช้เวลา 1 นาที หลังจากนั้น 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 ได้ 2 วิธีดังนี้

  • REST
  • gRPC

ส่งคำขอและรับการตอบกลับผ่าน REST

การส่งคำขอและรับการตอบกลับผ่าน REST มี 3 ขั้นตอนง่ายๆ ดังนี้

  1. สร้างคำขอ REST
  2. ส่งคำขอ REST ไปยัง TensorFlow Serving
  3. ดึงผลลัพธ์ที่คาดการณ์ไว้จากการตอบกลับ REST และแสดงผล UI

คุณทำตามขั้นตอนเหล่านี้ในไฟล์ 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');
}

โค้ดหลังการประมวลผลจะดึงความน่าจะเป็นที่ประโยคอินพุตเป็นข้อความสแปมจากคำตอบและแสดงผลการจัดประเภทใน UI

เรียกใช้

  1. คลิก a19a0c68bc4046e6.png Start debugging แล้วรอให้แอปโหลด
  2. ป้อนข้อความ แล้วเลือก REST > จัดประเภท

8e21d795af36d07a.png e79a0367a03c2169.png

9. เชื่อมต่อแอป Flutter กับ TensorFlow Serving ผ่าน gRPC

นอกจาก REST แล้ว TensorFlow Serving ยังรองรับ gRPC ด้วย

b6f4449c2c850b0e.png

gRPC เป็นเฟรมเวิร์กการเรียกกระบวนการระยะไกล (RPC) ที่ทันสมัย โอเพนซอร์ส และมีประสิทธิภาพสูง ซึ่งสามารถทำงานได้ในทุกสภาพแวดล้อม โดยจะเชื่อมต่อบริการในและทั่วทั้งศูนย์ข้อมูลได้อย่างมีประสิทธิภาพ พร้อมรองรับการเสียบปลั๊กสำหรับ Load Balancing, การติดตาม, การตรวจสอบสถานะ และการตรวจสอบสิทธิ์ เราพบว่าในทางปฏิบัติ gRPC มีประสิทธิภาพมากกว่า REST

ส่งคำขอและรับการตอบกลับด้วย gRPC

ขั้นตอนง่ายๆ 4 ขั้นตอนในการส่งคำขอและรับการตอบกลับด้วย gRPC มีดังนี้

  1. ไม่บังคับ: สร้างโค้ดสตับไคลเอ็นต์ gRPC
  2. สร้างคำขอ gRPC
  3. ส่งคำขอ gRPC ไปยัง TensorFlow Serving
  4. ดึงผลลัพธ์ที่คาดการณ์จากการตอบกลับของ gRPC และแสดงผล UI

คุณทำตามขั้นตอนเหล่านี้ในไฟล์ main.dart

ไม่บังคับ: สร้างโค้ดสตับไคลเอ็นต์ gRPC

หากต้องการใช้ gRPC กับ TensorFlow Serving คุณต้องทำตามเวิร์กโฟลว์ gRPC ดูรายละเอียดเพิ่มเติมได้ในเอกสารประกอบของ gRPC

a9d0e5cb543467b4.png

TensorFlow Serving และ TensorFlow จะกำหนด.protoไฟล์ให้คุณ ตั้งแต่ TensorFlow และ TensorFlow Serving 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

google/protobuf/any.proto
google/protobuf/wrappers.proto
  • ในเทอร์มินัล ให้ไปที่โฟลเดอร์ starter/lib/proto/ แล้วสร้าง Stub ดังนี้
bash generate_grpc_stub_dart.sh

สร้างคำขอ gRPC

คุณสร้างคำขอ gRPC ในสาขา gRPC เช่นเดียวกับคำขอ REST

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

ตอนนี้โค้ดหลังการประมวลผลจะดึงผลการจัดประเภทจากคำตอบและแสดงใน UI

เรียกใช้

  1. คลิก a19a0c68bc4046e6.png Start debugging แล้วรอให้แอปโหลด
  2. ป้อนข้อความ แล้วเลือก gRPC > จัดประเภท

e44e6e9a5bde2188.png 92644d723f61968c.png

10. ขอแสดงความยินดี

คุณใช้ TensorFlow Serving เพื่อเพิ่มความสามารถในการแยกประเภทข้อความลงในแอป

ในโค้ดแล็บถัดไป คุณจะปรับปรุงโมเดลเพื่อให้ตรวจจับข้อความสแปมที่เฉพาะเจาะจงซึ่งแอปปัจจุบันตรวจจับไม่ได้

ดูข้อมูลเพิ่มเติม