RESTful API

Oprócz interfejsów API gRPC TensorFlow ModelServer obsługuje także interfejsy API RESTful. Na tej stronie opisano punkty końcowe interfejsu API oraz kompleksowy przykład użycia.

Żądanie i odpowiedź są obiektem JSON. Skład tego obiektu zależy od typu żądania lub czasownika. Aby uzyskać szczegółowe informacje, zobacz poniższe sekcje dotyczące interfejsu API.

W przypadku błędu wszystkie interfejsy API zwrócą obiekt JSON w treści odpowiedzi z error jako kluczem i komunikatem o błędzie jako wartością:

{
  "error": <error message string>
}

Interfejs API stanu modelu

Ten interfejs API jest ściśle zgodny z interfejsem API gRPC ModelService.GetModelStatus . Zwraca status modelu na serwerze ModelServer.

Adres URL

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

Dołączenie /versions/${VERSION} lub /labels/${LABEL} jest opcjonalne. W przypadku pominięcia w odpowiedzi zwracany jest status dla wszystkich wersji.

Format odpowiedzi

Jeśli się powiedzie, zwraca reprezentację JSON protobuf GetModelStatusResponse .

Modelowy interfejs API metadanych

Ten interfejs API jest ściśle zgodny z interfejsem API PredictionService.GetModelMetadata gRPC. Zwraca metadane modelu w ModelServer.

Adres URL

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

Dołączenie /versions/${VERSION} lub /labels/${LABEL} jest opcjonalne. W przypadku pominięcia w odpowiedzi zwracane są metadane modelu dla najnowszej wersji.

Format odpowiedzi

Jeśli się powiedzie, zwraca reprezentację JSON protobuf GetModelMetadataResponse .

Klasyfikacja i regresja API

Ten interfejs API jest ściśle zgodny z metodami Classify i Regress interfejsu API gRPC PredictionService .

Adres URL

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

Dołączenie /versions/${VERSION} lub /labels/${LABEL} jest opcjonalne. W przypadku pominięcia używana jest najnowsza wersja.

Format żądania

Treść żądania dla interfejsów API classify i regress musi być obiektem JSON sformatowanym w następujący sposób:

{
  // 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> to liczba JSON (cała lub dziesiętna), ciąg JSON lub obiekt JSON reprezentujący dane binarne (szczegółowe informacje można znaleźć w sekcji Kodowanie wartości binarnych poniżej). <list> to lista takich wartości. Ten format jest podobny do protosów ClassificationRequest i RegressionRequest gRPC. Obie wersje akceptują listę obiektów Example .

Format odpowiedzi

Żądanie classify zwraca obiekt JSON w treści odpowiedzi, sformatowany w następujący sposób:

{
  "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> to ciąg znaków (który może być pustym ciągiem "" jeśli model nie ma etykiety powiązanej z wynikiem). <score> to liczba dziesiętna (zmiennoprzecinkowa).

Żądanie regress zwraca obiekt JSON w treści odpowiedzi w następującym formacie:

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

<value> jest liczbą dziesiętną.

Użytkownicy gRPC API zauważą podobieństwo tego formatu do protosów ClassificationResponse i RegressionResponse .

Przewiduj API

Ten interfejs API jest ściśle zgodny z interfejsem API PredictionService.Predict gRPC.

Adres URL

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

Dołączenie /versions/${VERSION} lub /labels/${LABEL} jest opcjonalne. W przypadku pominięcia używana jest najnowsza wersja.

Format żądania

Treść żądania dla predict API musi być obiektem JSON sformatowanym w następujący sposób:

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

Określanie tensorów wejściowych w formacie wierszowym.

Ten format jest podobny do prototypu PredictRequest interfejsu API gRPC i interfejsu API przewidywania CMLE . Użyj tego formatu, jeśli wszystkie nazwane tensory wejściowe mają ten sam wymiar zerowy . Jeśli tak nie jest, użyj formatu kolumnowego opisanego poniżej.

W formacie wierszowym dane wejściowe są przypisane do klucza instancji w żądaniu JSON.

Jeśli istnieje tylko jedno nazwane wejście, określ wartość klucza instancji jako wartość wejścia:

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

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

Tensory są wyrażane naturalnie w notacji zagnieżdżonej, ponieważ nie ma potrzeby ręcznego spłaszczania listy.

W przypadku wielu nazwanych danych wejściowych oczekuje się, że każdy element będzie obiektem zawierającym parę nazwa wejścia/wartość tensora, po jednym dla każdego nazwanego wejścia. Jako przykład, poniżej znajduje się żądanie z dwoma instancjami, każda z zestawem trzech nazwanych tensorów wejściowych:

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

Należy zauważyć, że każde nazwane wejście („znacznik”, „sygnał”, „czujnik”) domyślnie zakłada się, że ma ten sam wymiar zerowy (w powyższym przykładzie dwa , ponieważ na liście instancji znajdują się dwa obiekty). Jeśli nazwałeś dane wejściowe, które mają inny wymiar zerowy, użyj formatu kolumnowego opisanego poniżej.

Określanie tensorów wejściowych w formacie kolumnowym.

Użyj tego formatu, aby określić tensory wejściowe, jeśli poszczególne nazwane dane wejściowe nie mają tego samego wymiaru zerowego lub chcesz uzyskać bardziej zwartą reprezentację. Ten format jest podobny do pola inputs żądania gRPC Predict .

W formacie kolumnowym dane wejściowe są przypisane do klucza wejściowego w żądaniu JSON.

Wartością klucza wejściowego może być pojedynczy tensor wejściowy lub mapa nazwy wejściowej na tensory (wymienione w ich naturalnej formie zagnieżdżonej). Każde dane wejściowe mogą mieć dowolny kształt i nie muszą mieć tego samego wymiaru zerowego (czyli rozmiaru partii), jak wymaga tego format wiersza opisany powyżej.

Kolumnowa reprezentacja poprzedniego przykładu wygląda następująco:

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

Uwaga, inputs to obiekt JSON, a nie instancje przypominające listę (używane w reprezentacji wierszy). Ponadto wszystkie nazwane dane wejściowe są określone razem, w przeciwieństwie do rozwijania ich w poszczególnych wierszach w formacie wierszy opisanym wcześniej. Dzięki temu reprezentacja jest zwarta (ale może mniej czytelna).

Format odpowiedzi

Żądanie predict zwraca obiekt JSON w treści odpowiedzi.

Żądanie w formacie wiersza ma odpowiedź sformatowaną w następujący sposób:

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

Jeśli dane wyjściowe modelu zawierają tylko jeden nazwany tensor, pomijamy odwzorowania kluczy nazw i predictions na listę wartości skalarnych lub listowych. Jeśli model generuje wiele nazwanych tensorów, zamiast tego wyświetlamy listę obiektów, podobnie jak w przypadku żądania w formacie wierszowym wspomnianego powyżej.

Żądanie w formacie kolumnowym ma odpowiedź w następującym formacie:

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

Jeśli dane wyjściowe modelu zawierają tylko jeden nazwany tensor, pomijamy tę nazwę i outputs mapy kluczy na liście wartości skalarnych lub listowych. Jeśli model generuje wiele nazwanych tensorów, zamiast tego wyprowadzamy obiekt. Każdy klucz tego obiektu odpowiada nazwanemu tensorowi wyjściowemu. Format jest podobny do żądania w formacie kolumnowym wspomnianego powyżej.

Wyjście wartości binarnych

TensorFlow nie rozróżnia ciągów niebinarnych i binarnych. Wszystkie są typu DT_STRING . Nazwane tensory, które w nazwie mają przyrostek _bytes są uważane za posiadające wartości binarne. Takie wartości są kodowane inaczej, jak opisano w sekcji kodowania wartości binarnych poniżej.

Mapowanie JSON

Interfejsy API RESTful obsługują kodowanie kanoniczne w formacie JSON, co ułatwia udostępnianie danych pomiędzy systemami. W przypadku obsługiwanych typów kodowanie opisano według typu w poniższej tabeli. Typy niewymienione poniżej są uważane za nieobsługiwane.

Typ danych TF Wartość JSON Przykład JSON-a Notatki
DT_BOOL prawda, fałsz prawda, fałsz
DT_STRING smyczkowy „Witaj, świecie!” Jeśli DT_STRING reprezentuje bajty binarne (np. serializowane bajty obrazu lub protobuf), zakoduj je w Base64. Aby uzyskać więcej informacji, zobacz Kodowanie wartości binarnych .
DT_INT8, DT_UINT8, DT_INT16, DT_INT32, DT_UINT32, DT_INT64, DT_UINT64 numer 1, -10, 0 Wartość JSON będzie liczbą dziesiętną.
DT_FLOAT, DT_DOUBLE numer 1,1, -10,0, 0, NaN , Infinity Wartość JSON będzie liczbą lub jedną ze specjalnych wartości tokenów - NaN , Infinity i -Infinity . Aby uzyskać więcej informacji, zobacz zgodność z JSON . Akceptowany jest również zapis wykładniczy.

Precyzja zmiennoprzecinkowa

JSON ma typ danych z pojedynczą liczbą. W ten sposób możliwe jest podanie wartości wejściowej, która powoduje utratę precyzji. Na przykład, jeśli wejście x jest typem danych float , a wejście {"x": 1435774380} zostanie wysłane do modelu działającego na sprzęcie opartym na standardzie zmiennoprzecinkowym IEEE 754 (np. Intel lub AMD), wówczas wartość zostanie zostać po cichu przekonwertowany przez sprzęt podkładowy na 1435774336 ponieważ 1435774380 nie może być dokładnie reprezentowane w 32-bitowej liczbie zmiennoprzecinkowej. Zazwyczaj dane wejściowe do obsługi powinny mieć taki sam rozkład jak szkolenie, więc generalnie nie będzie to problematyczne, ponieważ te same konwersje miały miejsce w czasie treningu. Jeśli jednak wymagana jest pełna precyzja, pamiętaj o użyciu w modelu podstawowego typu danych, który może obsłużyć żądaną precyzję, i/lub rozważ sprawdzenie po stronie klienta.

Kodowanie wartości binarnych

JSON używa kodowania UTF-8. Jeśli masz funkcję wejściową lub wartości tensora, które muszą być binarne (np. bajty obrazu), musisz zakodować dane w formacie Base64 i enkapsulować je w obiekcie JSON z kluczem b64 w następujący sposób:

{ "b64": <base64 encoded string> }

Można określić ten obiekt jako wartość cechy wejściowej lub tensora. Ten sam format jest również używany do kodowania odpowiedzi wyjściowej.

Poniżej przedstawiono żądanie klasyfikacji zawierające image (dane binarne) i caption :

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

Zgodność z JSON

Wiele wartości cech lub tensorów to liczby zmiennoprzecinkowe. Oprócz wartości skończonych (np. 3,14, 1,0 itd.) mogą one mieć wartości NaN i nieskończone ( Infinity i -Infinity ). Niestety specyfikacja JSON ( RFC 7159 ) NIE rozpoznaje tych wartości (chociaż specyfikacja JavaScript tak).

Interfejs API REST opisany na tej stronie umożliwia obiektom JSON żądania/odpowiedzi przyjmowanie takich wartości. Oznacza to, że żądania takie jak poniższe są prawidłowe:

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

Parser JSON zgodny z (ścisłymi) standardami odrzuci to z błędem analizy (z powodu zmieszania tokenów NaN i Infinity z rzeczywistymi liczbami). Aby poprawnie obsługiwać żądania/odpowiedzi w kodzie, użyj parsera JSON obsługującego te tokeny.

Tokeny NaN , Infinity , -Infinity rozpoznawane są przez proto3 , moduł Python JSON i język JavaScript.

Przykład

Możemy użyć zabawkowego modelu half_plus_three , aby zobaczyć interfejsy API REST w akcji.

Uruchom ModelServer z punktem końcowym interfejsu API REST

Pobierz model half_plus_three z repozytorium git :

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

Do uruchomienia ModelServera użyjemy Dockera. Jeśli chcesz zainstalować ModelServer natywnie w swoim systemie, postępuj zgodnie z instrukcjami instalacji , aby zamiast tego zainstalować i uruchomić ModelServer z opcją --rest_api_port , aby wyeksportować punkt końcowy API REST (nie jest to potrzebne w przypadku korzystania z Dockera).

$ 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 ...

Wykonuj wywołania API REST do ModelServer

W innym terminalu użyj narzędzia curl , aby wykonać wywołania API REST.

Uzyskaj status modelu w następujący sposób:

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

Wywołanie predict wyglądałoby następująco:

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

A wywołanie regress wygląda następująco:

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

Uwaga: regress jest dostępna w przypadku nazwy podpisu innej niż domyślna i musi zostać określona jawnie. Nieprawidłowy adres URL lub treść żądania zwraca stan błędu 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)" }
$