設(shè)想這么一種情況:
你構(gòu)建了一個非常好的機器學(xué)習(xí)模型,比方說它可以預(yù)測某種交易中是否存在欺詐嫌疑。現(xiàn)在,你的一個朋友正在為某家銀行開發(fā)Android APP,他希望能把你的模型集成到他們的產(chǎn)品里,因為你的模型太實用了,性能也格外出色。
但是,那個Android APP是用JAVA寫的,你的模型是用Python寫的。怎么辦?難道你還要花時間花精力用JAVA重新寫一個?
這時候,你就需要一種秘密武器——API。在實踐中,上述情況是把機器學(xué)習(xí)模型轉(zhuǎn)換成API的常見需求之一,這一點非常重要,因為現(xiàn)在各行各業(yè)都在尋找可以把技術(shù)用于生產(chǎn)、經(jīng)營的數(shù)據(jù)科學(xué)家。本文將介紹創(chuàng)建API的具體操作,具體來說,它主要涵蓋以下內(nèi)容:
什么是API
Flask基礎(chǔ)入門
構(gòu)建機器學(xué)習(xí)模型
保存機器學(xué)習(xí)模型:序列化和反序列化
用Flask為模型創(chuàng)建API
在Postman中測試API
什么是API
簡單來說,一個API其實就是兩個軟件之間的(假定)契約,如果面向終端用戶的軟件能以預(yù)定義的格式提供輸入,另一個軟件就能擴展其功能,并向面向終端用戶的軟件提供輸出結(jié)果。——Analytics Vidhya
從本質(zhì)上講,API非常類似Web應(yīng)用程序,但前者往往以標準數(shù)據(jù)交換格式返回數(shù)據(jù)(如JSON、XML等)。一旦開發(fā)人員拿到了所需的輸出,他們就能按照各種需求對它進行設(shè)計。現(xiàn)在有很多流行的機器學(xué)習(xí)API,比如IBM Watson就有以下功能:
機器翻譯:將一種語言的文本輸入翻譯為最終用戶的目標語言,支持英語、葡萄牙語、西班牙語和法語。
Message Resonance:分析草稿內(nèi)容,并對它被一個特定的目標受眾接受的可能性進行評分。
Q&A:直接根據(jù)選定和收集到數(shù)據(jù)正文或“語料庫”中的主要數(shù)據(jù)來源,解釋和回答用戶問題。
User Modeling:使用語言分析從一個人的通信方式中提取一組個性和社會特征。
Google Vision API也是一個很好的例子,它主要面向計算機視覺任務(wù)。
基本上,大多數(shù)云服務(wù)提供商都會提供一系列大型、綜合性的API,而以小規(guī)模機器學(xué)習(xí)為重點的企業(yè)則提供即用型API。它們都滿足了那些沒有太多機器學(xué)習(xí)專業(yè)知識背景的開發(fā)人員/企業(yè)的需求,方便他們在流程和產(chǎn)品套件中部署機器學(xué)習(xí)技術(shù)。
在Web開發(fā)中,一些比較流行的機器學(xué)習(xí)API有DialogFlow、Microsoft的Cognitive Toolkit、TensorFlow.js等。
Flask基礎(chǔ)入門
要入門Flask,首先我們得知道什么是Web服務(wù)。Web服務(wù)是API的一種形式,它假定API通過服務(wù)器托管,并且可以被調(diào)用。Web API/Web Service——這些術(shù)語通常可以互換使用。
Flask是一個用Python編寫的輕量級Web服務(wù)框架,當(dāng)然,它不是Python中的唯一框架,同類競品還有Django、Falcon、Hug等。但本文只介紹如何用Flask創(chuàng)建API。
如果你下載了Anaconda版,里面就已經(jīng)包含了Flask。如果你想用pip:
pip install flask
你會發(fā)現(xiàn)它非常小,這也是它深受Python開發(fā)人員喜愛的一個原因。而另一個原因就是Flask框架附帶內(nèi)置的輕量級Web服務(wù)器,需要的配置少,而且可以用Python代碼直接控制。
下面的代碼很好地展示了Flask的簡約性。它創(chuàng)建一個簡單的Web-API,在接收到特定URL時會生成一個特定的輸出。
from flask importFlask
app = Flask(__name__)
@app.route("/")
def hello():
return"Welcome to machine learning model APIs!"
if __name__ == '__main__':
app.run(debug=True)
運行后,你可以在終端瀏覽器中輸入這個網(wǎng)址,然后觀察結(jié)果。
一些要點
Jupyter Notebook非常適合處理有關(guān)Python、R和markdown的東西。但一旦涉及構(gòu)建web服務(wù)器,它就會出現(xiàn)很多奇怪的bug。所以建議大家最好在Sublime等文本編輯器里編寫Flask代碼,并從終端/命令提示符運行代碼。
千萬不要把文件命名為flask.py。
默認情況下,運行Flask的端口號是5000。有時服務(wù)器能在這個端口上正常啟動,但有時,如果你是在Web瀏覽器或任何API客戶端(如Postman)中用URL啟動,它可能會報錯,比如下圖:
根據(jù)Flask的提示,這時服務(wù)器已經(jīng)在端口5000上成功啟動了,但是當(dāng)在瀏覽器中用URL啟動時,它沒有輸出任何內(nèi)容。因此,這可能是端口號沖突了。在這種情況下,我們可以把默認端口號5000改成所需的端口號,只需輸入app.run(debug=True,port=12345)。
輸入以上代碼后,F(xiàn)lask服務(wù)器將如下所示:
現(xiàn)在我們來看看輸入的代碼:
創(chuàng)建Flask實例后,Python會自動生成一個name變量。如果這個文件是作為腳本直接用Python運行的,那么這個變量將為“main”;如果是導(dǎo)入文件,那么“name” 的值將是你導(dǎo)入文件的名稱。例如,如果你有test.py和run.py,并且將test.py導(dǎo)入run.py,那么test.py的“name”值就會是test(app = Flask(test))。
關(guān)于上面hello()的定義,可以用@app.route("/")。同時,裝飾器route()可以告訴Flask什么URL可以觸發(fā)定義好的hello()。
hello()的作用是在使用API時生成輸出。在這種情況下,在Web瀏覽器轉(zhuǎn)到localhost:5000/會產(chǎn)生預(yù)期的輸出(假設(shè)是默認端口)。
如果我們想為機器學(xué)習(xí)模型創(chuàng)建API,下面是一些需要牢記的東西。
構(gòu)建機器學(xué)習(xí)模型
在這里,我們以最常規(guī)的Scikit-learn模型為例,介紹一下怎么用Flask學(xué)習(xí)Scikit-learn模型。首先,我們來回顧一下Scikit-learn的常用模塊:
聚類
回歸
分類
降維
模型選擇
預(yù)處理
對于一般數(shù)據(jù),我們在進行發(fā)送和接收時會涉及將對象轉(zhuǎn)化為便于傳輸?shù)母袷降牟僮鳎鼈円脖环Q為對象的序列化(serialization)和反序列化(deserialization)。模型和數(shù)據(jù)很不一樣,但Scikit-learn剛好支持對訓(xùn)練模型的序列化和反序列化,這就為我們節(jié)省了重新訓(xùn)練模型的時間。通過使用scikit-learn中的模型序列化副本,我們可以編寫Flask API。
同時,Scikit-learn模型的一個要求是數(shù)據(jù)必需采用數(shù)字格式,這就是為什么我們需要把數(shù)據(jù)集里的分類特征轉(zhuǎn)成數(shù)字特征0和1。事實上,除了分類,Scikit-learn的sklearn.preprocessing模塊還提供諸如LabelEncoder、OneHotEncoder等編碼方法。
此外,對于數(shù)據(jù)集里的缺失值,Scikit-learn不能自動填充,而是需要我們自己手動處理,然后再輸入模型。缺失值和上面提到的特征編碼其實都是數(shù)據(jù)預(yù)處理的重要步驟,它們對構(gòu)建性能良好的機器學(xué)習(xí)模型非常重要。
為了方便演示,這里我們以Kaggle上最受歡迎的數(shù)據(jù)集——泰坦尼克為例進行講解。這個數(shù)據(jù)集主要是個分類問題,我們的任務(wù)是根據(jù)表格數(shù)據(jù)預(yù)測乘客的生存概率。為了進一步簡化,我們只用四個變量:age(年齡)、sex(性別)、embarked(登船港口:C=Cherbourg, Q=Queenstown, S=Southampton)和survived。其中survived是個類別標簽。
# Import dependencies
import pandas as pd
import numpy as np
# Load the dataset in a dataframe object and include only four features as mentioned
url = "http://s3.amazonaws.com/assets.datacamp.com/course/Kaggle/train.csv"
df = pd.read_csv(url)
include = ['Age', 'Sex', 'Embarked', 'Survived'] # Only four features
df_ = df[include]
“Sex”和“Embarked”是非數(shù)字的分類特征,我們需要對它們進行編碼;“age”這個特征有不少缺失值,這點可以匯總統(tǒng)計后用中位數(shù)或平均數(shù)來填充;Scikit-learn不能識別NaN,所以我們還要為此編寫一個輔助函數(shù):
categoricals = []
for col, col_type in df_.dtypes.iteritems():
if col_type == 'O':
categoricals.append(col)
else:
df_[col].fillna(0, inplace=True)
上面的代碼是為數(shù)據(jù)集填補缺失值。這里需要注意一點,缺失值對模型性能其實很重要,尤其是當(dāng)空值過多時,我們用單個值填充要非常謹慎,不然很可能會導(dǎo)致很大的偏差。在這個數(shù)據(jù)集里,因為有缺失值的列是age,所以我們不應(yīng)該用0填充NaN。
至于把非數(shù)字特征轉(zhuǎn)成數(shù)字行駛,你可以用One Hot Encoding,也可以用Pandas提供的get_dummies():
df_ohe = pd.get_dummies(df_, columns=categoricals, dummy_na=True)
現(xiàn)在我們已經(jīng)完成了預(yù)處理,可以準備訓(xùn)練機器學(xué)習(xí)模型了:選擇Logistic回歸分類器。
from sklearn.linear_model importLogisticRegression
dependent_variable = 'Survived'
x = df_ohe[df_ohe.columns.difference([dependent_variable])]
y = df_ohe[dependent_variable]
lr = LogisticRegression()
lr.fit(x, y)
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,
intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1,
penalty='l2', random_state=None, solver='liblinear', tol=0.0001,
verbose=0, warm_start=False)
有了模型,之后就是保存模型。從技術(shù)上講這里我們應(yīng)該對模型做序列化,在Python里,這個操作被稱為Pickling。
保存機器學(xué)習(xí)模型:序列化和反序列化
調(diào)用sklearn的joblib:
from sklearn.externals import joblib
joblib.dump(lr, 'model.pkl')
['model.pkl']
Logistic回歸模型現(xiàn)在保持不變,我們可以用一行代碼把它加載到內(nèi)存中,而把模型加載回工作區(qū)的操作就是反序列化。
lr = joblib.load('model.pkl')
用Flask為模型創(chuàng)建API
要用Flask為模型創(chuàng)建服務(wù)器,我們要做兩件事:
當(dāng)APP啟動時把已經(jīng)存在的模型加載到內(nèi)存中。
創(chuàng)建一個API斷電,它接受輸入變量,將它們轉(zhuǎn)換為適當(dāng)?shù)母袷剑⒎祷仡A(yù)測。
更具體地說,你對API的輸入將如下所示:
[
{"Age": 85, "Sex": "male", "Embarked": "S"},
{"Age": 24, "Sex": '"female"', "Embarked": "C"},
{"Age": 3, "Sex": "male", "Embarked": "C"},
{"Age": 21, "Sex": "male", "Embarked": "S"}
]
與此同時,API的輸出會是:
{"prediction": [0, 1, 1, 0]}
其中0表示遇難,1表示幸存。這里輸入格式是JSON,它是最廣泛使用的數(shù)據(jù)交換格式之一。
要做到上述效果,我們需要先編寫一個函數(shù)predict(),它的目標如前所述:
當(dāng)APP啟動時把已經(jīng)存在的模型加載到內(nèi)存中。
創(chuàng)建一個API斷電,它接受輸入變量,將它們轉(zhuǎn)換為適當(dāng)?shù)母袷剑⒎祷仡A(yù)測。
我們已經(jīng)演示了如何加載已有模型,之后是根據(jù)接收的輸入預(yù)測人員生存狀態(tài):
from flask importFlask, jsonify
app = Flask(__name__)
@app.route('/predict', methods=['POST'])
def predict():
json_ = request.json
query_df = pd.DataFrame(json_)
query = pd.get_dummies(query_df)
prediction = lr.predict(query)
return jsonify({'prediction': list(prediction)})
雖然看起來挺簡單,但你可能會在這個步驟遇到一個小問題。
為了讓你編寫的函數(shù)能正常運行,傳入請求中必需包含這四個分類變量的所有可能值,這些值可能是實時的,也可能不是。如果傳入請求里出現(xiàn)必要值缺失,那么根據(jù)當(dāng)前方法定義的predict()生成的數(shù)據(jù)列會比分類器里少,模型就會報錯。
要解決這個問題,我們需要在模型訓(xùn)練期間把列保留下來,把任何Python對象序列化為.pkl文件。
model_columns = list(x.columns)
joblib.dump(model_columns, 'model_columns.pkl')
['model_columns.pkl']
由于已經(jīng)保留了列列表,所以你可以在預(yù)測時處理缺失值(記得在APP啟動前加載模型):
@app.route('/predict', methods=['POST']) # Your API endpoint URL would consist /predict
def predict():
if lr:
try:
json_ = request.json
query = pd.get_dummies(pd.DataFrame(json_))
query = query.reindex(columns=model_columns, fill_value=0)
prediction = list(lr.predict(query))
return jsonify({'prediction': prediction})
except:
return jsonify({'trace': traceback.format_exc()})
else:
print ('Train the model first')
return ('No model here to use')
你已經(jīng)在“/ predict”API中包含了所有必需元素,現(xiàn)在你只需要編寫主類:
if __name__ == '__main__':
try:
port = int(sys.argv[1]) # This is for a command-line argument
except:
port = 12345# If you don't provide any port then the port will be set to 12345
lr = joblib.load(model_file_name) # Load "model.pkl"
print ('Model loaded')
model_columns = joblib.load(model_columns_file_name) # Load "model_columns.pkl"
print ('Model columns loaded')
app.run(port=port, debug=True)
現(xiàn)在,這個API就全部完成可以托管了。
當(dāng)然,如果你想把Logistic回歸模型代碼和Flask API代碼分離為單獨的.py文件,這其實是一種很好的編程習(xí)慣。那么你的model.py代碼應(yīng)該如下所示:
# Import dependencies
import pandas as pd
import numpy as np
# Load the dataset in a dataframe object and include only four features as mentioned
url = "http://s3.amazonaws.com/assets.datacamp.com/course/Kaggle/train.csv"
df = pd.read_csv(url)
include = ['Age', 'Sex', 'Embarked', 'Survived'] # Only four features
df_ = df[include]
# Data Preprocessing
categoricals = []
for col, col_type in df_.dtypes.iteritems():
if col_type == 'O':
categoricals.append(col)
else:
df_[col].fillna(0, inplace=True)
df_ohe = pd.get_dummies(df_, columns=categoricals, dummy_na=True)
# Logistic Regression classifier
from sklearn.linear_model importLogisticRegression
dependent_variable = 'Survived'
x = df_ohe[df_ohe.columns.difference([dependent_variable])]
y = df_ohe[dependent_variable]
lr = LogisticRegression()
lr.fit(x, y)
# Save your model
from sklearn.externals import joblib
joblib.dump(lr, 'model.pkl')
print("Model dumped!")
# Load the model that you just saved
lr = joblib.load('model.pkl')
# Saving the data columns from training
model_columns = list(x.columns)
joblib.dump(model_columns, 'model_columns.pkl')
print("Models columns dumped!")
而api.py則是:
# Dependencies
from flask importFlask, request, jsonify
from sklearn.externals import joblib
import traceback
import pandas as pd
import numpy as np
# Your API definition
app = Flask(__name__)
@app.route('/predict', methods=['POST'])
def predict():
if lr:
try:
json_ = request.json
print(json_)
query = pd.get_dummies(pd.DataFrame(json_))
query = query.reindex(columns=model_columns, fill_value=0)
prediction = list(lr.predict(query))
return jsonify({'prediction': str(prediction)})
except:
return jsonify({'trace': traceback.format_exc()})
else:
print ('Train the model first')
return ('No model here to use')
if __name__ == '__main__':
try:
port = int(sys.argv[1]) # This is for a command-line input
except:
port = 12345# If you don't provide any port the port will be set to 12345
lr = joblib.load("model.pkl") # Load "model.pkl"
print ('Model loaded')
model_columns = joblib.load("model_columns.pkl") # Load "model_columns.pkl"
print ('Model columns loaded')
app.run(port=port, debug=True)
現(xiàn)在,你可以在名為Postman的API客戶端中測試此API 。只要確保model.py與api.py在同一個目錄下,并確保兩者都已在測試前編譯好了,如下圖所示:
如果所有文件都已成功編譯,目錄結(jié)構(gòu)應(yīng)該如下圖所示:
注:IPYNB文件是可選的。
在Postman中測試API
Postman是測試API最好用的工具之一。如果你下載了最新版本,它的界面應(yīng)該如下所示:
成功啟動Flask服務(wù)器后,你需要在Postman中輸入包含正確端口號的正確URL:
恭喜!你剛剛構(gòu)建了第一個機器學(xué)習(xí)API。這是個可以根據(jù)泰坦尼克號乘客age、sex和embarked信息預(yù)測他們生存狀態(tài)的API,現(xiàn)在,你的朋友就能用前端代碼調(diào)用它,輸出神奇的結(jié)果。
-
機器學(xué)習(xí)
+關(guān)注
關(guān)注
66文章
8438瀏覽量
133080 -
python
+關(guān)注
關(guān)注
56文章
4807瀏覽量
85037
原文標題:如何在Python中把機器學(xué)習(xí)模型轉(zhuǎn)成API
文章出處:【微信號:jqr_AI,微信公眾號:論智】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
CortexR52內(nèi)核Cache的具體操作
![CortexR52內(nèi)核Cache的<b class='flag-5'>具體操作</b>](https://file1.elecfans.com/web2/M00/FC/75/wKgZomaUis6AMjitAAAiibG6WqY593.png)
Cortex R52內(nèi)核Cache的具體操作(2)
![Cortex R52內(nèi)核Cache的<b class='flag-5'>具體操作</b>(2)](https://file1.elecfans.com/web2/M00/FC/94/wKgZomaU1E2AWljgAAAHVD5MO1Y922.png)
看門狗定時器的具體操作
移植Miracl庫的具體操作步驟
AssistiveTouch的輔助功能鎖定屏幕如何具體操作呢
Protel在線教程:如何在PCB中給PCB補淚滴的具體操作
![Protel在線教程:如何在PCB<b class='flag-5'>中</b>給PCB補淚滴的<b class='flag-5'>具體操作</b>](https://file1.elecfans.com//web2/M00/A5/91/wKgZomUMOQiAak8yAALIfPA-jXk962.jpg)
視覺圖像系統(tǒng)中,ROI如何做顯示處理以及具體操作步驟
![視覺圖像系統(tǒng)<b class='flag-5'>中</b>,ROI如何做顯示處理以及<b class='flag-5'>具體操作</b>步驟](https://file.elecfans.com/web1/M00/DD/1F/pIYBAGAQxN6ANOBTAAJGiiItAAM394.png)
Allegro打過孔的具體操作步驟
PCB電鍍工藝流程及具體操作方法
差分探頭的使用具體操作步驟
![差分探頭的使用<b class='flag-5'>具體操作</b>步驟](https://file.elecfans.com//web2/M00/9C/5D/poYBAGQqRZCAIxkQAAB0wtEOrvo46.jpeg)
三坐標測量儀的具體操作步驟
![三坐標測量儀的<b class='flag-5'>具體操作</b>步驟](https://file1.elecfans.com/web2/M00/89/57/wKgaomSBapqAQXi_AADdqSBz3_o191.png)
評論