머신러닝

본문 바로가기
사이트 내 전체검색


머신러닝
머신러닝

4. A Flask API for serving scikit-learn models

페이지 정보

작성자 관리자 댓글 0건 조회 1,257회 작성일 20-08-08 07:38

본문

4. A Flask API for serving scikit-learn models

1. 모델 생성


Scikit-learn은 직관적이고 강력한 Python 기계 학습 라이브러리로 많은 모델을 training하고 검증(validating)하기가 매우 쉽습니다. 

Scikit-learn 모델은 사용 될 때마다 모델을 retraining하지 않고 계속 사용할 수 있습니다. 

학습 모델을 사용하여 입력 변수 집합을 기반으로 예측을 제공할 수 있는 API를 만들기 위해 Flask 를 사용할 수 있습니다.


Flask에 들어가기 전에 scikit-learn이 categorical variables(범주형 변수)와 누락된 값(values)을 다루지 않는다는 점이 중요합니다. 

범주형 변수는 숫자 값으로 인코딩해야 합니다. 

일반적으로 범주형 변수는 OneHotEncoder(OHE) 또는 LabelEncoder를 사용하여 변환됩니다. LabelEncoder는 각 categorical value (범주값)에 integer (정수)를 할당하고 

원래 변수를 범주형 변수로 대체된 해당 정수에 상응하는 새 변수로 변환합니다. 

이 방법의 문제점은 nominal variable(명목상의 변수)가 효과적으로 order가 의미 있는 것으로 생각하도록 모델을 속일 수 있는 ordinal variable(정형 변수)로 변환된다는 것입니다.   

반면 OHE는 이 문제로 인해 고생하지는 않지만 범주 변수의 모든 값에 대해 새 변수가 생성되기 때문에 변환된 변수 수가 증가하는 경향이 있습니다.

LabelEncoder는 변수의 범주형 값 수에 따라 transformation (변환)이 변경된다는 것입니다. 

"gold" 및 "platinum" 값이 있는 "subscription" 변수가 있다고 가정해 보겠습니다. 

LabelEncoder는 각각 0과 1로 매핑됩니다. 

이제 "free"값을 추가하면 할당이 변경됩니다.

free는 0으로 인코딩되고, gold는 1, platinum은 2로 인코딩됩니다. 

이러한 이유로 원래 LabelEncoder을 유지하는 것이 중요합니다.


이 예제에서 타이타닉 데이터 집합을사용합니다. 

더 단순화하기 위해 나이, 성별, 착수, 생존의 네 가지 변수를 사용합니다.



import pandas as pd

df = pd.read_csv('titanic.csv')

include = ['Age', 'Sex', 'Embarked', 'Survived']

df_ = df[include]  # only using 4 variables



Sex 와 Embarked는 categorical variables(범주형 변수)이며 변형될 필요가 있습니다. 

"Age"에는 일반적으로 부여된 값이 없으므로 중앙값이나 평균과 같은 summary statistic (요약 통계)으로 대체됩니다. 

누락된 값은 매우 의미있을 수 있으며 실제 응용 프로그램에서 나타내는 내용을 조사할 가치가 있습니다.

여기서는 단순히 NAN을 0으로 대체할 것입니다.


categoricals = []

for col, col_type in df_.dtypes.iteritems():

     if col_type == 'O':

          categoricals.append(col)

     else:

          df_[col].fillna(0, inplace=True)



위의 소스는 df_의 모든 열을 반복하고 범주형 변수(data type "O")를 범주 목록에 추가합니다. 

이 경우 단지 age만 비범주 변수(integers  및 floats)이며, NAN을 0으로 대체합니다. 

단일 값으로 NA를 채우는 것은 의도하지 않은 결과를 초래할 수 있으며, 특히 NA를 대체하는 값이 숫자 변수에 대한 관찰된 범위 내에 있는 경우입니다.

0은 관찰되지 않고 합법적인 age 값이기 때문에 bias(편향)을 도입하지 않는다.


이제 범주형 변수를 OHE할 준비가 되었습니다. 
Pandas는 지정된 dataframe에 대한 OHE 변수를 만들기 위한 간단한 get_dummies 방법을 제공합니다.


df_ohe = pd.get_dummies(df, columns=categoricals, dummy_na=True) 

OHE의 장점은 결정적이라는 것입니다. 
다음 column_value 형식으로 모든 column/value 조합에 대해 새 열이 만들어집니다. 
예를 들어 "Embarked" 변수에 대해서는 "Embarked_C", "Embarked_Q", "Embarked_S", "Embarked_nan"을 얻을 것입니다.
이제 데이터 집합을 성공적으로 변환했으므로 모델을 training할 준비가 되었습니다.

# using a random forest classifier (can be any classifier)
from sklearn.ensemble import RandomForestClassifier as rf
dependent_variable = 'Survived'
x = df_ohe[df_ohe.columns.difference([dependent_variable])
y = df_ohe[dependent_variable]
clf = rf()
clf.fit(x, y)


학습된 모델이 준비가 되었습니다.
이제 sklearn’s joblib을 사용합니다.

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

이제 학습된 모델을 저장했습니다. 
이 모델을 메모리에 load할 수 있습니다.

clf = joblib.load('model.pkl')

이제 Flask를 사용하여 저장된 모델을 제공할 준비가 되었습니다.


2. Flask 어플리케이션 만들기


Flask는 꽤 미니멀한 것입니다. 
다음은 Flask application을 만드는 가장 기본적인 구조입니다.(포트 8080).

from flask import Flask
app = Flask(__name__)
if __name__ == '__main__':
     app.run(port=8080)
 

모델을 서빙하기위해 두가지 일을 해야 합니다.

(1) 응용 프로그램이 시작될 때 저장된 모델을 메모리로 로드합니다.
(2) 입력 변수를 얻고 적절한 형식으로 변환하고 예측을 반환하는 endpoint을 만들어야 합니다.

from flask import Flask, jsonify
from sklearn.externals import joblib
import pandas as pd

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 = clf.predict(query)
     return jsonify({'prediction': list(prediction)})

if __name__ == '__main__':
     clf = joblib.load('model.pkl')
     app.run(port=8080)

이는 들어오는 요청에 범주형 변수에 대해 가능한 모든 값을 포함하는 이상적인 상황에서만 작동합니다. 
그렇지 않은 경우 get_dummies 분류자보다 열이 적은 데이터 프레임을 생성하여 런타임 오류가 발생합니다. 
또한 숫자 변수는 모델을 학습한 것과 동일한 방법론을 사용하여 교체해야 합니다.
예상보다 적은 열 수에 대한 해결책은 training에서 열 목록을 유지하는 것입니다. 
Python objects (lists  및 dictionaries 포함)를 섞어서 지정할 수 있습니다. 
이렇게 하려면 예전처럼 joblib을 사용하여 열 목록을 pkl 파일에 덤프합니다.

model_columns = list(x.columns)
joblib.dumps(model_columns, 'model_columns.pkl')

이 목록이 저장되었기 때문에 예측 시 누락된 값을 0으로 바꿀 수 있습니다. 
또한 응용 프로그램이 시작될 때 모델 열을 로드해야 합니다.

@app.route('/predict', methods=['POST'])
def predict():
     json_ = request.json
     query_df = pd.DataFrame(json_)
     query = pd.get_dummies(query_df)
     for col in model_columns:
          if col not in query.columns:
               query[col] = 0
     prediction = clf.predict(query)
     return jsonify({'prediction': list(prediction)})

if __name__ == '__main__':
     clf = joblib.load('model.pkl')
     model_columns = joblib.load('model_columns.pkl')
     app.run(port=8080)

이 솔루션은 여전히 완벽하지 않습니다. 
training set의 일부로 보이지 않는 값을 보내는 경우 get_dummies 추가 열을 생성하고 오류가 발생합니다.
이 솔루션이 작동하려면 query dataframe에서 model_columns 일부가 아닌 추가 열을 제거해야 합니다.

댓글목록

등록된 댓글이 없습니다.


개인정보취급방침 서비스이용약관 모바일 버전으로 보기 상단으로

TEL. 063-469-4551 FAX. 063-469-4560 전북 군산시 대학로 558
군산대학교 컴퓨터정보공학과

Copyright © www.leelab.co.kr. All rights reserved.