API REST

Oltre alle API gRPC, TensorFlow ModelServer supporta anche le API RESTful. Questa pagina descrive questi endpoint API e un esempio end-to-end sull'utilizzo.

La richiesta e la risposta sono un oggetto JSON. La composizione di questo oggetto dipende dal tipo di richiesta o dal verbo. Consulta le sezioni specifiche dell'API riportate di seguito per i dettagli.

In caso di errore, tutte le API restituiranno un oggetto JSON nel corpo della risposta con error come chiave e il messaggio di errore come valore:

{
  "error": <error message string>
}

API di stato del modello

Questa API segue da vicino l'API gRPC ModelService.GetModelStatus . Restituisce lo stato di un modello nel ModelServer.

URL

GET http://host:port/v1/models/${MODEL_NAME}[/versions/${VERSION}|/labels/${LABEL}]

L'inclusione di /versions/${VERSION} o /labels/${LABEL} è facoltativa. Se nella risposta viene restituito lo stato omesso per tutte le versioni.

Formato della risposta

In caso di esito positivo, restituisce una rappresentazione JSON del protobuf GetModelStatusResponse .

API dei metadati del modello

Questa API segue da vicino l'API gRPC PredictionService.GetModelMetadata . Restituisce i metadati di un modello nel ModelServer.

URL

GET http://host:port/v1/models/${MODEL_NAME}[/versions/${VERSION}|/labels/${LABEL}]/metadata

L'inclusione di /versions/${VERSION} o /labels/${LABEL} è facoltativa. Se omessi, nella risposta vengono restituiti i metadati del modello per la versione più recente.

Formato della risposta

In caso di esito positivo, restituisce una rappresentazione JSON del protobuf GetModelMetadataResponse .

API di classificazione e regressione

Questa API segue da vicino i metodi Classify e Regress dell'API gRPC PredictionService .

URL

POST http://host:port/v1/models/${MODEL_NAME}[/versions/${VERSION}|/labels/${LABEL}]:(classify|regress)

L'inclusione di /versions/${VERSION} o /labels/${LABEL} è facoltativa. Se omesso viene utilizzata la versione più recente.

Richiedi formato

Il corpo della richiesta per le API classify e regress deve essere un oggetto JSON formattato come segue:

{
  // Optional: serving signature to use.
  // If unspecifed default serving signature is used.
  "signature_name": <string>,

  // Optional: Common context shared by all examples.
  // Features that appear here MUST NOT appear in examples (below).
  "context": {
    "<feature_name3>": <value>|<list>
    "<feature_name4>": <value>|<list>
  },

  // List of Example objects
  "examples": [
    {
      // Example 1
      "<feature_name1>": <value>|<list>,
      "<feature_name2>": <value>|<list>,
      ...
    },
    {
      // Example 2
      "<feature_name1>": <value>|<list>,
      "<feature_name2>": <value>|<list>,
      ...
    }
    ...
  ]
}

<value> è un numero JSON (intero o decimale), una stringa JSON o un oggetto JSON che rappresenta dati binari (per i dettagli, vedere la sezione Codifica dei valori binari di seguito). <list> è un elenco di tali valori. Questo formato è simile ai protocolli ClassificationRequest e RegressionRequest di gRPC. Entrambe le versioni accettano l'elenco di oggetti Example .

Formato della risposta

Una richiesta classify restituisce un oggetto JSON nel corpo della risposta, formattato come segue:

{
  "result": [
    // List of class label/score pairs for first Example (in request)
    [ [<label1>, <score1>], [<label2>, <score2>], ... ],

    // List of class label/score pairs for next Example (in request)
    [ [<label1>, <score1>], [<label2>, <score2>], ... ],
    ...
  ]
}

<label> è una stringa (che può essere una stringa vuota "" se il modello non ha un'etichetta associata al punteggio). <score> è un numero decimale (virgola mobile).

La richiesta regress restituisce un oggetto JSON nel corpo della risposta, formattato come segue:

{
  // One regression value for each example in the request in the same order.
  "result": [ <value1>, <value2>, <value3>, ...]
}

<value> è un numero decimale.

Gli utenti dell'API gRPC noteranno la somiglianza di questo formato con i protocolli ClassificationResponse e RegressionResponse .

Prevedere l'API

Questa API segue da vicino l'API gRPC PredictionService.Predict .

URL

POST http://host:port/v1/models/${MODEL_NAME}[/versions/${VERSION}|/labels/${LABEL}]:predict

L'inclusione di /versions/${VERSION} o /labels/${LABEL} è facoltativa. Se omesso viene utilizzata la versione più recente.

Richiedi formato

Il corpo della richiesta per l'API predict deve essere un oggetto JSON formattato come segue:

{
  // (Optional) Serving signature to use.
  // If unspecifed default serving signature is used.
  "signature_name": <string>,

  // Input Tensors in row ("instances") or columnar ("inputs") format.
  // A request can have either of them but NOT both.
  "instances": <value>|<(nested)list>|<list-of-objects>
  "inputs": <value>|<(nested)list>|<object>
}

Specifica dei tensori di input in formato riga.

Questo formato è simile al protocollo PredictRequest dell'API gRPC e all'API di previsione CMLE . Utilizza questo formato se tutti i tensori di input con nome hanno la stessa dimensione 0-esima . In caso contrario, utilizzare il formato a colonne descritto più avanti.

Nel formato riga, gli input vengono associati alla chiave delle istanze nella richiesta JSON.

Quando è presente un solo input denominato, specificare il valore della chiave delle istanze come valore dell'input:

{
  // List of 3 scalar tensors.
  "instances": [ "foo", "bar", "baz" ]
}

{
  // List of 2 tensors each of [1, 2] shape
  "instances": [ [[1, 2]], [[3, 4]] ]
}

I tensori sono espressi naturalmente in notazione annidata poiché non è necessario appiattire manualmente l'elenco.

Per più input denominati, è previsto che ciascun elemento sia un oggetto contenente una coppia nome input/valore tensore, uno per ciascun input denominato. Ad esempio, quella che segue è una richiesta con due istanze, ciascuna con un set di tre tensori di input denominati:

{
 "instances": [
   {
     "tag": "foo",
     "signal": [1, 2, 3, 4, 5],
     "sensor": [[1, 2], [3, 4]]
   },
   {
     "tag": "bar",
     "signal": [3, 4, 1, 2, 5]],
     "sensor": [[4, 5], [6, 8]]
   }
 ]
}

Nota, si presuppone implicitamente che ciascun input denominato ("tag", "segnale", "sensore") abbia la stessa dimensione 0 ( due nell'esempio sopra, poiché ci sono due oggetti nell'elenco delle istanze ). Se hai denominato input con dimensione 0 diversa, utilizza il formato colonnare descritto di seguito.

Specifica dei tensori di input nel formato colonna.

Utilizza questo formato per specificare i tensori di input, se i singoli input denominati non hanno la stessa dimensione 0 o se desideri una rappresentazione più compatta. Questo formato è simile al campo inputs della richiesta gRPC Predict .

Nel formato colonnare, gli input vengono associati alla chiave input nella richiesta JSON.

Il valore per la chiave degli input può essere un singolo tensore di input o una mappa del nome dell'input sui tensori (elencati nella loro forma nidificata naturale). Ciascun input può avere una forma arbitraria e non è necessario che condivida la stessa dimensione 0 (nota anche come dimensione batch) come richiesto dal formato di riga descritto sopra.

La rappresentazione a colonne dell'esempio precedente è la seguente:

{
 "inputs": {
   "tag": ["foo", "bar"],
   "signal": [[1, 2, 3, 4, 5], [3, 4, 1, 2, 5]],
   "sensor": [[[1, 2], [3, 4]], [[4, 5], [6, 8]]]
 }
}

Nota, gli input sono un oggetto JSON e non un elenco di istanze (utilizzate nella rappresentazione della riga). Inoltre, tutti gli input con nome vengono specificati insieme, anziché svolgerli in singole righe nel formato di riga descritto in precedenza. Questo rende la rappresentazione compatta (ma forse meno leggibile).

Formato della risposta

La richiesta predict restituisce un oggetto JSON nel corpo della risposta.

Una richiesta in formato riga ha una risposta formattata come segue:

{
  "predictions": <value>|<(nested)list>|<list-of-objects>
}

Se l'output del modello contiene un solo tensore con nome, omettiamo il nome e le mappe chiave predictions in un elenco di valori scalari o di elenco. Se il modello restituisce più tensori con nome, restituiamo invece un elenco di oggetti, simile alla richiesta in formato riga menzionata sopra.

Una richiesta in formato colonnare ha una risposta formattata come segue:

{
  "outputs": <value>|<(nested)list>|<object>
}

Se l'output del modello contiene solo un tensore con nome, omettiamo il nome e outputs le mappe chiave a un elenco di valori scalari o di elenco. Se il modello restituisce più tensori con nome, restituiamo invece un oggetto. Ciascuna chiave di questo oggetto corrisponde a un tensore di output denominato. Il formato è simile alla richiesta in formato colonna menzionata sopra.

Uscita di valori binari

TensorFlow non distingue tra stringhe non binarie e binarie. Sono tutti di tipo DT_STRING . I tensori denominati che hanno _bytes come suffisso nel nome sono considerati valori binari. Tali valori sono codificati in modo diverso come descritto nella sezione relativa alla codifica dei valori binari di seguito.

Mappatura JSON

Le API RESTful supportano una codifica canonica in JSON, semplificando la condivisione dei dati tra i sistemi. Per i tipi supportati, le codifiche sono descritte tipo per tipo nella tabella seguente. È implicito che i tipi non elencati di seguito non siano supportati.

Tipo di dati TF Valore JSON Esempio JSON Appunti
DT_BOOL vero falso vero falso
DT_STRING corda "Ciao mondo!" Se DT_STRING rappresenta byte binari (ad esempio byte immagine serializzati o protobuf), codificarli in Base64. Vedi Codifica dei valori binari per maggiori informazioni.
DT_INT8, DT_UINT8, DT_INT16, DT_INT32, DT_UINT32, DT_INT64, DT_UINT64 numero 1, -10, 0 Il valore JSON sarà un numero decimale.
DT_FLOAT, DT_DOUBLE numero 1.1, -10.0, 0, NaN , Infinity Il valore JSON sarà un numero o uno dei valori token speciali: NaN , Infinity e -Infinity . Per ulteriori informazioni, vedere Conformità JSON . È accettata anche la notazione esponenziale.

Precisione in virgola mobile

JSON ha un solo tipo di dati numerico. Pertanto è possibile fornire un valore per un input che comporta una perdita di precisione. Ad esempio, se l'input x è un tipo di dati float e l'input {"x": 1435774380} viene inviato al modello in esecuzione su hardware basato sullo standard in virgola mobile IEEE 754 (ad esempio Intel o AMD), il valore sarà essere convertito silenziosamente dall'hardware underyling in 1435774336 poiché 1435774380 non può essere rappresentato esattamente in un numero in virgola mobile a 32 bit. In genere, gli input per la pubblicazione dovrebbero avere la stessa distribuzione dell'addestramento, quindi generalmente questo non sarà problematico perché le stesse conversioni si sono verificate al momento dell'addestramento. Tuttavia, nel caso in cui sia necessaria la massima precisione, assicurati di utilizzare un tipo di dati sottostante nel modello in grado di gestire la precisione desiderata e/o prendere in considerazione il controllo lato client.

Codifica di valori binari

JSON utilizza la codifica UTF-8. Se si dispone di funzionalità di input o valori tensoriali che devono essere binari (come i byte di immagine), è necessario codificare Base64 i dati e incapsularli in un oggetto JSON avente b64 come chiave come segue:

{ "b64": <base64 encoded string> }

È possibile specificare questo oggetto come valore per una funzionalità di input o un tensore. Lo stesso formato viene utilizzato anche per codificare la risposta in uscita.

Di seguito è mostrata una richiesta di classificazione con funzionalità image (dati binari) e caption :

{
  "signature_name": "classify_objects",
  "examples": [
    {
      "image": { "b64": "aW1hZ2UgYnl0ZXM=" },
      "caption": "seaside"
    },
    {
      "image": { "b64": "YXdlc29tZSBpbWFnZSBieXRlcw==" },
      "caption": "mountains"
    }
  ]
}

Conformità JSON

Molti valori di caratteristiche o tensori sono numeri in virgola mobile. Oltre ai valori finiti (es. 3.14, 1.0 ecc.) questi possono avere valori NaN e non finiti ( Infinity e -Infinity ). Sfortunatamente la specifica JSON ( RFC 7159 ) NON riconosce questi valori (sebbene la specifica JavaScript lo faccia).

L'API REST descritta in questa pagina consente agli oggetti JSON di richiesta/risposta di avere tali valori. Ciò implica che sono valide richieste come la seguente:

{
  "example": [
    {
      "sensor_readings": [ 1.0, -3.14, Nan, Infinity ]
    }
  ]
}

Un parser JSON (rigoroso) conforme agli standard lo rifiuterà con un errore di analisi (a causa dei token NaN e Infinity mescolati con numeri effettivi). Per gestire correttamente le richieste/risposte nel codice, utilizza un parser JSON che supporti questi token.

I token NaN , Infinity -Infinity sono riconosciuti da proto3 , modulo JSON Python e linguaggio JavaScript.

Esempio

Possiamo utilizzare il modello giocattolo half_plus_tre per vedere le API REST in azione.

Avvia ModelServer con l'endpoint API REST

Scarica il modello half_plus_three dal repository git :

$ mkdir -p /tmp/tfserving
$ cd /tmp/tfserving
$ git clone --depth=1 https://github.com/tensorflow/serving

Utilizzeremo Docker per eseguire ModelServer. Se desideri installare ModelServer in modo nativo sul tuo sistema, segui invece le istruzioni di configurazione per l'installazione e avvia ModelServer con l'opzione --rest_api_port per esportare l'endpoint API REST (questo non è necessario quando si utilizza Docker).

$ cd /tmp/tfserving
$ docker pull tensorflow/serving:latest
$ docker run --rm -p 8501:8501 \
    --mount type=bind,source=$(pwd),target=$(pwd) \
    -e MODEL_BASE_PATH=$(pwd)/serving/tensorflow_serving/servables/tensorflow/testdata \
    -e MODEL_NAME=saved_model_half_plus_three -t tensorflow/serving:latest
...
.... Exporting HTTP/REST API at:localhost:8501 ...

Effettua chiamate API REST a ModelServer

In un terminale diverso, utilizza lo strumento curl per effettuare chiamate API REST.

Ottieni lo stato del modello come segue:

$ curl http://localhost:8501/v1/models/saved_model_half_plus_three
{
 "model_version_status": [
  {
   "version": "123",
   "state": "AVAILABLE",
   "status": {
    "error_code": "OK",
    "error_message": ""
   }
  }
 ]
}

Una chiamata predict avrebbe il seguente aspetto:

$ curl -d '{"instances": [1.0,2.0,5.0]}' -X POST http://localhost:8501/v1/models/saved_model_half_plus_three:predict
{
    "predictions": [3.5, 4.0, 5.5]
}

E una chiamata regress appare come segue:

$ curl -d '{"signature_name": "tensorflow/serving/regress", "examples": [{"x": 1.0}, {"x": 2.0}]}' \
  -X POST http://localhost:8501/v1/models/saved_model_half_plus_three:regress
{
    "results": [3.5, 4.0]
}

Tieni presente che regress è disponibile su un nome di firma non predefinito e deve essere specificato in modo esplicito. Un URL o un corpo della richiesta errato restituisce uno stato di errore HTTP.

$ curl -i -d '{"instances": [1.0,5.0]}' -X POST http://localhost:8501/v1/models/half:predict
HTTP/1.1 404 Not Found
Content-Type: application/json
Date: Wed, 06 Jun 2018 23:20:12 GMT
Content-Length: 65

{ "error": "Servable not found for request: Latest(half)" }
$