Criar um app do Flutter para classificar textos

1. Antes de começar

Neste codelab, você aprenderá a executar uma inferência de classificação de texto usando um app do Flutter com o TensorFlow Serving usando REST e gRPC.

Pré-requisitos

O que você vai aprender

  • Como criar um app do Flutter simples e classificar textos usando o TensorFlow Serving (REST e gRPC).
  • Como exibir os resultados na IU.

Pré-requisitos

2. Começar a configuração

Para fazer o download do código para este codelab, faça o seguinte:

  1. Navegue até o repositório do GitHub deste codelab.
  2. Clique em Code > Download zip para fazer o download de todo o código para este codelab.

2cd45599f51fb8a2.png

  1. Descompacte o arquivo ZIP baixado para descompactar uma pasta raiz codelabs-master com todos os recursos necessários.

Neste codelab, você só precisará dos arquivos no subdiretório tfserving-flutter/codelab2 do repositório, que contém duas pastas:

  • A pasta starter contém o código inicial que você usará como base para este codelab.
  • A pasta finished contém o código concluído do app de exemplo finalizado.

3. Fazer o download das dependências do projeto

  1. No VS Code, clique em File > Open Folder e selecione a pasta starter no código-fonte que você salvou anteriormente.
  2. Se aparecer uma caixa de diálogo pedindo para você fazer o download dos pacotes necessários para o app inicial, clique em Get packages.
  3. Se a caixa de diálogo não aparecer, abra o terminal e execute o comando flutter pub get na pasta starter.

7ada07c300f166a6.png

4. Executar o app inicial

  1. No VS Code, verifique se o Android Emulator ou simulador de iOS está configurado corretamente e aparece na barra de status.

Por exemplo, veja o que você verá ao usar o Pixel 5 com o Android Emulator:

9767649231898791.png

Veja o que você verá ao usar o iPhone 13 com o iOS Simulator:

95529e3a682268b2.png

  1. Clique em a19a0c68bc4046e6.png Iniciar depuração.

Executar e explorar o app

O app será iniciado no Android Emulator ou no iOS Simulator. A IU é bem direta. Há um campo de texto que permite ao usuário digitar o texto. O usuário pode escolher se quer enviar os dados para o back-end com REST ou gRPC. O back-end usa um modelo do TensorFlow para realizar a classificação de texto na entrada pré-processada e retorna o resultado da classificação para o app cliente, que, por sua vez, atualiza a IU.

b298f605d64dc132.png d3ef3ccd3c338108.png

Se você clicar em Classificar, nada acontecerá porque ainda não há comunicação com o back-end.

5. Implantar um modelo de classificação de texto com o TensorFlow Serving

A classificação de texto é uma tarefa de machine learning muito comum que classifica textos em categorias predefinidas. Neste codelab, você vai implantar o modelo pré-treinado do codelab Treinar um modelo de detecção de spam de comentários com o TensorFlow Lite Model Maker com o TensorFlow Serving e chamar o back-end no front-end do Flutter para classificar o texto de entrada como spam ou não é spam.

Iniciar o TensorFlow Serving

  • No seu terminal, inicie o TensorFlow Serving com o Docker, mas substitua o marcador de posição PATH/TO/SAVEDMODEL pelo caminho absoluto da pasta mm_spam_savedmodel no seu computador.
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

Primeiro, o Docker faz o download automático da imagem do TensorFlow Serving em um minuto. Depois disso, o TensorFlow Serving será iniciado. O registro será semelhante a este snippet de código:

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. Tokenizar a frase de entrada

O back-end está pronto, mas, antes de enviar solicitações de cliente ao TensorFlow Serving, primeiro você precisa tokenizar a frase de entrada. Se você inspecionar o tensor de entrada do modelo, verá que ele espera uma lista de 20 números inteiros em vez de strings brutas. A tokenização ocorre quando você mapeia as palavras individuais digitadas no app para uma lista de números inteiros com base em um dicionário de vocabulário antes de enviá-las ao back-end para classificação. Por exemplo, se você digitar buy book online to learn more, o processo de tokenização o mapeará para [32, 79, 183, 10, 224, 631, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]. Os números específicos podem variar de acordo com o dicionário do vocabulário.

  1. No arquivo lib/main.dart, adicione esse código ao método predict() para criar o dicionário de vocabulário _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. Imediatamente após o snippet de código anterior, inclua este código para implementar a tokenização:
// 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;
  }
}

Esse código reduz a string de frase, remove caracteres não alfabéticos e mapeia as palavras para 20 índices inteiros com base na tabela do vocabulário.

7. Conectar o app do Flutter com o TensorFlow Serving pelo REST

Há duas maneiras de enviar solicitações ao TensorFlow Serving:

  • REST
  • gRPC

Enviar solicitações e receber respostas pelo REST

Há três etapas simples para enviar solicitações e receber respostas pelo REST:

  1. Crie a solicitação REST.
  2. Envie a solicitação REST para o TensorFlow Serving.
  3. Extraia o resultado previsto da resposta REST e renderize a IU.

Realize essas etapas no arquivo main.dart.

Criar e enviar a solicitação REST para o TensorFlow Serving

  1. No momento, a função predict() não envia a solicitação REST para o TensorFlow Serving. Você precisa implementar a ramificação REST para criar uma solicitação desse tipo:
if (_connectionMode == ConnectionModeType.rest) {
  // TODO: Create and send the REST request.

}
  1. Adicione este código à ramificação 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],
  }),
);

Processar a resposta REST do TensorFlow Serving

  • Adicione este código logo após o snippet de código anterior para processar a resposta 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');
}

O código de pós-processamento extrai a probabilidade de a frase de entrada ser uma mensagem de spam na resposta e exibe o resultado da classificação na IU.

Executar

  1. Clique em a19a0c68bc4046e6.png Start debugging e aguarde o app carregar.
  2. Digite um texto e selecione REST > Classify.

8e21d795af36d07a.png e79a0367a03c2169.png

8. Conectar o app Flutter com o TensorFlow Serving pelo gRPC

Além do REST, o TensorFlow Serving também é compatível com o gRPC.

b6f4449c2c850b0e.png

O gRPC é um framework de chamada de procedimento remoto (RPC) moderno, de código aberto e de alta performance que pode ser executado em qualquer ambiente. Ele pode conectar serviços de forma eficiente em data centers e com compatibilidade conectável para balanceamento de carga, rastreamento, verificação de integridade e autenticação. Observamos que o gRPC tem uma performance melhor do que o REST na prática.

Enviar solicitações e receber respostas com o gRPC

Há quatro etapas simples para enviar solicitações e receber respostas com o gRPC:

  1. Opcional: gerar o código stub do cliente gRPC
  2. Criar a solicitação gRPC
  3. Enviar a solicitação gRPC para o TensorFlow Serving
  4. Extrair o resultado previsto da resposta gRPC e renderizar a IU

Realize essas etapas no arquivo main.dart.

Opcional: gerar o código stub do cliente gRPC

Para usar o gRPC com o TensorFlow Serving, é necessário seguir o fluxo de trabalho do gRPC. Caso queira saber mais, consulte a documentação do gRPC.

a9d0e5cb543467b4.png

O TensorFlow Serving e o TensorFlow definem os arquivos .proto automaticamente. No TensorFlow e no TensorFlow Serving 2.8, os seguintes arquivos .proto são necessários:

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
  • No terminal, navegue até a pasta starter/lib/proto/ e gere o stub:
bash generate_grpc_stub_dart.sh

Criar a solicitação gRPC

Semelhante à solicitação REST, você cria a solicitação gRPC na ramificação gRPC.

if (_connectionMode == ConnectionModeType.rest) {

} else {
  // TODO: Create and send the gRPC request.

}
  • Adicione este código para criar a solicitação 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});

Observação: os nomes dos tensores de entrada e saída podem ser diferentes para cada modelo, mesmo que as arquiteturas sejam iguais. Atualize-os ao treinar seu próprio modelo.

Enviar a solicitação gRPC para o TensorFlow Serving

  • Adicione este código após o snippet de código anterior para enviar a solicitação gRPC ao TensorFlow Serving:
// Send the gRPC request.
PredictResponse response = await _stub.predict(request);

Processar a resposta gRPC do TensorFlow Serving

  • Adicione este código após o snippet de código anterior para implementar as funções de callback e gerenciar a resposta:
// 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');
}

Agora, o código de pós-processamento extrai o resultado da classificação da resposta e o exibe na IU.

Executar

  1. Clique em a19a0c68bc4046e6.png Start debugging e aguarde o app carregar.
  2. Digite um texto e selecione gRPC > Classify.

e44e6e9a5bde2188.png 92644d723f61968c.png

9. Parabéns

Você usou o TensorFlow Serving para adicionar recursos de classificação de texto ao seu app.

No próximo codelab, você vai aprimorar o modelo para detectar mensagens de spam específicas que não podem ser detectadas pelo app atual.

Saiba mais