Tạo một trang web đơn giản để phân loại hình ảnh

1. Trước khi bắt đầu

Trong lớp học lập trình này, bạn sẽ tìm hiểu cách chạy quy trình suy luận phân loại hình ảnh từ một trang web bằng cách sử dụng TensorFlow Serving với REST và gRPC.

Điều kiện tiên quyết

  • Kiến thức cơ bản về phát triển web, chẳng hạn như HTML và JavaScript
  • Kiến thức cơ bản về học máy bằng TensorFlow, chẳng hạn như huấn luyện và triển khai
  • Kiến thức cơ bản về thiết bị đầu cuối và Docker

Kiến thức bạn sẽ học được

  • Cách tìm các mô hình phân loại hình ảnh được huấn luyện trước trên TensorFlow Hub.
  • Cách tạo một trang web đơn giản và đưa ra dự đoán bằng mô hình phân loại hình ảnh đã tải xuống thông qua TensorFlow Serving (REST và gRPC).
  • Cách hiển thị kết quả phát hiện trong giao diện người dùng.

Bạn cần có

2. Bắt đầu thiết lập

Cách tải mã xuống cho lớp học lập trình này:

  1. Chuyển đến kho lưu trữ này trên GitHub.
  2. Nhấp vào Code > Download zip (Mã > Tải xuống tệp ZIP) để tải tất cả mã cho lớp học lập trình này xuống.

a72f2bb4caa9a96.png

  1. Giải nén tệp zip đã tải xuống để giải nén một thư mục gốc codelabs có tất cả tài nguyên bạn cần.

Đối với lớp học lập trình này, bạn chỉ cần các tệp trong thư mục con TFServing/ImageClassificationWeb trong kho lưu trữ. Thư mục này chứa 2 thư mục:

  • Thư mục starter chứa mã khởi đầu mà bạn sẽ dùng để xây dựng trong lớp học lập trình này.
  • Thư mục finished chứa mã hoàn chỉnh cho ứng dụng mẫu đã hoàn thành.

3. Cài đặt các phần phụ thuộc

Cách cài đặt các phần phụ thuộc:

  • Trong cửa sổ dòng lệnh, hãy chuyển đến thư mục starter rồi cài đặt các gói NPM bắt buộc:
npm install

4. Chạy trang web khởi đầu

Sử dụng Web Server for Chrome để tải tệp TFServing/ImageClassificationWeb/starter/dist/index.html:

  1. Nhập Chrome://apps/ vào thanh địa chỉ của Chrome rồi tìm Web Server for Chrome trong danh sách ứng dụng.
  2. Khởi chạy Web Server for Chrome rồi chọn thư mục TFServing/ImageClassificationWeb/starter/dist/.
  3. Nhấp vào nút bật/tắt Web Server (Máy chủ web) để bật nút này, sau đó chuyển đến http://localhost:8887/ trong trình duyệt.

f7b43cd44ebf1f1b.png

Chạy và khám phá trang web

Lúc này, bạn sẽ thấy trang web. Giao diện người dùng khá đơn giản: có một hình ảnh mèo mà bạn muốn phân loại và người dùng có thể gửi dữ liệu đến phần phụ trợ bằng REST hoặc gRPC. Phần phụ trợ thực hiện việc phân loại hình ảnh và trả về kết quả phân loại cho trang web. Trang web sẽ hiển thị kết quả này.

837d97a27c59a0b3.png

Nếu bạn nhấp vào Phân loại, sẽ không có gì xảy ra vì ứng dụng chưa thể giao tiếp với phần phụ trợ.

5. Triển khai mô hình phân loại hình ảnh bằng TensorFlow Serving

Phân loại hình ảnh là một nhiệm vụ rất phổ biến của học máy, giúp phân loại một hình ảnh vào các danh mục được xác định trước dựa trên nội dung chính của hình ảnh. Sau đây là ví dụ về cách phân loại hoa:

a6da16b4a7665db0.png

Có một số mô hình phân loại hình ảnh được huấn luyện trước trên TensorFlow Hub. Bạn sẽ sử dụng mô hình Inception v3 phổ biến cho lớp học lập trình này.

Cách triển khai mô hình phân loại hình ảnh bằng TensorFlow Serving:

  1. Tải tệp mô hình Inception v3 xuống.
  2. Giải nén tệp .tar.gz đã tải xuống bằng một công cụ giải nén, chẳng hạn như 7-Zip.
  3. Tạo một thư mục inception_v3 rồi tạo một thư mục con 123 bên trong thư mục đó.
  4. Đặt thư mục variables và tệp saved_model.pb đã giải nén vào thư mục con 123.

Bạn có thể tham khảo thư mục inception_v3 dưới dạng thư mục SavedModel. 123 là một ví dụ về số phiên bản. Nếu muốn, bạn có thể chọn một số khác.

Cấu trúc thư mục sẽ có dạng như hình ảnh sau:

21a8675ac8d31907.png

Bắt đầu TensorFlow Serving

  • Trong thiết bị đầu cuối, hãy bắt đầu TensorFlow Serving bằng Docker, nhưng thay thế PATH/TO/SAVEDMODEL bằng đường dẫn tuyệt đối của thư mục inception_v3 trên máy tính.
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

Trước tiên, Docker sẽ tự động tải hình ảnh TensorFlow Serving xuống. Quá trình này mất một phút. Sau đó, TensorFlow Serving sẽ bắt đầu. Nhật ký sẽ có dạng như đoạn mã sau:

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. Thiết lập proxy Envoy

Hiện tại, TensorFlow Serving không đặt tiêu đề Access-Control-Allow-Origin, vì vậy, trình duyệt sẽ chặn yêu cầu từ JavaScript giao diện người dùng đến TensorFlow Serving vì lý do bảo mật. Để giải quyết vấn đề này, bạn cần sử dụng một proxy, chẳng hạn như Envoy, để chuyển yêu cầu từ JavaScript đến phần phụ trợ TensorFlow Serving.

Start Envoy

  • Trong thiết bị đầu cuối, hãy tải hình ảnh Envoy xuống và khởi động Envoy bằng Docker, nhưng thay thế phần giữ chỗ PATH/TO/ENVOY-CUSTOM.YAML bằng đường dẫn tuyệt đối của tệp envoy-custom.yaml trong thư mục starter.
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

Trước tiên, Docker sẽ tự động tải hình ảnh Envoy xuống. Sau đó, Envoy sẽ bắt đầu. Nhật ký sẽ có dạng như đoạn mã sau:

[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. Kết nối trang web với TensorFlow thông qua REST

Phần phụ trợ hiện đã sẵn sàng để bạn có thể gửi yêu cầu của ứng dụng đến TensorFlow Serving để phân loại hình ảnh. Có hai cách để gửi yêu cầu đến TensorFlow Serving:

  • REST
  • gRPC

Gửi yêu cầu và nhận phản hồi thông qua REST

Có 3 bước đơn giản để gửi và nhận yêu cầu thông qua REST:

  1. Tạo yêu cầu REST.
  2. Gửi yêu cầu REST đến TensorFlow Serving.
  3. Trích xuất kết quả dự đoán từ phản hồi REST và hiển thị kết quả.

Bạn thực hiện các bước này trong tệp src/index.js.

Tạo yêu cầu REST

Hiện tại, hàm classify_img() không gửi yêu cầu REST đến TensorFlow Serving. Bạn cần triển khai nhánh REST này để tạo yêu cầu REST trước:

if (radioButtons[0].checked) {
    console.log('Using REST');
    // TODO: Add code to send a REST request to TensorFlow Serving.

} 

TensorFlow Serving dự kiến sẽ có một yêu cầu POST chứa tensor hình ảnh cho mô hình Inception v3 mà bạn sử dụng. Vì vậy, bạn cần trích xuất các giá trị RGB từ mỗi pixel của hình ảnh vào một mảng, sau đó gói mảng đó trong một JSON (đây là tải trọng của yêu cầu).

  • Thêm mã này vào nhánh 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');
}

Gửi yêu cầu REST đến TensorFlow Serving

Giờ đây, bạn có thể gửi yêu cầu.

  • Thêm mã này ngay sau mã ở trên trong nhánh REST:
// Send the REST request.
xhr.send(data);

Xử lý phản hồi REST từ TensorFlow Serving

Mô hình Inception v3 trả về một mảng xác suất cho biết hình ảnh thuộc các danh mục được xác định trước. Khi dự đoán thành công, bạn nên xuất danh mục có khả năng xảy ra nhất trong giao diện người dùng.

Bạn triển khai trình nghe onload() để xử lý phản hồi.

xhr.onload = () => {

}
  • Thêm mã này vào trình nghe onload():
// Process the REST response.
const response = JSON.parse(xhr.responseText);
const maxIndex = argmax(response['predictions'][0])
document.getElementById('category').textContent = 'Predicted category: ' + maxIndex;

Giờ đây, trình nghe sẽ trích xuất các xác suất dự đoán từ phản hồi, xác định danh mục có khả năng xảy ra nhất của đối tượng và hiển thị kết quả trong giao diện người dùng.

Chạy ứng dụng

  1. Trong thiết bị đầu cuối, hãy chuyển đến thư mục starter và dùng webpack để nhóm tất cả các tệp JavaScript thành một tệp duy nhất mà bạn có thể nhúng vào tệp dist/index.html:
npm install -g npx
npm install --save-dev webpack
npx webpack
  1. Làm mới http://localhost:8887/ trong trình duyệt rồi nhấp vào REST > Classify (REST > Phân loại).

Trang web hiển thị 286 dưới dạng danh mục được dự đoán, tương ứng với nhãn Egyptian Cat trong tập dữ liệu ImageNet.

c865a93b9b58335d.png

8. Kết nối trang web với TensorFlow Serving thông qua gRPC

Ngoài REST, TensorFlow Serving còn hỗ trợ gRPC.

b6f4449c2c850b0e.png

gRPC là một khung Lệnh gọi thủ tục từ xa (RPC) hiện đại, nguồn mở, hiệu suất cao, có thể chạy trong mọi môi trường. Nền tảng này có thể kết nối hiệu quả các dịch vụ trong và trên các trung tâm dữ liệu với khả năng hỗ trợ có thể cắm cho việc cân bằng tải, theo dõi, kiểm tra tình trạng và xác thực. Theo quan sát, gRPC hoạt động hiệu quả hơn REST trong thực tế.

Gửi yêu cầu và nhận phản hồi bằng gRPC

Có 4 bước đơn giản:

  1. Không bắt buộc: Tạo mã giả lập ứng dụng gRPC.
  2. Tạo yêu cầu gRPC.
  3. Gửi yêu cầu gRPC đến TensorFlow Serving.
  4. Trích xuất kết quả dự đoán từ phản hồi gRPC và hiển thị kết quả đó trong giao diện người dùng.

Bạn hoàn tất các bước này trong tệp src/index.js.

Không bắt buộc: Tạo mã giả lập ứng dụng gRPC

Để sử dụng gRPC với TensorFlow Serving, bạn cần làm theo quy trình gRPC. Để tìm hiểu thêm về thông tin chi tiết, hãy xem tài liệu về gRPC.

a9d0e5cb543467b4.png

TensorFlow Serving và TensorFlow xác định các tệp .proto cho bạn. Kể từ TensorFlow và TensorFlow Serving 2.8, đây là những tệp .proto cần thiết:

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
  • Trong thiết bị đầu cuối, hãy chuyển đến thư mục starter/src/proto/ rồi tạo phần giữ chỗ:
bash generate_grpc_stub_js.sh

Tạo yêu cầu gRPC

Tương tự như yêu cầu REST, bạn tạo yêu cầu gRPC trong nhánh gRPC.

if (connectionMode[picker.selectedRow(inComponent: 0)] == "REST") {

}
else {
    print("Using gRPC")
    // TODO: Add code to send a gRPC request to TensorFlow Serving.
    
}
  • Thêm mã này vào nhánh 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);

Gửi yêu cầu gRPC đến TensorFlow Serving

Giờ đây, bạn có thể gửi yêu cầu.

  • Thêm mã này ngay sau mã trong nhánh gRPC ở đoạn mã trước:
// Send the gRPC request.
stub.predict(predictionServiceRequest, {}, function(err, response) {
    // TODO: Add code to process the response.
});

Xử lý phản hồi gRPC từ TensorFlow Serving

Cuối cùng, bạn triển khai hàm gọi lại ở trên để xử lý phản hồi.

  • Thêm mã này vào phần nội dung hàm trong đoạn mã trước:
// 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;
}

Giờ đây, trình nghe sẽ trích xuất các xác suất dự đoán từ phản hồi, xác định danh mục có khả năng xảy ra nhất của đối tượng và hiển thị kết quả trong giao diện người dùng.

Chạy ứng dụng

  1. Trong thiết bị đầu cuối, hãy dùng webpack để gói tất cả các tệp JavaScript vào một tệp duy nhất mà bạn có thể nhúng vào tệp index.html:
npx webpack
  1. Làm mới http://localhost:8887/ trong trình duyệt.
  2. Nhấp vào gRPC > Phân loại.

Trang web hiển thị danh mục được dự đoán của 286, danh mục này tương ứng với nhãn Egyptian Cat trong tập dữ liệu ImageNet.

9. Xin chúc mừng

Bạn đã sử dụng TensorFlow Serving để thêm khả năng phân loại hình ảnh vào trang web của mình!

Tìm hiểu thêm