API RESTful

Además de las API de gRPC, TensorFlow ModelServer también admite API RESTful. Esta página describe estos puntos finales de API y un ejemplo de uso de un extremo a otro.

La solicitud y la respuesta es un objeto JSON. La composición de este objeto depende del tipo de solicitud o del verbo. Consulte las secciones específicas de API a continuación para obtener más detalles.

En caso de error, todas las API devolverán un objeto JSON en el cuerpo de la respuesta con error como clave y el mensaje de error como valor:

{
  "error": <error message string>
}

API de estado del modelo

Esta API sigue de cerca la API gRPC ModelService.GetModelStatus . Devuelve el estado de un modelo en ModelServer.

URL

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

Incluir /versions/${VERSION} o /labels/${LABEL} es opcional. Si se omite el estado de todas las versiones, se devuelve en la respuesta.

Formato de respuesta

Si tiene éxito, devuelve una representación JSON del protobuf GetModelStatusResponse .

API de metadatos del modelo

Esta API sigue de cerca la API gRPC PredictionService.GetModelMetadata . Devuelve los metadatos de un modelo en ModelServer.

URL

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

Incluir /versions/${VERSION} o /labels/${LABEL} es opcional. Si se omite, los metadatos del modelo para la última versión se devuelven en la respuesta.

Formato de respuesta

Si tiene éxito, devuelve una representación JSON del protobuf GetModelMetadataResponse .

API de clasificación y regresión

Esta API sigue de cerca los métodos Classify y Regress de la API PredictionService gRPC.

URL

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

Incluir /versions/${VERSION} o /labels/${LABEL} es opcional. Si se omite, se utiliza la última versión.

Formato de solicitud

El cuerpo de la solicitud para las API classify y regress debe ser un objeto JSON con el formato siguiente:

{
  // 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> es un número JSON (completo o decimal), una cadena JSON o un objeto JSON que representa datos binarios (consulte la sección Codificación de valores binarios a continuación para obtener más detalles). <list> es una lista de dichos valores. Este formato es similar a los protos ClassificationRequest y RegressionRequest de gRPC. Ambas versiones aceptan listas de objetos Example .

Formato de respuesta

Una solicitud classify devuelve un objeto JSON en el cuerpo de la respuesta, con el siguiente formato:

{
  "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> es una cadena (que puede ser una cadena vacía "" si el modelo no tiene una etiqueta asociada a la puntuación). <score> es un número decimal (punto flotante).

La solicitud regress devuelve un objeto JSON en el cuerpo de la respuesta, con el siguiente formato:

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

<value> es un número decimal.

Los usuarios de la API gRPC notarán la similitud de este formato con los protos ClassificationResponse y RegressionResponse .

Predecir API

Esta API sigue de cerca la API PredictionService.Predict gRPC.

URL

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

Incluir /versions/${VERSION} o /labels/${LABEL} es opcional. Si se omite, se utiliza la última versión.

Formato de solicitud

El cuerpo de la solicitud para la API predict debe tener el formato de objeto JSON de la siguiente manera:

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

Especificación de tensores de entrada en formato de fila.

Este formato es similar al protocolo PredictRequest de la API gRPC y la API de predicción CMLE . Utilice este formato si todos los tensores de entrada con nombre tienen la misma dimensión 0 . Si no es así, utilice el formato de columnas que se describe más adelante.

En el formato de fila, las entradas se codifican para la clave de instancias en la solicitud JSON.

Cuando solo hay una entrada con nombre, especifique el valor de la clave de instancias para que sea el valor de la entrada:

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

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

Los tensores se expresan de forma natural en notación anidada, ya que no es necesario aplanar la lista manualmente.

Para múltiples entradas con nombre, se espera que cada elemento sea un objeto que contenga un par de nombre de entrada/valor tensor, uno para cada entrada con nombre. Como ejemplo, la siguiente es una solicitud con dos instancias, cada una con un conjunto de tres tensores de entrada con nombre:

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

Tenga en cuenta que se supone implícitamente que cada entrada con nombre ("etiqueta", "señal", "sensor") tiene la misma dimensión 0 ( dos en el ejemplo anterior, ya que hay dos objetos en la lista de instancias ). Si ha nombrado entradas que tienen una dimensión 0 diferente, utilice el formato de columnas que se describe a continuación.

Especificación de tensores de entrada en formato de columna.

Utilice este formato para especificar sus tensores de entrada, si las entradas individuales con nombre no tienen la misma dimensión 0 o si desea una representación más compacta. Este formato es similar al campo inputs de la solicitud Predict de gRPC.

En el formato de columnas, las entradas se codifican según la clave de entrada en la solicitud JSON.

El valor de la clave de entradas puede ser un tensor de entrada único o un mapa del nombre de entrada a los tensores (enumerados en su forma anidada natural). Cada entrada puede tener una forma arbitraria y no necesita compartir la misma dimensión 0 (también conocida como tamaño de lote) como lo requiere el formato de fila descrito anteriormente.

La representación en columnas del ejemplo anterior es la siguiente:

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

Tenga en cuenta que las entradas son un objeto JSON y no una lista como instancias (utilizadas en la representación de filas). Además, todas las entradas con nombre se especifican juntas, en lugar de desenrollarlas en filas individuales en el formato de fila descrito anteriormente. Esto hace que la representación sea compacta (pero quizás menos legible).

Formato de respuesta

La solicitud predict devuelve un objeto JSON en el cuerpo de la respuesta.

Una solicitud en formato de fila tiene una respuesta con el siguiente formato:

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

Si la salida del modelo contiene solo un tensor con nombre, omitimos el nombre y los mapas clave predictions en una lista de valores escalares o de lista. Si el modelo genera varios tensores con nombre, generamos una lista de objetos, similar a la solicitud en formato de fila mencionada anteriormente.

Una solicitud en formato de columnas tiene una respuesta con el siguiente formato:

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

Si la salida del modelo contiene solo un tensor con nombre, omitimos el nombre y outputs mapas clave en una lista de valores escalares o de lista. Si el modelo genera varios tensores con nombre, en su lugar generamos un objeto. Cada clave de este objeto corresponde a un tensor de salida con nombre. El formato es similar a la solicitud en formato de columna mencionada anteriormente.

Salida de valores binarios

TensorFlow no distingue entre cadenas binarias y no binarias. Todos son del tipo DT_STRING . Se considera que los tensores con nombre que tienen _bytes como sufijo en su nombre tienen valores binarios. Dichos valores se codifican de manera diferente, como se describe en la sección de codificación de valores binarios a continuación.

mapeo JSON

Las API RESTful admiten una codificación canónica en JSON, lo que facilita el intercambio de datos entre sistemas. Para los tipos admitidos, las codificaciones se describen tipo por tipo en la siguiente tabla. Se supone que los tipos que no se enumeran a continuación no son compatibles.

Tipo de datos TF Valor JSON Ejemplo JSON Notas
DT_BOOL verdadero Falso verdadero Falso
DT_STRING cadena "¡Hola Mundo!" Si DT_STRING representa bytes binarios (por ejemplo, bytes de imagen serializados o protobuf), codifíquelos en Base64. Consulte Codificación de valores binarios para obtener más información.
DT_INT8, DT_UINT8, DT_INT16, DT_INT32, DT_UINT32, DT_INT64, DT_UINT64 número 1, -10, 0 El valor JSON será un número decimal.
DT_FLOAT, DT_DOBLE número 1,1, -10,0, 0, NaN , Infinity El valor JSON será un número o uno de los valores de token especiales: NaN , Infinity y -Infinity . Consulte Conformidad JSON para obtener más información. También se acepta la notación de exponentes.

Precisión de coma flotante

JSON tiene un tipo de datos de un solo número. Por lo tanto, es posible proporcionar un valor para una entrada que resulte en una pérdida de precisión. Por ejemplo, si la entrada x es un tipo de datos float y la entrada {"x": 1435774380} se envía al modelo que se ejecuta en hardware basado en el estándar de punto flotante IEEE 754 (por ejemplo, Intel o AMD), entonces el valor El hardware subyacente lo convertirá silenciosamente a 1435774336 , ya que 1435774380 no se puede representar exactamente en un número de punto flotante de 32 bits. Normalmente, las entradas para la publicación deben tener la misma distribución que la del entrenamiento, por lo que esto generalmente no será problemático porque las mismas conversiones ocurrieron en el momento del entrenamiento. Sin embargo, en caso de que se necesite una precisión total, asegúrese de utilizar un tipo de datos subyacente en su modelo que pueda manejar la precisión deseada y/o considere la verificación del lado del cliente.

Codificación de valores binarios

JSON utiliza codificación UTF-8. Si tiene una característica de entrada o valores de tensor que deben ser binarios (como bytes de imagen), debe codificar los datos en Base64 y encapsularlos en un objeto JSON que tenga b64 como clave de la siguiente manera:

{ "b64": <base64 encoded string> }

Puede especificar este objeto como valor para una característica de entrada o tensor. También se utiliza el mismo formato para codificar la respuesta de salida.

A continuación se muestra una solicitud de clasificación con funciones image (datos binarios) y caption :

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

Conformidad JSON

Muchos valores de características o tensores son números de punto flotante. Aparte de los valores finitos (por ejemplo, 3,14, 1,0, etc.), estos pueden tener valores NaN y no finitos ( Infinity y -Infinity ). Lamentablemente, la especificación JSON ( RFC 7159 ) NO reconoce estos valores (aunque la especificación JavaScript sí).

La API REST descrita en esta página permite que los objetos JSON de solicitud/respuesta tengan dichos valores. Esto implica que solicitudes como la siguiente son válidas:

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

Un analizador JSON (estricto) que cumpla con los estándares rechazará esto con un error de análisis (debido a los tokens NaN e Infinity mezclados con números reales). Para manejar correctamente las solicitudes/respuestas en su código, utilice un analizador JSON que admita estos tokens.

Los tokens NaN , Infinity , -Infinity son reconocidos por proto3 , el módulo Python JSON y el lenguaje JavaScript.

Ejemplo

Podemos usar el modelo toy half_plus_tres para ver las API REST en acción.

Inicie ModelServer con el punto final de la API REST

Descargue el modelo half_plus_three del repositorio de git :

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

Usaremos Docker para ejecutar ModelServer. Si desea instalar ModelServer de forma nativa en su sistema, siga las instrucciones de configuración para instalarlo e inicie ModelServer con la opción --rest_api_port para exportar el punto final de la API REST (esto no es necesario cuando se usa 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 ...

Realizar llamadas API REST a ModelServer

En una terminal diferente, use la herramienta curl para realizar llamadas a la API REST.

Obtenga el estado del modelo de la siguiente manera:

$ 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 llamada predict tendría el siguiente aspecto:

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

Y una llamada regress tiene el siguiente aspecto:

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

Tenga en cuenta que regress está disponible en un nombre de firma no predeterminado y debe especificarse explícitamente. Una URL o un cuerpo de solicitud incorrectos devuelven un estado de error 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)" }
$