В дополнение к API-интерфейсам gRPC TensorFlow ModelServer также поддерживает API-интерфейсы RESTful. На этой странице описаны эти конечные точки API и комплексный пример использования.
Запрос и ответ представляют собой объект JSON. Состав этого объекта зависит от типа запроса или глагола. Подробности см. в разделах, посвященных API, ниже.
В случае ошибки все API вернут объект JSON в теле ответа с error
в качестве ключа и сообщением об ошибке в качестве значения:
{
"error": <error message string>
}
API статуса модели
Этот API очень похож на API gRPC ModelService.GetModelStatus
. Он возвращает статус модели на ModelServer.
URL-адрес
GET http://host:port/v1/models/${MODEL_NAME}[/versions/${VERSION}|/labels/${LABEL}]
Включение /versions/${VERSION}
или /labels/${LABEL}
не является обязательным. Если опущен статус для всех версий, возвращается в ответе.
Формат ответа
В случае успеха возвращает JSON-представление protobuf GetModelStatusResponse
.
API метаданных модели
Этот API тесно связан с API gRPC PredictionService.GetModelMetadata
. Он возвращает метаданные модели в ModelServer.
URL-адрес
GET http://host:port/v1/models/${MODEL_NAME}[/versions/${VERSION}|/labels/${LABEL}]/metadata
Включение /versions/${VERSION}
или /labels/${LABEL}
не является обязательным. Если этот параметр опущен, в ответе возвращаются метаданные модели для последней версии.
Формат ответа
В случае успеха возвращает JSON-представление GetModelMetadataResponse
protobuf.
API классификации и регрессии
Этот API точно соответствует методам Classify
и Regress
API PredictionService
gRPC.
URL-адрес
POST http://host:port/v1/models/${MODEL_NAME}[/versions/${VERSION}|/labels/${LABEL}]:(classify|regress)
Включение /versions/${VERSION}
или /labels/${LABEL}
не является обязательным. Если этот параметр опущен, используется последняя версия.
Формат запроса
Тело запроса для API classify
и regress
должно представлять собой объект JSON, отформатированный следующим образом:
{
// 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>
— это число JSON (целое или десятичное), строка JSON или объект JSON, представляющий двоичные данные (подробности см. в разделе «Кодирование двоичных значений» ниже). <list>
— список таких значений. Этот формат аналогичен прототипам ClassificationRequest
и RegressionRequest
gRPC. Обе версии принимают список объектов- Example
.
Формат ответа
Запрос classify
возвращает объект JSON в теле ответа, отформатированный следующим образом:
{
"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>
— это строка (которая может быть пустой строкой ""
если у модели нет метки, связанной с оценкой). <score>
— десятичное число (с плавающей запятой).
Запрос regress
возвращает объект JSON в теле ответа, отформатированный следующим образом:
{
// One regression value for each example in the request in the same order.
"result": [ <value1>, <value2>, <value3>, ...]
}
<value>
— десятичное число.
Пользователи gRPC API заметят сходство этого формата с прототипами ClassificationResponse
и RegressionResponse
.
Прогноз API
Этот API тесно связан с API PredictionService.Predict
gRPC.
URL-адрес
POST http://host:port/v1/models/${MODEL_NAME}[/versions/${VERSION}|/labels/${LABEL}]:predict
Включение /versions/${VERSION}
или /labels/${LABEL}
не является обязательным. Если этот параметр опущен, используется последняя версия.
Формат запроса
Тело запроса для API predict
должно быть объектом JSON, отформатированным следующим образом:
{
// (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>
}
Указание входных тензоров в формате строки.
Этот формат аналогичен прототипу PredictRequest
API gRPC и API прогнозирования CMLE . Используйте этот формат, если все именованные входные тензоры имеют одинаковое 0-е измерение . Если это не так, используйте столбчатый формат, описанный ниже.
В формате строки входные данные привязаны к ключу экземпляров в запросе JSON.
Если имеется только один именованный ввод, укажите значение ключа экземпляров , которое будет значением ввода:
{
// List of 3 scalar tensors.
"instances": [ "foo", "bar", "baz" ]
}
{
// List of 2 tensors each of [1, 2] shape
"instances": [ [[1, 2]], [[3, 4]] ]
}
Тензоры естественным образом выражаются во вложенной записи, поскольку нет необходимости вручную сглаживать список.
Для нескольких именованных входных данных ожидается, что каждый элемент будет объектом, содержащим пару входное имя/значение тензора, по одному для каждого именованного входа. В качестве примера ниже приведен запрос с двумя экземплярами, каждый из которых имеет набор из трех именованных входных тензоров:
{
"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]]
}
]
}
Обратите внимание: неявно предполагается, что каждый именованный вход («тег», «сигнал», «датчик») имеет одно и то же 0-е измерение ( два в приведенном выше примере, поскольку в списке экземпляров есть два объекта). Если вы назвали входные данные, которые имеют другое 0-е измерение, используйте столбчатый формат, описанный ниже.
Указание входных тензоров в формате столбца.
Используйте этот формат, чтобы указать входные тензоры, если отдельные именованные входы не имеют одинакового 0-го измерения или вам нужно более компактное представление. Этот формат аналогичен полю inputs
запроса gRPC Predict
.
В столбчатом формате входные данные привязаны к ключу входных данных в запросе JSON.
Значением входного ключа может быть либо один входной тензор, либо отображение входного имени в тензоры (перечисленные в их естественной вложенной форме). Каждый ввод может иметь произвольную форму и не обязательно должен иметь одно и то же 0-е измерение (то есть размер пакета), как того требует формат строки, описанный выше.
Столбчатое представление предыдущего примера выглядит следующим образом:
{
"inputs": {
"tag": ["foo", "bar"],
"signal": [[1, 2, 3, 4, 5], [3, 4, 1, 2, 5]],
"sensor": [[[1, 2], [3, 4]], [[4, 5], [6, 8]]]
}
}
Обратите внимание: входные данные — это объект JSON, а не экземпляры , подобные списку (используемые в представлении строк). Кроме того, все именованные входные данные указываются вместе, а не развертываются в отдельные строки в формате строк, описанном ранее. Это делает представление компактным (но, возможно, менее читаемым).
Формат ответа
Запрос predict
возвращает объект JSON в теле ответа.
Запрос в формате строки имеет ответ, отформатированный следующим образом:
{
"predictions": <value>|<(nested)list>|<list-of-objects>
}
Если выходные данные модели содержат только один именованный тензор, мы опускаем имя и карты ключей predictions
в список скалярных или списочных значений. Если модель выводит несколько именованных тензоров, вместо этого мы выводим список объектов, аналогично запросу в формате строки, упомянутому выше.
Запрос в столбцовом формате имеет ответ, отформатированный следующим образом:
{
"outputs": <value>|<(nested)list>|<object>
}
Если выходные данные модели содержат только один именованный тензор, мы опускаем имя и outputs
карты ключей в список скалярных или списочных значений. Если модель выводит несколько именованных тензоров, вместо этого мы выводим объект. Каждый ключ этого объекта соответствует именованному выходному тензору. Формат аналогичен запросу в формате столбца, упомянутому выше.
Вывод двоичных значений
TensorFlow не различает недвоичные и двоичные строки. Все они относятся к типу DT_STRING
. Именованные тензоры, у которых в имени есть суффикс _bytes
считаются имеющими двоичные значения. Такие значения кодируются по-другому, как описано в разделе кодирования двоичных значений ниже.
JSON-сопоставление
API-интерфейсы RESTful поддерживают каноническую кодировку JSON, что упрощает обмен данными между системами. Для поддерживаемых типов кодировки описаны по типам в таблице ниже. Подразумевается, что типы, не перечисленные ниже, не поддерживаются.
Тип данных ТФ | Значение JSON | Пример JSON | Примечания |
---|---|---|---|
DT_BOOL | правда, ложь | правда, ложь | |
DT_STRING | нить | "Привет, мир!" | Если DT_STRING представляет двоичные байты (например, байты сериализованного изображения или protobuf), закодируйте их в Base64. Дополнительную информацию см. в разделе «Кодирование двоичных значений» . |
DT_INT8, DT_UINT8, DT_INT16, DT_INT32, DT_UINT32, DT_INT64, DT_UINT64 | число | 1, -10, 0 | Значение JSON будет десятичным числом. |
DT_FLOAT, DT_DOUBLE | число | 1,1, -10,0, 0, NaN , Infinity | Значением JSON будет число или одно из специальных значений токена — NaN , Infinity и -Infinity . Дополнительную информацию см. в разделе «Соответствие JSON» . Также допускается экспоненциальное обозначение. |
Точность с плавающей запятой
JSON имеет один числовой тип данных. Таким образом, можно указать значение для входных данных, что приведет к потере точности. Например, если входные данные x
имеют тип данных float
, а входные данные {"x": 1435774380}
отправляются в модель, работающую на оборудовании на основе стандарта с плавающей запятой IEEE 754 (например, Intel или AMD), тогда значение будет быть автоматически преобразовано базовым оборудованием в 1435774336
поскольку 1435774380
не может быть точно представлено в 32-битном числе с плавающей запятой. Как правило, входные данные для обслуживания должны распределяться так же, как и для обучения, поэтому в целом это не будет проблемой, поскольку во время обучения происходили те же самые конверсии. Однако, если необходима полная точность, обязательно используйте в своей модели базовый тип данных, который может обеспечить желаемую точность, и/или рассмотрите возможность проверки на стороне клиента.
Кодирование двоичных значений
JSON использует кодировку UTF-8. Если у вас есть входные функции или значения тензора, которые должны быть двоичными (например, байты изображения), вы должны закодировать данные в Base64 и инкапсулировать их в объект JSON, имеющий ключ b64
, следующим образом:
{ "b64": <base64 encoded string> }
Вы можете указать этот объект как значение для входного объекта или тензора. Тот же формат используется и для кодирования выходного ответа.
Запрос классификации с image
(двоичными данными) и caption
показан ниже:
{
"signature_name": "classify_objects",
"examples": [
{
"image": { "b64": "aW1hZ2UgYnl0ZXM=" },
"caption": "seaside"
},
{
"image": { "b64": "YXdlc29tZSBpbWFnZSBieXRlcw==" },
"caption": "mountains"
}
]
}
Соответствие JSON
Многие значения признаков или тензоров представляют собой числа с плавающей запятой. Помимо конечных значений (например, 3,14, 1,0 и т. д.), они могут иметь значения NaN
и неконечные ( Infinity
и -Infinity
). К сожалению, спецификация JSON ( RFC 7159 ) НЕ распознает эти значения (хотя спецификация JavaScript распознает).
REST API, описанный на этой странице, позволяет объектам JSON запроса/ответа иметь такие значения. Это означает, что допустимы запросы, подобные следующему:
{
"example": [
{
"sensor_readings": [ 1.0, -3.14, Nan, Infinity ]
}
]
}
Парсер JSON, соответствующий (строгим) стандартам, отклонит это с ошибкой анализа (из-за того, что токены NaN
и Infinity
смешаны с фактическими числами). Чтобы правильно обрабатывать запросы/ответы в вашем коде, используйте анализатор JSON, поддерживающий эти токены.
Токены NaN
, Infinity
, -Infinity
распознаются proto3 , модулем Python JSON и языком JavaScript.
Пример
Мы можем использовать игрушечную модель half_plus_three , чтобы увидеть REST API в действии.
Запустите ModelServer с конечной точкой REST API.
Загрузите модель half_plus_three
из репозитория git :
$ mkdir -p /tmp/tfserving
$ cd /tmp/tfserving
$ git clone --depth=1 https://github.com/tensorflow/serving
Мы будем использовать Docker для запуска ModelServer. Если вы хотите установить ModelServer в своей системе, следуйте инструкциям по установке и запустите ModelServer с параметром --rest_api_port
чтобы экспортировать конечную точку REST API (это не требуется при использовании 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 ...
Выполнение вызовов REST API для ModelServer.
В другом терминале используйте инструмент curl
для выполнения вызовов REST API.
Получите статус модели следующим образом:
$ curl http://localhost:8501/v1/models/saved_model_half_plus_three
{
"model_version_status": [
{
"version": "123",
"state": "AVAILABLE",
"status": {
"error_code": "OK",
"error_message": ""
}
}
]
}
predict
вызов будет выглядеть следующим образом:
$ 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]
}
И вызов regress
выглядит следующим образом:
$ 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]
}
Обратите внимание, regress
доступен для имени подписи, отличного от умолчанию, и его необходимо указать явно. Неправильный URL-адрес или тело запроса возвращает статус ошибки 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)" }
$