Guide de déploiement de modèles d'apprentissage automatique dans un environnement de production en tant qu'API à l'aide de Flask

Chers amis, fin mars, nous lançons un nouveau volet sur le cours «Data Scientist» . Et maintenant, nous commençons à partager avec vous des informations utiles sur le cours.

Présentation

Rappelant les premières expériences de ma passion pour l'apprentissage automatique (ML), je peux dire que beaucoup d'efforts ont été consacrés à la construction d'un très bon modèle. J'ai consulté des experts dans ce domaine pour comprendre comment améliorer mon modèle, pensé aux fonctions nécessaires, essayé de m'assurer que tous les conseils qu'ils proposaient étaient pris en compte. Mais j'ai quand même rencontré un problème.

Comment implémenter le modèle dans un vrai projet? Je n'avais aucune idée sur ce point. Toute la littérature que j'ai étudiée jusqu'à présent ne portait que sur l'amélioration des modèles. Je n'ai pas vu la prochaine étape de leur développement.



C'est pourquoi j'écris ce guide maintenant. Je veux que vous affrontiez le problème que j'ai rencontré à mon époque, mais je pourrais le résoudre rapidement. Vers la fin de cet article, je vais vous montrer comment implémenter un modèle d'apprentissage automatique en utilisant le framework Flask en Python.

Table des matières

  1. Options de mise en œuvre pour les modèles d'apprentissage automatique.
  2. Qu'est-ce qu'une API?
  3. Installation de l'environnement Python et des informations de base sur Flask.
  4. Création d'un modèle d'apprentissage automatique.
  5. Enregistrement de modèles d'apprentissage automatique: sérialisation et désérialisation.
  6. Création d'une API à l'aide de Flask.

Options de mise en œuvre pour les modèles d'apprentissage automatique.

Dans la plupart des cas, l'utilisation réelle des modèles d'apprentissage automatique est un élément central du développement, même s'il ne s'agit que d'un petit composant d'un système de distribution de courrier électronique automatisé ou d'un chatbot. Il arrive parfois que les obstacles à la mise en œuvre semblent insurmontables.

Par exemple, la plupart des spécialistes ML utilisent R ou Python pour leurs recherches scientifiques. Cependant, les ingénieurs logiciels qui utilisent une pile technologique complètement différente seront les consommateurs de ces modèles. Deux options peuvent résoudre ce problème:

Option 1: réécrire tout le code dans le langage avec lequel les ingénieurs de développement travaillent. Cela semble dans une certaine mesure logique, mais il faut beaucoup de temps et d'efforts pour reproduire les modèles développés. En fin de compte, cela s'avère juste une perte de temps. La plupart des langages, tels que JavaScript, ne disposent pas de bibliothèques pratiques pour travailler avec ML. Par conséquent, ce sera une solution rationnelle de ne pas utiliser cette option.

Option 2: utilisez l'API. Les API réseau ont résolu le problème de travailler avec des applications dans différentes langues. Si le développeur front-end doit utiliser votre modèle d'apprentissage automatique pour créer une application Web sur sa base, il n'a qu'à obtenir l'URL du serveur de destination discutant de l'API.

Qu'est-ce qu'une API?

En termes simples, l'API (Application Programming Interface) est une sorte de contrat entre deux programmes, qui dit que si un programme utilisateur fournit des données d'entrée dans un format spécifique, le programme développeur (API) les transmet et fournit à l'utilisateur des données de sortie.

Vous pourrez lire vous-même quelques articles, qui décrivent bien pourquoi l'API est un choix assez populaire parmi les développeurs.


La plupart des grands fournisseurs de services cloud et les petites entreprises axées sur l'apprentissage automatique fournissent des API prêtes à l'emploi. Ils répondent aux besoins des développeurs qui ne comprennent pas le machine learning, mais souhaitent intégrer cette technologie dans leurs solutions.

Par exemple, l'un de ces fournisseurs d'API est Google avec son API Google Vision .

Tout ce que le développeur doit faire est simplement d'appeler l'API REST (Representational State Transfer) à l'aide du SDK fourni par Google. Découvrez ce que vous pouvez faire en utilisant l' API Google Vision .

Sonne bien, non? Dans cet article, nous découvrirons comment créer votre propre API à l'aide de Flask, un framework Python.

Remarque : Flask n'est pas la seule infrastructure réseau à cet effet. Il existe également Django, Falcon, Hug et bien d'autres qui ne sont pas mentionnés dans cet article. Par exemple, pour R, il existe un package appelé plombier

Installation de l'environnement Python et des informations de base sur Flask.

1) Création d'un environnement virtuel à l'aide d'Anaconda. Si vous devez créer votre propre environnement virtuel pour Python et maintenir l'état nécessaire des dépendances, Anaconda propose de bonnes solutions pour cela. Next fonctionnera avec la ligne de commande.

  • Vous trouverez ici le programme d' installation de miniconda pour Python;
  • wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh
  • bash Miniconda3-latest-Linux-x86_64.sh
  • Suivez la sĂ©quence de questions.
  • source .bashrc
  • Si vous tapez: conda , vous pouvez voir une liste des commandes et de l'aide disponibles.
  • Pour crĂ©er un nouvel environnement, tapez: conda create --name <environment-name> python=3.6
  • Suivez les Ă©tapes qui vous seront demandĂ©es et Ă  la fin entrez: source activate <environment-name>
  • Installez les packages Python requis. Les plus importants sont le flacon et le gunicorn.

2) Nous allons essayer de créer notre application Flask «Hello world» simple en utilisant gunicorn .

  • Ouvrez votre Ă©diteur de texte prĂ©fĂ©rĂ© et crĂ©ez le fichier hello-world.py dans le dossier
  • Écrivez le code suivant:

 """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)) 

  • Enregistrez le fichier et revenez au terminal.
  • Pour lancer l'API, exĂ©cutez dans le terminal: gunicorn --bind 0.0.0.0:8000 hello-world:app
  • Si vous obtenez ce qui suit, alors vous ĂŞtes sur la bonne voie:



  • Dans le navigateur, entrez les informations suivantes: https://localhost:8000/users/any-name



Hourra! Vous avez écrit votre premier programme Flask! Puisque vous avez déjà une certaine expérience de ces étapes simples, nous pouvons créer des points de terminaison réseau accessibles localement.

En utilisant Flask, nous pouvons envelopper nos modèles et les utiliser comme API Web. Si nous voulons créer des applications réseau plus complexes (par exemple, en JavaScript), nous devons ajouter quelques modifications.

Création d'un modèle d'apprentissage automatique.

  • Pour commencer, jetons un coup d'Ĺ“il au concours d' apprentissage automatique Loan Prediction Competition . L'objectif principal est de mettre en place un pipeline de prĂ©traitement et de crĂ©er des modèles ML pour faciliter la tâche de prĂ©diction lors du dĂ©ploiement.

 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") 

  • Enregistrez l'ensemble de donnĂ©es dans le dossier:

 !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>
Trouvez les valeurs null / Nan dans les colonnes:

 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 

  • L'Ă©tape suivante consiste Ă  crĂ©er des ensembles de donnĂ©es pour la formation et les tests:

 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) 

  • Pour nous assurer que toutes les Ă©tapes de prĂ©-traitement sont terminĂ©es correctement mĂŞme après avoir expĂ©rimentĂ©, et que nous n'avons rien manquĂ© pendant la prĂ©diction, nous allons crĂ©er notre propre Ă©valuateur Scikit-learn pour le prĂ©-traitement (prĂ©-traitement de l'estimateur Scikit-learn) .

Pour comprendre comment nous l'avons créé, lisez ce qui suit .

 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 

  • Convertissez y_train et y_test en np.array :

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

Créons un pipeline pour nous assurer que toutes les étapes de prétraitement que nous effectuons sont le travail de l'évaluateur 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))]) 

Pour rechercher des hyper paramètres appropriés (degré pour les objets polynomiaux et alpha pour une arête), nous allons faire une recherche de grille (Grid Search):

  • DĂ©finissez 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]} 

  • Lance la recherche dans la grille:

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

  • Nous ajustons les donnĂ©es de formation pour l'estimateur 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) 

  • Voyons quel paramètre la recherche sur la grille a choisi:

 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} 

  • Calculez:

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

 Validation set score: 0.79 

  • TĂ©lĂ©chargez la suite de tests:

 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]) 

Notre pipeline semble assez bon pour passer à la prochaine étape importante: la sérialisation du modèle d'apprentissage automatique.

Enregistrement d'un modèle d'apprentissage automatique: sérialisation et désérialisation.

«En informatique, dans le contexte du stockage de données, la sérialisation est le processus de traduction des structures de données ou des états d'objet dans un format stocké (par exemple, un fichier ou un tampon de mémoire) et plus tard de le reconstruire dans le même ou un autre environnement informatique.»


En Python, le décapage est le moyen standard de stocker des objets et de les récupérer plus tard dans leur état d'origine. Pour le rendre plus clair, je vais donner un exemple simple:

 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.' 

Ensuite, nous déchargeons à nouveau l'objet en conserve:

 loaded_pickle = pickle.loads(list_pickle) 

 loaded_pickle 

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

Nous pouvons enregistrer des objets en conserve dans un fichier et les utiliser. Cette méthode est similaire à la création de fichiers .rda , comme dans la programmation R, par exemple.

Remarque: Certains peuvent ne pas aimer cette méthode de conservation pour la sérialisation. Une alternative pourrait être h5py .

Nous avons une classe personnalisée (Class) que nous devons importer pendant que la formation est en cours, nous allons donc utiliser le module dill pour emballer l'évaluateur de classe avec l'objet grille.

Il est conseillé de créer un fichier training.py séparé contenant tout le code pour l'apprentissage du modèle. (Un exemple peut être vu ici ).

  • Installer l' 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) 

Le modèle sera enregistré dans le répertoire sélectionné ci-dessus. Une fois qu'un modèle est mis en veilleuse, il peut être emballé dans un emballage de flacon. Cependant, avant cela, vous devez vous assurer que le fichier en conserve fonctionne. Rechargons-le et faisons une prédiction:

 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]) 

Puisque nous avons suivi les étapes de prétraitement afin que les données nouvellement arrivées fassent partie du pipeline, nous avons juste besoin d'exécuter Predict (). En utilisant la bibliothèque scikit-learn, il est assez simple de travailler avec des pipelines. Les évaluateurs et les pipelines prennent soin de votre temps et de vos nerfs, même si la mise en œuvre initiale semble sauvage.

Création d'une API à l'aide de Flask

Gardons la structure des dossiers aussi simple que possible:



La création d'un wrapper pour la fonction apicall() comprend trois parties importantes:

  • RĂ©ception des donnĂ©es de request (pour lesquelles une prĂ©vision sera faite);
  • Chargement d'un Ă©valuateur en conserve;
  • Traduction de nos prĂ©visions au format JSON et rĂ©ception d'un status code: 200 rĂ©ponse status code: 200 ;

Les messages HTTP sont créés à partir de l'en-tête et du corps. En général, le contenu du corps principal est transmis au format JSON. Nous enverrons ( POST url-endpoint/ ) les données entrantes sous forme de package pour la réception des prévisions.

Remarque: Vous pouvez envoyer du texte brut, XML, cvs ou une image directement pour l'interchangeabilité du format, cependant il est préférable d'utiliser JSON dans notre cas.

 """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) 

Après l'exécution, entrez: gunicorn --bind 0.0.0.0:8000 server:app
Générons les données pour les prévisions et la file d'attente pour le lancement local de l'API à 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},{... 

Conclusion

Dans cet article, nous n'avons fait que la moitié du chemin, créant une API fonctionnelle qui donne des prévisions, et nous sommes devenus un pas de plus vers l'intégration de solutions ML directement dans les applications développées. Nous avons créé une API assez simple qui aidera à prototyper le produit et à le rendre vraiment fonctionnel, mais pour l'envoyer en production, vous devez faire quelques ajustements qui ne sont plus dans le domaine du machine learning.

Il y a quelques points à garder à l'esprit lors de la création de l'API:

  • La crĂ©ation d'une API de qualitĂ© Ă  partir d'un code spaghetti est presque impossible, alors utilisez vos connaissances en apprentissage automatique pour crĂ©er une API utile et pratique.
  • Essayez d'utiliser le contrĂ´le de version pour les modèles et le code API. Gardez Ă  l'esprit que Flask ne prend pas en charge les outils de contrĂ´le de version. L'enregistrement et le suivi des modèles ML est une tâche difficile, trouvez un moyen qui vous convient. Il y a un article ici qui explique comment procĂ©der.
  • En raison des spĂ©cificitĂ©s des modèles scikit-learn, vous devez vous assurer que l'Ă©valuateur et le code de formation sont cĂ´te Ă  cĂ´te (si vous utilisez un Ă©valuateur personnalisĂ© pour le prĂ©traitement ou d'autres tâches similaires). Ainsi, le modèle en conserve aura un Ă©valuateur de classe Ă  cĂ´tĂ© de lui.

La prochaine étape logique consiste à créer des mécanismes pour déployer une telle API sur une petite machine virtuelle. Il existe différentes façons de procéder, mais nous les aborderons dans le prochain article.

Code et explication de cet article

Sources utiles:

[1] Ne décapez pas vos données.
[2] Construction de transformateurs compatibles Scikit Learn .
[3] Utilisation de jsonify dans Flask .
[4] Flask-QuickStart.

Voici un tel matériau. Abonnez-vous à nous si vous avez aimé la publication et inscrivez-vous à un webinaire ouvert gratuit sur le sujet: «Algorithmes de classification métrique», qui sera organisé le 12 mars par le développeur et scientifique des données avec 5 ans d'expérience - Alexander Nikitin .

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


All Articles