RESTful API

علاوه بر API های gRPC، TensorFlow ModelServer از API های RESTful نیز پشتیبانی می کند. این صفحه این نقاط پایانی API و یک مثال سرتاسری در مورد استفاده را شرح می‌دهد.

درخواست و پاسخ یک شی JSON است. ترکیب این شی به نوع درخواست یا فعل بستگی دارد. برای جزئیات بیشتر به بخش های خاص API در زیر مراجعه کنید.

در صورت بروز خطا، همه API ها یک شی JSON را در بدنه پاسخ با error به عنوان کلید و پیام خطا به عنوان مقدار برمی گردانند:

{
  "error": <error message string>
}

API وضعیت مدل

این API دقیقاً از ModelService.GetModelStatus gRPC API پیروی می کند. وضعیت یک مدل را در ModelServer برمی گرداند.

URL

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

شامل /versions/${VERSION} یا /labels/${LABEL} اختیاری است. اگر وضعیت حذف شده برای همه نسخه ها در پاسخ برگردانده شود.

فرمت پاسخ

در صورت موفقیت آمیز بودن، یک نمایش JSON از پروتوباف GetModelStatusResponse را برمی گرداند.

API فراداده مدل

این API دقیقاً از PredictionService.GetModelMetadata gRPC API پیروی می کند. فراداده یک مدل را در ModelServer برمی گرداند.

URL

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

شامل /versions/${VERSION} یا /labels/${LABEL} اختیاری است. در صورت حذف، فراداده مدل برای آخرین نسخه در پاسخ بازگردانده می شود.

فرمت پاسخ

در صورت موفقیت آمیز بودن، یک نمایش JSON از پروتوباف GetModelMetadataResponse را برمی گرداند.

طبقه بندی و پسرفت API

این API دقیقاً از روش‌های Classify و Regress در PredictionService gRPC API پیروی می‌کند.

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 دقیقاً از PredictionService.Predict gRPC API پیروی می کند.

URL

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

شامل /versions/${VERSION} یا /labels/${LABEL} اختیاری است. در صورت حذف از آخرین نسخه استفاده می شود.

فرمت درخواست

بدنه درخواست برای predict API باید شی 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 از gRPC API و CMLE predict API است. اگر همه تانسورهای ورودی نام‌گذاری‌شده دارای بعد صفر هستند از این قالب استفاده کنید. اگر این کار را نمی کنند، از قالب ستونی که در ادامه در زیر توضیح داده شده است استفاده کنید.

در قالب ردیف، ورودی‌ها به کلید نمونه‌ها در درخواست JSON کلید می‌خورند.

وقتی فقط یک ورودی با نام وجود دارد، مقدار کلید instances را به مقدار ورودی مشخص کنید:

{
  // 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 کلید می زنند.

مقدار کلید ورودی‌ها می‌تواند یک تانسور ورودی منفرد یا یک نقشه از نام ورودی برای تانسورها (در شکل تودرتوی طبیعی آنها فهرست شده باشد). هر ورودی می‌تواند شکل دلخواه داشته باشد و نیازی نیست همان بعد صفر (معروف به اندازه دسته‌ای) که در قالب ردیف توضیح داده شده در بالا مورد نیاز است، به اشتراک بگذارد.

نمایش ستونی مثال قبلی به شرح زیر است:

{
 "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 پشتیبانی می کنند که اشتراک گذاری داده ها را بین سیستم ها آسان تر می کند. برای انواع پشتیبانی شده، رمزگذاری ها بر اساس نوع به نوع در جدول زیر توضیح داده شده است. انواعی که در زیر فهرست نشده اند، به طور ضمنی پشتیبانی نمی شوند.

نوع داده TF مقدار 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 اجرا می شود (مثلاً اینتل یا 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 ) این مقادیر را تشخیص نمی دهد (اگرچه مشخصات جاوا اسکریپت این کار را می کند).

REST API توضیح داده شده در این صفحه به اشیاء JSON درخواست/پاسخ اجازه می دهد چنین مقادیری داشته باشند. این بدان معناست که درخواست هایی مانند مورد زیر معتبر هستند:

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

تجزیه‌کننده JSON مطابق با استانداردهای (سخت) این را با خطای تجزیه رد می‌کند (به دلیل مخلوط شدن توکن‌های NaN و Infinity با اعداد واقعی). برای رسیدگی صحیح به درخواست ها/پاسخ ها در کد خود، از تجزیه کننده JSON استفاده کنید که از این نشانه ها پشتیبانی می کند.

توکن های NaN ، Infinity ، -Infinity توسط proto3 ، ماژول JSON پایتون و زبان جاوا اسکریپت شناسایی می شوند.

مثال

ما می‌توانیم از مدل toy 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)" }
$