Crea una app de Flutter para clasificar textos

1. Antes de comenzar

En este codelab, aprenderás a ejecutar una inferencia de clasificación de texto desde una app de Flutter con TensorFlow Serving a través de REST y gRPC.

Requisitos previos

Qué aprenderás

  • Cómo crear una app sencilla de Flutter y clasificar textos a través de TensorFlow Serving (REST y gRPC)
  • Cómo mostrar los resultados en la IU

Requisitos

2. Prepárate

Para descargar el código de este codelab, haz lo siguiente:

  1. Navega al repositorio de GitHub de este codelab.
  2. Haz clic en Code > Download ZIP para descargar todo el código de este codelab.

2cd45599f51fb8a2.png

  1. Descomprime el archivo ZIP descargado para descomprimir una carpeta raíz codelabs-master con todos los recursos que necesitas.

Para este codelab, solo necesitas los archivos del subdirectorio tfserving-flutter/codelab2 en el repositorio, que contiene dos carpetas:

  • La carpeta starter contiene el código de inicio en el que se basa este codelab.
  • La carpeta finished contiene el código completado de la app de ejemplo finalizada.

3. Descarga las dependencias del proyecto

  1. En VS Code, haz clic en Archivo > Abrir carpeta y, luego, selecciona la carpeta starter del código fuente que descargaste antes.
  2. Si aparece un diálogo en el que se te pide descargar los paquetes necesarios para la app de inicio, haz clic en Obtener paquetes (Get packages).
  3. Si no ves este diálogo, abre la terminal y, luego, ejecuta el comando flutter pub get en la carpeta starter.

7ada07c300f166a6.png

4. Ejecuta la app de inicio

  1. En VS Code, asegúrate de que Android Emulator o el simulador de iOS estén configurados correctamente y aparezcan en la barra de estado.

Por ejemplo, a continuación, se muestra lo que ves cuando usas un Pixel 5 con Android Emulator:

9767649231898791.png

A continuación, se muestra lo que ves cuando usas un iPhone 13 con el simulador de iOS:

95529e3a682268b2.png

  1. Haz clic en a19a0c68bc4046e6.png Iniciar depuración.

Ejecuta y explora la app

La app debe iniciarse en Android Emulator o el simulador de iOS. La IU es bastante sencilla. Hay un campo de texto que le permite al usuario escribir el texto. El usuario puede elegir si desea enviar los datos al backend con REST o gRPC. El backend usa un modelo de TensorFlow para realizar la clasificación de texto en la entrada procesada previamente y muestra el resultado de la clasificación a la app cliente, que, a su vez, actualiza la IU.

b298f605d64dc132.png d3ef3ccd3c338108.png

Si haces clic en Classify, no sucede nada porque todavía no puede comunicarse con el backend.

5. Implementa un modelo de clasificación de texto con TensorFlow Serving

La clasificación de texto es una tarea de aprendizaje automático muy común, que clasifica textos en categorías predefinidas. En este codelab, implementarás el modelo previamente entrenado del codelab Entrena un modelo de detección de comentarios spam con Model Maker de TensorFlow Lite con TensorFlow Serving y llama al backend desde el frontend de Flutter para clasificar el texto de entrada en formato spam o no spam.

Inicia TensorFlow Serving

  • En tu terminal, inicia TensorFlow Serving con Docker, pero reemplaza el marcador de posición PATH/TO/SAVEDMODEL por la ruta de acceso absoluta de la carpeta mm_spam_savedmodel en tu computadora.
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 descarga automáticamente la imagen de TensorFlow Serving primero, lo cual tarda un minuto. Luego, TensorFlow Serving debería iniciarse. El registro debería verse como este fragmento 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. Asigna un token a la oración de entrada

Ahora el backend está preparado, así que estás casi listo para enviar solicitudes de clientes a TensorFlow Serving, pero primero debes asignar un token a la oración de entrada. Si inspeccionas el tensor de entrada del modelo, verás que espera una lista de 20 números enteros, en lugar de strings sin procesar. La asignación de token se produce cuando asignas las palabras individuales que escribes en la app a una lista de números enteros basada en un diccionario de vocabulario antes de enviarlas al backend para su clasificación. Por ejemplo, si escribes buy book online to learn more, el proceso de asignación de token lo asigna a [32, 79, 183, 10, 224, 631, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]. Las cantidades específicas pueden variar en función del diccionario de vocabulario.

  1. En el archivo lib/main.dart, agrega este código al método predict() a fin de compilar el diccionario de vocabulario _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. Inmediatamente después del fragmento de código anterior, agrega este código para implementar la asignación de token:
// 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;
  }
}

Este código coloca la string de la oración en minúsculas, quita los caracteres que no son alfabéticos y asigna las palabras a 20 índices enteros según la tabla de vocabulario.

7. Conecta la app de Flutter con TensorFlow Serving mediante REST

Existen dos métodos de enviar solicitudes a TensorFlow Serving:

  • REST
  • gRPC

Envía solicitudes y recibe respuestas a través de REST

Existen tres pasos sencillos para enviar solicitudes y recibir respuestas a través de REST:

  1. Crea la solicitud REST.
  2. Envía la solicitud REST a TensorFlow Serving.
  3. Extrae el resultado previsto de la respuesta de REST y renderiza la IU.

Completa estos pasos en el archivo main.dart.

Crea y envía la solicitud de REST a TensorFlow Serving

  1. En este momento, la función predict() no envía la solicitud de REST a TensorFlow Serving. Debes implementar la rama de REST para crear una solicitud de REST:
if (_connectionMode == ConnectionModeType.rest) {
  // TODO: Create and send the REST request.

}
  1. Agrega este código a la rama de 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],
  }),
);

Procesa la respuesta de REST de TensorFlow Serving

  • Agrega este código inmediatamente después del fragmento de código anterior para controlar la respuesta de 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');
}

El código de procesamiento posterior extrae la probabilidad de que la oración de entrada sea un mensaje de spam de la respuesta y muestra el resultado de la clasificación en la IU.

Ejecuta la app

  1. Haz clic en a19a0c68bc4046e6.png Iniciar depuración y espera a que se cargue la app.
  2. Ingresa texto y, luego, selecciona REST > Classify.

8e21d795af36d07a.png e79a0367a03c2169.png

8. Conecta la app de Flutter con TensorFlow Serving mediante gRPC

Además de REST, TensorFlow Serving también admite gRPC.

b6f4449c2c850b0e.png

gRPC es un framework de llamada de procedimiento remoto (RPC) moderno de código abierto y alto rendimiento, que se puede ejecutar en cualquier entorno. Puede conectar de forma eficaz servicios en centros de datos (y entre ellos) y ofrece compatibilidad conectable con balanceo de cargas, seguimiento, verificación de estado y autenticación. Se observó que gRPC tiene un mejor rendimiento que REST en la práctica.

Envía solicitudes y recibe respuestas con gRPC

Existen cuatro pasos sencillos para enviar solicitudes y recibir respuestas con gRPC:

  1. Genera el código de stub del cliente de gRPC (opcional).
  2. Crea la solicitud de gRPC.
  3. Envía la solicitud de gRPC a TensorFlow Serving.
  4. Extrae el resultado previsto de la respuesta de gRPC y renderiza la IU.

Completa estos pasos en el archivo main.dart.

Genera el código de stub del cliente de gRPC (opcional)

Para usar gRPC con TensorFlow Serving, debes seguir el flujo de trabajo de gRPC. Si quieres obtener más información, consulta la documentación de gRPC.

a9d0e5cb543467b4.png

TensorFlow Serving y TensorFlow definen los archivos .proto por ti. A partir de TensorFlow y TensorFlow Serving 2.8, se necesitan estos archivos .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
  • En tu terminal, navega a la carpeta starter/lib/proto/ y genera el stub:
bash generate_grpc_stub_dart.sh

Crea la solicitud de gRPC

Al igual que con la solicitud de REST, debes crear la solicitud de gRPC en la rama de gRPC.

if (_connectionMode == ConnectionModeType.rest) {

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

}
  • Agrega este código para crear la solicitud de 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});

Nota: Los nombres de los tensores de entrada y salida podrían diferir de un modelo a otro, incluso si las arquitecturas del modelo son iguales. Asegúrate de actualizarlas si entrenas tu propio modelo.

Envía la solicitud de gRPC a TensorFlow Serving

  • Agrega este código después del fragmento de código anterior para enviar la solicitud de gRPC a TensorFlow Serving:
// Send the gRPC request.
PredictResponse response = await _stub.predict(request);

Procesa la respuesta de gRPC de TensorFlow Serving

  • Agrega este código después del fragmento de código anterior para implementar las funciones de devolución de llamada a fin de controlar la respuesta:
// 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');
}

Ahora, el código de procesamiento posterior extrae el resultado de la clasificación de la respuesta y lo muestra en la IU.

Ejecuta la app

  1. Haz clic en a19a0c68bc4046e6.png Iniciar depuración y espera a que se cargue la app.
  2. Ingresa texto y, luego, selecciona gRPC > Classify.

e44e6e9a5bde2188.png 92644d723f61968c.png

9. Felicitaciones

Usaste TensorFlow Serving para agregar capacidades de clasificación de texto a tu app.

En el siguiente codelab, mejorarás el modelo, de manera que puedas detectar mensajes de spam específicos que la app actual no puede detectar.

Más información