Come distribuire i modelli TensorFlow alla produzione utilizzando TF Serving

introduzione

Mettere in produzione i modelli di Machine Learning (ML) è diventato un argomento popolare e ricorrente. Molte aziende e framework offrono diverse soluzioni che mirano ad affrontare questo problema.

Per risolvere questo problema, Google ha rilasciato TensorFlow (TF) Serving nella speranza di risolvere il problema della distribuzione dei modelli ML nella produzione.

Questo pezzo offre un tutorial pratico su come servire una rete di segmentazione semantica convoluzionale pre-addestrata. Entro la fine di questo articolo, sarai in grado di utilizzare TF Serving per distribuire e inviare richieste a una Deep CNN addestrata in TF. Inoltre, presenterò una panoramica dei principali blocchi di TF Serving, e parlerò delle sue API e di come funziona.

Una cosa che noterai subito è che richiede pochissimo codice per servire effettivamente un modello TF. Se vuoi seguire il tutorial ed eseguire l'esempio sulla tua macchina, seguilo così com'è. Tuttavia, se desideri conoscere solo TensorFlow Serving, puoi concentrarti sulle prime due sezioni.

Questo articolo sottolinea parte del lavoro che stiamo facendo qui al Daitan Group.

Librerie di servizio di TensorFlow: panoramica

Prendiamoci un po 'di tempo per capire come TF Serving gestisce l'intero ciclo di vita dei modelli di ML di servizio. Qui, esamineremo (ad alto livello) ciascuno dei principali elementi costitutivi di TF Serving. L'obiettivo di questa sezione è fornire un'introduzione semplificata alle API di servizio TF. Per una panoramica approfondita, vai alla pagina della documentazione di TF Serving.

TensorFlow Serving è composto da alcune astrazioni. Queste astrazioni implementano le API per attività diverse. I più importanti sono Servable, Loader, Source e Manager. Vediamo come interagiscono.

In poche parole, il ciclo di vita del servizio inizia quando TF Serving identifica un modello su disco. Il componente Source si occupa di questo. È responsabile dell'identificazione dei nuovi modelli che dovrebbero essere caricati. In pratica, tiene d'occhio il file system per identificare quando una nuova versione del modello arriva sul disco. Quando vede una nuova versione, procede creando un caricatore per quella specifica versione del modello.

In sintesi, il Loader sa quasi tutto del modello. Include come caricarlo e come stimare le risorse richieste dal modello, come la RAM richiesta e la memoria GPU. Il Loader ha un puntatore al modello su disco insieme a tutti i metadati necessari per caricarlo. Ma c'è un problema qui: il caricatore non è ancora autorizzato a caricare il modello.

Dopo aver creato il caricatore, l'origine lo invia al gestore come una versione desiderata.

Dopo aver ricevuto la versione desiderata del modello, il gestore procede con il processo di pubblicazione. Qui ci sono due possibilità. Uno è che la prima versione del modello viene spinta per la distribuzione. In questa situazione, il Manager si assicurerà che le risorse richieste siano disponibili. Una volta che lo sono, il Manager concede al caricatore l'autorizzazione a caricare il modello.

Il secondo è che stiamo spingendo una nuova versione di un modello esistente. In questo caso, il Manager deve consultare il plugin Version Policy prima di andare oltre. Il criterio della versione determina come avviene il processo di caricamento di una nuova versione del modello.

In particolare, quando si carica una nuova versione di un modello, possiamo scegliere tra preservare (1) disponibilità o (2) risorse. Nel primo caso, ci interessa fare in modo che il nostro sistema sia sempre disponibile per le richieste dei clienti in arrivo. Sappiamo che il Manager consente al Loader di istanziare il nuovo grafico con i nuovi pesi.

A questo punto, abbiamo due versioni del modello caricate contemporaneamente. Ma il Manager scarica la versione precedente solo dopo che il caricamento è completo ed è sicuro passare da un modello all'altro.

D'altra parte, se vogliamo risparmiare risorse non avendo il buffer aggiuntivo (per la nuova versione), possiamo scegliere di preservare le risorse. Potrebbe essere utile per i modelli molto pesanti avere un piccolo divario nella disponibilità, in cambio del risparmio di memoria.

Alla fine, quando un client richiede un handle per il modello, il Manager restituisce un handle al Servable.

Con questa panoramica, siamo pronti per immergerci in un'applicazione del mondo reale. Nelle sezioni successive, descriviamo come servire una rete neurale convoluzionale (CNN) utilizzando TF Serving.

Esportazione di un modello per la pubblicazione

Il primo passaggio per servire un modello ML costruito in TensorFlow è assicurarsi che sia nel formato corretto. A tale scopo, TensorFlow fornisce la classe SavedModel.

SavedModel è il formato di serializzazione universale per i modelli TensorFlow. Se hai familiarità con TF, probabilmente hai utilizzato TensorFlow Saver per rendere persistenti le variabili del tuo modello.

TensorFlow Saver fornisce funzionalità per salvare / ripristinare i file del punto di arresto del modello su / dal disco. In effetti, SavedModel avvolge TensorFlow Saver ed è pensato per essere il modo standard di esportare modelli TF per la pubblicazione.

L'oggetto SavedModel ha alcune caratteristiche interessanti.

Innanzitutto, ti consente di salvare più di un meta-grafico in un singolo oggetto SavedModel. In altre parole, ci consente di avere grafici diversi per compiti diversi.

Ad esempio, supponi di aver appena finito di addestrare il tuo modello. Nella maggior parte delle situazioni, per eseguire l'inferenza, il grafico non necessita di alcune operazioni specifiche per l'addestramento. Queste operazioni potrebbero includere le variabili dell'ottimizzatore, tensori di pianificazione della frequenza di apprendimento, operazioni di pre-elaborazione extra e così via.

Inoltre, potresti voler servire una versione quantizzata di un grafico per la distribuzione mobile.

In questo contesto, SavedModel consente di salvare grafici con diverse configurazioni. Nel nostro esempio, avremmo tre diversi grafici con tag corrispondenti come "training", "inferenza" e "mobile". Inoltre, questi tre grafici condividono tutti lo stesso insieme di variabili, il che enfatizza l'efficienza della memoria.

Non molto tempo fa, quando volevamo distribuire modelli TF su dispositivi mobili, avevamo bisogno di conoscere i nomi dei tensori di input e output per l'alimentazione e il recupero dei dati al / dal modello. Questa necessità ha costretto i programmatori a cercare il tensore di cui avevano bisogno tra tutti i tensori del grafo. Se i tensori non fossero stati denominati correttamente, l'attività potrebbe essere molto noiosa.

Per rendere le cose più facili, SavedModel offre supporto per SignatureDefs. In sintesi, SignatureDefs definisce la firma di un calcolo supportato da TensorFlow. Determina i tensori di input e output appropriati per un grafo computazionale. In poche parole, con queste firme è possibile specificare i nodi esatti da utilizzare per l'input e l'output.

Per utilizzare le sue API di servizio integrate, TF Serving richiede che i modelli includano uno o più SignatureDefs.

Per creare tali firme, dobbiamo fornire definizioni per input, output e il nome del metodo desiderato. Input e Output rappresentano una mappatura dalla stringa agli oggetti TensorInfo (più su quest'ultimo). Qui, definiamo i tensori predefiniti per l'alimentazione e la ricezione dei dati da e verso un grafico. Il parametro method_name ha come target una delle API di elaborazione di alto livello TF.

Attualmente sono disponibili tre API di servizio: Classificazione, Previsione e Regressione. Ogni definizione di firma corrisponde a una specifica API RPC. Classification SegnatureDef viene utilizzato per Classify RPC API. Predict SegnatureDef viene utilizzato per Predict RPC API e così via.

Per la firma di classificazione, deve essere presente un tensore di input (per ricevere dati) e almeno uno dei due possibili tensori di output: classi e / o punteggi. La regressione SignatureDef richiede esattamente un tensore per l'input e un altro per l'output. Infine, la firma Predict consente un numero dinamico di tensori di input e output.

Inoltre, SavedModel supporta l'archiviazione delle risorse per i casi in cui l'inizializzazione delle operazioni dipende da file esterni. Inoltre, ha meccanismi per cancellare i dispositivi prima di creare il SavedModel.

Ora vediamo come possiamo farlo in pratica.

Predisporre l'ambiente

Prima di iniziare, clona questa implementazione di TensorFlow DeepLab-v3 da Github.

DeepLab è il miglior ConvNet di segmentazione semantica di Google. Fondamentalmente, la rete prende un'immagine come input e produce un'immagine simile a una maschera che separa determinati oggetti dallo sfondo.

Questa versione è stata addestrata sul set di dati di segmentazione VOC Pascal. Pertanto, può segmentare e riconoscere fino a 20 classi. Se vuoi saperne di più sulla segmentazione semantica e DeepLab-v3, dai un'occhiata a Diving into Deep Convolutional Semantic Segmentation Networks e Deeplab_V3.

Tutti i file relativi alla pubblicazione risiedono in: ./deeplab_v3/serving/. Lì troverai due file importanti: deeplab_saved_model.py e deeplab_client.ipynb

Prima di andare oltre, assicurati di scaricare il modello pre-addestrato Deeplab-v3. Vai al repository GitHub sopra, fai clic sul link dei checkpoint e scarica la cartella denominata 16645 / .

Alla fine, dovresti avere una cartella chiamata tboard_logs / con la cartella 16645 / al suo interno.

Ora, dobbiamo creare due ambienti virtuali Python. Uno per Python 3 e un altro per Python 2. Per ogni env, assicurati di installare le dipendenze necessarie. Puoi trovarli nei file serving_requirements.txt e client_requirements.txt.

Abbiamo bisogno di due env Python perché il nostro modello, DeepLab-v3, è stato sviluppato con Python 3. Tuttavia, l'API TensorFlow Serving Python è pubblicata solo per Python 2. Pertanto, per esportare il modello ed eseguire TF serving, usiamo l'env Python 3 . Per eseguire il codice client utilizzando l'API python di TF Serving, utilizziamo il pacchetto PIP (disponibile solo per Python 2).

Nota che puoi rinunciare all'env di Python 2 utilizzando le API di servizio di bazel. Fare riferimento a TF Serving Instalation per maggiori dettagli.

Una volta completato questo passaggio, iniziamo con ciò che conta davvero.

Come farlo

Per utilizzare SavedModel, TensorFlow fornisce una classe di utilità di alto livello facile da usare chiamata SavedModelBuilder. La classe SavedModelBuilder fornisce funzionalità per salvare più meta grafici, variabili associate e risorse.

Esaminiamo un esempio in esecuzione di come esportare un modello CNN a segmentazione profonda per la pubblicazione.

Come accennato in precedenza, per esportare il modello, utilizziamo la classe SavedModelBuilder. Verrà generato un file di buffer del protocollo SavedModel insieme alle variabili e agli asset del modello (se necessario).

Analizziamo il codice.

Il SavedModelBuilder riceve (come input) la directory dove salvare i dati del modello. Qui, la variabile export_path è la concatenazione di export_path_base e model_version . Di conseguenza, diverse versioni del modello verranno salvate in directory separate all'interno della cartella export_path_base .

Supponiamo di avere una versione di base del nostro modello in produzione, ma vogliamo distribuirne una nuova versione. Abbiamo migliorato la precisione del nostro modello e vogliamo offrire questa nuova versione ai nostri clienti.

Per esportare una versione diversa dello stesso grafico, possiamo semplicemente impostare FLAGS.model_version su un valore intero più alto. Quindi una cartella diversa (contenente la nuova versione del nostro modello) verrà creata all'interno della cartella export_path_base .

Ora, dobbiamo specificare i tensori di input e output del nostro modello. Per farlo, usiamo SignatureDefs. Le firme definiscono il tipo di modello che vogliamo esportare. Fornisce una mappatura dalle stringhe (nomi logici di Tensor) agli oggetti TensorInfo. L'idea è che, invece di fare riferimento ai nomi effettivi del tensore per input / output, i client possono fare riferimento ai nomi logici definiti dalle firme.

Per servire una CNN a segmentazione semantica, creeremo una firma di previsione . Si noti che la funzione build_signature_def () accetta la mappatura per i tensori di input e output, nonché l'API desiderata.

Un SignatureDef richiede la specifica di: input, output e nome del metodo. Si noti che ci aspettiamo tre valori per gli input: un'immagine e altri due tensori che ne specificano le dimensioni (altezza e larghezza). Per gli output , abbiamo definito un solo risultato: la maschera di output della segmentazione.

Nota che le stringhe "image", "height", "width" e "segmentation_map" non sono tensori. Sono invece nomi logici che fanno riferimento ai tensori effettivi input_tensor , image_height_tensor e image_width_tensor . Pertanto, possono essere qualsiasi stringa univoca che ti piace.

Inoltre, le mappature in SignatureDefs si riferiscono agli oggetti protobuf TensorInfo, non ai tensori effettivi. Per creare oggetti TensorInfo, utilizziamo la funzione di utilità: tf.saved_model.utils.build_tensor_info (tensore) .

Questo è tutto. Ora chiamiamo la funzione add_meta_graph_and_variables () per creare l'oggetto buffer del protocollo SavedModel. Quindi eseguiamo il metodo save () e manterrà un'istantanea del nostro modello sul disco contenente le variabili e le risorse del modello.

Ora possiamo eseguire deeplab_saved_model.py per esportare il nostro modello.

Se tutto è andato bene vedrai la cartella ./serving/versions/1 . Notare che "1" rappresenta la versione corrente del modello. All'interno di ogni sottodirectory di versione, vedrai i seguenti file:

  • save_model.pb o saved_model.pbtxt. Questo è il file SavedModel serializzato. Include una o più definizioni di grafici del modello, nonché le definizioni di firma.
  • Variabili. Questa cartella contiene le variabili serializzate dei grafici.

Ora siamo pronti per avviare il nostro server modello. Per farlo, esegui:

$ tensorflow_model_server --port=9000 --model_name=deeplab --model_base_path=

Il model_base_path si riferisce al punto in cui il modello esportato è stato salvato. Inoltre, non specifichiamo la cartella della versione nel percorso. Il controllo delle versioni del modello è gestito da TF Serving.

Generazione delle richieste dei clienti

Il codice client è molto semplice. Dai un'occhiata a: deeplab_client.ipynb.

Innanzitutto, leggiamo l'immagine che vogliamo inviare al server e la convertiamo nel formato corretto.

Successivamente, creiamo uno stub gRPC. Lo stub ci consente di chiamare i metodi del server remoto. Per farlo, creiamo un'istanza della classe beta_create_PredictionService_stub del modulo prediction_service_pb2 . A questo punto, lo stub contiene la logica necessaria per chiamare procedure remote (dal server) come se fossero locali.

Ora dobbiamo creare e impostare l'oggetto richiesta. Poiché il nostro server implementa l'API TensorFlow Predict, dobbiamo analizzare una richiesta Predict. Per emettere una richiesta Predict, in primo luogo, istanziamo la classe PredictRequest dal modulo forecast_pb2 . È inoltre necessario specificare i parametri model_spec.name e model_spec.signature_name . Il nome param è l'argomento "nome_modello" che abbiamo definito quando abbiamo avviato il server. E la signature_name si riferisce al nome logico assegnato al signature_def_map () parametro del add_meta_graph di routine ().

Successivamente, dobbiamo fornire i dati di input come definito nella firma del server. Ricorda che, nel server, abbiamo definito un'API Predict per aspettarsi un'immagine e due scalari (altezza e larghezza dell'immagine). Per inserire i dati di input nell'oggetto richiesta, TensorFlow fornisce l'utilità tf.make_tensor_proto () . Questo metodo crea un oggetto TensorProto da un oggetto numpy / Python. Possiamo usarlo per alimentare l'immagine e le sue dimensioni all'oggetto richiesta.

Sembra che siamo pronti per chiamare il server. Per fare ciò, chiamiamo il metodo Predict () (usando lo stub) e passiamo l'oggetto richiesta come argomento.

Per le richieste che restituiscono una singola risposta, gRPC supporta entrambe le chiamate: sincrone e asincrone. Pertanto, se si desidera eseguire un po 'di lavoro durante l'elaborazione della richiesta, si potrebbe chiamare Predict.future () invece di Predict () .

Ora possiamo recuperare e goderci i risultati.

Spero ti sia piaciuto questo articolo. Grazie per aver letto!

Se vuoi di più, controlla:

Come addestrare la tua FaceID ConvNet utilizzando l'esecuzione di TensorFlow Eager

I volti sono ovunque: da foto e video sui siti web di social media, ad applicazioni per la sicurezza dei consumatori come ... medium.freecodecamp.org Immergersi nelle reti di segmentazione semantica convoluzionale profonda e Deeplab_V3

Le reti neurali convoluzionali profonde (DCNN) hanno ottenuto un notevole successo in varie applicazioni di visione artificiale medium.freecodecamp.org