Prédiction d'apprentissage automatique Python avec une API REST Flask

Publié: 2022-03-11

Cet article traite de l'utilisation de Python dans le contexte d'un système d'apprentissage automatique ou d'intelligence artificielle (IA) pour effectuer des prédictions en temps réel, avec une API REST Flask. L'architecture exposée ici peut être considérée comme un moyen de passer de la preuve de concept (PoC) au produit minimal viable (MVP) pour les applications d'apprentissage automatique.

Python n'est pas le premier choix auquel on peut penser lors de la conception d'une solution en temps réel. Mais comme Tensorflow et Scikit-Learn font partie des bibliothèques d'apprentissage automatique les plus utilisées prises en charge par Python, il est utilisé de manière pratique dans de nombreux PoC Jupyter Notebook.

Ce qui rend cette solution réalisable, c'est le fait que la formation prend beaucoup de temps par rapport à la prédiction. Si vous considérez la formation comme le processus consistant à regarder un film et à prédire les réponses aux questions à ce sujet, il semble alors assez efficace de ne pas avoir à revoir le film après chaque nouvelle question.

La formation est une sorte de vue compressée de ce "film" et la prédiction consiste à récupérer des informations à partir de la vue compressée. Cela devrait être très rapide, que le film soit complexe ou long.

Implémentons cela avec un exemple rapide [Flask] en Python !

Architecture d'apprentissage automatique générique

Commençons par décrire un flux d'architecture d'entraînement et de prédiction générique :

texte alternatif de l'image

Tout d'abord, un pipeline de formation est créé pour en savoir plus sur les données passées selon une fonction objective.

Cela devrait produire deux éléments clés :

  1. Fonctions d'ingénierie de caractéristiques : les transformations utilisées au moment de l'apprentissage doivent être réutilisées au moment de la prédiction.
  2. Paramètres du modèle : l'algorithme et les hyperparamètres finalement sélectionnés doivent être sauvegardés, afin qu'ils puissent être réutilisés au moment de la prédiction

Notez que l'ingénierie des fonctionnalités effectuée pendant le temps de formation doit être soigneusement enregistrée afin d'être applicable à la prédiction. Un problème habituel parmi tant d'autres qui peut survenir en cours de route est la mise à l' échelle des fonctionnalités qui est nécessaire pour de nombreux algorithmes.

Si l'entité X1 est mise à l'échelle de la valeur 1 à 1000 et est redimensionnée dans la plage [0,1] avec une fonction f(x) = x/max(X1) , que se passerait-il si l'ensemble de prédiction avait une valeur de 2000 ?

Certains ajustements minutieux doivent être pensés à l'avance afin que la fonction de mappage renvoie des sorties cohérentes qui seront correctement calculées au moment de la prédiction.

Formation à l'apprentissage automatique vs prédiction

Il y a une question majeure à aborder ici. Pourquoi séparons-nous la formation et la prédiction pour commencer ?

Il est absolument vrai que dans le cadre d'exemples et de cours d'apprentissage automatique, où toutes les données sont connues à l'avance (y compris les données à prédire), une façon très simple de construire le prédicteur consiste à empiler des données d'entraînement et de prédiction (généralement appelées un jeu de test).

Ensuite, il est nécessaire de s'entraîner sur le "jeu de formation" et de prédire sur le "jeu de test" pour obtenir les résultats, tout en faisant en même temps de l'ingénierie des fonctionnalités sur les données de train et de test, la formation et la prédiction dans le même et unique pipeline .

Cependant, dans les systèmes réels, vous avez généralement des données d'entraînement et les données à prédire arrivent juste au moment où elles sont traitées. En d'autres termes, vous regardez le film à un moment donné et vous avez des questions à ce sujet plus tard, ce qui signifie que les réponses doivent être simples et rapides.

De plus, il n'est généralement pas nécessaire de réentraîner l'intégralité du modèle chaque fois que de nouvelles données arrivent, car l'entraînement prend du temps (peut prendre des semaines pour certains ensembles d'images) et doit être suffisamment stable dans le temps.

C'est pourquoi la formation et la prédiction peuvent être, voire devraient être, clairement séparées sur de nombreux systèmes, ce qui reflète également mieux la façon dont un système intelligent (artificiel ou non) apprend.

Le lien avec le surajustement

La séparation de la formation et de la prédiction est également un bon moyen de résoudre le problème de surapprentissage.

En statistique, le surajustement est "la production d'une analyse qui correspond trop étroitement ou exactement à un ensemble particulier de données, et peut, par conséquent, ne pas ajuster des données supplémentaires ou prédire de manière fiable les observations futures".

texte alternatif de l'image

La ligne verte représente un modèle surajusté et la ligne noire représente un modèle régularisé. Bien que la ligne verte suive le mieux les données d'entraînement, elle dépend trop de ces données et elle est susceptible d'avoir un taux d'erreur plus élevé sur les nouvelles données invisibles, par rapport à la ligne noire._

Le surajustement est particulièrement observé dans les ensembles de données avec de nombreuses fonctionnalités ou avec des ensembles de données avec des données de formation limitées. Dans les deux cas, les données contiennent trop d'informations par rapport à ce qui peut être validé par le prédicteur, et certaines d'entre elles peuvent même ne pas être liées à la variable prédite. Dans ce cas, le bruit lui-même pourrait être interprété comme un signal.

Un bon moyen de contrôler le surajustement est de s'entraîner sur une partie des données et de prédire sur une autre partie sur laquelle on a la vérité terrain. Par conséquent, l'erreur attendue sur les nouvelles données est à peu près l'erreur mesurée sur cet ensemble de données, à condition que les données sur lesquelles nous nous entraînons soient représentatives de la réalité du système et de ses états futurs.

Donc, si nous concevons un pipeline de formation et de prédiction approprié avec une répartition correcte des données, non seulement nous résolvons le problème de surajustement, mais nous pouvons également réutiliser cette architecture pour prédire sur de nouvelles données.

La dernière étape serait de contrôler que l'erreur sur les nouvelles données est la même que celle attendue. Il y a toujours un décalage (l'erreur réelle est toujours inférieure à celle attendue), et il faut déterminer ce qui est un décalage acceptable, mais ce n'est pas le sujet de cet article.

Une API REST pour la prédiction

C'est là qu'il est utile de séparer clairement l'entraînement et la prédiction. Si nous avons enregistré nos méthodes d'ingénierie de fonctionnalités et nos paramètres de modèle, nous pouvons créer une API REST simple avec ces éléments.

texte alternatif de l'image

La clé ici est de charger le modèle et les paramètres au lancement de l'API. Une fois lancé et stocké en mémoire, chaque appel d'API déclenche le calcul d'ingénierie des fonctionnalités et la méthode "predict" de l'algorithme ML. Les deux sont généralement assez rapides pour assurer une réponse en temps réel.

L'API peut être conçue pour accepter un exemple unique à prédire, ou plusieurs différents (prédictions par lots).

Voici le code Python/Flask minimal qui implémente ce principe, avec JSON in et JSON out (question in, answer out) :

 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)

Notez que l'API peut être utilisée pour prédire à partir de nouvelles données, mais je ne recommande pas de l'utiliser pour entraîner le modèle. Il pourrait être utilisé, mais cela complexifie le code d'entraînement du modèle et pourrait être plus exigeant en termes de ressources mémoire.

Exemple de mise en œuvre – Vélos en libre-service

Prenons l'exemple d'un ensemble de données Kaggle, le partage de vélos. Supposons que nous soyons une entreprise de partage de vélos qui souhaite prévoir le nombre de locations de vélos chaque jour afin de mieux gérer l'entretien, la logistique et d'autres aspects de l'activité du vélo.

texte alternatif de l'image

Les locations dépendent principalement des conditions météorologiques, donc avec les prévisions météorologiques, cette entreprise pourrait avoir une meilleure idée du moment où les locations atteindront un pic et essayer d'éviter l'entretien ces jours-là.

Tout d'abord, nous formons un modèle et l'enregistrons en tant qu'objet cornichon visible dans le cahier Jupyter.

La formation et la performance des modèles ne sont pas traitées ici, c'est juste un exemple pour comprendre le processus complet.

Ensuite, nous écrivons la transformation de données qui sera effectuée à chaque appel d'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

Il s'agit simplement d'un calcul d'une variable (jour de l'année) pour inclure à la fois le mois et le jour précis. Il y a aussi une sélection de colonnes et leur ordre respectif à conserver.

Nous devons donc écrire l'API REST avec 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)

Exécutez ce programme, il servira l'API sur le port 5000 par défaut.

Si on teste une requête en local, toujours avec 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}})

La requête contient toutes les informations qui ont été transmises au modèle. Par conséquent, notre modèle répondra avec une prévision des locations de vélos pour les dates spécifiées (ici nous en avons trois).

 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 }

C'est ça! Ce service pourrait être utilisé facilement dans l'application de n'importe quelle entreprise, pour la planification de la maintenance ou pour que les utilisateurs soient au courant du trafic de vélos, de la demande et de la disponibilité des vélos de location.

Mettre tous ensemble

Le défaut majeur de nombreux systèmes d'apprentissage automatique, et en particulier des PoC, est de mélanger formation et prédiction.

S'ils sont soigneusement séparés, les prédictions en temps réel peuvent être effectuées assez facilement pour un MVP, à un coût et un effort de développement assez faibles avec Python/Flask, surtout si, pour de nombreux PoC, il a été initialement développé avec Scikit-learn, Tensorflow, ou toute autre bibliothèque d'apprentissage automatique Python.

Cependant, cela peut ne pas être faisable pour toutes les applications, en particulier les applications où l'ingénierie des fonctionnalités est lourde, ou les applications récupérant la correspondance la plus proche qui doivent disposer des dernières données disponibles à chaque appel.

Dans tous les cas, avez-vous besoin de regarder des films encore et encore pour répondre à des questions à leur sujet ? La même règle s'applique à l'apprentissage automatique !