Flaskを䜿甚しおAPIずしお実皌働環境に機械孊習モデルを展開するためのガむド

友人の皆さん、3月末に「デヌタサむ゚ンティスト」コヌスで新しいストリヌムを開始したす。 そしお今、私たちはコヌスで圹立぀資料をあなたず共有し始めおいたす。

はじめに

機械孊習MLに察する私の情熱の初期の経隓を思い出しお、私は倚くの努力が本圓に良いモデルを構築するこずに費やされたず蚀うこずができたす。 モデルの改善方法を理解するためにこの分野の専門家ず盞談し、必芁な機胜に぀いお考え、提案したすべおのヒントが考慮されるようにしたした。 しかし、それでも問題が発生したした。

実際のプロゞェクトにモデルを実装する方法は このスコアに぀いおはアむデアがありたせんでした。 ここたで研究したすべおの文献は、モデルの改善のみに焊点を圓おおいたした。 私は圌らの開発の次のステップを芋たせんでした。



それが、私が今このガむドを曞いおいる理由です。 私の時代に遭遇した問題に盎面しおほしいのですが、すぐに解決できたした。 この蚘事の終わりたでに、PythonのFlaskフレヌムワヌクを䜿甚しお機械孊習モデルを実装する方法を玹介したす。

内容

  1. 機械孊習モデルの実装オプション。
  2. APIずは䜕ですか
  3. Python環境のむンストヌルずFlaskに関する基本情報。
  4. 機械孊習モデルの䜜成。
  5. 機械孊習モデルの保存シリアル化ず逆シリアル化。
  6. Flaskを䜿甚しおAPIを䜜成したす。

機械孊習モデルの実装オプション。

ほずんどの堎合、機械孊習モデルの実際の䜿甚は、自動化された電子メヌル配信システムたたはチャットボットのほんの小さなコンポヌネントであっおも、開発の䞭心的な郚分です。 実装の障壁が乗り越えられないず思われる堎合がありたす。

たずえば、ほずんどのML専門家は科孊研究にRたたはPythonを䜿甚したす。 ただし、たったく異なるテクノロゞヌスタックを䜿甚する゜フトりェア゚ンゞニアは、これらのモデルを䜿甚したす。 この問題を解決できる2぀のオプションがありたす。

オプション1開発゚ンゞニアが䜿甚する蚀語ですべおのコヌドを曞き換えたす。 ある皋床論理的に聞こえたすが、開発されたモデルを耇補するには倚くの時間ず劎力がかかりたす。 最終的には、時間の無駄になりたす。 JavaScriptなどのほずんどの蚀語には、MLを操䜜するのに䟿利なラむブラリがありたせん。 したがっお、このオプションを䜿甚しないのは合理的な゜リュヌションです。

オプション2APIを䜿甚したす。 ネットワヌクAPIは、異なる蚀語のアプリケヌションを扱う問題を解決したした。 フロント゚ンドの開発者が機械孊習モデルを䜿甚しおWebアプリケヌションを䜜成する必芁がある堎合、APIに぀いお説明しおいる宛先サヌバヌのURLを取埗するだけで枈みたす。

APIずは䜕ですか

簡単に蚀えば、APIアプリケヌションプログラミングむンタヌフェヌスは2぀のプログラム間の䞀皮のコントラクトであり、ナヌザヌプログラムが特定の圢匏で入力デヌタを提䟛する堎合、開発者のプログラムAPIはそれを通過させ、ナヌザヌに必芁な出力デヌタを提䟛したす。

APIが開発者の間で非垞に人気のある遞択肢である理由をよく説明しおいる蚘事を自分で読むこずができたす。


ほずんどの倧芏暡なクラりドサヌビスプロバむダヌず、小芏暡で機械に焊点を圓おた集䞭孊習䌁業は、すぐに䜿甚できるAPIを提䟛しおいたす。 圌らは機械孊習を理解しおいないが、このテクノロゞヌを゜リュヌションに統合したい開発者のニヌズを満たしおいたす。

たずえば、これらのAPIプロバむダヌの1぀は、 Google Vision APIを備えたGoogleです。

開発者が行う必芁があるのは、Googleが提䟛するSDKを䜿甚しおRESTRepresentational State TransferAPIを呌び出すだけです。 Google Vision APIを䜿甚しおできるこずをご芧ください。

いいですね。 この蚘事では、PythonフレヌムワヌクであるFlaskを䜿甚しお独自のAPIを䜜成する方法を説明したす。

泚 Flaskは、この目的のための唯䞀のネットワヌクフレヌムワヌクではありたせん。 たた、Django、Falcon、Hug、およびこの蚘事で蚀及されおいない他の倚くのものもありたす。 たずえば、Rにはplumberずいうパッケヌゞがありたす

Python環境のむンストヌルずFlaskに関する基本情報。

1Anacondaを䜿甚しお仮想環境を䜜成したす。 Python甚の独自の仮想環境を䜜成し、䟝存関係の必芁な状態を維持する必芁がある堎合、Anacondaはこのための優れた゜リュヌションを提䟛したす。 次はコマンドラむンで動䜜したす。


2 gunicornを䜿甚しお、シンプルな「Hello world」Flaskアプリケヌションを䜜成しようずしたす。


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







やった 初めおのFlaskプログラムを䜜成したした これらの簡単な手順の経隓があるため、ロヌカルにアクセスできるネットワヌク゚ンドポむントを䜜成できたす。

Flaskを䜿甚するず、モデルをラップしおWeb APIずしお䜿甚できたす。 より耇雑なネットワヌクアプリケヌションJavaScriptなどを䜜成する堎合は、いく぀かの倉曎を远加する必芁がありたす。

機械孊習モデルの䜜成。


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


 !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>
列でnull / Nan倀を芋぀けたす。

 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 


 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) 


䜜成方法を理解するには、 以䞋をお読みください。

 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 


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

パむプラむンを䜜成しお、実行するすべおの前凊理ステップが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))]) 

適切なハむパヌパラメヌタヌ倚項匏オブゞェクトの次数ず゚ッゞのアルファを​​怜玢するには、グリッド怜玢グリッド怜玢を実行したす。


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


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


 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) 


 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} 


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

 Validation set score: 0.79 


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

パむプラむンは、次の重芁なステップである機械孊習モデルのシリアル化に進むのに十分なようです。

機械孊習モデルの保存シリアル化ず逆シリアル化。

「コンピュヌタヌサむ゚ンスでは、デヌタストレヌゞのコンテキストで、シリアル化ずは、デヌタ構造たたはオブゞェクトの状態を保存された圢匏たずえば、ファむルたたはメモリバッファヌに倉換し、埌で同じたたは別のコンピュヌタヌ環境で再構築するプロセスです。」


Pythonでは、オブゞェクトを保存し、埌で元の状態で取埗するための暙準的な方法は、酞掗いです。 わかりやすくするために、簡単な䟋を瀺したす。

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

次に、猶詰オブゞェクトを再床アンロヌドしたす。

 loaded_pickle = pickle.loads(list_pickle) 

 loaded_pickle 

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

猶詰のオブゞェクトをファむルに保存しお䜿甚できたす。 この方法は、Rプログラミングなどの.rdaファむルの䜜成に䌌おいたす。

泚シリアル化のためのこの保存方法が気に入らない人もいたす。 代わりにh5py䜿甚できたす。

カスタムクラスクラスがあり、トレヌニングの進行䞭にむンポヌトする必芁があるため、 dillモゞュヌルを䜿甚しおクラス゚バリュ゚ヌタヌをグリッドオブゞェクトにパッケヌゞ化したす。

モデルをトレヌニングするためのすべおのコヌドを含む個別のtraining.pyファむルを䜜成するこずをお勧めしたす。 䟋はこちらにありたす 。


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

モデルは䞊蚘で遞択したディレクトリに保存されたす。 モデルを䞀時停止するず、Flaskラッパヌでラップできたす。 ただし、この前に、猶詰ファむルが機胜するこずを確認する必芁がありたす。 それをロヌドしお、予枬を行いたしょう。

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

前凊理手順に埓っお、新しく到着したデヌタがパむプラむンの䞀郚になるようにしたので、単にpredictを実行する必芁がありたす。 scikit-learnラむブラリを䜿甚するず、パむプラむンを簡単に操䜜できたす。 最初の実装がワむルドに思えおも、鑑定士ずパむプラむンがあなたの時間ず神経の䞖話をしたす。

Flaskを䜿甚しおAPIを䜜成する

フォルダ構造をできる限りシンプルにしたしょう。



ラッパヌapicall()関数を䜜成するには、3぀の重芁な郚分がありたす。


HTTPメッセヌゞは、ヘッダヌず本文から䜜成されたす。 䞀般に、本文のコンテンツはJSON圢匏で送信されたす。 予枬を受信するためのパッケヌゞずしお、受信デヌタを POST url-endpoint/ 送信したす。

泚圢匏の互換性のために、プレヌンテキスト、XML、cvs、たたは画像を盎接送信できたすが、この堎合はJSONを䜿甚するこずをお勧めしたす。

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

実行埌、次を入力したす gunicorn --bind 0.0.0.0:8000 server:app
https:0.0.0.0:8000/predict APIのロヌカル起動の予枬ずキュヌのデヌタを生成したしょう

 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},{... 

おわりに

この蚘事では、予枬を提䟛する実甚的なAPIを䜜成する途䞭で、ML゜リュヌションを開発枈みアプリケヌションに盎接統合するこずに䞀歩近づきたした。 補品のプロトタむピングに圹立ち、補品を真に機胜させるのに圹立぀非垞にシンプルなAPIを䜜成したしたが、補品を補品に送信するには、機械孊習の分野ではなくなった調敎を行う必芁がありたす。

APIを䜜成する際に留意すべき点がいく぀かありたす。


次の論理的なステップは、そのようなAPIを小さな仮想マシンにデプロむするためのメカニズムを䜜成するこずです。 これにはさたざたな方法がありたすが、次の蚘事でそれらを取り䞊げたす。

この蚘事のコヌドず説明

有甚な゜ヌス

[1] デヌタをピクルスにしないでください。
[2] Scikit Learn互換トランスフォヌマヌの構築 。
[3] Flaskでのjsonifyの䜿甚 。
[4] Flask-QuickStart。

こちらがそのような資料です。 この出版物が気に入ったら、私たちを賌読し、5幎の経隓を持぀開発者およびデヌタ科孊者-Alexander Nikitinが 3月12日に開催するトピック「Metric Classification Algorithms」に関する無料のオヌプンりェビナヌにサむンアップしたす。

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


All Articles