Creare un'app Flutter per classificare i testi

1. Prima di iniziare

In questo codelab, imparerai a eseguire un'inferenza di classificazione del testo da un'app Flutter con TensorFlow Serving tramite REST e gRPC.

Prerequisiti

Obiettivi didattici

  • Come creare un'app Flutter semplice e classificare i testi tramite TensorFlow Serving (REST e gRPC).
  • Come visualizzare i risultati nell'interfaccia utente.

Che cosa ti serve

2. Configurare l'ambiente di sviluppo Flutter

Per lo sviluppo di Flutter sono necessari due software per completare questo lab: l'SDK Flutter e un editor.

Puoi eseguire il codelab utilizzando uno dei seguenti dispositivi:

  • Il simulatore iOS (richiede l'installazione di strumenti Xcode).
  • L'emulatore Android (richiede la configurazione in Android Studio).
  • Un browser (per il debug è necessario Chrome).
  • Come applicazione desktop Windows, Linux o macOS. Devi sviluppare sulla piattaforma in cui intendi eseguire il deployment. Quindi, se vuoi sviluppare un'app desktop Windows, devi sviluppare su Windows per accedere alla catena di build appropriata. Esistono requisiti specifici di sistema operativo, illustrati in dettaglio su docs.flutter.dev/desktop.

3. Configura

Per scaricare il codice per questo codelab:

  1. Accedi al repository GitHub per questo codelab.
  2. Fai clic su Codice > Scarica zip per scaricare tutto il codice di questo codelab.

2cd45599f51fb8a2.png

  1. Decomprimi il file ZIP scaricato per decomprimere una cartella principale codelabs-main con tutte le risorse necessarie.

Per questo codelab, hai bisogno solo dei file nella sottodirectory tfserving-flutter/codelab2 del repository, che contiene due cartelle:

  • La cartella starter contiene il codice di avvio che crei per questo codelab.
  • La cartella finished contiene il codice completato per l'app di esempio terminata.

4. Scarica le dipendenze per il progetto

  1. In VS Code, fai clic su File > Apri cartella, quindi seleziona la cartella starter dal codice sorgente che hai scaricato in precedenza.
  2. Se viene visualizzata una finestra di dialogo che ti chiede di scaricare i pacchetti obbligatori per l'app iniziale, fai clic su Scarica i pacchetti.
  3. Se non vedi questa finestra di dialogo, apri il terminale ed esegui il comando flutter pub get nella cartella starter.

7ada07c300f166a6.png

5. Esegui l'app iniziale

  1. In VS Code, assicurati che l'emulatore Android o il simulatore di iOS siano configurati correttamente e vengano visualizzati nella barra di stato.

Ad esempio, ecco cosa vedi quando usi Pixel 5 con l'emulatore Android:

9767649231898791.png

Ecco cosa vedi quando usi un iPhone 13 con il simulatore di iOS:

95529e3a682268b2.png

  1. Fai clic su a19a0c68bc4046e6.png Avvia debug.

Esegui ed esplora l'app

L'app deve essere avviata sull'emulatore Android o sul simulatore di iOS. La UI è molto semplice. È presente un campo di testo che consente all'utente di digitare il testo. L'utente può scegliere se inviare i dati al backend con REST o gRPC. Il backend utilizza un modello TensorFlow per eseguire la classificazione del testo sull'input pre-elaborato e restituisce il risultato della classificazione all'app client, che aggiorna a sua volta la UI.

b298f605d64dc132.png d3ef3ccd3c338108.png

Se fai clic su Classificazione, non accade nulla perché non può ancora comunicare con il backend.

6. Esegui il deployment di un modello di classificazione del testo con TensorFlow Serving

La classificazione del testo è un'attività di machine learning molto comune che classifica i testi in categorie predefinite. In questo codelab, esegui il deployment del modello preaddestrato dal addestramento di un modello di rilevamento di commenti spam con TensorFlow Lite Model Maker con TensorFlow Serving e chiami il backend dal tuo frontend Flutter per classificare il testo di input come spam o non spam.

Avvia la pubblicazione TensorFlow

  • Nel terminale, avvia TensorFlow Serving con Docker, ma sostituisci il segnaposto PATH/TO/SAVEDMODEL con il percorso assoluto della cartella mm_spam_savedmodel sul tuo computer.
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 scarica automaticamente prima l'immagine di TensorFlow Serving, che richiede un minuto. In seguito, TensorFlow Serving dovrebbe iniziare. Il log dovrebbe avere il seguente aspetto:

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. Tokenizza input di token

Il backend è pronto, quindi puoi quasi inviare richieste client a TensorFlow Serving, ma devi prima tokenizzare la frase di input. Se esamini il tensore di input del modello, puoi notare che è previsto un elenco di 20 numeri interi anziché stringhe non elaborate. La tokenizzazione consiste nel mappare le singole parole che digiti nell'app a un elenco di numeri interi basati su un dizionario di vocabolario prima di inviarli al backend per la classificazione. Ad esempio, se digiti buy book online to learn more, il processo di tokenizzazione lo mappa a [32, 79, 183, 10, 224, 631, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]. I numeri specifici possono variare in base al dizionario del vocabolario.

  1. Nel file lib/main.dart, aggiungi questo codice al metodo predict() per creare il dizionario del vocabolario _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. Subito dopo lo snippet di codice precedente, aggiungi questo codice per implementare la tokenizzazione:
// 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;
  }
}

Questo codice contiene tutte le lettere minuscole e i caratteri non alfabetici. Inoltre, mappa le parole a 20 indici interi in base alla tabella del vocabolario.

8. Collega l'app Flutter a TensorFlow Serving tramite REST

Esistono due modi per inviare richieste a TensorFlow Serving:

  • REST
  • gRPC

Inviare richieste e ricevere risposte tramite REST

Esistono tre semplici passaggi per inviare richieste e ricevere risposte tramite REST:

  1. Crea la richiesta REST.
  2. Invia la richiesta REST a TensorFlow Serving.
  3. Estrai il risultato previsto dalla risposta REST e visualizza l'interfaccia utente.

Completa questi passaggi nel file main.dart.

Crea e invia la richiesta REST a TensorFlow Serving

  1. Attualmente, la funzione predict() non invia la richiesta REST a TensorFlow Serving. Devi implementare il ramo REST per creare una richiesta REST:
if (_connectionMode == ConnectionModeType.rest) {
  // TODO: Create and send the REST request.

}
  1. Aggiungi questo codice al ramo 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],
  }),
);

Elabora la risposta REST da TensorFlow Serving

  • Aggiungi questo codice subito dopo lo snippet di codice precedente per gestire la risposta 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');
}

Il codice post-elaborazione estrae la probabilità che la frase di input sia un messaggio di spam dalla risposta e mostra il risultato della classificazione nell'interfaccia utente.

Esegui

  1. Fai clic su a19a0c68bc4046e6.png Avvia debug e attendi il caricamento dell'app.
  2. Inserisci del testo, quindi seleziona REST > Classify.

8e21d795af36d07a.png e79a0367a03c2169.png

9. Connettere l'app Flutter a TensorFlow Serving tramite gRPC

Oltre a REST, TensorFlow Serving supporta anche gRPC.

b6f4449c2c850b0e.png

gRPC è un framework RPC (Open Procedure Call) moderno e open source eseguibile in qualsiasi ambiente. Può connettere i servizi in modo efficiente all'interno dei diversi data center e fornire un supporto collegabile per bilanciamento del carico, tracciamento, controllo di integrità e autenticazione. È stato osservato che gRPC ha un rendimento migliore rispetto a REST nella pratica.

Inviare richieste e ricevere risposte con gRPC

L'invio di richieste e la ricezione di risposte con gRPC prevede quattro semplici passaggi:

  1. (Facoltativo) Genera il codice stub client gRPC.
  2. Crea la richiesta gRPC.
  3. Invia la richiesta gRPC a TensorFlow Serving.
  4. Estrai il risultato previsto dalla risposta gRPC e visualizza l'interfaccia utente.

Completa questi passaggi nel file main.dart.

Facoltativo: genera il codice stub client gRPC

Per utilizzare gRPC con TensorFlow Serving, devi seguire il flusso di lavoro gRPC. Per saperne di più, consulta la documentazione di gRPC.

a9d0e5cb543467b4.png

TensorFlow Serving e TensorFlow definiscono i file .proto per tuo conto. In data TensorFlow e TensorFlow Serving 2.8, questi file .proto sono quelli necessari:

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
  • Nel terminale, passa alla cartella starter/lib/proto/ e genera lo stub:
bash generate_grpc_stub_dart.sh

Crea la richiesta gRPC

Come per la richiesta REST, puoi creare la richiesta gRPC nel ramo gRPC.

if (_connectionMode == ConnectionModeType.rest) {

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

}
  • Aggiungi questo codice per creare la richiesta 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: i nomi dei tensori di input e di output potrebbero variare in base al modello, anche se le architetture dei modelli sono le stesse. Assicurati di aggiornarli se addestri il tuo modello.

Invia la richiesta gRPC a TensorFlow Serving

  • Aggiungi questo codice dopo lo snippet di codice precedente per inviare la richiesta gRPC a TensorFlow Serving:
// Send the gRPC request.
PredictResponse response = await _stub.predict(request);

Elabora la risposta gRPC da TensorFlow Serving

  • Aggiungi questo codice dopo lo snippet di codice precedente per implementare le funzioni di callback per gestire la risposta:
// 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');
}

A questo punto, il codice di post-elaborazione estrae il risultato della classificazione dalla risposta e lo visualizza nell'interfaccia utente.

Esegui

  1. Fai clic su a19a0c68bc4046e6.png Avvia debug e attendi il caricamento dell'app.
  2. Inserisci del testo, quindi seleziona gRPC > Classify.

e44e6e9a5bde2188.png 92644d723f61968c.png

10. Complimenti

Hai utilizzato TensorFlow Serving per aggiungere funzionalità di classificazione del testo alla tua app.

Nel codelab successivo, migliorerai il modello in modo da poter rilevare messaggi di spam specifici che non possono essere rilevati dall'app corrente.

Scopri di più