画像を分類するシンプルなウェブサイトを作成する

1. 始める前に

この Codelab では、TensorFlow Serving と REST および gRPC を使用して、ウェブサイトから画像分類推論を実行する方法を学びます。

前提条件

  • HTML や JavaScript などのウェブ開発に関する基本的な知識
  • トレーニングやデプロイなど、TensorFlow による機械学習に関する基本的な知識
  • ターミナルと Docker に関する基本的な知識

学習内容

  • TensorFlow Hub でトレーニング済みの画像分類モデルを見つける方法
  • 簡単なウェブサイトを構築し、TensorFlow Serving(REST と gRPC)からダウンロードした画像分類モデルで予測を行う方法。
  • 検出結果を UI でレンダリングする方法。

必要なもの

2. 設定する

この Codelab のコードをダウンロードするには:

  1. この GitHub リポジトリに移動します。
  2. [Code] > [Download zip] をクリックして、この Codelab のすべてのコードをダウンロードします。

a72f2bb4caa9a96.png

  1. ダウンロードした zip ファイルを解凍して、必要なリソースがすべて揃った codelabs ルートフォルダを展開します。

この Codelab では、リポジトリの TFServing/ImageClassificationWeb サブディレクトリ内のファイルのみが必要です。このサブディレクトリには 2 つのフォルダが含まれています。

  • starter フォルダには、この Codelab で構築するスターター コードが含まれています。
  • finished フォルダには、完成したサンプルアプリの完成したコードが含まれています。

3. 依存関係をインストールする

依存関係をインストールするには:

  • ターミナルで starter フォルダに移動し、必要な NPM パッケージをインストールします。
npm install

4.スターター ウェブサイトを実行する

Chrome 用ウェブサーバーを使用して TFServing/ImageClassificationWeb/starter/dist/index.html ファイルを読み込みます。

  1. Chrome のアドレスバーに「Chrome://apps/」と入力し、アプリのリストで [Web Server for Chrome] を探します。
  2. Chrome 用ウェブサーバーを起動し、TFServing/ImageClassificationWeb/starter/dist/ フォルダを選択します。
  3. [ウェブサーバー] トグルをクリックして有効にし、ブラウザで http://localhost:8887/ に移動します。

f7b43cd44ebf1f1b.png

ウェブサイトを実行して探索する

ウェブサイトが表示されます。UI は非常にシンプルで、分類する猫の画像があり、ユーザーは REST または gRPC を使用してデータをバックエンドに送信できます。バックエンドは画像に対して画像分類を行い、その分類結果をウェブサイトに返します。その結果が表示されます。

2834-0534-0801

[分類] をクリックしても、まだバックエンドと通信できないため、何も起こりません。

5. TensorFlow Serving を使用して画像分類モデルをデプロイする

画像分類はごく一般的な ML タスクで、画像の主要なコンテンツに基づいて画像を事前定義されたカテゴリに分類します。花の分類例を次に示します。

a6da16b4a7665db0.png

TensorFlow Hub には、多数の事前トレーニング済み画像分類モデルがあります。この Codelab では一般的な Inception v3 モデルを使用します。

TensorFlow Serving を使用して画像分類モデルをデプロイするには:

  1. Inception v3 モデルファイルをダウンロードします。
  2. ダウンロードした .tar.gz ファイルを 7-Zip などの解凍ツールを使用して解凍します。
  3. inception_v3 フォルダを作成し、その中に 123 サブフォルダを作成します。
  4. 抽出した variables フォルダと saved_model.pb ファイルを 123 サブフォルダに配置します。

inception_v3 フォルダは、SavedModel フォルダとして参照できます。123 はバージョン番号の例です。別の数字を選択することもできます。

フォルダ構造は次の画像のようになります。

21a8675ac8d31907.png

TensorFlow サービスの提供を開始

  • ターミナルで、TensorFlow Serving を Docker で起動しますが、PATH/TO/SAVEDMODEL はコンピュータの inception_v3 フォルダの絶対パスに置き換えます。
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 の画像が自動的にダウンロードされます。これには 1 分ほどかかります。その後、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 プロキシの設定

現在、TensorFlow Serving は Access-Control-Allow-Origin ヘッダーを設定していないため、セキュリティ上の理由から、ブラウザはフロントエンドの JavaScript から TensorFlow Serving へのリクエストをブロックします。この問題を回避するには、JavaScript から TensorFlow Serving バックエンドにリクエストをプロキシする Envoy などのプロキシを使用する必要があります。

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 にリクエストを送信する方法は 2 つあります。

  • REST
  • gRPC

REST でリクエストを送信してレスポンスを受信する

REST を介してリクエストを送受信するには、次の 3 つの簡単な手順を行います。

  1. REST リクエストを作成します。
  2. TensorFlow Serving に REST リクエストを送信します。
  3. REST レスポンスから予測結果を抽出し、結果を表示します。

以下の手順は src/index.js ファイルで行います。

REST リクエストを作成する

現時点では、classify_img() 関数は TensorFlow 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 は、使用する Inception v3 モデルの画像テンソルを含む POST リクエストを想定しています。そのため、画像の各ピクセルから RGB 値を配列に抽出してから、配列をペイロードである 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');
}

TensorFlow Serving に REST リクエストを送信する

リクエストを送信できるようになりました。

  • 上記のコードを REST ブランチの直後に追加します。
// Send the REST request.
xhr.send(data);

TensorFlow Serving から REST レスポンスを処理する

Inception v3 モデルは、画像が事前定義されたカテゴリに属する確率の配列を返します。予測が成功したら、UI に最も可能性の高いカテゴリを出力します。

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;

リスナーはレスポンスから予測確率を抽出し、オブジェクトの最も可能性の高いカテゴリを特定して、その結果を UI に表示します。

実行

  1. ターミナルで starter フォルダに移動し、webpack を使用してすべての JavaScript ファイルを 1 つのファイルにまとめます。このファイルは dist/index.html ファイルに埋め込むことができます。
npm install -g npx
npm install --save-dev webpack
npx webpack
  1. 更新http://localhost:8887/にアクセスし、REST > 分類をご覧ください。

ウェブサイトには、予測カテゴリとして 286 が表示され、ImageNet データセットEgyptian Cat ラベルにマッピングされます。

c865a93b9b58335d.png

8. gRPC によってウェブサイトと TensorFlow Serving を接続する

TensorFlow Serving は REST に加えて、gRPC もサポートします。

b6f4449c2c850b0e.png

gRPC は、最新のオープンソースで高性能なリモート プロシージャ コール(RPC)フレームワークです。あらゆる環境で実行できます。負荷分散やトレース、ヘルスチェック、認証に対応するプラグイン可能なサポートにより、データセンター内やデータセンター間でサービスを効率的に接続できます。実際には、gRPC は REST よりもパフォーマンスが高いことがわかっています。

gRPC でリクエストを送信してレスポンスを受信する

次の 4 つの簡単な手順に沿ってください。

  1. (省略可)gRPC クライアント スタブコードを生成します。
  2. gRPC リクエストを作成する。
  3. gRPC サービングに gRPC リクエストを送信します。
  4. gRPC レスポンスから予測結果を抽出し、UI に表示します。

以下の手順は src/index.js ファイルで行います。

省略可: gRPC クライアント スタブコードを生成する

TensorFlow Serving で gRPC を使用するには、gRPC ワークフローに従う必要があります。詳細については、gRPC のドキュメントをご覧ください。

a9d0e5cb543467b4.png

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/ フォルダに移動し、スタブを生成します。
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);

TensorFlow Serving に gRPC リクエストを送信する

リクエストを送信できるようになりました。

  • 前のコード スニペットの 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;
}

リスナーはレスポンスから予測確率を抽出し、オブジェクトの最も可能性の高いカテゴリを特定して、その結果を UI に表示します。

実行

  1. ターミナルで webpack を使用して、すべての JavaScript ファイルを 1 つのファイルにまとめ、index.html ファイルに埋め込むことができます。
npx webpack
  1. ブラウザで http://localhost:8887/ を更新します。
  2. [gRPC] > [分類] をクリックします。

ウェブサイトには、286 の予測カテゴリが表示されます。これは、ImageNet データセットEgyptian Cat ラベルにマッピングされます。

9. 完了

TensorFlow Serving を使用して、画像分類機能をウェブサイトに追加しました。

詳細