程式碼研究室簡介
1. 事前準備
在這個程式碼研究室中,您將瞭解如何使用 TensorFlow Serving 搭配 REST 和 gRPC 執行網站的圖片分類推論。
必要條件
- 網頁開發的基本知識,例如 HTML 和 JavaScript
- TensorFlow 機器學習基本知識,例如訓練和部署
- 終端機和 Docker 的基本知識
課程內容
- 如何在 TensorFlow Hub 中尋找預先訓練的圖片分類模型。
- 如何使用 TensorFlow Serving (REST 和 gRPC) 使用下載的圖片分類模型建構簡易型網站並產生預測。
- 如何在 UI 中呈現偵測結果。
軟硬體需求
- Docker
- Google Chrome
- 適用 Chrome 的網路伺服器
- Node.js 和 NPM
- 現金
- 通訊協定緩衝區編譯器 (除非您想要重新產生 gRPC 存根) 才適用。
- gRPC-web 程式碼產生器外掛程式 (除非您想要重新產生 gRPC 虛設常式,才需要使用)
2. 做好準備
若要下載此程式碼研究室的程式碼:
- 前往這個 GitHub 存放區。
- 按一下 [程式碼 >下載 zip],即可下載這個程式碼研究室的所有程式碼。
- 將下載的 ZIP 檔案解壓縮,解壓縮您需要的所有
codelabs
根資料夾。
在這個程式碼研究室中,您只需要存放區的 TFServing/ImageClassificationWeb
子目錄中的檔案,其中包含兩個資料夾:
starter
資料夾包含您為這個程式碼研究室建立的範例程式碼。finished
資料夾包含已完成範例應用程式的範例程式碼。
4. 執行入門網站
使用 Web Server for Chrome 載入 TFServing/ImageClassificationWeb/starter/dist/index.html
檔案:
- 在 Chrome 的網址列中輸入
Chrome://apps/
,然後在應用程式清單中找到 Chrome 網路伺服器。 - 啟動 Chrome 的 Web Server,然後選擇
TFServing/ImageClassificationWeb/starter/dist/
資料夾。 - 按一下 [網路伺服器] 切換開關來啟用此功能,然後透過瀏覽器前往 http://localhost:8887/。
執行及探索網站
你現在應該可以看到該網站。使用者介面非常簡單:你要分類分類的貓咪圖片,使用者可以透過 REST 或 gRPC 將資料傳送至後端。後端會對圖片執行圖片分類,並將分類結果傳回網站以顯示結果。
假如您按一下 [分類],系統就沒有任何作用,因為這個代理程式尚未與後端通訊。
5. 使用 TensorFlow Serving 部署圖片分類模型
圖片分類是一種常見的機器學習工作,能夠根據圖片的主要內容將圖片分類到預先定義的類別。以下是將花卉分類的範例:
TensorFlow Hub 中有許多預先訓練的圖片分類模型。您在這個程式碼研究室使用的是熱門的 Inception v3 模型。
如何使用 TensorFlow Serving 部署圖片分類模型:
- 下載 Inception v3 模型檔案。
- 使用 7-Zip 等壓縮工具解壓縮已解壓縮的
.tar.gz
檔案。 - 建立
inception_v3
資料夾,然後在其中建立123
子資料夾。 - 將解壓縮的
variables
資料夾和saved_model.pb
檔案放入123
子資料夾。
inception_v3
資料夾可視為 SavedModel
資料夾。123
是範例版本號碼。如有需要,您可以挑選其他號碼。
資料夾結構應如下列所示:
啟動 TensorFlow Serving
- 在終端機中,以 Docker 啟動 TensorFlow Serving,但用電腦上的
inception_v3
資料夾的絕對路徑取代PATH/TO/SAVEDMODEL
。
docker pull tensorflow/serving docker run -it --rm -p 8500:8500 -p 8501:8501 -v "PATH/TO/SAVEDMODEL:/models/inception" -e MODEL_NAME=inception tensorflow/serving
Docker 會先自動下載 TensorFlow Serving 映像檔,這可能需要一分鐘的時間。之後,TensorFlow Serving 會隨即啟動。記錄應如下所示:
2022-02-25 06:01:12.513231: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:206] Restoring SavedModel bundle. 2022-02-25 06:01:12.585012: I external/org_tensorflow/tensorflow/core/platform/profile_utils/cpu_utils.cc:114] CPU Frequency: 3000000000 Hz 2022-02-25 06:01:13.395083: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:190] Running initialization op on SavedModel bundle at path: /models/inception/123 2022-02-25 06:01:13.837562: I external/org_tensorflow/tensorflow/cc/saved_model/loader.cc:277] SavedModel load for tags { serve }; Status: success: OK. Took 1928700 microseconds. 2022-02-25 06:01:13.877848: I tensorflow_serving/servables/tensorflow/saved_model_warmup_util.cc:59] No warmup data file found at /models/inception/123/assets.extra/tf_serving_warmup_requests 2022-02-25 06:01:13.929844: I tensorflow_serving/core/loader_harness.cc:87] Successfully loaded servable version {name: inception version: 123} 2022-02-25 06:01:13.985848: I tensorflow_serving/model_servers/server_core.cc:486] Finished adding/updating models 2022-02-25 06:01:13.985987: I tensorflow_serving/model_servers/server.cc:367] Profiler service is enabled 2022-02-25 06:01:13.988994: I tensorflow_serving/model_servers/server.cc:393] Running gRPC ModelServer at 0.0.0.0:8500 ... [warn] getaddrinfo: address family for nodename not supported 2022-02-25 06:01:14.033872: I tensorflow_serving/model_servers/server.cc:414] Exporting HTTP/REST API at:localhost:8501 ... [evhttp_server.cc : 245] NET_LOG: Entering the event loop ...
6. 設定 Envoy Proxy
基於安全性考量,TensorFlow Serving 目前並未設定 Access-Control-Allow-Origin
標頭,因此瀏覽器會禁止從前端 JavaScript 對 TensorFlow Serving 提出的要求。要解決這個問題,您必須使用 Envoy 這類 Proxy,透過 JavaScript 向 TensorFlow Serving 後端發出要求。
啟動 Envoy
- 在終端機中,下載 Envoy 映像檔並啟動 Envoy 和 Docker,但將
PATH/TO/ENVOY-CUSTOM.YAML
預留位置替換為starter
資料夾中envoy-custom.yaml
檔案的絕對路徑。
docker pull envoyproxy/envoy-dev:fd3e8370ddb7a96634c192d1461516e6de1d1797 docker run --add-host host.docker.internal:host-gateway --rm -it -p 9901:9901 -p 8000:8000 -p 8080:8080 -v PATH/TO/ENVOY-CUSTOM.YAML:/envoy-custom.yaml envoyproxy/envoy-dev:fd3e8370ddb7a96634c192d1461516e6de1d1797 -c /envoy-custom.yaml
Docker 會先自動下載 Envoy 映像檔。之後,Envoy 就會開始運作。記錄應如下所示:
[2022-03-02 07:51:48.563][1][info][main] [source/server/server.cc:436] response trailer map: 152 bytes: grpc-message,grpc-status [2022-03-02 07:51:48.681][1][info][main] [source/server/server.cc:772] runtime: {} [2022-03-02 07:51:48.682][1][info][admin] [source/server/admin/admin.cc:134] admin address: 0.0.0.0:9901 [2022-03-02 07:51:48.683][1][info][config] [source/server/configuration_impl.cc:127] loading tracing configuration [2022-03-02 07:51:48.683][1][info][config] [source/server/configuration_impl.cc:87] loading 0 static secret(s) [2022-03-02 07:51:48.683][1][info][config] [source/server/configuration_impl.cc:93] loading 2 cluster(s) [2022-03-02 07:51:48.687][1][info][config] [source/server/configuration_impl.cc:97] loading 2 listener(s) [2022-03-02 07:51:48.694][1][info][config] [source/server/configuration_impl.cc:109] loading stats configuration [2022-03-02 07:51:48.696][1][info][main] [source/server/server.cc:868] starting main dispatch loop [2022-03-02 07:51:48.881][1][info][runtime] [source/common/runtime/runtime_impl.cc:446] RTDS has finished initialization [2022-03-02 07:51:48.881][1][info][upstream] [source/common/upstream/cluster_manager_impl.cc:207] cm init: all clusters initialized [2022-03-02 07:51:48.881][1][info][main] [source/server/server.cc:849] all clusters initialized. initializing init manager [2022-03-02 07:51:48.881][1][info][config] [source/server/listener_manager_impl.cc:784] all dependencies initialized. starting workers [2022-03-02 07:51:48.902][1][warning][main] [source/server/server.cc:747] there is no configured limit to the number of allowed active connections. Set a limit via the runtime key overload.global_downstream_max_connections
7. 透過 REST 將網站與 TensorFlow 連結
後端已就緒,您可以傳送用戶端要求至 TensorFlow Serving,將圖片分類。向 TensorFlow Serving 傳送要求的方法有兩種:
- REST
- gRPC
透過 REST 傳送要求及接收回應
透過 REST 收發要求有三個簡單的步驟:
- 建立 REST 要求。
- 將 REST 要求傳送至 TensorFlow Serving。
- 從 REST 回應中擷取預測結果,並顯示結果。
您可以在 src/index.js
檔案中完成這些步驟。
建立 REST 要求
classify_img()
函式目前無法向 REST Serving 傳送 REST 要求。您必須先實作這個 REST 分支來建立 REST 要求:
if (radioButtons[0].checked) {
console.log('Using REST');
// TODO: Add code to send a REST request to TensorFlow Serving.
}
TensorFlow Serving 的 POST 要求包含您使用的 Inception v3 模型張量,因此您需要從圖片的每個像素擷取 RGB 值,接著將陣列包裝為 JSON,也就是 JSON 的要求的酬載。
- 將下列程式碼新增至 REST 分支版本:
//Create the REST request.
let imgTensor = new Array();
let pixelArray = new Array();
context.drawImage(img, 0, 0);
for(let i=0; i<inputImgHeight; i++) {
pixelArray[i] = new Array();
for (let j=0; j<inputImgWidth; j++) {
pixelArray[i][j] = new Array();
pixelArray[i][j].push(context.getImageData(i, j, 1, 1).data[0]/255);
pixelArray[i][j].push(context.getImageData(i, j, 1, 1).data[1]/255);
pixelArray[i][j].push(context.getImageData(i, j, 1, 1).data[2]/255);
}
}
imgTensor.push(pixelArray);
const RESTURL = 'http://localhost:8000/v1/models/inception:predict';
let xhr = new XMLHttpRequest();
xhr.open('POST', RESTURL);
xhr.setRequestHeader('Content-Type', 'application/json;charset=utf-8;');
let data = JSON.stringify({
instances: imgTensor
});
xhr.onload = () => {
}
xhr.onerror = () => {
console.log('REST request error');
}
將 REST 要求傳送至 TensorFlow Serving
您現在可以傳送要求。
- 在 REST 分支的上述程式碼後面加上以下程式碼:
// Send the REST request.
xhr.send(data);
處理 TensorFlow Serving 中的 REST 回應
Inception v3 模型會傳回圖片所屬的預先定義類別。如果預測成功,您應在使用者介面中輸出最可能的類別。
實作 onload()
接聽程式來處理回應。
xhr.onload = () => {
}
- 請將這段程式碼加進
onload()
事件監聽器:
// Process the REST response.
const response = JSON.parse(xhr.responseText);
const maxIndex = argmax(response['predictions'][0])
document.getElementById('category').textContent = 'Predicted category: ' + maxIndex;
現在,接聽程式會從回應中擷取預測機率,找出物件最可能的類別,並在使用者介面中顯示結果。
執行
- 在終端機中前往
starter
資料夾,然後使用 webpack 將所有 JavaScript 檔案組合成單一檔案,然後嵌入dist/index.html
檔案中:
npm install -g npx npm install --save-dev webpack npx webpack
- 在瀏覽器中重新整理 http://localhost:8887/,然後按一下 [REST > Classify]。
網站將 286
顯示為預測類別,該類別會對應至 ImageNet 資料集中的 Egyptian Cat
標籤。
8. 透過 gRPC 連結網站與 TensorFlow Serving
除了 REST 以外,TensorFlow Serving 也支援 gRPC。
gRPC 是現代化、開放原始碼的高效能遠端程序呼叫 (RPC) 架構,可在任何環境中執行。這項服務具備可連接的負載平衡、追蹤、健康狀態檢查和驗證等功能,可透過高效率的方式連結資料中心內外的服務。發現在實驗中,gRPC 的成效比 REST 更好。
使用 gRPC 傳送要求及接收回應
這裡有四個簡單的步驟:
- 選用:產生 gRPC 用戶端 stub 程式碼。
- 建立 gRPC 要求。
- 將 gRPC 要求傳送至 TensorFlow Serving。
- 從 gRPC 回應中擷取預測結果,並在使用者介面中顯示預測結果。
您已完成 src/index.js
檔案中的步驟。
選用:產生 gRPC 用戶端 stub 程式碼
如要搭配 TensorFlow Serving 使用 gRPC,您需要遵守 gRPC 工作流程。如要進一步瞭解相關細節,請參閱 gRPC 說明文件。
TensorFlow Serving 和 TensorFlow 會為您定義 .proto
檔案。自 TensorFlow 和 TensorFlow Serving 2.8 起,下列 .proto
檔案為必要檔案:
tensorflow/core/example/example.proto
tensorflow/core/example/feature.proto
tensorflow/core/protobuf/struct.proto
tensorflow/core/protobuf/saved_object_graph.proto
tensorflow/core/protobuf/saver.proto
tensorflow/core/protobuf/trackable_object_graph.proto
tensorflow/core/protobuf/meta_graph.proto
tensorflow/core/framework/node_def.proto
tensorflow/core/framework/attr_value.proto
tensorflow/core/framework/function.proto
tensorflow/core/framework/types.proto
tensorflow/core/framework/tensor_shape.proto
tensorflow/core/framework/full_type.proto
tensorflow/core/framework/versions.proto
tensorflow/core/framework/op_def.proto
tensorflow/core/framework/graph.proto
tensorflow/core/framework/tensor.proto
tensorflow/core/framework/resource_handle.proto
tensorflow/core/framework/variable.proto
tensorflow_serving/apis/inference.proto
tensorflow_serving/apis/classification.proto
tensorflow_serving/apis/predict.proto
tensorflow_serving/apis/regression.proto
tensorflow_serving/apis/get_model_metadata.proto
tensorflow_serving/apis/input.proto
tensorflow_serving/apis/prediction_service.proto
tensorflow_serving/apis/model.proto
- 在終端機中,前往
starter/src/proto/
資料夾並產生 stub:
bash generate_grpc_stub_js.sh
建立 gRPC 要求
與 REST 要求類似,您會在 gRPC 分支中建立 gRPC 要求.
if (connectionMode[picker.selectedRow(inComponent: 0)] == "REST") {
}
else {
print("Using gRPC")
// TODO: Add code to send a gRPC request to TensorFlow Serving.
}
- 將這段程式碼新增至 gRPC 分支版本:
// Create the gRPC request.
const PredictModule = require('./proto/generated/tensorflow_serving/apis/predict_pb.js');
const PredictionServiceClientModule = require('./proto/generated/tensorflow_serving/apis/prediction_service_grpc_web_pb.js');
const ModelModule = require('./proto/generated/tensorflow_serving/apis/model_pb.js');
const TensorModule = require('./proto/generated/tensorflow/core/framework/tensor_pb.js');
const GPRCURL = 'http://localhost:8080';
const stub = new PredictionServiceClientModule.PredictionServiceClient(GPRCURL);
const modelSpec = new ModelModule.ModelSpec();
modelSpec.setName('inception');
const tensorProto = new TensorModule.TensorProto();
const tensorShapeProto = new TensorModule.TensorShapeProto();
const batchDim = (new TensorModule.TensorShapeProto.Dim()).setSize(1);
const heightDim = (new TensorModule.TensorShapeProto.Dim()).setSize(inputImgHeight);
const widthDim = (new TensorModule.TensorShapeProto.Dim()).setSize(inputImgWidth);
const channelDim = (new TensorModule.TensorShapeProto.Dim()).setSize(3);
tensorShapeProto.setDimList([batchDim, heightDim, widthDim, channelDim]);
tensorProto.setDtype(proto.tensorflow.DataType.DT_FLOAT);
tensorProto.setTensorShape(tensorShapeProto);
context.drawImage(img, 0, 0);
for(let i=0; i<inputImgHeight; i++) {
for (let j=0; j<inputImgWidth; j++) {
tensorProto.addFloatVal(context.getImageData(i, j, 1, 1).data[0]/255);
tensorProto.addFloatVal(context.getImageData(i, j, 1, 1).data[1]/255);
tensorProto.addFloatVal(context.getImageData(i, j, 1, 1).data[2]/255);
}
}
const predictionServiceRequest = new PredictModule.PredictRequest();
predictionServiceRequest.setModelSpec(modelSpec);
predictionServiceRequest.getInputsMap().set('inputs', tensorProto);
將 gRPC 要求傳送至 TensorFlow Serving
您現在可以傳送要求。
- 請將這段程式碼加入前一個程式碼片段中 gRPC 分支程式碼後方的後方:
// Send the gRPC request.
stub.predict(predictionServiceRequest, {}, function(err, response) {
// TODO: Add code to process the response.
});
處理 TensorFlow Serving 中的 gRPC 回應
最後,您需實作上述的回呼函式來處理回應。
- 請將這段程式碼加進前一個程式碼片段中的函式主體:
// Process the gRPC response.
if (err) {
console.log(err.code);
console.log(err.message);
}
else {
const maxIndex = argmax(response.getOutputsMap().get('logits').getFloatValList());
document.getElementById('category').textContent = 'Predicted category: ' + maxIndex;
}
現在,接聽程式會從回應中擷取預測機率,找出物件最可能的類別,並在使用者介面中顯示結果。
執行
- 在終端機中,使用 webpack 將所有 JavaScript 檔案打包成一個檔案,然後嵌入
index.html
檔案中:
npx webpack
- 在瀏覽器中重新整理 http://localhost:8887/。
- 按一下 [gRPC > Classify]。
網站顯示 286
的預測類別,對應至 ImageNet 資料集中的 Egyptian Cat
標籤。