텍스트를 분류하는 Flutter 앱 만들기

1. 시작하기 전에

이 Codelab에서는 REST 및 gRPC를 통해 TensorFlow Serving으로 Flutter 앱에서 텍스트 분류 추론을 실행하는 방법을 알아봅니다.

기본 요건

학습할 내용

  • 간단한 Flutter 앱을 빌드하고 TensorFlow Serving(REST 및 gRPC)을 통해 텍스트를 분류하는 방법
  • UI에 결과를 표시하는 방법

필요한 항목

2. 설정

이 Codelab의 코드를 다운로드하려면 다음 안내를 따르세요.

  1. 이 Codelab의 GitHub 저장소로 이동합니다.
  2. Code > Download zip을 클릭하여 이 Codelab의 모든 코드를 다운로드합니다.

2cd45599f51fb8a2.png

  1. 다운로드한 ZIP 파일에서 필요한 리소스가 모두 포함된 codelabs-master 루트 폴더의 압축을 해제합니다.

이 Codelab에서는 저장소의 tfserving-flutter/codelab2 하위 디렉터리에 있는 파일만 필요하며, 여기에는 다음 두 폴더가 포함됩니다.

  • starter 폴더에는 이 Codelab에서 빌드를 시작하는 시작 코드가 포함되어 있습니다.
  • finished 폴더에는 완료된 샘플 앱의 완성된 코드가 포함되어 있습니다.

3. 프로젝트의 종속 항목 다운로드

  1. VS Code에서 File > Open folder를 클릭한 다음 이전에 다운로드한 소스 코드에서 starter 폴더를 선택합니다.
  2. 시작 앱에 필요한 패키지를 다운로드하라는 대화상자가 표시되면 Get packages를 클릭합니다.
  3. 이 대화상자가 표시되지 않으면 터미널을 열고 starter 폴더에서 flutter pub get 명령어를 실행합니다.

7ada07c300f166a6.png

4. 시작 앱 실행

  1. VS Code에서 Android Emulator 또는 iOS 시뮬레이터가 올바르게 설정되어 있고 상태 표시줄에 표시되는지 확인합니다.

예를 들어 Android Emulator로 Pixel 5를 사용하는 경우 다음과 같이 표시됩니다.

9767649231898791.png

iOS 시뮬레이터로 iPhone 13을 사용하는 경우 다음과 같이 표시됩니다.

95529e3a682268b2.png

  1. a19a0c68bc4046e6.png Start debugging을 클릭합니다.

앱 실행 및 탐색

Android Emulator 또는 iOS 시뮬레이터에서 앱이 실행됩니다. UI는 매우 단순합니다. 사용자가 텍스트를 입력할 수 있는 텍스트 필드가 있습니다. 사용자는 백엔드로 데이터를 전송할 때 REST를 사용할지 아니면 gRPC를 사용할지를 선택할 수 있습니다. 백엔드는 TensorFlow 모델을 사용하여 사전 처리된 입력에 대해 텍스트 분류를 실행하고 분류 결과를 클라이언트 앱에 반환하며, 클라이언트 앱은 그에 따라 UI를 업데이트합니다.

b298f605d64dc132.png d3ef3ccd3c338108.png

Classify를 클릭해도 아직 백엔드와 통신할 수 없으므로 아무 동작도 수행되지 않습니다.

5. TensorFlow Serving으로 텍스트 분류 모델 배포

텍스트 분류는 텍스트를 사전 정의된 카테고리로 분류하는 매우 일반적인 머신러닝 작업입니다. 이 Codelab에서는TensorFlow Lite Model Maker로 댓글 스팸 감지 모델 학습 Codelab에서 사전 학습한 모델을 TensorFlow Serving으로 배포하고, Flutter 프런트엔드에서 백엔드를 호출하여 입력 텍스트를 스팸 또는 스팸 아님으로 분류합니다.

TensorFlow Serving 시작

  • 터미널에서 Docker로 TensorFlow Serving을 시작하면서 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 ...

6. 입력 문장 토큰화

이제 백엔드가 준비되었으므로 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개의 정수 색인에 매핑합니다.

7. REST를 통해 Flutter 앱과 TensorFlow Serving 연결

TensorFlow Serving에 요청을 보내는 방법에는 두 가지가 있습니다.

  • REST
  • gRPC

REST를 통해 요청 보내기 및 응답 수신

간단한 세 단계로 REST를 통해 요청을 보내고 응답을 받을 수 있습니다.

  1. REST 요청을 만듭니다.
  2. TensorFlow Serving에 REST 요청을 보냅니다.
  3. REST 응답에서 예측 결과를 추출하고 UI를 렌더링합니다.

main.dart 파일에서 이러한 단계를 완료합니다.

REST 요청을 만들어 TensorFlow Serving으로 보내기

  1. 현재 predict() 함수는 TensorFlow Serving에 REST 요청을 전송하지 않습니다. 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],
  }),
);

TensorFlow Serving의 REST 응답 처리

  • 이전 코드 스니펫 바로 뒤에 다음 코드를 추가하여 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 > Classify를 선택합니다.

8e21d795af36d07a.png e79a0367a03c2169.png

8. gRPC를 통해 Flutter 앱과 TensorFlow Serving 연결

TensorFlow Serving은 REST 외에 gRPC도 지원합니다.

b6f4449c2c850b0e.png

gRPC는 모든 환경에서 실행할 수 있는 최신형 오픈소스 고성능 리모트 프로시져 콜(RPC) 프레임워크입니다. 부하 분산, 추적, 상태 확인 및 인증을 위한 플러그형 지원을 통해 데이터 센터 내부 및 데이터 센터 간에 서비스를 효율적으로 연결할 수 있습니다. gRPC는 실무에서 REST보다 성능이 우수한 것으로 관찰된 바 있습니다.

gRPC로 요청 보내기 및 응답 수신

간단한 네 단계로 gRPC를 통해 요청을 보내고 응답을 받을 수 있습니다.

  1. 선택사항: gRPC 클라이언트 스텁 코드를 생성합니다.
  2. gRPC 요청을 만듭니다.
  3. TensorFlow Serving에 gRPC 요청을 보냅니다.
  4. gRPC 응답에서 예측 결과를 추출하고 UI를 렌더링합니다.

main.dart 파일에서 이러한 단계를 완료합니다.

선택사항: gRPC 클라이언트 스텁 코드 생성

TensorFlow Serving과 함께 gRPC를 사용하려면 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/ 폴더로 이동하여 스텁을 생성합니다.
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});

참고: 모델 아키텍처가 같더라도 입력 및 출력 텐서 이름은 모델마다 다를 수 있습니다. 자체 모델을 학습시키는 경우 텐서 이름을 업데이트해야 합니다.

TensorFlow Serving에 gRPC 요청 보내기

  • 이전 코드 스니펫 뒤에 다음 코드를 추가하여 TensorFlow Serving에 gRPC 요청을 보냅니다.
// Send the gRPC request.
PredictResponse response = await _stub.predict(request);

TensorFlow Serving의 gRPC 응답 처리

  • 이전 코드 스니펫 뒤에 다음 코드를 추가하여 응답을 처리하는 콜백 함수를 구현합니다.
// 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 > Classify를 선택합니다.

e44e6e9a5bde2188.png 92644d723f61968c.png

9. 수고하셨습니다.

TensorFlow Serving을 사용하여 앱에 텍스트 분류 기능을 추가했습니다.

다음 Codelab에서는 현재 앱에서 감지할 수 없는 특정 스팸 메시지를 감지할 수 있도록 모델을 개선합니다.

자세히 알아보기