Previsão de aprendizado de máquina Python com uma API REST Flask
Publicados: 2022-03-11Este artigo é sobre o uso do Python no contexto de um sistema de aprendizado de máquina ou inteligência artificial (IA) para fazer previsões em tempo real, com uma API REST do Flask. A arquitetura exposta aqui pode ser vista como uma forma de passar da prova de conceito (PoC) ao produto mínimo viável (MVP) para aplicações de aprendizado de máquina.
Python não é a primeira escolha que se pode pensar ao projetar uma solução em tempo real. Mas como o Tensorflow e o Scikit-Learn são algumas das bibliotecas de aprendizado de máquina mais usadas suportadas pelo Python, ele é usado convenientemente em muitos PoCs do Jupyter Notebook.
O que torna essa solução viável é o fato de que o treinamento leva muito tempo em comparação com a previsão. Se você pensar no treinamento como o processo de assistir a um filme e prever as respostas às perguntas sobre ele, parece bastante eficiente não ter que assistir novamente ao filme após cada nova pergunta.
O treinamento é uma espécie de visualização compactada desse “filme” e a previsão é recuperar informações da visualização compactada. Deve ser muito rápido, seja o filme complexo ou longo.
Vamos implementar isso com um exemplo rápido [Flask] em Python!
Arquitetura genérica de aprendizado de máquina
Vamos começar descrevendo um fluxo genérico de arquitetura de treinamento e previsão:
Primeiro, um pipeline de treinamento é criado para aprender sobre os dados anteriores de acordo com uma função objetivo.
Isso deve gerar dois elementos principais:
- Funções de engenharia de recursos : as transformações usadas no tempo de treinamento devem ser reutilizadas no tempo de previsão.
- Parâmetros do modelo : o algoritmo e os hiperparâmetros finalmente selecionados devem ser salvos, para que possam ser reutilizados no momento da previsão
Observe que a engenharia de recursos feita durante o tempo de treinamento deve ser cuidadosamente salva para ser aplicável à previsão. Um problema comum entre muitos outros que podem surgir ao longo do caminho é o dimensionamento de recursos que é necessário para muitos algoritmos.
Se o recurso X1 for dimensionado do valor 1 para 1000 e for redimensionado para o intervalo [0,1] com uma função f(x) = x/max(X1)
, o que aconteceria se o conjunto de previsão tivesse um valor de 2000?
Alguns ajustes cuidadosos devem ser pensados com antecedência para que a função de mapeamento retorne saídas consistentes que serão calculadas corretamente no momento da previsão.
Treinamento de aprendizado de máquina versus previsão
Há uma questão importante a ser abordada aqui. Por que estamos separando treinamento e previsão para começar?
É absolutamente verdade que no contexto de exemplos e cursos de aprendizado de máquina, onde todos os dados são conhecidos antecipadamente (incluindo os dados a serem previstos), uma maneira muito simples de construir o preditor é empilhar dados de treinamento e previsão (geralmente chamados um conjunto de teste).
Então, é necessário treinar no “conjunto de treinamento” e prever no “conjunto de teste” para obter os resultados, enquanto ao mesmo tempo faz engenharia de recursos nos dados de treinamento e teste, treinamento e previsão no mesmo e único pipeline .
No entanto, em sistemas da vida real, você geralmente tem dados de treinamento, e os dados a serem previstos chegam no momento em que estão sendo processados. Em outras palavras, você assiste ao filme de uma só vez e tem algumas perguntas sobre ele mais tarde, o que significa que as respostas devem ser fáceis e rápidas.
Além disso, geralmente não é necessário treinar novamente o modelo inteiro toda vez que novos dados chegam, pois o treinamento leva tempo (pode levar semanas para alguns conjuntos de imagens) e deve ser estável o suficiente ao longo do tempo.
É por isso que treinamento e previsão podem ser, ou mesmo deveriam ser, claramente separados em muitos sistemas, e isso também reflete melhor como um sistema inteligente (artificial ou não) aprende.
A conexão com o overfitting
A separação de treinamento e previsão também é uma boa maneira de resolver o problema de overfitting.
Em estatística, overfitting é “a produção de uma análise que corresponde muito próxima ou exatamente a um determinado conjunto de dados e pode, portanto, falhar em ajustar dados adicionais ou prever observações futuras de forma confiável”.
O overfitting é particularmente visto em conjuntos de dados com muitos recursos ou com conjuntos de dados com dados de treinamento limitados. Em ambos os casos, os dados possuem muitas informações em relação ao que pode ser validado pelo preditor, e alguns deles podem nem estar vinculados à variável predita. Nesse caso, o próprio ruído pode ser interpretado como um sinal.
Uma boa maneira de controlar o overfitting é treinar em parte dos dados e prever em outra parte na qual temos a verdade básica. Portanto, o erro esperado em novos dados é aproximadamente o erro medido nesse conjunto de dados, desde que os dados em que treinamos sejam representativos da realidade do sistema e seus estados futuros.
Portanto, se projetarmos um pipeline de treinamento e previsão adequado junto com uma divisão correta de dados, não apenas abordaremos o problema de overfitting, mas também podemos reutilizar essa arquitetura para prever novos dados.
A última etapa seria controlar que o erro nos novos dados seja o mesmo esperado. Sempre há uma mudança (o erro real está sempre abaixo do esperado) e deve-se determinar o que é uma mudança aceitável - mas esse não é o tópico deste artigo.
Uma API REST para previsão
É aí que a separação clara de treinamento e previsão é útil. Se salvarmos nossos métodos de engenharia de recursos e nossos parâmetros de modelo, podemos construir uma API REST simples com esses elementos.
A chave aqui é carregar o modelo e os parâmetros na inicialização da API. Uma vez iniciada e armazenada na memória, cada chamada de API aciona o cálculo de engenharia de recursos e o método “prever” do algoritmo de ML. Ambos são geralmente rápidos o suficiente para garantir uma resposta em tempo real.

A API pode ser projetada para aceitar um único exemplo a ser previsto, ou vários diferentes (previsões em lote).
Aqui está o código Python/Flask mínimo que implementa este princípio, com JSON dentro e JSON fora (pergunta dentro, resposta):
app = Flask(__name__) @app.route('/api/makecalc/', methods=['POST']) def makecalc(): """ Function run at each API call No need to re-load the model """ # reads the received json jsonfile = request.get_json() res = dict() for key in jsonfile.keys(): # calculates and predicts res[key] = model.predict(doTheCalculation(key)) # returns a json file return jsonify(res) if __name__ == '__main__': # Model is loaded when the API is launched model = pickle.load(open('modelfile', 'rb')) app.run(debug=True)
Observe que a API pode ser usada para prever novos dados, mas não recomendo usá-la para treinar o modelo. Poderia ser usado, mas isso complexifica o código de treinamento do modelo e pode ser mais exigente em termos de recursos de memória.
Exemplo de implementação - Compartilhamento de bicicletas
Vamos pegar um conjunto de dados Kaggle, compartilhamento de bicicletas, como exemplo. Digamos que somos uma empresa de compartilhamento de bicicletas que deseja prever o número de aluguel de bicicletas por dia para gerenciar melhor a manutenção da bicicleta, a logística e outros aspectos do negócio.
Os aluguéis dependem principalmente das condições climáticas, portanto, com a previsão do tempo, essa empresa pode ter uma ideia melhor de quando os aluguéis atingirão o pico e tentar evitar manutenções nesses dias.
Primeiro, treinamos um modelo e o salvamos como um objeto pickle que pode ser visto no notebook Jupyter.
O treinamento e o desempenho do modelo não são tratados aqui, este é apenas um exemplo para entender o processo completo.
Em seguida, escrevemos a transformação de dados que será feita a cada chamada de API:
import numpy as np import pandas as pd from datetime import date def doTheCalculation(data): data['dayofyear']=(data['dteday']- data['dteday'].apply(lambda x: date(x.year,1,1)) .astype('datetime64[ns]')).apply(lambda x: x.days) X = np.array(data[['instant','season','yr','holiday','weekday','workingday', 'weathersit','temp','atemp','hum','windspeed','dayofyear']]) return X
Este é apenas um cálculo de uma variável (dia do ano) para incluir o mês e o dia exato. Há também uma seleção de colunas e sua respectiva ordem a ser mantida.
Precisamos, então, escrever a API REST com Flask:
from flask import Flask, request, redirect, url_for, flash, jsonify from features_calculation import doTheCalculation import json, pickle import pandas as pd import numpy as np app = Flask(__name__) @app.route('/api/makecalc/', methods=['POST']) def makecalc(): """ Function run at each API call """ jsonfile = request.get_json() data = pd.read_json(json.dumps(jsonfile),orient='index',convert_dates=['dteday']) print(data) res = dict() ypred = model.predict(doTheCalculation(data)) for i in range(len(ypred)): res[i] = ypred[i] return jsonify(res) if __name__ == '__main__': modelfile = 'modelfile.pickle' model = pickle.load(open(modelfile, 'rb')) print("loaded OK") app.run(debug=True)
Execute este programa, ele servirá a API na porta 5000 por padrão.
Se testarmos uma requisição localmente, ainda com Python:
import requests, json url = '[http://127.0.0.1:5000/api/makecalc/](http://127.0.0.1:5000/api/makecalc/)' text = json.dumps({"0":{"instant":1,"dteday":"2011-01-01T00:00:00.000Z","season":1,"yr":0,"mnth":1,"holiday":0,"weekday":6,"workingday":0,"weathersit":2,"temp":0.344167,"atemp":0.363625,"hum":0.805833,"windspeed":0.160446}, "1":{"instant":2,"dteday":"2011-01-02T00:00:00.000Z","season":1,"yr":0,"mnth":1,"holiday":0,"weekday":3,"workingday":0,"weathersit":2,"temp":0.363478,"atemp":0.353739,"hum":0.696087,"windspeed":0.248539}, "2":{"instant":3,"dteday":"2011-01-03T00:00:00.000Z","season":1,"yr":0,"mnth":1,"holiday":0,"weekday":1,"workingday":1,"weathersit":1,"temp":0.196364,"atemp":0.189405,"hum":0.437273,"windspeed":0.248309}})
A solicitação contém todas as informações que foram alimentadas ao modelo. Portanto, nosso modelo responderá com uma previsão de aluguel de bicicletas para as datas especificadas (aqui temos três).
headers = {'content-type': 'application/json', 'Accept-Charset': 'UTF-8'} r = requests.post(url, data=text, headers=headers) print(r,r.text) <Response [200]> { "0": 1063, "1": 1028, "2": 1399 }
É isso! Este serviço pode ser utilizado facilmente em qualquer aplicativo da empresa, para planejamento de manutenção ou para que os usuários fiquem atentos ao tráfego de bicicletas, demanda e disponibilidade de bicicletas para aluguel.
Juntando tudo
A principal falha de muitos sistemas de aprendizado de máquina, e especialmente PoCs, é misturar treinamento e previsão.
Se eles forem cuidadosamente separados, as previsões em tempo real podem ser realizadas com bastante facilidade para um MVP, a um custo e esforço de desenvolvimento bastante baixos com Python/Flask, especialmente se, para muitos PoCs, ele foi desenvolvido inicialmente com Scikit-learn, Tensorflow, ou qualquer outra biblioteca de aprendizado de máquina Python.
No entanto, isso pode não ser viável para todos os aplicativos, especialmente aplicativos em que a engenharia de recursos é pesada ou aplicativos que recuperam a correspondência mais próxima que precisam ter os dados mais recentes disponíveis em cada chamada.
De qualquer forma, você precisa assistir filmes repetidamente para responder a perguntas sobre eles? A mesma regra se aplica ao aprendizado de máquina!