Guia para implantar modelos de aprendizado de máquina em um ambiente de produção como uma API usando o Flask

Amigos, no final de março, estamos lançando um novo fluxo no curso "Cientista de Dados" . E agora, estamos começando a compartilhar material útil sobre o curso com você.

1. Introdução

Lembrando a experiência inicial de minha paixão pelo aprendizado de máquina (ML), posso dizer que foi feito muito esforço na construção de um modelo realmente bom. Consultei especialistas nesta área para entender como melhorar meu modelo, pensei nas funções necessárias, tentei garantir que todas as dicas propostas fossem consideradas. Mas ainda assim eu tive um problema.

Como implementar o modelo em um projeto real? Eu não tinha idéias sobre esse ponto. Toda a literatura que estudei até o momento focou apenas na melhoria de modelos. Não vi o próximo passo no desenvolvimento deles.



É por isso que estou escrevendo este guia agora. Quero que você enfrente o problema que encontrei no meu tempo, mas poderia resolvê-lo rapidamente. No final deste artigo, mostrarei como implementar um modelo de aprendizado de máquina usando a estrutura Flask no Python.

Conteúdo

  1. Opções de implementação para modelos de aprendizado de máquina.
  2. O que é uma API?
  3. Instalando o ambiente Python e informações básicas sobre o Flask.
  4. Criando um modelo de aprendizado de máquina.
  5. Salvando modelos de aprendizado de máquina: serialização e desserialização.
  6. Criando uma API usando o Flask.

Opções de implementação para modelos de aprendizado de máquina.

Na maioria dos casos, o uso real de modelos de aprendizado de máquina é uma parte central do desenvolvimento, mesmo que seja apenas um pequeno componente de um sistema automatizado de distribuição de e-mail ou chatbot. Às vezes, há momentos em que as barreiras à implementação parecem intransponíveis.

Por exemplo, a maioria dos especialistas em ML usa R ou Python para suas pesquisas científicas. No entanto, os engenheiros de software que usam uma pilha de tecnologia completamente diferente serão consumidores desses modelos. Existem duas opções que podem resolver esse problema:

Opção 1: reescreva todo o código no idioma com o qual os engenheiros de desenvolvimento trabalham. Parece até certo ponto lógico, mas é preciso muito tempo e esforço para replicar os modelos desenvolvidos. No final, acaba sendo apenas uma perda de tempo. A maioria dos idiomas, como JavaScript, não possui bibliotecas convenientes para trabalhar com o ML. Portanto, será uma solução racional não usar esta opção.

Opção 2: use a API. As APIs de rede resolveram o problema de trabalhar com aplicativos em diferentes idiomas. Se o desenvolvedor front-end precisar usar seu modelo de aprendizado de máquina para criar um aplicativo da Web com base, precisará obter apenas o URL do servidor de destino que discute a API.

O que é uma API?

Em palavras simples, a API (Application Programming Interface) é um tipo de contrato entre dois programas, que diz que, se um programa do usuário fornecer dados de entrada em um formato específico, o programa do desenvolvedor (API) passará por ele próprio e fornecerá ao usuário os dados de saída.

Você poderá ler alguns artigos por conta própria, que descrevem bem por que a API é uma escolha bastante popular entre os desenvolvedores.


A maioria dos grandes provedores de serviços em nuvem e empresas menores, focadas no aprendizado de máquina, fornecem APIs prontas para uso. Eles atendem às necessidades de desenvolvedores que não entendem o aprendizado de máquina, mas desejam integrar essa tecnologia em suas soluções.

Por exemplo, um desses provedores de API é o Google com sua API do Google Vision .

Tudo o que o desenvolvedor precisa fazer é simplesmente chamar a API REST (Representational State Transfer) usando o SDK fornecido pelo Google. Veja o que você pode fazer usando a API do Google Vision .

Parece ótimo, certo? Neste artigo, descobriremos como criar sua própria API usando o Flask, uma estrutura Python.

Nota : O Flask não é a única estrutura de rede para essa finalidade. Existem também Django, Falcon, Hug e muitos outros que não são mencionados neste artigo. Por exemplo, para R há um pacote chamado encanador

Instalando o ambiente Python e informações básicas sobre o Flask.

1) Criando um ambiente virtual usando o Anaconda. Se você precisar criar seu próprio ambiente virtual para Python e manter o estado necessário de dependências, o Anaconda oferece boas soluções para isso. Em seguida, funcionará com a linha de comando.

  • Aqui você encontrará o instalador da miniconda para Python;
  • wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh
  • bash Miniconda3-latest-Linux-x86_64.sh
  • Siga a sequência de perguntas.
  • source .bashrc
  • Se você digitar: conda , poderá ver uma lista de comandos e ajuda disponíveis.
  • Para criar um novo ambiente, digite: conda create --name <environment-name> python=3.6
  • Siga as etapas que você será solicitado a fazer e, no final, digite: source activate <environment-name>
  • Instale os pacotes Python necessários. Os mais importantes são o balão e o gunicorn.

2) Vamos tentar criar nosso aplicativo simples de balão "Hello world" usando gunicorn .

  • Abra seu editor de texto favorito e crie o arquivo hello-world.py na pasta
  • Escreva o seguinte código:

 """Filename: hello-world.py """ from flask import Flask app = Flask(__name__) @app.route('/users/<string:username>') def hello_world(username=None): return("Hello {}!".format(username)) 

  • Salve o arquivo e retorne ao terminal.
  • Para iniciar a API, execute o terminal: gunicorn --bind 0.0.0.0:8000 hello-world:app
  • Se você obtiver o seguinte, estará no caminho certo:



  • No navegador, digite o seguinte: https://localhost:8000/users/any-name



Viva! Você escreveu seu primeiro programa Flask! Como você já tem alguma experiência com essas etapas simples, podemos criar pontos de extremidade de rede que podem ser acessados ​​localmente.

Usando o Flask, podemos agrupar nossos modelos e usá-los como uma API da Web. Se queremos criar aplicativos de rede mais complexos (por exemplo, em JavaScript), precisamos adicionar algumas alterações.

Criando um modelo de aprendizado de máquina.

  • Para começar, vamos dar uma olhada na competição de aprendizado de máquina da Loan Prediction Competition . O objetivo principal é configurar um pipeline de pré-processamento e criar modelos de ML para facilitar a tarefa de previsão durante a implantação.

 import os import json import numpy as np import pandas as pd from sklearn.externals import joblib from sklearn.model_selection import train_test_split, GridSearchCV from sklearn.base import BaseEstimator, TransformerMixin from sklearn.ensemble import RandomForestClassifier from sklearn.pipeline import make_pipeline import warnings warnings.filterwarnings("ignore") 

  • Salve o conjunto de dados na pasta:

 !ls /home/pratos/Side-Project/av_articles/flask_api/data/ 

 test.csv training.csv 

 data = pd.read_csv('../data/training.csv') 

 list(data.columns) 

 ['Loan_ID', 'Gender', 'Married', 'Dependents', 'Education', 'Self_Employed', 'ApplicantIncome', 'CoapplicantIncome', 'LoanAmount', 'Loan_Amount_Term', 'Credit_History', 'Property_Area', 'Loan_Status'] 

 data.shape 


 (614, 13) 
ul>
Encontre os valores nulos / Nan nas colunas:

 for _ in data.columns: print("The number of null values in:{} == {}".format(_, data[_].isnull().sum())) 


 The number of null values in:Loan_ID == 0 The number of null values in:Gender == 13 The number of null values in:Married == 3 The number of null values in:Dependents == 15 The number of null values in:Education == 0 The number of null values in:Self_Employed == 32 The number of null values in:ApplicantIncome == 0 The number of null values in:CoapplicantIncome == 0 The number of null values in:LoanAmount == 22 The number of null values in:Loan_Amount_Term == 14 The number of null values in:Credit_History == 50 The number of null values in:Property_Area == 0 The number of null values in:Loan_Status == 0 

  • A próxima etapa é criar conjuntos de dados para treinamento e teste:

 red_var = ['Gender','Married','Dependents','Education','Self_Employed','ApplicantIncome','CoapplicantIncome',\ 'LoanAmount','Loan_Amount_Term','Credit_History','Property_Area'] X_train, X_test, y_train, y_test = train_test_split(data[pred_var], data['Loan_Status'], \ test_size=0.25, random_state=42) 

  • Para garantir que todas as etapas de pré-processamento sejam concluídas corretamente, mesmo após a experiência, e não perdemos nada durante a previsão, criaremos nosso próprio avaliador Scikit-learn para pré-processamento (pré-processamento do estimador Scikit-learn) .

Para entender como a criamos, leia o seguinte .

 from sklearn.base import BaseEstimator, TransformerMixin class PreProcessing(BaseEstimator, TransformerMixin): """Custom Pre-Processing estimator for our use-case """ def __init__(self): pass def transform(self, df): """Regular transform() that is a help for training, validation & testing datasets (NOTE: The operations performed here are the ones that we did prior to this cell) """ pred_var = ['Gender','Married','Dependents','Education','Self_Employed','ApplicantIncome',\ 'CoapplicantIncome','LoanAmount','Loan_Amount_Term','Credit_History','Property_Area'] df = df[pred_var] df['Dependents'] = df['Dependents'].fillna(0) df['Self_Employed'] = df['Self_Employed'].fillna('No') df['Loan_Amount_Term'] = df['Loan_Amount_Term'].fillna(self.term_mean_) df['Credit_History'] = df['Credit_History'].fillna(1) df['Married'] = df['Married'].fillna('No') df['Gender'] = df['Gender'].fillna('Male') df['LoanAmount'] = df['LoanAmount'].fillna(self.amt_mean_) gender_values = {'Female' : 0, 'Male' : 1} married_values = {'No' : 0, 'Yes' : 1} education_values = {'Graduate' : 0, 'Not Graduate' : 1} employed_values = {'No' : 0, 'Yes' : 1} property_values = {'Rural' : 0, 'Urban' : 1, 'Semiurban' : 2} dependent_values = {'3+': 3, '0': 0, '2': 2, '1': 1} df.replace({'Gender': gender_values, 'Married': married_values, 'Education': education_values, \ 'Self_Employed': employed_values, 'Property_Area': property_values, \ 'Dependents': dependent_values}, inplace=True) return df.as_matrix() def fit(self, df, y=None, **fit_params): """Fitting the Training dataset & calculating the required values from train eg: We will need the mean of X_train['Loan_Amount_Term'] that will be used in transformation of X_test """ self.term_mean_ = df['Loan_Amount_Term'].mean() self.amt_mean_ = df['LoanAmount'].mean() return self 

  • Converta y_train e y_test em np.array :

 y_train = y_train.replace({'Y':1, 'N':0}).as_matrix() y_test = y_test.replace({'Y':1, 'N':0}).as_matrix() 

Vamos criar um pipeline para garantir que todas as etapas de pré-processamento que realizamos sejam o trabalho do avaliador do scikit-learn.

 pipe = make_pipeline(PreProcessing(), RandomForestClassifier()) 

 pipe 

 Pipeline(memory=None, steps=[('preprocessing', PreProcessing()), ('randomforestclassifier', RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini', max_depth=None, max_features='auto', max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, min_samples_leaf=1, min_samples_split=2, min_weight_fraction_leaf=0.0, n_estimators=10, n_jobs=1, oob_score=False, random_state=None, verbose=0, warm_start=False))]) 

Para procurar por hiper parâmetros adequados (grau para objetos polinomiais e alfa para uma aresta), faremos uma pesquisa em grade (Pesquisa em grade):

  • Defina param_grid:

 param_grid = {"randomforestclassifier__n_estimators" : [10, 20, 30], "randomforestclassifier__max_depth" : [None, 6, 8, 10], "randomforestclassifier__max_leaf_nodes": [None, 5, 10, 20], "randomforestclassifier__min_impurity_split": [0.1, 0.2, 0.3]} 

  • Inicia a pesquisa na grade:

 grid = GridSearchCV(pipe, param_grid=param_grid, cv=3) 

  • Ajustamos os dados de treinamento para o estimador de pipeline:

 grid.fit(X_train, y_train) 

 GridSearchCV(cv=3, error_score='raise', estimator=Pipeline(memory=None, steps=[('preprocessing', PreProcessing()), ('randomforestclassifier', RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini', max_depth=None, max_features='auto', max_leaf_nodes=None, min_impurity_decrease=0.0, min_impu..._jobs=1, oob_score=False, random_state=None, verbose=0, warm_start=False))]), fit_params=None, iid=True, n_jobs=1, param_grid={'randomforestclassifier__n_estimators': [10, 20, 30], 'randomforestclassifier__max_leaf_nodes': [None, 5, 10, 20], 'randomforestclassifier__min_impurity_split': [0.1, 0.2, 0.3], 'randomforestclassifier__max_depth': [None, 6, 8, 10]}, pre_dispatch='2*n_jobs', refit=True, return_train_score=True, scoring=None, verbose=0) 

  • Vamos ver qual parâmetro a pesquisa na grade escolheu:

 print("Best parameters: {}".format(grid.best_params_)) 

 Best parameters: {'randomforestclassifier__n_estimators': 30, 'randomforestclassifier__max_leaf_nodes': 20, 'randomforestclassifier__min_impurity_split': 0.2, 'randomforestclassifier__max_depth': 8} 

  • Calcular:

 print("Validation set score: {:.2f}".format(grid.score(X_test, y_test))) 

 Validation set score: 0.79 

  • Faça o download do conjunto de testes:

 test_df = pd.read_csv('../data/test.csv', encoding="utf-8-sig") test_df = test_df.head() 

 grid.predict(test_df) 

 array([1, 1, 1, 1, 1]) 

Nosso pipeline parece bom o suficiente para passar para a próxima etapa importante: serializar o modelo de aprendizado de máquina.

Salvando um modelo de aprendizado de máquina: serialização e desserialização.

“Na ciência da computação, no contexto do armazenamento de dados, a serialização é o processo de converter estruturas de dados ou estados de objetos em um formato armazenado (por exemplo, um arquivo ou buffer de memória) e depois reconstruí-lo no mesmo ou em outro ambiente de computador.”


No Python, a decapagem é a maneira padrão de armazenar objetos e recuperá-los posteriormente em seu estado original. Para deixar mais claro, darei um exemplo simples:

 list_to_pickle = [1, 'here', 123, 'walker'] #Pickling the list import pickle list_pickle = pickle.dumps(list_to_pickle) 

 list_pickle 

 b'\x80\x03]q\x00(K\x01X\x04\x00\x00\x00hereq\x01K{X\x06\x00\x00\x00walkerq\x02e.' 

Em seguida, descarregamos o objeto fixo novamente:

 loaded_pickle = pickle.loads(list_pickle) 

 loaded_pickle 

 [1, 'here', 123, 'walker'] 

Podemos salvar objetos enlatados em um arquivo e usá-los. Esse método é semelhante à criação de arquivos .rda , como na programação R, por exemplo.

Nota: Alguns podem não gostar desse método de preservação para serialização. Uma alternativa pode ser o h5py .

Como temos uma classe personalizada (Classe) que precisamos importar enquanto o treinamento está em andamento, usaremos o módulo dill para compactar o avaliador de classe com o objeto de grade.

É recomendável criar um arquivo training.py separado contendo todo o código para o treinamento do modelo. (Um exemplo pode ser visto aqui ).

  • Instalar dill

 !pip install dill 

 Requirement already satisfied: dill in /home/pratos/miniconda3/envs/ordermanagement/lib/python3.5/site-packages 

 import dill as pickle filename = 'model_v1.pk' 

 with open('../flask_api/models/'+filename, 'wb') as file: pickle.dump(grid, file) 

O modelo será salvo no diretório selecionado acima. Uma vez que o modelo esteja naftalina, ele pode ser embrulhado em um invólucro do Flask. No entanto, antes disso, você precisa garantir que o arquivo fixo funcione. Vamos carregá-lo de volta e fazer uma previsão:

 with open('../flask_api/models/'+filename ,'rb') as f: loaded_model = pickle.load(f) 

 loaded_model.predict(test_df) 

 array([1, 1, 1, 1, 1]) 

Como seguimos as etapas de pré-processamento para que os dados recém-chegados façam parte do pipeline, precisamos apenas executar o forecast (). Usando a biblioteca scikit-learn, é bastante simples trabalhar com pipelines. Os avaliadores e os oleodutos cuidam do seu tempo e dos seus nervos, mesmo que a implementação inicial pareça selvagem.

Criando uma API usando o Flask

Vamos manter a estrutura de pastas o mais simples possível:



Existem três partes importantes na criação de um wrapper para a função apicall() :

  • Receber dados da request (para os quais será feita uma previsão);
  • Carregando um avaliador enlatado;
  • Tradução de nossas previsões no formato JSON e recebimento de um status code: 200 resposta status code: 200 ;

As mensagens HTTP são criadas a partir do cabeçalho e do corpo. Em geral, o conteúdo do corpo principal é transmitido no formato JSON. Enviaremos os dados recebidos ( POST url-endpoint/ ) como um pacote para receber previsões.

Nota: Você pode enviar texto sem formatação, XML, cvs ou uma imagem diretamente para a permutabilidade do formato; no entanto, é preferível usar JSON no nosso caso.

 """Filename: server.py """ import os import pandas as pd from sklearn.externals import joblib from flask import Flask, jsonify, request app = Flask(__name__) @app.route('/predict', methods=['POST']) def apicall(): """API Call Pandas dataframe (sent as a payload) from API Call """ try: test_json = request.get_json() test = pd.read_json(test_json, orient='records') #To resolve the issue of TypeError: Cannot compare types 'ndarray(dtype=int64)' and 'str' test['Dependents'] = [str(x) for x in list(test['Dependents'])] #Getting the Loan_IDs separated out loan_ids = test['Loan_ID'] except Exception as e: raise e clf = 'model_v1.pk' if test.empty: return(bad_request()) else: #Load the saved model print("Loading the model...") loaded_model = None with open('./models/'+clf,'rb') as f: loaded_model = pickle.load(f) print("The model has been loaded...doing predictions now...") predictions = loaded_model.predict(test) """Add the predictions as Series to a new pandas dataframe OR Depending on the use-case, the entire test data appended with the new files """ prediction_series = list(pd.Series(predictions)) final_predictions = pd.DataFrame(list(zip(loan_ids, prediction_series))) """We can be as creative in sending the responses. But we need to send the response codes as well. """ responses = jsonify(predictions=final_predictions.to_json(orient="records")) responses.status_code = 200 return (responses) 

Após a execução, digite: gunicorn --bind 0.0.0.0:8000 server:app
Vamos gerar dados para previsão e uma fila para executar a API localmente em https:0.0.0.0:8000/predict

 import json import requests 

 """Setting the headers to send and accept json responses """ header = {'Content-Type': 'application/json', \ 'Accept': 'application/json'} """Reading test batch """ df = pd.read_csv('../data/test.csv', encoding="utf-8-sig") df = df.head() """Converting Pandas Dataframe to json """ data = df.to_json(orient='records') 

 data 

 '[{"Loan_ID":"LP001015","Gender":"Male","Married":"Yes","Dependents":"0","Education":"Graduate","Self_Employed":"No","ApplicantIncome":5720,"CoapplicantIncome":0,"LoanAmount":110.0,"Loan_Amount_Term":360.0,"Credit_History":1.0,"Property_Area":"Urban"},{"Loan_ID":"LP001022","Gender":"Male","Married":"Yes","Dependents":"1","Education":"Graduate","Self_Employed":"No","ApplicantIncome":3076,"CoapplicantIncome":1500,"LoanAmount":126.0,"Loan_Amount_Term":360.0,"Credit_History":1.0,"Property_Area":"Urban"},{"Loan_ID":"LP001031","Gender":"Male","Married":"Yes","Dependents":"2","Education":"Graduate","Self_Employed":"No","ApplicantIncome":5000,"CoapplicantIncome":1800,"LoanAmount":208.0,"Loan_Amount_Term":360.0,"Credit_History":1.0,"Property_Area":"Urban"},{"Loan_ID":"LP001035","Gender":"Male","Married":"Yes","Dependents":"2","Education":"Graduate","Self_Employed":"No","ApplicantIncome":2340,"CoapplicantIncome":2546,"LoanAmount":100.0,"Loan_Amount_Term":360.0,"Credit_History":null,"Property_Area":"Urban"},{"Loan_ID":"LP001051","Gender":"Male","Married":"No","Dependents":"0","Education":"Not Graduate","Self_Employed":"No","ApplicantIncome":3276,"CoapplicantIncome":0,"LoanAmount":78.0,"Loan_Amount_Term":360.0,"Credit_History":1.0,"Property_Area":"Urban"}]' 

 """POST <url>/predict """ resp = requests.post("http://0.0.0.0:8000/predict", \ data = json.dumps(data),\ headers= header) 

 resp.status_code 

 200 

 """The final response we get is as follows: """ resp.json() 

 {'predictions': '[{"0":"LP001015","1":1},{... 

Conclusão

Neste artigo, avançamos apenas pela metade, criando uma API funcional que fornece previsões e nos aproximamos um passo da integração de soluções de ML diretamente em aplicativos desenvolvidos. Criamos uma API bastante simples que ajudará na prototipagem do produto e o tornará verdadeiramente funcional, mas para enviá-lo à produção, é necessário fazer alguns ajustes que não estão mais no campo do aprendizado de máquina.

Lembre-se de algumas coisas ao criar a API:

  • Criar uma API de qualidade a partir de um código de espaguete é quase impossível, portanto, use seu conhecimento no aprendizado de máquina para criar uma API útil e conveniente.
  • Tente usar o controle de versão para modelos e código da API. Lembre-se de que o Flask não fornece suporte para ferramentas de controle de versão. Salvar e rastrear modelos de ML é uma tarefa difícil, encontre uma maneira conveniente para você. um artigo aqui que fala sobre como fazer isso.
  • Devido às especificidades dos modelos scikit-learn, é necessário garantir que o avaliador e o código de treinamento estejam próximos (se você estiver usando um avaliador personalizado para pré-processamento ou outras tarefas semelhantes). Assim, o modelo fixo terá um avaliador de classe ao lado.

O próximo passo lógico é criar mecânicas para implantar essa API em uma pequena máquina virtual. Existem várias maneiras de fazer isso, mas as abordaremos no próximo artigo.

Código e explicação para este artigo

Fontes úteis:

[1] Não selecione seus dados.
[2] Construindo o Scikit Learn transformadores compatíveis .
[3] Usando o jsonify no Flask .
[4] Início rápido do balão.

Aqui está esse material. Inscreva-se se você gostou da publicação e inscreva-se em um seminário on- line gratuito sobre o tópico: “Algoritmos de classificação métrica”, que será realizado em 12 de março pelo desenvolvedor e cientista de dados com 5 anos de experiência - Alexander Nikitin .

Source: https://habr.com/ru/post/pt442918/


All Articles