Criar um site simples que classifique as imagens

1. Antes de começar

Neste codelab, você aprenderá a executar uma inferência de classificação de imagem de um site usando o TensorFlow Serving com REST e gRPC.

Prerequisites

  • Conhecimento básico de desenvolvimento para Web, como HTML e JavaScript
  • Conhecimento básico de machine learning com o TensorFlow, como treinamento e implantação
  • Conhecimento básico de terminais e Docker

O que você aprenderá

  • Como encontrar modelos de classificação de imagens pré-treinados no TensorFlow Hub.
  • Como criar um site simples e fazer previsões com o modelo de classificação de imagem transferido por download usando o TensorFlow Serving (REST e gRPC).
  • Como renderizar o resultado da detecção na IU.

Pré-requisitos

2. Começar a configuração

Para fazer o download do código para este codelab, faça o seguinte:

  1. Navegue até este repositório do GitHub.
  2. Clique em Code > Download zip para fazer o download de todo o código para este codelab.

a72f2bb4caa9a96.png

  1. Descompacte o arquivo ZIP transferido por download para descompactar uma pasta raiz do codelabs com todos os recursos que você precisa.

Neste codelab, você só precisará dos arquivos no subdiretório TFServing/ImageClassificationWeb do repositório, que contém duas pastas:

  • A pasta starter contém o código inicial que você usará como base para este codelab.
  • A pasta finished contém o código concluído do app de exemplo finalizado.

3. Instale as dependências

Para instalar as dependências:

  • No seu terminal, navegue até a pasta starter e instale os pacotes NPM necessários:
npm install

4. Executar o site inicial

Use o Web Server for Chrome para carregar o arquivo TFServing/ImageClassificationWeb/starter/dist/index.html:

  1. Digite Chrome://apps/ na barra de endereços do Chrome e localize Web Server for Chrome na lista de aplicativos.
  2. Inicie o servidor da Web para o Chrome e, em seguida, escolha a pasta TFServing/ImageClassificationWeb/starter/dist/.
  3. Clique no botão Web Server para ativá-lo e navegue até http://localhost:8887/ no navegador.

f7b43cd44ebf1f1b.png

Executar e explorar o site

Você verá o site agora. A IU é muito simples: há uma imagem de gato em que você quer classificar e o usuário pode enviar os dados para o back-end com REST ou gRPC. O back-end realiza a classificação da imagem e retorna o resultado da classificação para o site, que exibe o resultado.

837d97a27c59a0b3.png

Se você clicar em Classificar, nada acontecerá porque ainda não há comunicação com o back-end.

5. Implante um modelo de classificação de imagens com o TensorFlow Serving

A classificação de imagens é uma tarefa muito comum de ML que classifica imagens em categorias predefinidas com base no conteúdo principal delas. Veja um exemplo de classificação de flores:

a6da16b4a7665db0.png

Há vários modelos de classificação de imagens pré-treinados no TensorFlow Hub. Você usa um modelo conhecido do Inception v3 para este codelab.

Para implantar o modelo de classificação de imagem com o TensorFlow Serving:

  1. Faça o download do arquivo de modelo Inception v3.
  2. Descompacte o arquivo .tar.gz salvo com uma ferramenta de descompactação, como o 7-Zip.
  3. Crie uma pasta inception_v3 e uma subpasta 123 dentro dela.
  4. Coloque a pasta variables extraída e o arquivo saved_model.pb na subpasta 123.

Você pode se referir à pasta inception_v3 como a pasta SavedModel. 123 é um exemplo de número da versão. Se quiser, você pode escolher outro número.

A estrutura de pasta será semelhante a esta imagem:

21a8675ac8d31907.png

Iniciar o TensorFlow Serving

  • No seu terminal, inicie o TensorFlow Serving com o Docker, mas substitua PATH/TO/SAVEDMODEL pelo caminho absoluto da pasta inception_v3 no seu computador.
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

Primeiro, o Docker faz o download automático da imagem do TensorFlow Serving em um minuto. Depois disso, o TensorFlow Serving será iniciado. O registro será semelhante a este snippet de código:

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. Configurar o proxy Envoy

Atualmente, o TensorFlow Serving não define o cabeçalho Access-Control-Allow-Origin. Portanto, o navegador bloqueia a solicitação do JavaScript de front-end para o TensorFlow Serving por motivos de segurança. Para contornar esse problema, você precisa usar um proxy, como o Envoy, para enviar a solicitação do JavaScript pelo back-end do TensorFlow Serving.

Iniciar Envoy

  • No seu terminal, faça o download da imagem do Envoy e inicie o Envoy com o Docker. Substitua o marcador PATH/TO/ENVOY-CUSTOM.YAML pelo caminho absoluto do arquivo envoy-custom.yaml na pasta 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

Primeiro, o Docker faz o download automático da imagem do Envoy. Depois disso, o Envoy será iniciado. O registro será semelhante a este snippet de código:

[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. Conectar o site ao TensorFlow usando a API REST

O back-end está pronto para que você possa enviar solicitações de cliente ao TensorFlow Serving para classificar imagens. Há duas maneiras de enviar solicitações ao TensorFlow Serving:

  • REST
  • gRPC

Enviar solicitações e receber respostas pelo REST

Há três etapas simples para enviar e receber solicitações pela REST:

  1. Crie a solicitação REST.
  2. Envie a solicitação REST para o TensorFlow Serving.
  3. Extraia o resultado previsto da resposta REST e exiba-o.

Você realiza essas etapas no arquivo src/index.js.

Criar a solicitação REST

No momento, a função classify_img() não envia a solicitação REST para o TensorFlow Serving. Primeiro, implemente essa ramificação da REST para criar uma solicitação REST:

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

}

O TensorFlow Serving espera uma solicitação POST que contenha o tensor de imagem do modelo do Inception v3 que você usa. Portanto, você precisa extrair os valores RGB de cada pixel da imagem em uma matriz e, em seguida, unir a matriz em um JSON, que é o payload da solicitação.

  • Adicione este código à ramificação 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');
}

Enviar a solicitação REST para o TensorFlow Serving

Agora você pode enviar a solicitação.

  • Adicione este código logo após o código acima na ramificação REST:
// Send the REST request.
xhr.send(data);

Processar a resposta REST do TensorFlow Serving

O modelo do Inception v3 retorna uma matriz de probabilidades de que a imagem pertence a categorias predefinidas. Quando a previsão for bem-sucedida, você deve gerar a categoria mais provável na IU.

Implemente o listener onload() para processar a resposta.

xhr.onload = () => {

}
  • Adicione este código ao listener onload():
// Process the REST response.
const response = JSON.parse(xhr.responseText);
const maxIndex = argmax(response['predictions'][0])
document.getElementById('category').textContent = 'Predicted category: ' + maxIndex;

Agora, o listener extrai as probabilidades previstas da resposta, identifica a categoria mais provável do objeto e exibe o resultado na IU.

Executar

  1. No terminal, acesse a pasta starter e use o webpack para agrupar todos os arquivos JavaScript em um único arquivo que você pode incorporar ao arquivo dist/index.html:
npm install -g npx
npm install --save-dev webpack
npx webpack
  1. Atualize http://localhost:8887/ no navegador e clique em REST > Classificar.

O site exibe 286 como a categoria prevista, que é mapeada para o rótulo Egyptian Cat no conjunto de dados ImageImage.

c865a93b9b58335d.png

8. Conectar o site ao TensorFlow Serving por meio do gRPC

Além do REST, o TensorFlow Serving também é compatível com o gRPC.

b6f4449c2c850b0e.png

O gRPC é um framework de chamada de procedimento remoto (RPC) moderno, de código aberto e de alta performance que pode ser executado em qualquer ambiente. Ele pode conectar serviços de forma eficiente em data centers e com compatibilidade conectável para balanceamento de carga, rastreamento, verificação de integridade e autenticação. Observamos que o gRPC tem uma performance melhor do que o REST na prática.

Enviar solicitações e receber respostas com o gRPC

Há quatro etapas simples:

  1. Opcional: gerar o código stub do cliente gRPC
  2. Criar a solicitação gRPC
  3. Enviar a solicitação gRPC para o TensorFlow Serving
  4. Extraia o resultado previsto da resposta gRPC e exiba-o na IU.

Realize essas etapas no arquivo src/index.js.

Opcional: gerar o código stub do cliente gRPC

Para usar o gRPC com o TensorFlow Serving, é necessário seguir o fluxo de trabalho do gRPC. Caso queira saber mais, consulte a documentação do gRPC.

a9d0e5cb543467b4.png

O TensorFlow Serving e o TensorFlow definem os arquivos .proto automaticamente. No TensorFlow e no TensorFlow Serving 2.8, os seguintes arquivos .proto são necessários:

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
  • No terminal, navegue até a pasta starter/src/proto/ e gere o stub:
bash generate_grpc_stub_js.sh

Criar a solicitação gRPC

Semelhante à solicitação REST, você cria a solicitação gRPC na ramificação gRPC..

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

}
else {
    print("Using gRPC")
    // TODO: Add code to send a gRPC request to TensorFlow Serving.

}
  • Adicione este código à ramificação 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);

Enviar a solicitação gRPC para o TensorFlow Serving

Agora você pode enviar a solicitação.

  • Adicione este código imediatamente após o código na ramificação gRPC no snippet de código anterior:
// Send the gRPC request.
stub.predict(predictionServiceRequest, {}, function(err, response) {
    // TODO: Add code to process the response.
});

Processar a resposta gRPC do TensorFlow Serving

Por fim, implemente a função de callback acima para processar a resposta.

  • Adicione este código ao corpo da função no snippet de código anterior:
// 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;
}

Agora, o listener extrai as probabilidades previstas da resposta, identifica a categoria mais provável do objeto e exibe o resultado na IU.

Executar

  1. No seu terminal, use o webpack para agrupar todos os arquivos JavaScript em um único arquivo que você possa incorporar ao arquivo index.html:
npx webpack
  1. Atualize http://localhost:8887/ no navegador.
  2. Clique em gRPC > Classificar.

O site exibe a categoria prevista de 286, que mapeia para o rótulo Egyptian Cat no conjunto de dados do ImageNet.

9. Parabéns

Você usou o TensorFlow Serving para adicionar recursos de classificação de imagens ao seu site.

Saiba mais