API RESTful

Além das APIs gRPC, o TensorFlow ModelServer também oferece suporte a APIs RESTful. Esta página descreve esses endpoints de API e um exemplo completo de uso.

A solicitação e a resposta são um objeto JSON. A composição deste objeto depende do tipo de solicitação ou verbo. Consulte as seções específicas da API abaixo para obter detalhes.

Em caso de erro, todas as APIs retornarão um objeto JSON no corpo da resposta com error como chave e a mensagem de erro como valor:

{
  "error": <error message string>
}

API de status do modelo

Esta API segue de perto a API gRPC ModelService.GetModelStatus . Ele retorna o status de um modelo no ModelServer.

URL

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

Incluir /versions/${VERSION} ou /labels/${LABEL} é opcional. Se o status for omitido para todas as versões, será retornado na resposta.

Formato de resposta

Se for bem-sucedido, retornará uma representação JSON do protobuf GetModelStatusResponse .

API de metadados de modelo

Esta API segue de perto a API gRPC PredictionService.GetModelMetadata . Ele retorna os metadados de um modelo no ModelServer.

URL

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

Incluir /versions/${VERSION} ou /labels/${LABEL} é opcional. Se omitido, os metadados do modelo da versão mais recente serão retornados na resposta.

Formato de resposta

Se for bem-sucedido, retornará uma representação JSON do protobuf GetModelMetadataResponse .

API de classificação e regressão

Esta API segue de perto os métodos Classify e Regress da API gRPC do PredictionService .

URL

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

Incluir /versions/${VERSION} ou /labels/${LABEL} é opcional. Se omitido, a versão mais recente será usada.

Formato de solicitação

O corpo da solicitação para as APIs classify e regress deve ser um objeto JSON formatado da seguinte forma:

{
  // 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> é um número JSON (inteiro ou decimal), uma string JSON ou um objeto JSON que representa dados binários (consulte a seção Codificação de valores binários abaixo para obter detalhes). <list> é uma lista desses valores. Este formato é semelhante aos protos ClassificationRequest e RegressionRequest do gRPC. Ambas as versões aceitam lista de objetos Example .

Formato de resposta

Uma solicitação classify retorna um objeto JSON no corpo da resposta, formatado da seguinte forma:

{
  "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> é uma string (que pode ser uma string vazia "" se o modelo não tiver um rótulo associado à pontuação). <score> é um número decimal (ponto flutuante).

A solicitação regress retorna um objeto JSON no corpo da resposta, formatado da seguinte forma:

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

<value> é um número decimal.

Os usuários da API gRPC notarão a semelhança deste formato com os protos ClassificationResponse e RegressionResponse .

Prever API

Esta API segue de perto a API PredictionService.Predict gRPC.

URL

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

Incluir /versions/${VERSION} ou /labels/${LABEL} é opcional. Se omitido, a versão mais recente será usada.

Formato de solicitação

O corpo da solicitação para a API predict deve ser um objeto JSON formatado da seguinte maneira:

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

Especificando tensores de entrada em formato de linha.

Este formato é semelhante ao proto PredictRequest da API gRPC e à API de previsão CMLE . Use este formato se todos os tensores de entrada nomeados tiverem a mesma dimensão 0 . Caso contrário, use o formato colunar descrito abaixo.

No formato de linha, as entradas são codificadas para chaves de instância na solicitação JSON.

Quando houver apenas uma entrada nomeada, especifique o valor da chave de instâncias como o valor da entrada:

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

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

Os tensores são expressos naturalmente em notação aninhada, pois não há necessidade de nivelar manualmente a lista.

Para múltiplas entradas nomeadas, espera-se que cada item seja um objeto contendo o par nome de entrada/valor do tensor, um para cada entrada nomeada. Como exemplo, a seguir está uma solicitação com duas instâncias, cada uma com um conjunto de três tensores de entrada nomeados:

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

Observe que cada entrada nomeada ("tag", "sinal", "sensor") é implicitamente assumida como tendo a mesma dimensão 0 ( duas no exemplo acima, pois há dois objetos na lista de instâncias ). Se você nomeou entradas com dimensão 0 diferente, use o formato colunar descrito abaixo.

Especificando tensores de entrada em formato de coluna.

Use este formato para especificar seus tensores de entrada, se as entradas nomeadas individuais não tiverem a mesma dimensão 0 ou se você desejar uma representação mais compacta. Este formato é semelhante ao campo inputs da solicitação gRPC Predict .

No formato colunar, as entradas são codificadas para a chave de entradas na solicitação JSON.

O valor da chave de entradas pode ser um único tensor de entrada ou um mapa do nome de entrada para tensores (listados em sua forma aninhada natural). Cada entrada pode ter formato arbitrário e não precisa compartilhar a mesma dimensão 0 (também conhecida como tamanho do lote), conforme exigido pelo formato de linha descrito acima.

A representação colunar do exemplo anterior é a seguinte:

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

Observe que inputs é um objeto JSON e não uma lista como instâncias (usadas na representação de linha). Além disso, todas as entradas nomeadas são especificadas juntas, em vez de desenrolá-las em linhas individuais, feito no formato de linha descrito anteriormente. Isto torna a representação compacta (mas talvez menos legível).

Formato de resposta

A solicitação predict retorna um objeto JSON no corpo da resposta.

Uma solicitação em formato de linha tem a resposta formatada da seguinte forma:

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

Se a saída do modelo contiver apenas um tensor nomeado, omitiremos o nome e os mapas de chaves predictions para uma lista de valores escalares ou de lista. Se o modelo gerar vários tensores nomeados, em vez disso, geraremos uma lista de objetos, semelhante à solicitação em formato de linha mencionada acima.

Uma solicitação em formato colunar tem a resposta formatada da seguinte forma:

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

Se a saída do modelo contiver apenas um tensor nomeado, omitimos o nome e outputs mapas de chaves para uma lista de valores escalares ou de lista. Se o modelo gerar vários tensores nomeados, em vez disso, geraremos um objeto. Cada chave deste objeto corresponde a um tensor de saída nomeado. O formato é semelhante ao pedido em formato de coluna mencionado acima.

Saída de valores binários

O TensorFlow não faz distinção entre strings não binárias e binárias. Todos são do tipo DT_STRING . Tensores nomeados que possuem _bytes como sufixo em seu nome são considerados como tendo valores binários. Esses valores são codificados de forma diferente, conforme descrito na seção de codificação de valores binários abaixo.

Mapeamento JSON

As APIs RESTful suportam codificação canônica em JSON, facilitando o compartilhamento de dados entre sistemas. Para tipos suportados, as codificações são descritas tipo por tipo na tabela abaixo. Os tipos não listados abaixo estão implícitos como não suportados.

Tipo de dados TF Valor JSON Exemplo JSON Notas
DT_BOOL verdadeiro, falso verdadeiro, falso
DT_STRING corda "Olá, mundo!" Se DT_STRING representa bytes binários (por exemplo, bytes de imagem serializados ou protobuf), codifique-os em Base64. Consulte Codificando valores binários para obter mais informações.
DT_INT8, DT_UINT8, DT_INT16, DT_INT32, DT_UINT32, DT_INT64, DT_UINT64 número 1, -10, 0 O valor JSON será um número decimal.
DT_FLOAT, DT_DOUBLE número 1,1, -10,0, 0, NaN , Infinity O valor JSON será um número ou um dos valores de token especiais - NaN , Infinity e -Infinity . Consulte Conformidade com JSON para obter mais informações. A notação de expoente também é aceita.

Precisão de ponto flutuante

JSON possui um único tipo de dados numérico. Assim é possível fornecer um valor para uma entrada que resulte em perda de precisão. Por exemplo, se a entrada x for um tipo de dados float e a entrada {"x": 1435774380} for enviada para o modelo rodando em hardware baseado no padrão de ponto flutuante IEEE 754 (por exemplo, Intel ou AMD), então o valor será ser convertido silenciosamente pelo hardware subjacente para 1435774336 pois 1435774380 não pode ser representado exatamente em um número de ponto flutuante de 32 bits. Normalmente, as entradas para servir devem ter a mesma distribuição que o treinamento, então isso geralmente não será problemático porque as mesmas conversões aconteceram no momento do treinamento. No entanto, caso seja necessária precisão total, certifique-se de usar um tipo de dados subjacente em seu modelo que possa lidar com a precisão desejada e/ou considere a verificação do lado do cliente.

Codificando valores binários

JSON usa codificação UTF-8. Se você tiver recursos de entrada ou valores de tensor que precisam ser binários (como bytes de imagem), você deve codificar os dados em Base64 e encapsulá-los em um objeto JSON tendo b64 como chave da seguinte forma:

{ "b64": <base64 encoded string> }

Você pode especificar este objeto como um valor para um recurso ou tensor de entrada. O mesmo formato também é usado para codificar a resposta de saída.

Uma solicitação de classificação com recursos image (dados binários) e caption é mostrada abaixo:

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

Conformidade JSON

Muitos valores de recursos ou tensores são números de ponto flutuante. Além dos valores finitos (por exemplo, 3,14, 1,0 etc.), eles podem ter valores NaN e não finitos ( Infinity e -Infinity ). Infelizmente a especificação JSON ( RFC 7159 ) NÃO reconhece esses valores (embora a especificação JavaScript reconheça).

A API REST descrita nesta página permite que objetos JSON de solicitação/resposta tenham tais valores. Isso implica que solicitações como a seguinte são válidas:

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

Um analisador JSON compatível com padrões (estritos) rejeitará isso com um erro de análise (devido aos tokens NaN e Infinity misturados com números reais). Para lidar corretamente com solicitações/respostas em seu código, use um analisador JSON que suporte esses tokens.

Os tokens NaN , Infinity , -Infinity são reconhecidos pelo proto3 , módulo Python JSON e linguagem JavaScript.

Exemplo

Podemos usar o modelo de brinquedo half_plus_três para ver as APIs REST em ação.

Inicie o ModelServer com o terminal da API REST

Baixe o modelo half_plus_three do repositório git :

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

Usaremos o Docker para executar o ModelServer. Se você deseja instalar o ModelServer nativamente em seu sistema, siga as instruções de configuração para instalar e inicie o ModelServer com a opção --rest_api_port para exportar o endpoint da API REST (isso não é necessário ao usar o 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 ...

Faça chamadas de API REST para ModelServer

Em um terminal diferente, use a ferramenta curl para fazer chamadas à API REST.

Obtenha o status do modelo da seguinte maneira:

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

Uma chamada predict teria a seguinte aparência:

$ 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 uma chamada de regress tem a seguinte aparência:

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

Observe que regress está disponível em um nome de assinatura não padrão e deve ser especificada explicitamente. Um URL ou corpo de solicitação incorreto retorna um status de erro 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)" }
$