Laboratório 1 - Introdução gRPC
Nesse laboratório vamos explorar a utilização do Framework gRPC para chamada de procedimento remoto. Além disso, também vamos utilizar protobuffer para criação de menssagens e serviços que serão utilizados pelo gRPC. Ao final, vamos explorar um cenário de treinamento de modelos utilizando uma arquitetura cliente servidor
O que é gRPC?
gRPC ou Google Remote Procedure Call é uma estrutura RPC moderna de alto desempenho e código aberto que pode ser executada em qualquer ambiente. Ele pode conectar serviços de forma eficiente dentro e entre data centers com suporte plugável para balanceamento de carga, rastreamento, verificação de integridade e autenticação
RPC ou chamadas de procedimento remoto são as mensagens que o servidor envia ao sistema remoto para executar a tarefa (ou subrotinas). O gRPC foi projetado para facilitar a comunicação suave e eficiente entre os serviços. Ele pode ser utilizado de diferentes maneiras, como: * Conectando serviços poliglotas de forma eficiente em arquitetura de estilo de microsserviços * Conectando dispositivos móveis, clientes de navegador a serviços de backend * Gerando bibliotecas de cliente eficientes
Para o exemplo deste laboratório, utilizaremos Python para desenvolvimento. Dessa forma, para usar o grpc devemos instalar os seguintes pacotes
pip install grpcio grpcio-tools sklearn
Note
O módulo sklearn será utilizado no exercício descrito no final do laboratório.
Utilizando Proto Buffer
Protocol Buffer é mecanismo eficiente e automatizado para serializar dados estruturados. Eles fornecem uma maneira de definir a estrutura dos dados a serem transmitidos (i.e., mensagens). O são melhores que XML, pois:
- Mais simples
- Até dez vezes menor
- Até 100 vezes mais rápido
- gera classes de acesso a dados que facilitam seu uso
Os protobuf são definidos em aquivos .proto, os quais são bastante simples de usar. Nesse arquivos, definimos as mensagems que serão trocadas entre as RPCs e também os serviços, que definem as mensagem de entrada e também mensagens de resposta. Após definir a estrutura do arquivo .proto podemos utilizar o compilador protoc para gerar os arquivos de stubs e também métodos de codificação e decodificação que serão utilizados pelo programa de forma automática para linguagem de programação desejada.
Para o exemplo do PingPong vamos definir o arquivo ping_pong.proto com o seguinte conteúdo:
syntax = "proto3";
package pingponggprc;
message Ping {
string mensagem = 1;
}
message Pong {
string mensagem = 1;
double tempo = 2;
}
service PingPong{
rpc GetServerResponse(Ping) returns (Pong) {}
}
Compilando arquivo proto
Após definir o arquivo .proto devemos compilá-lo utilizando o compilador de gRPC protoc. Esse compilador está presente no módulo grpc_tools e pode ser gerado utilizando o seguinte comando
python -m grpc_tools.protoc -I=. --python_out=. --grpc_python_out=. ping_pong.proto
Note
como resultado o compilador irá gerar os códigos para o apêndice do servidor (i.e., server stub ) e também para o apêndice do cliente (i.e., client stub ) os quais são responsáveis por enviar e receber as mensagens entre as chamadas RPC e também codificar e decodificar os dados que serão transmitidos.
Implementado Código Servidor
Agora precisamos implementar o código do servidor. Para esse código, devemos implementar uma classe que implemente o método GetServerResponse que será responsável pela chamada definida no arquivo .proto, esse método recebe uma requisição (i.e., a mensagem enviada pelo cliente) e também um contexto que oferece funcionalidade para a conexão com o cliente. Além disso, devemos implementar o método serve() que ficará responsável por disponibilizar o serviço. Portanto, implementamos o seguinte código em um arquivo servidor.py:
import grpc
from concurrent import futures
import time
import ping_pong_pb2_grpc as pb2_grpc
import ping_pong_pb2 as pb2
class ExemploServer(pb2_grpc.PingPongServicer):
def GetServerResponse(self, request, context):
mensagem = request.mensagem
resposta = f"Pong!"
mensagem_resposta = {
'mensagem' : resposta,
'tempo' : time.time()
}
return pb2.Pong(**mensagem_resposta)
def serve():
server = grpc.server(futures.ThreadPoolExecutor(max_workers=10))
pb2_grpc.add_PingPongServicer_to_server(ExemploServer(), server)
server.add_insecure_port('[::]:50051')
server.start()
print("Server started at 50051")
server.wait_for_termination()
if __name__ == '__main__':
serve()
Implementando Código Cliente
No cliente devemos implementar uma classe que se conecta no servidor e também invoca o método descrito no serviço passando a mensagem de entrada e recebendo uma mensagem de resposta. Dessa forma, para implementação do ping pong implementamos o seguinte código para o cliente em um arquivo cliente.py:
import grpc
import ping_pong_pb2 as pb2
import ping_pong_pb2_grpc as pb2_grpc
import time
class ExemploGRPC(object):
def __init__(self):
self.host = 'localhost'
self.server_port = 50051
self.channel = grpc.insecure_channel(f"{self.host}:{self.server_port}")
self.stub = pb2_grpc.PingPongStub(self.channel)
def get_ping_response(self, message):
message = pb2.Ping(mensagem=message)
return self.stub.GetServerResponse(message)
if __name__ == '__main__':
client = ExemploGRPC()
mensagem = 'Ping!'
while True:
tempo = time.time()
print(f'Cliente -> {mensagem} {tempo}')
resposta = client.get_ping_response(mensagem)
print(f'Servidor -> {resposta.mensagem} {resposta.tempo}')
print(f'Duração Ping -> Pong: {time.time() - tempo}')
print('--------------------------------')
time.sleep(1)
Ping Pong
Por fim, para realizar o ping pong entre cliente servidor basta executar os respectivos códigos
Example
python servidor.py
python cliente.py
Exercício - Treinamento Modelos baseado em Cliente Servidor
Agora está na hora de colocar em prática o que foi abordado nesse laboratório. A idéia é realizar o treinamento de um modelo de aprendizado de máquina supervisionado para classificação (i.e, KNN, SVM, Decision Tree, Random Forest, etc). Os dados utilizados serão do dataset iris presente no sklearn, que pode ser importado utilizando
sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
iris = load_iris()
atributos = iris.data
rotulos = iris.target
x_treino, x_teste, y_treino, y_test = train_test_split(atributos, rotulos, test_size=0.2)
x corresponde aos atributos e y corresponde aos rótulos utilizaremos o train_test_split do sklearn. Mais detalhes sobre preparação dos dados para treinamento e avaliação serão apresentados nas próximas aulas.
Nesse exercício os dados estão disponíveis no cliente e o modelo está implementado no servidor, assim como apresentado na figura. Nesse cenário, o cliente deve encaminhar os uma mensagem com os dados para o servidor que deve treinar o modelo e retornar a acurácia de treinamento para os dados enviados via RPC.
Info
O código para treinamento do modelo no servidor será explicado detalhadamente na aula de revisão sobre aprendizado supervisionado e também na aula de treinamento de modelos.
Dessa forma, as seguintes tarefas devem ser realizadas:
- Gerar arquivo
.protocom as mensagens que serão utilizadas pelo serviço para treinamento e avaliação de modelo. Por exemplo,FitRequest,FitResponse,PredictRequest,PredictResponse - Compilar o arquivo
.protopara gerar os arquivos de stubs do cliente e servidor - Implementar o código do cliente que deve solicitar o treinamento do modelo e também a predição para outros dados após o treinamento
- Implementar o código do servidor que terá dois serviços, um para treinamento e outro para predição. O serviço de treinamento deve treinar o modelo e retornar a acurácia de treinamento, enquanto o serviço de predição deve receber uma amostra de dados e retornar a classificação para tal amostra
- Caso você já tenha experiência com modelos de ML, outros algoritmos para classificação também podem ser implementados.
Tip
Para definir as mensagens que irá conter o dataset que será enviado para o servidor (i.e., array), você pode criar uma mensagem que represente uma amosta com dois campos, um utilizando repeated float para representar os atributos do dataset e outra int32 para representar o rótulo. Em seguida, você pode criar uma outra mensagem que é um conjunto de amostras usando repeated nome_da_mensagem
Você pode utilizar qualquer classificador disponível no sklearn, como por exemplo
from sklearn.neighbors import KNeighborsClassifier
model = KNeighborsClassifier()
.append()
Para o treinamento do modelo no servidor você pode utilizar o seguinte código
#Treina o modelo para os dados entradas e labels recebidos
model.fit(input_data, input_labels)
#calcula a acurácia do modelo para os dados de treino.
accuracy = model.score(input_data, input_labels)
model.predict(entrada) o qual retorna a predição (i.e., classe) para a entrada fornecida