Neste artigo vamos mostrar como colocar seu modelo de Machine Learning em um container Docker para ser utilizado através de uma API desenvolvida com o framework Flask.
O objetivo é mostrar um breve exemplo de uma aplicação dockerizada, que nada mais é do que garantir que a aplicação vai rodar da mesma forma em qualquer ambiente, seja local, em um servidor on-premises ou em um servidor na cloud.
Para conseguir realizar esse pequeno exemplo será necessário ter instalado em seu computador o Docker e o Python.
Para fins de exemplo, um modelo de classificação foi treinado utilizando o dataset Iris, disponível no Scikit-Learn. Foi utilizado o algoritmo Logistic Regression e foi aplicado o pré-processamento StandardScaler. Esta pode não ser a melhor abordagem para esse tipo de dado e problema, porém o objetivo é mostrar como colocar o seu modelo em um container Docker e usá-lo para fazer predições através de uma API. O modelo e o pré-processamento foram salvos em arquivos .pkl, que são arquivos binários que podem ser carregados no Python utilizando a biblioteca pickle. O projeto foi estruturado da seguinte forma, cada arquivo será explicado mais a frente:
.
├── Dockerfile
├── requirements.txt
└── src
├── inference.py
├── model
│ ├── model.pkl
│ └── scaler.pkl
├── prediction.py
└── preprocessing.py
O arquivo requirements.txt contém as dependências do projeto, neste caso, apenas o Flask e o Scikit-Learn.
Pré-processamento
No arquivo preprocessing.py é onde todo o pré-processamento dos dados acontece. No cenário montado para esse exemplo, foi criada uma classe onde carrega o arquivo com o algoritmo de pré-processamento já configurado. Este arquivo nada mais é do que um objeto do tipo StandardScaler, que foi treinado com os dados de treino do dataset Iris. Vale lembrar que outras abordagens podem ser tomadas para esse cenário.
import pickle
class PreProcessor:
def __init__(self):
self.scaler = pickle.load(open('./model/scaler.pkl', 'rb'))
def preprocess(self, data):
data = self.scaler.transform(data)
return data
Modelo
No arquivo prediction.py é onde o modelo é carregado e utilizado para fazer predições. Nesta etapa é criada a classe responsável pela inferência, onde é carregado o modelo e criado uma instância da classe do pré-processamento. Por fim, é criado o método predict que recebe os dados, aplica o pré-processamento e retorna a predição.
from preprocessing import PreProcessor
import pickle
class Prediction:
def __init__(self):
self.preprocessor = PreProcessor()
self.model = pickle.load(open('./model/model.pkl', 'rb'))
def predict(self, data):
data = self.preprocessor.preprocess(data)
return self.model.predict(data)
Inferência
No arquivo inference.py é onde a API é criada e o modelo é carregado. Aqui é criado uma instância da classe Prediction e a rota de predição da API é criada com o Flask. A rota de predição basicamente recebe os dados em formato JSON, faz a predição em cima dos dados e retorna a predição em formato JSON. Uma etapa importante quando estamos utilizando o Flask é executar o comando app.run(). Neste comando podemos configurar a porta que a API vai rodar, no caso foi configurado a porta 5000, e também o IP do host, que foi configurado como 0.0.0.0 para que seja acessível fora do container Docker.
from prediction import Prediction
from flask import Flask, request, jsonify
app = Flask(__name__)
model = Prediction()
@app.route('/predict', methods=['POST'])
def predict():
data = request.get_json(force=True)
data = data['data']
prediction = model.predict([data]).tolist()
return jsonify(prediction=prediction)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000)
Container Docker
No arquivo Dockerfile é onde o container Docker é configurado. Nele configuramos todos os detalhes para que o container execute nossa API. Primeiro é definido a imagem base, no caso foi utilizado a imagem python:3.9-slim, que é uma imagem oficial do Python com o Python 3.9 e o gerenciador de pacotes pip. Em seguida é instalado as dependências do projeto, que estão definidas no arquivo requirements.txt. Após isso, todo o código do projeto é copiado para a pasta /app dentro do container. Em seguida é definido o diretório padrão para /app e exposto a porta 5000, que é a porta utilizada pelo Flask. Por fim, é definido o comando que será executado quando o container for iniciado, no caso é o arquivo inference.py.
FROM python:3.9-slim
# Instala as dependências do projeto
COPY requirements.txt ./requirements.txt
RUN pip install --no-cache-dir -r requirements.txt
# Copia todo o código para a pasta /app
COPY ./src /app
# Configura o diretório padrão para /app
WORKDIR /app
# Expõe a porta 5000, porta utilizada pelo Flask
EXPOSE 5000
# Executa o arquivo inference.py quando o container for iniciado
CMD ["python", "inference.py"]
Executando o container
Antes de executar o container, é necessário criar a imagem do container. Para isso, basta executar o comando abaixo na pasta raiz do projeto:
docker build -t minha-api .
O argumento -t é utilizado para definir o nome da imagem, no caso foi utilizado minha-api. E o argumento . é utilizado para definir o diretório onde o Dockerfile está localizado.
Após a criação da imagem, basta executar o comando abaixo para iniciar o container:
docker run -d -p 5000:5000 minha-api
O argumento -d é utilizado para executar o container em modo detached, ou seja, o container vai rodar em background e o argumento -p é utilizado para fazer o mapeamento da porta do host para a porta do container.
Testando a API
Para testar a API, basta executar o comando abaixo:
curl -X POST -H "Content-Type: application/json" -d '{"data": [5.1, 3.5, 1.4, 0.2]}' http://localhost:5000/predict