IT & 개발공부/파이썬(Python)

머신러닝과 교차검증(+ 하이퍼 파라미터 튜닝)

규딩코딩 2023. 8. 17. 21:43

 

- 이번 시간에는 샘플 데이터를 이용해서 머신러닝 모델을 만들어보고, 모델의 정확도를 검증해보는 과정 등을 학습해보려고 한다.

- 붓꽃의 품종을 분류하는 것으로 꽃잎의 길이와 너비, 꽃받침의 길이와 너비 피처를 기반으로 꽃의 "품종"을 예측하기 위한 모델을 만들 것이다.

 

0. 준비하기

- 사이킷런(sklearn)에서 사용할 모듈들을 먼저 임포트하고, 사용할 데이터를 가져오자.

 

- iris.data 는 Iris 데이터 세트에서 피처(feature)만으로 된 데이터를 numpy로 가지고 있다.

* 머신러닝에서의 feature란?

더보기

: 머신러닝에서 "피처" 또는 "특성"은 데이터의 속성이나 변수를 나타내는 용어입니다. 머신러닝 모델은 입력 데이터의 다양한 피처를 기반으로 학습되며, 이러한 피처들은 모델이 패턴을 학습하고 예측을 수행하는 데 사용됩니다. 피처는 데이터의 다양한 측면을 나타내며, 데이터 포인트를 설명하고 분석하는 데 도움을 줍니다.

- 데이터를 자세하게 보기 위해 pandas로 DataFrame으로 변환해보면 다음과 같이 확인된다.

- 피처에는 sepal length / sepal widh / petal length / petal width가 있고, label(결정값)은 0,1,2 세가지로 각 Setosa, Versicolor, Virginica 품종을 의미 한다.

 

1. 데이터 분리하기

- 본격적으로 머신러닝을 위하여 train_test_split()이라는 API 로 학습용 데이터와 테스트용 데이터를 분리해보자.

* API란?

더보기

사이킷 런은 편리한 API(Application Programming Interface)를 제공하여 사용자가 머신러닝 모델을 구축, 훈련, 평가하고 예측하는 과정을 단순화하고 추상화합니다.

사이킷 런의 API는 다음과 같은 주요 구성 요소로 이루어져 있습니다:

  1. Estimators (추정기): 사이킷 런의 모든 머신러닝 모델은 추정기라는 객체로 표현됩니다. 추정기는 데이터를 기반으로 모델을 학습하고 예측하는 데 사용됩니다. fit() 메서드로 학습하고, predict() 메서드로 예측합니다.
  2. Transformers (변환기): 변환기는 데이터의 전처리나 특성 공학을 위한 기능을 제공합니다. 예를 들어, 데이터 스케일링, 차원 축소, 피처 추출 등이 있습니다. 변환기는 fit() 메서드로 학습하고, transform() 메서드로 데이터를 변환합니다.
  3. Pipelines (파이프라인): 파이프라인은 여러 변환기와 추정기를 순차적으로 연결하여 작업 흐름을 구성하는 도구입니다. 파이프라인을 사용하면 데이터 전처리와 모델 학습 단계를 간편하게 연결할 수 있습니다.
  4. Model Selection (모델 선택): 사이킷 런은 교차 검증, 하이퍼파라미터 튜닝 등을 위한 도구와 함수를 제공하여 모델의 성능을 평가하고 최적의 모델을 선택할 수 있도록 돕습니다.
  5. Utilities (유틸리티): 데이터셋 로딩, 예제 데이터 생성 등의 유틸리티 함수도 포함되어 있습니다.

사이킷 런의 API는 일관된 인터페이스를 제공하여 머신러닝 작업을 효율적으로 수행할 수 있도록 도와줍니다. 또한 문서화가 잘 되어 있어서 사용자가 필요한 정보와 예제를 쉽게 찾을 수 있습니다.(by.chatGPT 23.08.17)

학습 데이터 사이즈 = 1 - 테스트 데이터 사이즈

결과 해석

X_train: 이것은 훈련 데이터셋의 독립 변수 (feature)를 나타냅니다. 모델이 학습할 때 사용되는 데이터로, 예측을 수행하기 위한 특징들이 들어있는 배열입니다. (120, 4)의 형태는 120개의 샘플과 각 샘플마다 4개의 특징(feature)이 있다는 것을 의미합니다 (sepal length / sepal widh / petal length / petal width).

X_test: 이것은 테스트 데이터셋의 독립 변수 (feature)를 나타냅니다. 모델이 훈련 후 성능을 평가할 때 사용되며, 예측을 수행하는 데에도 사용됩니다. (30, 4)의 형태는 30개의 샘플과 각 샘플마다 4개의 특징(feature)이 있다는 것을 의미합니다.

y_train: 이것은 훈련 데이터셋의 종속 변수 (target)를 나타냅니다. 모델이 학습할 때 사용되는 실제 정답 레이블들이 들어있는 배열입니다. (120,)의 형태는 120개의 샘플에 대한 레이블이 있다는 것을 의미합니다. 훈련 데이터셋에는 총 120개의 샘플이 있으며, 각 샘플에 대한 레이블이 포함됩니다.

y_test: 이것은 테스트 데이터셋의 종속 변수 (target)를 나타냅니다. 모델의 예측과 실제 결과를 비교하여 모델의 성능을 평가하는 데 사용됩니다. (30,)의 형태는 30개의 샘플에 대한 레이블이 있다는 것을 의미합니다. 테스트 데이터셋에는 총 30개의 샘플이 있으며, 각 샘플에 대한 레이블이 포함됩니다.

 

 

2. 학습과 예측

2-1 학습

- 학습데이터를 확보하였으니, 의사 결정 트리 클래스인 DecisionTreeClassifier를 통해 학습과 예측을 수행해보도록 하자.

- 별다른 변화는 없어보이지만, 지금 해당 클래스가 학습을 마친 상태이다.

2-2 예측

- 학습된 데이터를 바탕으로 .predict() 메서드를 이용하여 예측한 결과(여기서는 레이블 값)를 pred 변수에 저장하는 코드.

2-3 평가

- 일반적으로 머신러닝 모델 성능 평가 방법은 다양하나, 여기서는 정확도를 측정해보았다. 예측한 품종(label값)이 실제 품종(label값)과 얼마나 정확하게 맞는지를 평가하는 코드인 것이다. 

*accuracy_score() : 정확도 측정을 위한 사이킷런 함수

 

3. 교차 검증(K Fold) : 데이터가 적을 때

- 실제 사례로 생각해봤을 때 교차 검증의 필요성은 다음과 같이 설명할 수 있겠다. 고객사는 서울시민 전체라는 아주 큰 모집단을 대상으로 고객이 우리 제품을 구매할지 안할지를 예측해달라고 하는 상황이고, 수집된 데이터는 1000명 정도이다. 800명을 훈련데이터, 200명을 테스트 데이터로 분리를 하는 과정에서 "샘플링 이슈"가 발생할 수 있다.

 

전체 데이터에서 구매의향이 있는 사람이 800명, 없는 사람이 200명이었다고 했을 때 200명의 테스트 데이터 중 구매 의향이 없는 사람이 180명이나 뽑히게 되는 것이 "샘플링 이슈"의 극단적 사례이다. 이러한 문제를 해결하기 위한 방법으로써 교차 검증을 사용하는 것이다. 여러 번 검증을 반복하다보면 모델의 정확도도 여러 번 나올 것이고, 그 값들의 평균을 모델의 정확도로 했을 때, 샘플링 이슈가 있을 수 있음에도 전보다 신뢰성이 높아지게 되는 것이다.

 

한마디로 정리하자면 통계는 처음부터 확률, 끝까지 확률이다. 이번 경우도 대수의 법칙을 떠올려보면 왜 실무에서 바쁜 와중에 이러한 검증을 거듭하여 소위 "교차 검증"을 시행하는지 짐작할 수 있을 것이다.

 

3-1 KFold 실제 코드

- K가 만약 5라면 1,2,3,4,5로 데이터를 5등분 하고 첫번째 검증에서는 1을 검증 데이터 나머지를 학습데이터, 두번째 검증에서는 2를 검증데이터 나머지를 학습데이터 ... 하는 식의 반복문이다.

- 또한 split()이 어떤 값(array)을 실제로 반환하는지도 print 한다.

- 최종 마지막에서 이 정확도들의 평균을 "평균 검증 정확도"로 표현한다.

kfold = KFold(n_splits=5)  #5개의 폴드 데이터 생성(k=임의의 수)
cv_accuracy = []
n_iter = 0

# KFold 객체의 split()를 호출하면 폴드별 학습용, 검증용 테스트의 로우 인덱스를 array로 반환
for train_index, test_index in kfold.split(features):

  X_train, X_test = features[train_index], features[test_index] # 독립변수
  y_train, y_test = label[train_index], label[test_index] # 종속변수

  # 학습 및 예측
  dt_clf.fit(X_train, y_train)
  pred = dt_clf.predict(X_test)

  n_iter += 1

  # 반복 시마다 정확도 측정
  accuracy = np.round(accuracy_score(y_test, pred), 4)
  train_size = X_train.shape[0]
  test_size = X_test.shape[0]
  print('\n#{0} 교차 검증 정확도 :{1}, 학습 데이터 크기: {2}, 검증 데이터 크기: {3}'
          .format(n_iter, accuracy, train_size, test_size))
  print('#{0} 검증 세트 인덱스:{1}'.format(n_iter,test_index))
  cv_accuracy.append(accuracy)

print("\n## 평균 검증 정확도", np.mean(cv_accuracy))

위 KFold 코드 결과

 

4. Stratified K Fold

- 층화추출에서 유래한 것으로 분류모델에서는 사실 꼭 Stratified K Fold를 써야한다(사실 이전의 K Fold도 정확하게 샘플링 이슈들을 정제하지는 못하는 것으로 보였다 = 만약의 만약이었을 때를 제거 못하는 것으로 느껴짐).

 

* 층화추출이란?

더보기

층화추출(Stratified Sampling)은 통계학과 확률론에서 사용되는 표본추출 방법 중 하나입니다. 이 방법은 모집단을 서로 다른 하위 그룹 또는 층(strata)으로 나눈 후, 각 층에서 표본을 추출하는 방식입니다. 각 층은 모집단의 다양한 속성이나 특징을 고려하여 형성됩니다. 

층화추출의 목적은 다양한 하위 그룹의 특성을 반영하면서도 표본을 추출함으로써, 추출된 표본이 모집단의 전체적인 특성을 대표할 수 있도록 하는 것입니다. 이는 무작위 추출과 비교하여 더 정확한 통계적 추론을 할 수 있도록 도와줍니다. 특히, 모집단 내에서 특정 특성이나 변수에 따라 다른 분포를 가지는 경우에 층화추출은 유용하게 활용될 수 있습니다. 

예를 들어, 전국의 인구를 지역별로 층화한 후 각 지역별로 표본을 추출한다고 가정해봅시다. 이렇게 하면 우리나라 지역별 인구 분포 특성을 고려하여 표본을 추출하게 되어 전체 인구의 특성을 더 정확하게 반영할 수 있습니다.

 

층화추출은 설문조사, 품질 관리, 실험 설계 등 다양한 분야에서 활용되며, 모집단의 다양한 특성을 고려한 효과적인 표본추출을 위해 중요한 도구 중 하나입니다.

 

이러한 설문조사 중 유명한 것이 국정지지도인데, 만약 특정 정당 지지세가 뚜렷한 우리나라 지역 특성을 고려하지 않고 단순 무작위 추출을 하게 되었을 때 우연에 의해 지지도가 들쑥날쑥할 것이다.

 

4 - 1 실제 코드 적용 방법

from sklearn.model_selection import StratifiedKFold
kfold = KFold(n_splits=3)
# 폴드 세트를 3번 반복한다.

skf = StratifiedKFold(n_splits=3)
n_iter = 0
for train_index, test_index in skf.split(iris_df,iris_df['label']):
  n_iter +=1
  label_train = iris_df['label'].iloc[train_index]
  label_test = iris_df['label'].iloc[test_index]
  print('## 교차 검증: {0}'.format(n_iter))
  print('학습 레이블 데이터 분포:\n', label_train.value_counts())
  print('검증 레이블 데이터 분포:\n', label_test.value_counts())

위 StratifiedKFold 코드 결과값

- 출력 결과를 보면 학습 레이블과 검증 레이블 데이터 값의 분포도가 거의 동일한 것을 확인할 수 있다.

=> 학습 33/33/34 = 100 검증 17/17/16 = 50

 

4-2 Stratified KFold로 나눠진 데이터를 이용한 학습 및 예측 평가

dt_clf = DecisionTreeClassifier(random_state=11)
skfold = StratifiedKFold(n_splits=3)
n_iter = 0
cv_accuracy = []

for train_index, test_index in skfold.split(features, label):
    # print(train_index,test_index) 균일하게 잘 분배되었는지(skfold)확인
    X_train, X_test = features[train_index], features[test_index]
    y_train, y_test = label[train_index], label[test_index]

    # 학습 및 예측
    dt_clf.fit(X_train, y_train)
    pred = dt_clf.predict(X_test)

    # 반복 마다 정확도 측정
    n_iter +=1
    accuracy = np.round(accuracy_score(y_test, pred), 4)
    train_size = X_train.shape[0]
    test_size = X_test.shape[0]
    print('\n#{0} 교차 검증 정확도 :{1}, 학습 데이터 크기: {2}, 검증 데이터 크기: {3}'
            .format(n_iter, accuracy, train_size, test_size))
    print('#{0} 검증 세트 인덱스:{1}'.format(n_iter,test_index))
    cv_accuracy.append(accuracy)

# 교차 검증별 정확도 및 평균 정확도 계산
print('\n## 교차 검증별 정확도:', np.round(cv_accuracy, 4))
print('## 평균 검증 정확도:', np.round(np.mean(cv_accuracy), 4))

기존의 KFold 보다 평균 검증 정확도가 높아졌다. 즉 모델이 보다 안정적으로 변했다 !

 

4-3 API 소개 : cross_val_score()

- 분명 같은 결과가 나오는데, for 반복문을 사용하는 것보다 훨씬 코드가 간편해졌다.

cross_val_score는 scikit-learn 라이브러리에서 제공하는 교차 검증을 수행하는 함수입니다. 이 함수를 사용하면 데이터를 여러 개의 폴드(fold)로 나누어 각 폴드에 대해 모델을 학습하고 평가할 수 있습니다. 교차 검증을 통해 모델의 성능을 더 정확하게 평가할 수 있습니다. 이 함수는 기본적으로 층화 추출(stratified sampling)을 시행합니다.

 

cross_val_score 함수의 주요 파라미터와 사용법 : 

python Copy code cross_val_score(estimator, X, y=None, scoring=None, cv=None, n_jobs=None)

 

estimator: 학습하고 평가할 모델(estimator) 객체를 지정합니다.

X: 특성 데이터 배열입니다.

y: 타겟(레이블) 데이터 배열입니다. (기본값: None)

scoring: 모델의 성능을 평가할 지표를 지정합니다. (기본값: None)

cv: 교차 검증 폴드 수나 교차 검증 분할 전략을 지정합니다. (기본값: None)

n_jobs: 병렬 실행 시 사용할 CPU 코어 수를 지정합니다. (기본값: None)

 

5. GridSearchCV : 교차 검증과 최적 하이퍼 파라미터 튜닝을 한 번에 !

- GridSearchCV를 이해하려면 먼저 하이퍼 파라미터를 알아야한다. 하이퍼 파라미터는 머신러닝 알고리즘의 주요 구성요소로, 이 값을 조정하여 알고리즘 예측 성능을 개선할 수 있다. 사이킷런은 Classifier 나 Regressor와 같은 알고리즘에 사용되는 하이퍼 파라미터를 순차적으로 입력하면서 편리하게 최적 파라미터를 도출할 수 있는 방안을 제공하는데! 바로 그것이 GridSearchCV API이다.

 

- 다음의 코드를 통해 먼저 결정 트리 모형에 대해 이해해보자

from sklearn.datasets import load_iris               # 예제 데이터 불러오기
from sklearn.tree import DecisionTreeClassifier      # 결정트리 머신러닝 알고리즘 중 하나
from sklearn.model_selection import train_test_split # 훈련 데이터 / 테스트 데이터
from sklearn.metrics import accuracy_score
from sklearn.tree import export_graphviz
import graphviz

iris = load_iris()
iris_data = iris.data  # 독립변수
iris_label = iris.target # 종속변수

# 0은 setosa, 1은 versicolor 2는 virginica 품종으로 구분
X_train, X_test, y_train, y_test = train_test_split(
    iris_data     # 독립변수
    , iris_label # 종속변수
    , test_size=0.2
    , random_state=2023
)

# DecisionTreeClassifier 객체 생성
dt_clf = DecisionTreeClassifier(random_state=11, max_depth = 1) # maxdepth 질문의 개수라고 생각해보자

# 학습 수행
dt_clf.fit(X_train, y_train)

# 결정 트리 시각화
export_graphviz(dt_clf, out_file="tree.dot", class_names=iris.target_names,
                feature_names= iris.feature_names, impurity=True, filled=True)

# 모델 평가 및 시각화
pred = dt_clf.predict(X_test)
print(accuracy_score(y_test, pred))

with open('tree.dot') as f:
  dot_graph = f.read()
graphviz.Source(dot_graph)

위 코드 결과

- 실제 GridSearchCV 를 코드로서 사용하는 방법

코드 및 정확도 결과
프린트 된 데이터프레임에서 주목할 점

 

- 이렇게 분류 모델 설계 - 생성 - 평가에 대한 간단한 과정을 학습했다.

 

- 한편, 현업에서는 RandomSearch, GridSearch 중에서 RandomSearch를 많이 쓰게 된다고 하니 간략하게 양지하고 있으면 좋겠다.

 

 

 

 

 

이 글은 파이썬 머신러닝 완벽 가이드 개정 2판(권철민, 2022)을 일부 참고하여 작성하였습니다.

반응형