[PYTHON] Procédure de génération et d'enregistrement d'un modèle d'apprentissage par machine learning, en faisant un serveur API et en communiquant avec JSON depuis un navigateur

introduction

En utilisant le modèle d'apprentissage généré par l'apprentissage automatique en tant que serveur API, j'ai envoyé des données du navigateur via une communication JSON et renvoyé la valeur prédite. Ce serveur API appris par machine est implémenté par trois programmes principaux. Tout d'abord, effectuez un entraînement machine sur XGBoost pour générer et enregistrer le modèle d'entraînement. Ensuite, implémentez le serveur d'API du modèle d'apprentissage dans Flask. Enfin, écrivez la balise form dans le fichier HTML afin que les données obtenues à partir de la balise form puissent être communiquées avec JSON par Ajax de javascript. Avec ces trois programmes, vous pouvez créer quelque chose qui envoie des données du navigateur au serveur API et renvoie la valeur prévue.

Environnement requis pour exécuter ce programme

Des bibliothèques telles que Anaconda, XGBoost, joblib, Flask et flask-cors sont installées.

Processus principal

Cette communication API par machine learning peut être implémentée en suivant le processus suivant.

--Créez un modèle d'apprentissage avec l'apprentissage automatique --Créer un serveur API avec flask

Créez un modèle d'apprentissage avec l'apprentissage automatique

Ici, XGBoost est utilisé pour générer un modèle d'entraînement. Les données d'entraînement utilisent l'ensemble de données Titanic de Kaggle.

Prétraitement de l'ensemble de données

Avant de faire du machine learning avec XGBoost, nous effectuons un pré-traitement. L'ensemble de données Titanic de Kaggle est divisé en données de train et de test, de sorte qu'ils sont concaténés avec concat pour effectuer le prétraitement ensemble. Le prétraitement comprend le traitement des valeurs manquantes, le remplacement des données catégorielles par des valeurs numériques et la suppression des fonctionnalités inutiles.

Chargement des bibliothèques et des ensembles de données

Chargez la bibliothèque requise pour le prétraitement. Nous chargeons également l'ensemble de données en tant que dataframe pandas.

import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import LabelEncoder
train_df = pd.read_csv('titanic/train.csv')
test_df = pd.read_csv('titanic/test.csv')
train_df.head()
PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 5 0 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S
test_df.head()
PassengerId Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 892 3 Kelly, Mr. James male 34.5 0 0 330911 7.8292 NaN Q
1 893 3 Wilkes, Mrs. James (Ellen Needs) female 47.0 1 0 363272 7.0000 NaN S
2 894 2 Myles, Mr. Thomas Francis male 62.0 0 0 240276 9.6875 NaN Q
3 895 3 Wirz, Mr. Albert male 27.0 0 0 315154 8.6625 NaN S
4 896 3 Hirvonen, Mrs. Alexander (Helga E Lindqvist) female 22.0 1 1 3101298 12.2875 NaN S

Concaténation de jeux de données

Puisque je souhaite effectuer le prétraitement collectivement, je concatène les données de train et les données de test avec concat.

all_df = pd.concat((train_df.loc[:, 'Pclass' : 'Embarked'], test_df.loc[:, 'Pclass' : 'Embarked']))
all_df.info()
<class 'pandas.core.frame.DataFrame'>  
Int64Index: 1309 entries, 0 to 417  
Data columns (total 10 columns):  
Pclass      1309 non-null int64  
Name        1309 non-null object  
Sex         1309 non-null object  
Age         1046 non-null float64  
SibSp       1309 non-null int64  
Parch       1309 non-null int64  
Ticket      1309 non-null object  
Fare        1308 non-null float64  
Cabin       295 non-null object  
Embarked    1307 non-null object  
dtypes: float64(2), int64(3), object(5)  
memory usage: 112.5+ KB  

Traitement des valeurs manquantes

Étant donné que les valeurs Âge, Tarif et Embarqué sont manquantes, elles sont remplies avec les valeurs moyennes et les plus fréquentes.

all_df['Age'] = all_df['Age'].fillna(all_df['Age'].mean())
all_df['Fare'] = all_df['Fare'].fillna(all_df['Fare'].mean())
all_df['Embarked'] = all_df['Embarked'].fillna(all_df['Embarked'].mode()[0])
all_df.info()
<class 'pandas.core.frame.DataFrame'>  
Int64Index: 1309 entries, 0 to 417  
Data columns (total 10 columns):  
Pclass      1309 non-null int64  
Name        1309 non-null object  
Sex         1309 non-null object  
Age         1309 non-null float64  
SibSp       1309 non-null int64  
Parch       1309 non-null int64  
Ticket      1309 non-null object  
Fare        1309 non-null float64  
Cabin       295 non-null object  
Embarked    1309 non-null object  
dtypes: float64(2), int64(3), object(5)  
memory usage: 112.5+ KB
all_df.head()
Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
3 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) female 35.0 1 0 113803 53.1000 C123 S
4 3 Allen, Mr. William Henry male 35.0 0 0 373450 8.0500 NaN S

Remplacer les données catégoriques par des nombres

Sex et Embarked sont des données catégoriques, donc LabelEncoder les remplace par des nombres.

cat_features = ['Sex', 'Embarked']

for col in cat_features:
    lbl = LabelEncoder()
    all_df[col] = lbl.fit_transform(list(all_df[col].values))
all_df.head()
Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 3 Braund, Mr. Owen Harris 1 22.0 1 0 A/5 21171 7.2500 NaN 2
1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... 0 38.0 1 0 PC 17599 71.2833 C85 0
2 3 Heikkinen, Miss. Laina 0 26.0 0 0 STON/O2. 3101282 7.9250 NaN 2
3 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) 0 35.0 1 0 113803 53.1000 C123 2
4 3 Allen, Mr. William Henry 1 35.0 0 0 373450 8.0500 NaN 2

Supprimer les fonctionnalités inutiles

Le nom et le ticket sont des valeurs catégorielles et uniques, supprimez-les. En outre, Cabin a de nombreuses valeurs manquantes, supprimez-le.

all_df = all_df.drop(columns = ['Name', 'Ticket', 'Cabin'])
all_df.head()
Pclass Sex Age SibSp Parch Fare Embarked
0 3 1 22.0 1 0 7.2500 2
1 1 0 38.0 1 0 71.2833 0
2 3 0 26.0 0 0 7.9250 2
3 1 0 35.0 1 0 53.1000 2
4 3 1 35.0 0 0 8.0500 2

Séparez le train et testez comme avant

Puisque le train et le test ont été connectés, le train et le test sont séparés pour devenir des données d'entraînement. Vous pouvez séparer le train et le test en utilisant la valeur de forme de train_df.

train = all_df[:train_df.shape[0]]
test = all_df[train_df.shape[0]:]
train.info()
<class 'pandas.core.frame.DataFrame'>  
Int64Index: 891 entries, 0 to 890  
Data columns (total 7 columns):  
Pclass      891 non-null int64  
Sex         891 non-null int64  
Age         891 non-null float64  
SibSp       891 non-null int64  
Parch       891 non-null int64  
Fare        891 non-null float64  
Embarked    891 non-null int64  
dtypes: float64(2), int64(5)  
memory usage: 55.7 KB  

Générer un modèle d'entraînement avec XGBoost

Maintenant que le prétraitement est terminé, nous continuerons à effectuer un apprentissage automatique avec XGBoost. Cette fois, plutôt que d'améliorer la précision du modèle d'apprentissage, le but est de créer un serveur API à l'aide du modèle d'apprentissage, de sorte que les paramètres, etc., sont appris avec des valeurs presque par défaut.

y = train_df['Survived']
X_train, X_test, y_train, y_test = train_test_split(train, y, random_state = 0)
import xgboost as xgb
params = {
    "objective": "binary:logistic",
    "eval_metric": "auc",
    "eta": 0.1,
    "max_depth": 6,
    "subsample": 1,
    "colsample_bytree": 1,
    "silent": 1
}

dtrain = xgb.DMatrix(X_train, label = y_train)
dtest = xgb.DMatrix(X_test, label = y_test)

model = xgb.train(params = params,
                 dtrain = dtrain,
                 num_boost_round = 100,
                 early_stopping_rounds = 10,
                 evals = [(dtest, 'test')])
[0]	test-auc:0.886905  
Will train until test-auc hasn't improved in 10 rounds.  
[1]	test-auc:0.89624  
[2]	test-auc:0.893243  
[3]	test-auc:0.889603  
[4]	test-auc:0.892857  
[5]	test-auc:0.886005  
[6]	test-auc:0.890673  
[7]	test-auc:0.894741  
[8]	test-auc:0.889603  
[9]	test-auc:0.888832  
[10]	test-auc:0.889431  
[11]	test-auc:0.89153  
Stopping. Best iteration:  
[1]	test-auc:0.89624  

Enregistrer le modèle de formation avec joblib

Il existe plusieurs façons d'enregistrer un modèle d'entraînement entraîné par machine, mais ici, nous utilisons joblib pour l'enregistrer en tant que fichier pkl. Étant donné que le fichier pkl enregistré est enregistré à l'emplacement spécifié, copiez-le dans le dossier du serveur API après cela et utilisez-le.

from sklearn.externals import joblib
joblib.dump(model, 'titanic_model.pkl')

['titanic_model.pkl']

Créer un serveur API avec Flask

Ici, nous essayons d'utiliser le modèle d'apprentissage généré par l'apprentissage automatique en tant que serveur API. Flask, un framework de microservices Python, est utilisé pour le développement de serveurs API. Le flux de développement consiste à créer un environnement virtuel avec conda, à tester un serveur API simple et à y placer le modèle d'apprentissage créé avec XGBoost.

Créez un environnement virtuel avec conda

L'environnement virtuel utilise la conda d'Anaconda. Créez un dossier pour le développement d'applications (titanic_api dans ce cas) dans le terminal et déplacez-le vers ce dossier. Ensuite, conda create crée l'environnement virtuel et conda activate active l'environnement virtuel.

mkdir titanic_api
cd titanic_api
conda create -n titanictenv
conda activate titanictenv

Développer une API avec Flask

Pour développer un serveur API avec Flask, commencez par créer et tester un serveur API simple. Créez les dossiers et fichiers suivants dans le dossier que vous avez créé précédemment. Si vous pouvez écrire le code suivant dans chaque fichier, démarrer le serveur API et communiquer à partir de curl, le test simple du serveur API réussit.

Générez les dossiers et fichiers requis dans le terminal.

Créez des dossiers et des fichiers afin qu'ils aient la hiérarchie suivante. Si vous souhaitez créer un fichier vide, il est pratique d'utiliser la commande tactile.

titanic_api
├── api
│   ├── __init__.py
│   └── views
│       └── user.py
├── titanic_app.py
└── titanic_model.pkl

Ecrire du code dans le fichier créé

Écrivez le code suivant dans le fichier que vous avez créé précédemment. Trois fichiers sont nécessaires pour tester un serveur API simple: api / views / user.py, api / __ init__.py et titanic_app.py. Il est pratique d'utiliser vim lors de l'écriture dans le terminal et Atom lors de l'écriture dans l'interface graphique.

api/views/user.py


from flask import Blueprint, request, make_response, jsonify

#Paramètres de routage
user_router = Blueprint('user_router', __name__)

#Spécifiez le chemin et la méthode HTTP
@user_router.route('/users', methods=['GET'])
def get_user_list():

  return make_response(jsonify({
    'users': [
       {
         'id': 1,
         'name': 'John'
       }
     ]
  }))

api/__init__.py


from flask import Flask, make_response, jsonify
from .views.user import user_router

def create_app():

  app = Flask(__name__)
  app.register_blueprint(user_router, url_prefix='/api')

  return app

app = create_app()

titanic_app.py


from api import app

if __name__ == '__main__':
  app.run()

Accédez au serveur API avec curl.

Après avoir écrit le code ci-dessus, démarrez le serveur avec python titanic_app.py. S'il démarre correctement, ouvrez un autre terminal et testez la communication avec la commande curl comme suit. Si la communication réussit, les données suivantes seront renvoyées.

curl http://127.0.0.1:5000/api/users
{
  "users": [
    {
      "id": 1, 
      "name": "John"
    }
  ]
}

Faire du modèle xgboost un serveur API

Réécrivez titanic_app.py, qui est le fichier de démarrage du serveur API simple, comme suit. À ce stade, le modèle d'apprentissage doit être enregistré directement sous titanic_api.

titanic_app.py


import json

from flask import Flask
from flask import request
from flask import abort

import pandas as pd
from sklearn.externals import joblib
import xgboost as xgb

model = joblib.load("titanic_model.pkl")

app = Flask(__name__)

# Get headers for payload
headers = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']

@app.route('/titanic', methods=['POST'])
def titanic():
    if not request.json:
        abort(400)
    payload = request.json['data']
    values = [float(i) for i in payload.split(',')]
    data1 = pd.DataFrame([values], columns=headers, dtype=float)
    predict = model.predict(xgb.DMatrix(data1))
    return json.dumps(str(predict[0]))


if __name__ == "__main__":
    app.run(debug=True, port=5000)

Faites un test de communication API avec curl

Après avoir réécrit le code, redémarrez le serveur API avec python titanic_app.py. Une fois le serveur API démarré, le test de communication est effectué avec la commande curl comme indiqué ci-dessous. Il réussit si une valeur avec un point décimal de 1 ou moins est renvoyée pour les données JSON envoyées. Vous avez maintenant transformé le modèle d'apprentissage généré par l'apprentissage automatique en serveur API.

curl http://localhost:5000/titanic -s -X POST -H "Content-Type: application/json" -d '{"data": "3, 1, 22.0, 1, 0, 7.2500, 2"}'

Communication API depuis le navigateur

Enfin, en plus de la communication à l'aide de la commande curl depuis le terminal, nous allons créer quelque chose qui renvoie la valeur prédite lorsqu'une valeur est saisie depuis le navigateur. Ce que nous devons faire ici est d'activer la communication Ajax avec le serveur API créé précédemment et d'activer la communication du navigateur au serveur API avec un fichier HTML.

Autoriser json à être POSTÉ sur le serveur API via la communication Ajax

Ici, pour pouvoir communiquer depuis HTML avec javascript Ajax, il est nécessaire d'ajouter le fichier de démarrage du serveur API écrit en Flask comme suit. J'ajouterai une bibliothèque appelée flask_cors et le code associé. flask_cors doit être préinstallé.

titanic_app.py


import json

from flask import Flask
from flask import request
from flask import abort
from flask_cors import CORS #ajouter

import pandas as pd
from sklearn.externals import joblib
import xgboost as xgb

model = joblib.load("titanic_model.pkl")

app = Flask(__name__)

#ajouter à
@app.after_request
def after_request(response):
  response.headers.add('Access-Control-Allow-Origin', '*')
  response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
  response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS')
  return response
#↑ Ajouter ici

# Get headers for payload
headers = ['Pclass', 'Sex', 'Age', 'SibSp', 'Parch', 'Fare', 'Embarked']

@app.route('/titanic', methods=['POST'])
def titanic():
    if not request.json:
        abort(400)
    payload = request.json['data']
    values = [float(i) for i in payload.split(',')]
    data1 = pd.DataFrame([values], columns=headers, dtype=float)
    predict = model.predict(xgb.DMatrix(data1))
    return json.dumps(str(predict[0]))


if __name__ == "__main__":
    app.run(debug=True, port=5000)

Envoyer des données JSON par POST à partir d'un fichier HTML

Dans le fichier HTML, la partie interface est la balise d'entrée à l'intérieur de la balise , etc., et crée un formulaire d'entrée de données. Les données d'entrée sont reçues par javascript, formatées, converties au format JSON et POSTées par la communication Ajax. Lorsque la communication est réussie, la valeur prédite du serveur API est reçue et affichée dans la zone de la balise textarea.

index.html


<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Envoyer des données JSON par POST à partir d'un fichier HTML</title>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>

<script type="text/javascript">
    $(function(){
        $("#response").html("Response Values");

        $("#button").click( function(){
            var url = $("#url_post").val();
            var feature1 =
                $("#value1").val() + "," +
                $("#value2").val() + "," +
                $("#value3").val() + "," +
                $("#value4").val() + "," +
                $("#value5").val() + "," +
                $("#value6").val() + "," +
                $("#value7").val();

            var JSONdata = {
                    data: feature1
                };

            alert(JSON.stringify(JSONdata));

            $.ajax({
                type: 'POST',
                url: url,
                data: JSON.stringify(JSONdata),
                contentType: 'application/JSON',
                dataType: 'JSON',
                scriptCharset: 'utf-8',
                success : function(data) {

                    // Success
                    alert("success");
                    alert(JSON.stringify(JSONdata));
                    $("#response").html(JSON.stringify(data));
                },
                error : function(data) {

                    // Error
                    alert("error");
                    alert(JSON.stringify(JSONdata));
                    $("#response").html(JSON.stringify(data));
                }
            });
        })
    })
</script>

</head>
<body>
    <h1>Envoyer des données JSON par POST à partir d'un fichier HTML</h1>
    <p>URL: <input type="text" id="url_post" name="url" size="100" value="http://localhost:5000/titanic"></p>
    <p>Pclass: <input type="number" id="value1" size="30" value=3></p>
    <p>Sex: <input type="number" id="value2" size="30" value=1></p>
    <p>Age: <input type="number" id="value3" size="30" value="22.0"></p>
    <p>SibSp: <input type="number" id="value4" size="30" value="1"></p>
    <p>Parch: <input type="number" id="value5" size="30" value="0"></p>
    <p>Fare: <input type="number" id="value6" size="30" value="7.2500"></p>
    <p>Embarked: <input type="number" id="value7" size="30" value="2"></p>
    <p><button id="button" type="button">submit</button></p>
    <textarea id="response" cols=120 rows=10 disabled></textarea>
</body>
</html>

référence Construisez un environnement virtuel avec conda: https://code-graffiti.com/how-to-build-a-virtual-environment-with-conda/ Développer une API avec flask: https://swallow-incubate.com/archives/blog/20190819 Faites du modèle xgboost un serveur API: https://towardsdatascience.com/publishing-machine-learning-api-with-python-flask-98be46fb2440 Autoriser json à être POSTÉ sur le serveur API via la communication Ajax: https://www.hands-lab.com/tech/entry/3716.html Envoyer des données JSON par POST à partir d'un fichier HTML: https://qiita.com/kidatti/items/21cc5c5154dbbb1aa27f

Recommended Posts

Procédure de génération et d'enregistrement d'un modèle d'apprentissage par machine learning, en faisant un serveur API et en communiquant avec JSON depuis un navigateur
Un exemple de mécanisme qui renvoie une prédiction par HTTP à partir du résultat de l'apprentissage automatique
J'ai créé un serveur avec socket Python et ssl et j'ai essayé d'y accéder depuis le navigateur
J'ai créé une API avec Docker qui renvoie la valeur prédite du modèle d'apprentissage automatique
(Deep learning) J'ai collecté des images de l'API Flickr et essayé de discriminer en transférant l'apprentissage avec VGG16
J'ai essayé d'appeler l'API de prédiction du modèle d'apprentissage automatique de WordPress
Fabriquez un thermomètre avec Raspberry Pi et rendez-le visible sur le navigateur Partie 3
Mise en place d'un modèle de prédiction des taux de change (taux dollar-yen) par machine learning
[Apprentissage automatique] Créez un modèle d'apprentissage automatique en effectuant un apprentissage par transfert avec votre propre ensemble de données
De l'introduction de Flask sur CentOS au service sur Nginx et uWSGI
Jusqu'à ce que vous créiez un environnement d'apprentissage automatique avec Python sur Windows 7 et que vous l'exécutiez
Un script qui envoie un ping au serveur enregistré et envoie un e-mail avec Gmail un certain nombre de fois en cas d'échec
POSTER l'image avec json et la recevoir avec flask
Créez un environnement d'apprentissage automatique à partir de zéro avec Winsows 10
Introduction à l'apprentissage automatique à partir de Simple Perceptron
Une méthode concrète pour prédire les courses de chevaux et simuler le taux de récupération par apprentissage automatique
Comment dessiner de manière interactive un pipeline d'apprentissage automatique avec scikit-learn et l'enregistrer au format HTML
[Didacticiel d'analyse Python dans la base de données avec SQL Server 2017] Étape 5: Formation et enregistrement des modèles à l'aide de T-SQL
Précautions lors de la saisie à partir de CSV avec Python et de la sortie vers json pour faire exe