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. Configurar o ambiente de desenvolvimento do Flutter

Para o desenvolvimento em Flutter, você precisa de dois softwares para concluir este laboratório: o SDK do Flutter e um editor.

É possível executar o codelab usando qualquer um destes dispositivos:

  • O simulador para iOS, que exige a instalação de ferramentas do Xcode.
  • O Android Emulator, que requer configuração no Android Studio.
  • Um navegador (o Chrome é necessário para depuração).
  • Como um aplicativo para computador Windows, Linux ou macOS. Você precisa desenvolver na plataforma em que planeja implantar. Portanto, se quiser desenvolver um app para um computador Windows, você terá que desenvolver no Windows para acessar a cadeia de builds adequada. Há requisitos específicos de cada sistema operacional que são abordados em detalhes em docs.flutter.dev/desktop.

3. 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-main 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.

4. 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

5. 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.

6. 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 ...

7. 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.

8. 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

9. Conectar o app do 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

10. 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