Construindo uma API de machine learning (Continuação)
Este post é complementar ao anterior. No primeiro vimos como construir modelos de machine learning e salvá-los como pickle, em seguida vimos como construir uma API serverless nativa na AWS. O objetivo deste é utilizamos o mesmo modelo salvo anteriormente e criamos uma API agnóstica com container, o que isso significa é que poderemos executar nossa API em qualquer ambiente em que o Docker esteja disponível.
Falcon API
Vamos contar com a ajuda de um framework web chamado Falcon.
Preparação do ambiente
Vamos criar a seguinte estrutura de diretórios:
predict-api/
└── app/
Dentro da pasta predict-api vamos inicializar nosso virtual environment e instalar a biblioteca do framework Falcon.
cd predict-api
pip install --user virtualenv
virtualenv venv
source venv/bin/activate
pip install falcon
Ping Pong
Vamos criar oo arquivo que irá conter o método de inicialização da api.
app/app.py
import falcon
from ping_handler import Ping
def create_api():
api = falcon.API()
# Routes
api.add_route('/ping', Ping())
return api
# Init app
app = create_api()
if __name__ == '__main__':
from wsgiref import simple_server
httpd = simple_server.make_server('0.0.0.0', 8000, app)
httpd.serve_forever()
Agora vamos criar a classe Ping. Essa classe terá o método on_get que irá receber a requisição http e irá responder um simples json.
app/ping_handler.py
class Ping:
def on_get(self, req, resp):
resp.media = {"ping": "pong"}
O método on_get indica que quando a api receber uma ação de GET, esse método será executado. Para mais detalhes consulte a documentação
Agora vamos gerar nosso requirements.txt
pip freeze > app/requirements.txt
Preparando o container
Vamos preparar nosso Dockerfile para instalar as bibliotecas necessárias para nosso projeto.
Dockerfile
FROM python:3.8-slim
WORKDIR /api
ADD app/ .
RUN pip install -r requirements.txt
EXPOSE 8000
CMD [ "python", "app.py" ]
Nossa estrutura final de diretórios está da seguinte maneira:
predict-api/
├── app/
│ ├── app.py
│ ├── ping_handler.py
│ └── requirements.txt
└── Dockerfile
Para executarmos o container precisamos fazer o build da imagem e depois instanciá-la. Para isso, no diretório predict-api vamos executar os seguintes comandos no terminal:
docker build -t predict-api:latest .
docker run --rm -p 8000:8000 predict-api:latest
No comando de build não se esqueça do ponto ao final, esse ponto indica o contexto de onde o Dockerfile se encontra.
Vamos acessar o endereço http://localhost:8000
e ver nosso endpoint ping funcionando. Como alternativa podemos executar o seguinte comando no terminal:
curl -w "\n" -X GET http://localhost:8000/ping
Endpoint de previsão
Nessa etapa vamos criar o endpoint de previsão. Para isso precisamos do nosso modelo salvo em pickle, caso não tenha, basta seguir os primeiros passos do post anterior.
Vamos colocar o arquivo finalized_model.pkl no diretório app e criar um método para efetuar uma previsão com base em uma entrada de dados.
Antes de mais nada vamos adicionar as bibliotecas necessárias para executar a previsão.
pip install scikit-learn
pip freeze > app/requirements.txt
app/predict_handler.py
import json
import falcon
import pickle
from sklearn.linear_model import LogisticRegression
class Predict(object):
def on_post(self, req, resp):
raw_body = req.bounded_stream.read()
body_data = json.loads(raw_body, encoding='utf-8')
pregnancies = body_data.get('pregnancies')
glucose = body_data.get('glucose')
blood_pressure = body_data.get('blood_pressure')
skin_thickness = body_data.get('skin_thickness')
insulin = body_data.get('insulin')
bmi = body_data.get('bmi')
diabetes_pedigree_function = body_data.get('diabetes_pedigree_function')
age = body_data.get('age')
loaded_model = pickle.load(open('finalized_model.pkl', 'rb'))
result = int(loaded_model.predict([[
pregnancies,
glucose,
blood_pressure,
skin_thickness,
insulin,
bmi,
diabetes_pedigree_function,
age
]])[0])
resp.media = {"outcome": result}
resp.status = falcon.HTTP_200
A variável body_data irá carregar nosso payload, ou seja, nossos dados de entrada que serão necessários para a previsão.
Precisamos agora adicionar uma nova rota na api.
app/app.py
import falcon
from ping_handler import Ping
from predict_handler import Predict
def create_api():
api = falcon.API()
# Routes
api.add_route('/ping', Ping())
api.add_route('/predict', Predict())
return api
# Init app
app = create_api()
if __name__ == '__main__':
from wsgiref import simple_server
httpd = simple_server.make_server('0.0.0.0', 8000, app)
httpd.serve_forever()
A estrutura final de diretórios ficará da seguinte forma:
predict-api/
├── app/
│ ├── app.py
│ ├── ping_handler.py
│ ├── predict_handler.py
│ ├── finalized_model.pkl
│ └── requirements.txt
└── Dockerfile
Vamos agora fazer o build da imagem novamente e testar o endpoint de previsão usando o curl na linha de comando:
docker build -t predict-api:latest .
docker run -d --rm -p 8000:8000 predict-api:latest
curl -w '\n' -X POST http://localhost:8000/predict -d '{ "pregnancies": 2, "glucose": 148, "blood_pressure": 72, "skin_thickness": 35, "insulin": 0, "bmi": 33.6, "diabetes_pedigree_function": 0.674, "age": 22 }'
O código completo pode ser encontrado nesse repositório