1. Antes de começar
Neste codelab, você vai aprender a executar uma inferência de classificação de imagens em um site usando o TensorFlow Serving com REST e gRPC.
Pré-requisitos
- Conhecimento básico de desenvolvimento da 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ê vai 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 imagens baixado usando o TensorFlow Serving (REST e gRPC).
- Como renderizar o resultado da detecção na interface.
O que é necessário
- Docker
- Google Chrome
- Servidor da Web para Chrome
- Node.js e NPM
- Bash
- Compilador de buffer de protocolo (apenas necessário se você quiser gerar o stub do gRPC novamente)
- Plug-in gerador de código gRPC-web (apenas necessário se você quiser gerar o stub do gRPC novamente)
2. Começar a configuração
Para fazer o download do código para este codelab, faça o seguinte:
- Acesse este repositório do GitHub.
- Clique em Code > Download zip para fazer o download de todo o código para este codelab.
- Descompacte o arquivo ZIP baixado para acessar uma pasta raiz
codelabs
com todos os recursos necessários.
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 terminal, navegue até a pasta
starter
e instale os pacotes NPM necessários:
npm install
4. Executar o site inicial
Use o servidor da Web para Chrome para carregar o arquivo TFServing/ImageClassificationWeb/starter/dist/index.html
:
- Digite
Chrome://apps/
na barra de endereço do Chrome e encontre Servidor da Web para Chrome na lista de apps. - Inicie o servidor da Web para Chrome e escolha a pasta
TFServing/ImageClassificationWeb/starter/dist/
. - Clique na chave Servidor da Web para ativar e acesse http://localhost:8887/ no navegador.
Executar e conhecer o site
O site vai aparecer. A interface é bem simples: há uma imagem de um gato 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 de imagens e retorna o resultado para o site, que o exibe.
Se você clicar em Classificar, nada acontecerá porque ainda não há comunicação com o back-end.
5. Implantar um modelo de classificação de imagens com o TensorFlow Serving
A classificação de imagens é uma tarefa de ML muito comum que classifica uma imagem em categorias predefinidas com base no conteúdo principal dela. Confira um exemplo de classificação de flores:
Há vários modelos de classificação de imagens pré-treinados no TensorFlow Hub. Você vai usar um modelo Inception v3 conhecido neste codelab.
Para implantar o modelo de classificação de imagens com o TensorFlow Serving:
- Faça o download do arquivo do modelo Inception v3.
- Descompacte o arquivo
.tar.gz
baixado com uma ferramenta de descompactação, como o 7-Zip. - Crie uma pasta
inception_v3
e uma subpasta123
dentro dela. - Coloque a pasta
variables
extraída e o arquivosaved_model.pb
na subpasta123
.
Você pode se referir à pasta inception_v3
como SavedModel
. 123
é um exemplo de número de versão. Se preferir, você pode escolher outro número.
A estrutura de pastas deve ser semelhante a esta imagem:
Iniciar o TensorFlow Serving
- No seu terminal, inicie o TensorFlow Serving com o Docker, mas substitua
PATH/TO/SAVEDMODEL
pelo caminho absoluto da pastainception_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 do Envoy
No momento, o TensorFlow Serving não define o cabeçalho Access-Control-Allow-Origin
. Por isso, o navegador bloqueia a solicitação do JavaScript do front-end para o TensorFlow Serving por motivos de segurança. Para contornar isso, use um proxy, como o Envoy, para encaminhar a solicitação do JavaScript ao back-end do TensorFlow Serving.
Iniciar o Envoy
- No terminal, faça o download da imagem do Envoy e inicie o Envoy com o Docker, mas substitua o marcador de posição
PATH/TO/ENVOY-CUSTOM.YAML
pelo caminho absoluto do arquivoenvoy-custom.yaml
na pastastarter
.
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
O Docker faz o download automático da imagem do Envoy primeiro. 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 REST
O back-end está pronto, então você pode 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 pelo REST:
- Crie a solicitação REST.
- Envie a solicitação REST para o TensorFlow Serving.
- Extraia o resultado previsto da resposta REST e mostre o resultado.
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. Você precisa implementar essa ramificação REST para criar uma solicitação desse tipo:
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 para o modelo Inception v3 usado. Portanto, é necessário extrair os valores RGB de cada pixel da imagem em uma matriz e, em seguida, encapsular a matriz em um JSON, que é a carga útil 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 Inception v3 retorna uma matriz de probabilidades de que a imagem pertença a categorias predefinidas. Quando a previsão for bem-sucedida, mostre a categoria mais provável na interface.
Você implementa 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 mostra o resultado na interface.
Executar
- No terminal, acesse a pasta
starter
e use o webpack para agrupar todos os arquivos JavaScript em um único arquivo que pode ser incorporado ao arquivodist/index.html
:
npm install -g npx npm install --save-dev webpack npx webpack
- Atualize http://localhost:8887/ no navegador e clique em REST > Classify.
O site mostra 286
como a categoria prevista, que corresponde ao rótulo Egyptian Cat
no conjunto de dados do ImageNet.
8. Conectar o site ao TensorFlow Serving pelo gRPC
Além do REST, o TensorFlow Serving também é compatível com o gRPC.
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:
- Opcional: gerar o código stub do cliente gRPC
- Criar a solicitação gRPC
- Enviar a solicitação gRPC para o TensorFlow Serving
- Extrair o resultado previsto da resposta gRPC e mostrar na interface.
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.
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 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 mostra o resultado na interface.
Executar
- No terminal, use o webpack para agrupar todos os arquivos JavaScript em um único arquivo que pode ser incorporado ao arquivo
index.html
:
npx webpack
- Atualize http://localhost:8887/ no navegador.
- Clique em gRPC > Classify.
O site mostra a categoria prevista de 286
, que é mapeada 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.