Pickle joblib 차이 - Pickle joblib chai

프로젝트를 통해 배우는 기계학습 (4회)

기계학습의 적용- 학습부터 모델의 평가까지

[필자 소개]
장하영은 서울대학교 바이오지능연구실에서 기계학습을 전공했다. 현재는 연구실에서 창업한 스타트업에서 기계학습과 딥러닝 기반의 인공지능 플랫폼을 개발하고 있다. 강한 인공지능(Strong AI)이 아니라 인간의 문제 해결을 도와줄 수 있는 인공지능이 앞으로의 기술 발전 방향이고, 기계학습이 이를 위한 좋은 방법론이 될 수 있을 것이라 믿고 있다.

기계학습으로 알아보는 데이터의 의미
데이터가 비즈니스를 리드하는 시대. 많은 사람이 인공지능을 이용해 데이터를 처리하고 싶어한다.
하지만 대부분의 경우에 인공지능으로 풀고 싶은 문제가 무엇인지 모르고 있고,
심지어는 많은 비용을 들여 유지하고 있는 데이터가 풀고자 하는 문제 해결에 전혀 도움이 안 되는 경우도 많다.
모두가 데이터를 이야기하는 상황에서 도대체 왜 이러한 일이 발생하는 것일까 어떠한 방법으로
어떤 데이터를 이용해 어떤 문제를 풀어야 하는가에 대한 고민 없이 인공지능을 단순히 많은 데이터를 주면
원하는 결과를 쏟아내는 블랙박스처럼 생각하기에 이러한 일들이 발생하게 된다.
이 문제를 해결하기 위해서는 방법론에 대한 이해가 필요하다.
이를 위해 인공지능의 대표적인 방법론인 기계학습을 함께 알아보면서 인공지능을
이해하고 데이터가 갖는 의미를 알아보는 것을 본 연재의 목표로 한다.
본 연재를 제대로 따라하려면 파이썬과 기본적인 확률·통계, 선형대수에 대한 이해가 있으면 좋다.
하지만 프로그래밍 언어 하나쯤을 사용할 수 있고, 고등학생 수준의 확률/통계 지식만 있다면
이해할 수 있도록 진행을 할 예정이니 별도로 이에 대한 공부를 미리 해야 하는 부담을 가질 필요는 없다.

와인 품질 데이터로 기계학습 적용

실세계 데이터에 대한 기계학습의 적용은 지금까지 알아본 것처럼 데이터와 모델의 선정, 학습이라는 간단한 과정을 통해서 이루어지지는 않는다. 실제로 데이터의 특성에 따라서 어떤 모델이 더 좋은 성능을 낼 수 있을 것인가에 대한 이해도 필요하고 모델의 학습 과정에서 어떤 파라메터를 사용하는가에 따라서도 성능의 차이가 발생한다. 이를 해결하기 위해서는 이에 대한 경험적 지식과 이를 적용하기 위한 방법론에 익숙해져야 한다. 연재의 마지막인 이번회에서는 기계학습을 데이터에 적용하기 위해 필요한 과정들을 순차적으로 따라갈 수 있도록 예제 데이터를 통해 알아보도록 하겠다. 이를 위해서 UCI Machine Learning Repository에서 제공하는 와인 품질 데이터를 분석하겠다. 와인 품질 데이터는 pH와 고정산(fixed acidity), 0에서 10사이의 품질점수 등의 다양한 속성(feature) 정보를 포함하고 있다. 품질 점수는 최소 3명의 맛 평가자들(taste testers)의 평균으로 구성돼 있다.

스텝 1. 필요한 라이브러와 모듈 가져오기

먼저 고성능의 다차원 배열 객체와 이를 처리하는 도구들을 제공해 주는 numpy를 임포트한다.

import numpy as np

이어서 다양한 데이터 객체들을 이용해서 쉽게 데이터의 가공을 도와주는 pandas를 임포트한다.

import pandas as pd

이제 기계학습을 위해 필요한 함수들을 불러올 차례인데 먼저 model_selection module에 있는 train_test_split() 함수를 임포트한다. Model_selection module은 다양한 모델들 사이에서 모델의 선택에 대한 정보를 얻을 수 있는 다양한 기능을 제공한다.

from sklearn.model_selection import train_test_split

다음으로는 데이터의 변환을 위한 다양한 기능을 제공하는 preprocessing modle을 임포트한다.

from sklearn import preprocessing

분류를 위한 모델은 랜덤 포레스트(Random Forest)를 사용할 것이다. 랜덤 포레스트는 결정트리(Decision Tree)를 앙상블(Ensemble)하여 사용하는 기계학습 모델이다. 단순하고 직관적인 모델의 특성에도 불구하고 많은 경우에 높은 성능을 보이는 모델이다. 이를 위하여 랜덤 포레스트 모델을 임포트한다.

from sklearn.ensemble import RandomForestRegressor

랜덤 포레스트는 여러 개의 결정 트리를 임의적으로 학습하는 방식의 앙상블 방법이다. 랜덤 포레스트 방법은 크게 다수의 결정 트리를 구성하는 학습 단계와 입력 벡터가 들어왔을 때, 분류하거나 예측하는 테스트 단계로 구성돼 있다. 일반적으로 결정 트리를 이용한 방법의 경우, 그 결과 또는 성능의 변동 폭이 크다는 결점을 갖고 있다. 특히 학습 데이터에 따라 생성되는 결정 트리가 매우 달라지기 때문에 일반화하여 사용하기에 매우 어렵다. 특히 결정 트리는 계층적 접근방식이기 때문에 만약 중간에 에러가 발생한다면 다음 단계로 에러가 계속 전파되는 특성을 가진다. 배깅(Bagging) 또는 임의 노드 최적화(Randomized node optimization)와 같은 임의화 기술은 결정 트리가 가진 이러한 단점을 극복하고 좋은 일반화 성능을 갖도록 한다. 랜덤 포레스트는 검출, 분류, 회귀 등 다양한 애플리케이션으로 활용되고 있다.

Pickle joblib 차이 - Pickle joblib chai

[그림 1] 랜덤 포레스트 모델
이제 모델의 선택이 끝났으므로 모델의 학습 과정에서 사용될 교차 검증(cross validation)을 위해 사용할 도구들을 임포트한다. 교차검증은 데이터의 숫자가 충분히 많지 않아서 전체 데이터를 n개로 나누어서 이 중 하나를 테스트 데이터로 이용하고, 나머지를 학습 데이터로 이용해 총 n번의 학습을 진행해 모델의 성능을 평가하는 방법론이다.

from sklearn.pipeline import make_pipeline
from sklearn.model_selection import GridSearchCV

일반적으로 테스트 데이터가 별도로 존재하는 경우가 많지 않기 때문에 보통은 원래 학습용으로 확보한 데이터 중 일부를 떼어내어 테스트 데이터로 사용하는 경우가 많다. 그런데 학습이나 테스트 데이터를 어떻게 골라 내느냐에 따라 모형의 성능이 조금씩 달라질 수 있다. 따라서 한 세트의 학습/테스트 데이터만 사용하는 것이 아니라 여러 가지 서로 다른 학습·테스트 데이터를 사용해 복수의 테스트를 실시한 후 이 성능 자료로부터 평균 성능(mean performance)과 성능 분산(performance variance)을 모두 구하는 것이 좋다. 교차검증은 아래와 같은 방식으로 진행된다.

1. 전체 데이터를 k개의 부분집합(fold)으로 나눈다(데이터의 크기에 따라 다르지만 k=10을 많이 사용함.)
2. K-1개의 fold를 이용해 학습을 진행한다.
3. 학습에 사용하지 않은 나머지 1개의 fold를하면서 매 수행 시 서로 다른 fold를 이용해 성능을 테스트한다.
5. K개의 테스트 결과를 종합해 평균 성능과 성능 분산을 구한다.

Pickle joblib 차이 - Pickle joblib chai

[그림 2] K-fold 교차검증 모델
이제 학습된 모델의 평가를 위한 메트릭(metric)을 임포트한다.

from sklearn.metrics import mean_squared_error, r2_score

마지막으로 학습된 모델의 저장을 위한 joblib을 임포트한다. Joblib은 이전 회에서 알아봤던 pickle과 마찬가지로 파이썬에서 제공하는 file IO를 위한 package이지만, 대용량의 numpy array를 저장하는 데 있어서 더 효율적으로 동작한다.

from sklearn.externals import joblib

스텝 2. 데이터 로드

데이터의 로드를 위해서 pandas에서는 다양한 방법을 제공한다. 이 중의 하나가 read_csv() 함수로 다음과 같이 사용할 수 있다.

dataset_url = 'http://mlr.cs.umass.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv'
data = pd.read_csv(dataset_url)

로드된 데이터의 일부를 확인하기 위해서는 head() 함수를 사용할 수 있다.

print data.head()
# fixed acidity;"volatile acidity";"citric acid"...
# 0 7.4;0.7;0;1.9;0.076;11;34;0.9978;3.51;0.56...
# 1 7.8;0.88;0;2.6;0.098;25;67;0.9968;3.2;0.68...
# 2 7.8;0.76;0.04;2.3;0.092;15;54;0.997;3.26;0...
# 3 11.2;0.28;0.56;1.9;0.075;17;60;0.998;3.16;...
# 4 7.4;0.7;0;1.9;0.076;11;34;0.9978;3.51;0.56...

이를 통해 데이터를 저장한 csv 파일이 세미콜론을 구분자로 사용한 것을 확인할 수 있는데, 구분자의 제거를 위해서는 아래와 같은 방법을 사용한다.

data = pd.read_csv(dataset_url, sep=';')

print data.head()
# fixed acidity volatile acidity citric acid...
# 0 7.4 0.70 0.00...
# 1 7.8 0.88 0.00...
# 2 7.8 0.76 0.04...
# 3 11.2 0.28 0.56...
# 4 7.4 0.70 0.00...

데이터의 샘플 수와 속성의 개수는 아래와 같이 확인할 수 있다. 전체 데이터는 총 1599개의 샘플로 구성되어 있고, 각각의 샘플은 12개의 속성을 갖고 있다.

print data.shape
# (1599, 12)

또한 describe() 함수를 통해 데이터에 대한 통계적 정보를 얻는 것도 가능하다.

print data.describe()
# fixed acidity volatile acidity citric acid...
# count 1599.000000 1599.000000 1599.000000...
# mean 8.319637 0.527821 0.270976...
# std 1.741096 0.179060 0.194801...
# min 4.600000 0.120000 0.000000...
# 25% 7.100000 0.390000 0.090000...
# 50% 7.900000 0.520000 0.260000...
# 75% 9.200000 0.640000 0.420000...
# max 15.900000 1.580000 1.000000...

스텝 3. 학습 데이터와 테스트 데이터의 분리

전체 데이터를 학습 데이터와 테스트 데이터로 나누어주는 함수인 train_test_split()을 이용하기 위해서는 먼저 데이터의 타깃값을 다른 속성들과 구분시켜 주어야 한다.

y = data.quality
X = data.drop('quality', axis=1)

이제 전체 데이터를 train_test_split() 함수를 이용해 학습 데이터와 테스트 데이터로 구분할 수 있다.

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=123, stratify=y)

이 코드는 전체 데이터의 20%를 테스트 데이터로 분리하게 되고, random state는 분리할 데이터를 선택하기 위해서 사용하는 random seed를 고정시킴으로써 매번 동일한 샘플이 테스트 데이터로 분리되도록 해준다. Statify는 분리된 테스트 데이터와 학습 데이터 상에서 각각의 타깃값들이 동일한 비율로 나타나도록 해주는 역할을 한다. 만약 테스트 데이터와 학습 데이터에 존재하는 타깃값들의 비율이 다르다면 이는 학습 결과와 성능평가 결과 모두에 잘못된 영향을 미치기 때문에 이를 반드시 확인할 필요가 있다.

스텝 4. 데이터의 전처리

대부분의 기계학습 알고리즘은 알고리즘이 제대로 수행되기 위해서 데이터에 적절한 전처리 작업을 필요로 한다. 우리가 사용하고 있는 와인 품질 데이터의 경우에는 각각의 속성 값들 간의 스케일이 매우 크다. 이 경우에 적절한 전처리를 수행하지 않는다면 스케일의 차이로 인해서 특정 속성이 가지고 있는 특성만이 반영될 가능성이 매우 높다. 이를 예방하기 위해서 싸이킷런에서 제공하는 transformer api를 이용해 스케일의 차이를 정규화할 수 있다. 일반적으로 transformer api를 이용한 정규화는 학습 데이터에 대한 transformer를 정의해 평균과 표준편차를 구하고 이를 이용해 학습 데이터를 정규화한 후에 다시 테스트 데이터를 정규화하는 과정을 거친다. 하지만 교차검증을 사용할 경우에는 아래와 같은 교차검증 파이프라인을 이용해서 간단하게 정규화할 수 있다.

pipeline = make_pipeline(preprocessing.StandardScaler(),RandomForestRegressor(n_estimators=100))

스텝 5. 학습에 사용할 하이퍼파라메터의 선언

기계학습에서 고려해야할 파라메터는 두 가지가 있다. 하나는 데이터로부터 직접 학습되는 모델 파라메터이고, 다른 하나는 학습을 반복해 가면서 결정하게 되는 하이퍼파라메터다. 사용하는 모델에서 결정해야 하는 하이퍼파라메터는 다음과 같이 확인할 수 있다.

print pipeline.get_params()
# ...
# 'randomforestregressor__criterion': 'mse',
# 'randomforestregressor__max_depth': None,
# 'randomforestregressor__max_features': 'auto',
# 'randomforestregressor__max_leaf_nodes': None,
# ...

이 하이퍼파라메터들은 앞에서 정의한 파이프라인을 통해서 결정되기 때문에 다음과 같이 교차검증 과정을 통해 결정해야 하는 하이퍼파라메터들을 선언해 준다.

hyperparameters = { 'randomforestregressor__max_features' : ['auto', 'sqrt', 'log2'],
'randomforestregressor__max_depth': [None, 5, 3, 1]}

스텝 6. 교차검증 파이프라인을 통한 모델 학습

앞에서 설명한 교차검증은 아래와 같은 코드를 통해 구현된다.

clf = GridSearchCV(pipeline, hyperparameters, cv=10)

# Fit and tune model
clf.fit(X_train, y_train)

교차검증을 통해서 학습된 최적의 파라메터는 다음과 같이 확인할 수 있다.

print clf.best_params_
# {'randomforestregressor__max_depth': None, 'randomforestregressor__max_features': 'auto'}

스텝 7. 전체 학습 데이터를 이용한 재학습

이렇게 구해진 최적의 파라메터는 전체 학습 데이터를 이용한 추가학습을 통해 약간의 성능 향상을 기대할 수 있다. GridSearchCV()의 경우에는 refit의 실행이 기본값으로 설정돼 있기 때문에 따로 실행할 필요는 없고 아래와 같이 확인을 해 보는 것으로 충분하다.

print clf.refit
# True

스텝 8. 테스트 데이터를 이용한 모델의 평가

학습이 마무리된 clf 객체를 이용해서 아래와 같이 테스트 데이터에 대한 예측값을 구할 수 있다.

y_pred = clf.predict(X_test)

이제 앞에서 임포트한 메트릭들을 이용해서 모델의 성능을 평가하는 것이 가능하다.

print r2_score(y_test, y_pred)
# 0.45044082571584243

print mean_squared_error(y_test, y_pred)
# 0.35461593750000003

마지막으로 결정할 것은 이 성능이 충분히 사용할 수 있는 성능인지 결정하는 것이다. 그렇지 않다면 학습 데이터의 추가나, 전처리 방법의 변경 또는 모델의 변경 등을 통해 원하는 성능을 가진 모델을 구할 때까지 모델을 학습시키는 것이 필요하다.

스텝 9. 학습된 모델의 저장

이전 회에서 pickle을 이용해 학습된 모델을 저장한 것처럼, joblib을 이용해 다음과 같이 모델을 저장할 수 있다.

joblib.dump(clf, 'rf_regressor.pkl')

이렇게 저장된 결과 파일은 다음과 같이 로드해 사용할 수 있다.

clf2 = joblib.load('rf_regressor.pkl')

# Predict data set using loaded model
clf2.predict(X_test)

전체 코드

지금까지 살펴본 코드들은 아래와 같이 사용된다.

# 1. 필요한 library와 module을 가져오기
import numpy as np
import pandas as pd

from sklearn.model_selection import train_test_split
from sklearn import preprocessing
from sklearn.ensemble import RandomForestRegressor
from sklearn.pipeline import make_pipeline
from sklearn.model_selection import GridSearchCV
from sklearn.metrics import mean_squared_error, r2_score
from sklearn.externals import joblib

# 2. 데이터 로드
dataset_url = 'http://mlr.cs.umass.edu/ml/machine-learning-databases/wine-quality/winequality-red.csv'
data = pd.read_csv(dataset_url, sep=';')

# 3. 학습 데이터와 테스트 데이터의 분리
y = data.quality
X = data.drop('quality', axis=1)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=123, stratify=y)

# 4. 데이터의 전처리
pipeline = make_pipeline(preprocessing.StandardScaler(),RandomForestRegressor(n_estimators=100))

# 5. 학습에 사용할 하이퍼파라메터의 선언
hyperparameters = { 'randomforestregressor__max_features' : ['auto', 'sqrt', 'log2'],
'randomforestregressor__max_depth': [None, 5, 3, 1]}

# 6. 교차검증 파이프라인을 통한 모델 학습
clf = GridSearchCV(pipeline, hyperparameters, cv=10)

clf.fit(X_train, y_train)

# 7. 전체 학습 데이터를 이용한 재학습
# No additional code needed if clf.refit == True (default is True)

# 8. 테스트 데이터를 이용한 모델의 평가
pred = clf.predict(X_test)
print r2_score(y_test, pred)
print mean_squared_error(y_test, pred)

# 9. 학습된 모델의 저장
joblib.dump(clf, 'rf_regressor.pkl')
# To load: clf2 = joblib.load('rf_regressor.pkl')

이로써 프로젝트를 통해 배우는 기계학습 4회 연재를 마친다. 결론 부분 조금만 더 추가해 주시면 고맙겠습니다.