이미지를 분류하는 간단한 웹사이트 만들기

이미지를 분류하는 간단한 웹사이트 만들기

이 Codelab 정보

subject최종 업데이트: 4월 5, 2022
account_circle작성자: 웨이 웨이

1. 시작하기 전에

이 Codelab에서는 REST 및 gRPC와 함께 TensorFlow Serving을 사용하여 웹사이트에서 이미지 분류 추론을 실행하는 방법을 알아봅니다.

기본 요건

  • HTML 및 자바스크립트와 같은 웹 개발에 대한 기본 지식
  • 학습 및 배포와 같은 TensorFlow를 활용한 머신러닝에 관한 기본 지식
  • 터미널 및 Docker에 관한 기본 지식

실습 내용

  • TensorFlow Hub에서 선행 학습된 이미지 분류 모델을 찾는 방법
  • TensorFlow Serving (REST 및 gRPC)을 통해 간단한 웹사이트를 빌드하고 다운로드한 이미지 분류 모델을 사용하여 예측하는 방법
  • UI에서 감지 결과를 렌더링하는 방법

준비물

2. 설정

이 Codelab의 코드를 다운로드하려면 다음 안내를 따르세요.

  1. 이 GitHub 저장소로 이동합니다.
  2. Code > 다운로드 zip을 클릭하여 이 Codelab의 모든 코드를 다운로드합니다.

A72f2bb4caa9a96.png

  1. 다운로드한 ZIP 파일의 압축을 풀고 필요한 모든 리소스로 codelabs 루트 폴더를 압축 해제합니다.

이 Codelab에서는 저장소의 TFServing/ImageClassificationWeb 하위 디렉터리에 있는 다음 두 폴더만 있는 파일이 필요합니다.

  • starter 폴더에는 이 Codelab을 위해 빌드하는 시작 코드가 포함되어 있습니다.
  • finished 폴더에는 완료된 샘플 앱의 완성된 코드가 포함되어 있습니다.

3. 종속성 설치

종속 항목을 설치하려면 다음 단계를 따르세요.

  • 터미널에서 starter 폴더로 이동한 다음 필요한 NPM 패키지를 설치합니다.
npm install

4. 시작 웹사이트 실행

Chrome용 웹 서버를 사용하여 TFServing/ImageClassificationWeb/starter/dist/index.html 파일을 로드합니다.

  1. Chrome의 주소 표시줄에 Chrome://apps/를 입력하고 앱 목록에서 Chrome의 웹 서버를 찾습니다.
  2. Chrome용 웹 서버를 실행한 다음 TFServing/ImageClassificationWeb/starter/dist/ 폴더를 선택합니다.
  3. 웹 서버 전환 버튼을 클릭하여 사용 설정한 다음 브라우저에서 http://localhost:8887/로 이동합니다.

f7b43cd44ebf1f1b.png

웹사이트 실행 및 탐색

이제 웹사이트가 표시됩니다. UI는 매우 간단합니다. 사용자가 분류하려는 고양이 이미지가 있으며 사용자는 REST 또는 gRPC를 사용하여 백엔드로 데이터를 전송할 수 있습니다. 백엔드가 이미지에서 이미지 분류를 수행하고 분류 결과를 웹사이트에 반환합니다.

837d97a27c59a0b3.png

분류를 클릭하면 아직 백엔드와 통신할 수 없으므로 아무 일도 일어나지 않습니다.

5. TensorFlow Serving으로 이미지 분류 모델 배포하기

이미지 분류는 이미지의 기본 콘텐츠를 기반으로 이미지를 사전 정의된 카테고리로 분류하는 매우 일반적인 ML 작업입니다. 다음은 꽃 분류 예입니다.

A6da16b4a7665db0.png

TensorFlow Hub에는 선행 학습된 이미지 분류 모델이 많이 있습니다. 이 Codelab에서는 널리 사용되는 Inception v3 모델을 사용합니다.

TensorFlow Serving으로 이미지 분류 모델을 배포하려면 다음 안내를 따르세요.

  1. Inception v3 모델 파일을 다운로드합니다.
  2. 7-Zip과 같은 압축 해제 도구로 다운로드한 .tar.gz 파일의 압축을 풉니다.
  3. inception_v3 폴더를 만든 후 그 안에 123 하위 폴더를 만듭니다.
  4. 추출된 variables 폴더와 saved_model.pb 파일을 123 하위 폴더에 넣습니다.

inception_v3 폴더를 SavedModel 폴더로 참조할 수 있습니다. 123는 버전 번호의 예입니다. 원하는 경우 다른 번호를 선택할 수 있습니다.

폴더 구조는 다음과 같습니다.

21a8675ac8d31907.png

TensorFlow Serving 시작

  • 터미널에서 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 이미지를 먼저 자동으로 다운로드합니다. 몇 분 정도 걸립니다. 그런 다음 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 헤더를 설정하지 않으므로 브라우저에서 보안상의 이유로 프런트엔드 자바스크립트에서 TensorFlow Serving으로의 요청을 차단합니다. 이 문제를 해결하려면 Envoy 같은 프록시를 사용하여 자바스크립트에서 TensorFlow Serving 백엔드로 요청을 프록시해야 합니다.

엔보이 시작

  • 터미널에서 Envoy 이미지를 다운로드하고 Docker로 Envoy를 시작하지만 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가 클라이언트 요청을 TensorFlow Serving으로 전송하여 이미지를 분류할 수 있으므로 백엔드가 준비되었습니다. TensorFlow Serving에 요청을 보내는 방법에는 두 가지가 있습니다.

  • REST
  • gRPC

REST를 통해 요청 보내기 및 응답 수신

REST를 통해 요청을 주고받는 간단한 세 가지 단계는 다음과 같습니다.

  1. REST 요청을 만듭니다.
  2. TensorFlow Serving에 REST 요청을 보냅니다.
  3. REST 응답에서 예측 결과를 추출하고 결과를 표시합니다.

이 단계는 src/index.js 파일에서 진행합니다.

REST 요청 만들기

현재 classify_img() 함수는 REST 요청을 TensorFlow Serving에 전송하지 않습니다. 먼저 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을 사용하여 모든 자바스크립트 파일을 dist/index.html 파일에 삽입할 수 있는 단일 파일로 묶습니다.
npm install -g npx
npm install --save-dev webpack
npx webpack
  1. 브라우저에서 http://localhost:8887/을 새로고침한 다음 REST > Classify를 클릭합니다.

웹사이트에서 286를 예측 카테고리로 표시하고 ImageNet 데이터 세트Egyptian Cat 라벨에 매핑합니다.

C865a93b9b58335d.png

8. gRPC를 통해 웹사이트를 TensorFlow Serving과 연결

TensorFlow Serving은 REST 외에 gRPC도 지원합니다.

b6f4449c2c850b0e.png

gRPC는 어느 환경에서나 실행할 수 있는 최신 오픈소스 RPC (Remote Procedure Call) 프레임워크입니다. 부하 분산, 추적, 상태 확인, 인증을 지원하며 데이터 센터 안팎의 서비스를 효율적으로 연결할 수 있습니다. gRPC가 실제로 REST보다 성능이 더 좋은 것으로 관찰되었습니다.

gRPC로 요청 보내기 및 응답 수신

다음의 간단한 4단계가 있습니다.

  1. 선택사항: gRPC 클라이언트 스텁 코드를 생성합니다.
  2. gRPC 요청을 만듭니다.
  3. TensorFlow Serving에 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을 사용하여 모든 자바스크립트 파일을 index.html 파일에 삽입할 수 있는 단일 파일로 묶습니다.
npx webpack
  1. 브라우저에서 http://localhost:8887/을 새로고침합니다.
  2. gRPC > 분류를 클릭합니다.

웹사이트에서 ImageNet 데이터 세트Egyptian Cat 라벨에 매핑되는 286의 예측 카테고리를 표시합니다.

9. 수고하셨습니다.

여러분은 TensorFlow Serving을 사용하여 웹사이트에 이미지 분류 기능을 추가했습니다!

자세히 알아보기