API RESTful

En plus des API gRPC, TensorFlow ModelServer prend également en charge les API RESTful. Cette page décrit ces points de terminaison d'API et un exemple de bout en bout sur leur utilisation.

La demande et la réponse sont un objet JSON. La composition de cet objet dépend du type de requête ou du verbe. Consultez les sections spécifiques à l'API ci-dessous pour plus de détails.

En cas d'erreur, toutes les API renverront un objet JSON dans le corps de la réponse avec error comme clé et le message d'erreur comme valeur :

{
  "error": <error message string>
}

API d'état du modèle

Cette API suit de près l'API gRPC ModelService.GetModelStatus . Il renvoie l'état d'un modèle dans le ModelServer.

URL

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

L'inclusion de /versions/${VERSION} ou /labels/${LABEL} est facultative. En cas d'omission, l'état de toutes les versions est renvoyé dans la réponse.

Format de réponse

En cas de succès, renvoie une représentation JSON du protobuf GetModelStatusResponse .

API de métadonnées de modèle

Cette API suit de près l'API gRPC PredictionService.GetModelMetadata . Il renvoie les métadonnées d'un modèle dans le ModelServer.

URL

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

L'inclusion de /versions/${VERSION} ou /labels/${LABEL} est facultative. En cas d'omission, les métadonnées du modèle pour la dernière version sont renvoyées dans la réponse.

Format de réponse

En cas de succès, renvoie une représentation JSON du protobuf GetModelMetadataResponse .

API de classification et de régression

Cette API suit de près les méthodes Classify et Regress de l'API gRPC PredictionService .

URL

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

L'inclusion de /versions/${VERSION} ou /labels/${LABEL} est facultative. En cas d'omission, la dernière version est utilisée.

Format de demande

Le corps de la requête pour les API classify et regress doit être un objet JSON formaté comme suit :

{
  // 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> est un nombre JSON (entier ou décimal), une chaîne JSON ou un objet JSON qui représente des données binaires (voir la section Encodage des valeurs binaires ci-dessous pour plus de détails). <list> est une liste de ces valeurs. Ce format est similaire aux protos ClassificationRequest et RegressionRequest de gRPC. Les deux versions acceptent la liste d’objets Example .

Format de réponse

Une requête classify renvoie un objet JSON dans le corps de la réponse, formaté comme suit :

{
  "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> est une chaîne (qui peut être une chaîne vide "" si le modèle n'a pas de label associé à la partition). <score> est un nombre décimal (à virgule flottante).

La requête regress renvoie un objet JSON dans le corps de la réponse, formaté comme suit :

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

<value> est un nombre décimal.

Les utilisateurs de l'API gRPC remarqueront la similitude de ce format avec les protos ClassificationResponse et RegressionResponse .

API de prédiction

Cette API suit de près l'API gRPC PredictionService.Predict .

URL

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

L'inclusion de /versions/${VERSION} ou /labels/${LABEL} est facultative. En cas d'omission, la dernière version est utilisée.

Format de demande

Le corps de la requête pour l'API predict doit être un objet JSON formaté comme suit :

{
  // (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>
}

Spécification des tenseurs d'entrée au format ligne.

Ce format est similaire au proto PredictRequest de l'API gRPC et à l' API de prédiction CMLE . Utilisez ce format si tous les tenseurs d'entrée nommés ont la même 0ème dimension . Si ce n’est pas le cas, utilisez le format en colonnes décrit plus loin ci-dessous.

Dans le format de ligne, les entrées sont associées à la clé d'instance dans la requête JSON.

Lorsqu'il n'y a qu'une seule entrée nommée, spécifiez la valeur de la clé des instances comme étant la valeur de l'entrée :

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

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

Les tenseurs sont exprimés naturellement en notation imbriquée puisqu'il n'est pas nécessaire d'aplatir manuellement la liste.

Pour plusieurs entrées nommées, chaque élément doit être un objet contenant une paire nom d'entrée/valeur de tenseur, une pour chaque entrée nommée. À titre d'exemple, voici une requête avec deux instances, chacune avec un ensemble de trois tenseurs d'entrée nommés :

{
 "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]]
   }
 ]
}

Notez que chaque entrée nommée ("tag", "signal", "capteur") est implicitement supposée avoir la même 0ème dimension ( deux dans l'exemple ci-dessus, car il y a deux objets dans la liste des instances ). Si vous avez nommé des entrées qui ont une dimension 0 différente, utilisez le format en colonnes décrit ci-dessous.

Spécification des tenseurs d'entrée au format colonne.

Utilisez ce format pour spécifier vos tenseurs d'entrée, si les entrées nommées individuelles n'ont pas la même dimension 0 ou si vous souhaitez une représentation plus compacte. Ce format est similaire au champ inputs de la requête gRPC Predict .

Dans le format en colonnes, les entrées sont associées à la clé d'entrée dans la requête JSON.

La valeur de la clé d'entrée peut soit être un seul tenseur d'entrée, soit une carte du nom d'entrée aux tenseurs (répertoriés sous leur forme imbriquée naturelle). Chaque entrée peut avoir une forme arbitraire et n'a pas besoin de partager la même dimension 0 (c'est-à-dire la taille du lot) comme l'exige le format de ligne décrit ci-dessus.

La représentation en colonnes de l'exemple précédent est la suivante :

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

Notez que les entrées sont un objet JSON et non une liste comme des instances (utilisées dans la représentation en ligne). De plus, toutes les entrées nommées sont spécifiées ensemble, au lieu de les dérouler en lignes individuelles effectuées dans le format de ligne décrit précédemment. Cela rend la représentation compacte (mais peut-être moins lisible).

Format de réponse

La requête predict renvoie un objet JSON dans le corps de la réponse.

Une requête au format ligne a une réponse formatée comme suit :

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

Si la sortie du modèle ne contient qu'un seul tenseur nommé, nous omettons le nom et les clés predictions mappées à une liste de valeurs scalaires ou de liste. Si le modèle génère plusieurs tenseurs nommés, nous générons à la place une liste d'objets, similaire à la requête au format ligne mentionnée ci-dessus.

Une demande au format colonne a une réponse formatée comme suit :

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

Si la sortie du modèle ne contient qu'un seul tenseur nommé, nous omettons le nom et outputs des mappages clés vers une liste de valeurs scalaires ou de liste. Si le modèle génère plusieurs tenseurs nommés, nous générons un objet à la place. Chaque clé de cet objet correspond à un tenseur de sortie nommé. Le format est similaire à la requête en format colonne mentionnée ci-dessus.

Sortie de valeurs binaires

TensorFlow ne fait pas de distinction entre les chaînes non binaires et binaires. Tous sont de type DT_STRING . Les tenseurs nommés qui ont _bytes comme suffixe dans leur nom sont considérés comme ayant des valeurs binaires. Ces valeurs sont codées différemment, comme décrit dans la section de codage des valeurs binaires ci-dessous.

Mappage JSON

Les API RESTful prennent en charge un encodage canonique en JSON, facilitant le partage de données entre systèmes. Pour les types pris en charge, les codages sont décrits type par type dans le tableau ci-dessous. Les types non répertoriés ci-dessous sont implicitement non pris en charge.

Type de données TF Valeur JSON Exemple JSON Remarques
DT_BOOL vrai, faux vrai, faux
DT_STRING chaîne "Bonjour le monde!" Si DT_STRING représente des octets binaires (par exemple des octets d'image sérialisés ou protobuf), codez-les en Base64. Voir Encodage des valeurs binaires pour plus d'informations.
DT_INT8, DT_UINT8, DT_INT16, DT_INT32, DT_UINT32, DT_INT64, DT_UINT64 nombre 1, -10, 0 La valeur JSON sera un nombre décimal.
DT_FLOAT, DT_DOUBLE nombre 1,1, -10,0, 0, NaN , Infinity La valeur JSON sera un nombre ou l'une des valeurs de jeton spéciales - NaN , Infinity et -Infinity . Voir Conformité JSON pour plus d'informations. La notation des exposants est également acceptée.

Précision en virgule flottante

JSON a un type de données numérique unique. Il est ainsi possible de fournir une valeur pour une entrée qui entraîne une perte de précision. Par exemple, si l'entrée x est un type de données float et que l'entrée {"x": 1435774380} est envoyée au modèle exécuté sur un matériel basé sur la norme à virgule flottante IEEE 754 (par exemple Intel ou AMD), alors la valeur sera être converti silencieusement par le matériel sous-jacent en 1435774336 puisque 1435774380 ne peut pas être exactement représenté dans un nombre à virgule flottante de 32 bits. En règle générale, les entrées pour le service doivent être la même distribution que pour la formation, donc cela ne posera généralement pas de problème car les mêmes conversions se sont produites au moment de la formation. Cependant, si une précision totale est nécessaire, veillez à utiliser un type de données sous-jacent dans votre modèle capable de gérer la précision souhaitée et/ou d'envisager une vérification côté client.

Encodage de valeurs binaires

JSON utilise le codage UTF-8. Si vous avez des valeurs de caractéristiques ou de tenseurs d'entrée qui doivent être binaires (comme les octets d'image), vous devez encoder les données en Base64 et les encapsuler dans un objet JSON ayant b64 comme clé comme suit :

{ "b64": <base64 encoded string> }

Vous pouvez spécifier cet objet comme valeur pour une entité ou un tenseur en entrée. Le même format est également utilisé pour coder la réponse de sortie.

Une demande de classification avec des fonctionnalités image (données binaires) et caption est présentée ci-dessous :

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

Conformité JSON

De nombreuses valeurs de caractéristiques ou de tenseurs sont des nombres à virgule flottante. Outre les valeurs finies (par exemple 3,14, 1,0, etc.), celles-ci peuvent avoir des valeurs NaN et non finies ( Infinity et -Infinity ). Malheureusement, la spécification JSON ( RFC 7159 ) ne reconnaît PAS ces valeurs (bien que la spécification JavaScript le fasse).

L'API REST décrite sur cette page permet aux objets JSON de requête/réponse d'avoir de telles valeurs. Cela implique que les requêtes comme celle-ci sont valides :

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

Un analyseur JSON conforme aux normes (strictes) rejettera cela avec une erreur d'analyse (en raison du mélange de jetons NaN et Infinity avec des nombres réels). Pour gérer correctement les requêtes/réponses dans votre code, utilisez un analyseur JSON qui prend en charge ces jetons.

Les jetons NaN , Infinity , -Infinity sont reconnus par proto3 , le module Python JSON et le langage JavaScript.

Exemple

Nous pouvons utiliser le modèle toy half_plus_trois pour voir les API REST en action.

Démarrez ModelServer avec le point de terminaison de l'API REST

Téléchargez le modèle half_plus_three depuis le dépôt git :

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

Nous utiliserons Docker pour exécuter le ModelServer. Si vous souhaitez installer ModelServer de manière native sur votre système, suivez les instructions de configuration pour l'installer à la place et démarrez ModelServer avec l'option --rest_api_port pour exporter le point de terminaison de l'API REST (cela n'est pas nécessaire lors de l'utilisation de 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 ...

Effectuer des appels d'API REST à ModelServer

Dans un autre terminal, utilisez l'outil curl pour effectuer des appels d'API REST.

Obtenez l'état du modèle comme suit :

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

Un appel predict ressemblerait à ceci :

$ 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]
}

Et un appel regress ressemble à ceci :

$ 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]
}

Notez que regress est disponible sur un nom de signature autre que celui par défaut et doit être spécifiée explicitement. Une URL ou un corps de requête incorrect renvoie un statut d'erreur 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)" }
$