API RESTful

Além das APIs gRPC, o TensorFlow ModelServer também suporta 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

Essa 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 omitido para todas as versões for retornado na resposta.

Formato de resposta

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

API de metadados do modelo

Essa API segue de perto a API PredictionService.GetModelMetadata gRPC. 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 para a versão mais recente são retornados na resposta.

Formato de resposta

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

Classificar e regredir API

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

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 é 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), 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 de tais valores. Esse 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 perceberão a semelhança desse formato com os protos ClassificationResponse e RegressionResponse .

API de previsão

Essa 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 é usada.

Formato de solicitação

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

{
  // (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 no formato de linha.

Esse formato é semelhante ao proto PredictRequest da API gRPC e da 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 a chave de instâncias na solicitação JSON.

Quando houver apenas uma entrada nomeada, especifique o valor da chave de instâncias para ser 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 várias entradas nomeadas, espera-se que cada item seja um objeto contendo par de nome de entrada/valor do tensor, um para cada entrada nomeada. Como exemplo, veja a seguir 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ões 0 diferentes, use o formato colunar descrito abaixo.

Especificando tensores de entrada no 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ê quiser uma representação mais compacta. Esse 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 inputs pode ser um único tensor de entrada ou um mapa de nomes de entrada para tensores (listados em sua forma aninhada natural). Cada entrada pode ter uma forma arbitrária 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 as entradas são 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 desdobrá-las em linhas individuais feitas no formato de linha descrito anteriormente. Isso 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 resposta formatada da seguinte forma:

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

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

Uma solicitação em formato colunar tem 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 chave para uma lista de escalares ou valores de lista. Se o modelo gerar vários tensores nomeados, em vez disso, geramos 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 têm _bytes como sufixo em seus nomes são considerados valores binários. Esses valores são codificados de maneira diferente, conforme descrito na seção de valores binários de codificação abaixo.

Mapeamento JSON

As APIs RESTful suportam uma codificação canônica em JSON, facilitando o compartilhamento de dados entre sistemas. Para os tipos suportados, as codificações são descritas tipo a 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 de 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 Codificação de 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 JSON para obter mais informações. A notação de expoentes também é aceita.

Precisão de Ponto Flutuante

JSON tem um único tipo de dados de número. 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 executado em hardware baseado no padrão de ponto flutuante IEEE 754 (por exemplo, Intel ou AMD), o valor será ser silenciosamente convertido pelo hardware underyling para 1435774336 , pois 1435774380 não pode ser representado exatamente em um número de ponto flutuante de 32 bits. Normalmente, as entradas para veiculação devem ter a mesma distribuição do treinamento, então isso geralmente não será problemático porque as mesmas conversões ocorreram 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 valores de recurso ou tensor de entrada 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 a chave da seguinte forma:

{ "b64": <base64 encoded string> }

Você pode especificar esse objeto como um valor para um recurso de entrada ou tensor. 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 é apresentada a seguir:

{
  "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 de 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 esses 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 compatível com esses tokens.

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

Exemplo

Podemos usar o modelo toy half_plus_three para ver as APIs REST em ação.

Inicie o ModelServer com o endpoint 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 de 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 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)" }
$