ch02. 사이킷런으로 시작하는 머신러닝

Chapter 02. 사이킷런으로 시작하는 머신러닝

01. 사이킷런 소개와 특징

  • 사이킷런(scikit-learn)
    • 파이썬 머신러닝 라이브러리 중 가장 많이 사용되는 라이브러리
    • 파이썬 기반의 머신러닝을 위한 가장 쉽고 효율적인 개발 라이브러리를 제공
  • 사이킷런 특징
    • 쉽고 가장 파이썬스러운 API 제공
    • 머신러닝을 위한 다양한 알고리즘, 개발을 위한 편리한 프레임워크와 API 제공
    • 오랜 기간 실전 환경에서 검증됐으며, 매우 많은 환경에서 사용되는 성숙한 라이브러리
  • Anaconda를 설치하면 기본적으로 사이킷런까지 설치가 되기에 설치할 필요는 없음
1
2
3
# 사이킷런 버전 확인
import sklearn
print(sklearn.__version__)
0.23.1

02. 첫 번째 머신러닝 - 붓꽃 품종 예측

  • 붓꽃 데이터 세트로 붓꽃의 품종을 분류(classification)
  • 붓꽃 데이터 세트: 꽃잎 길이, 너비, 꽃받침 길이, 너비 피차(feature)를 기반으로 꽃 품종을 예측하기 위한 데이터 세트
  • 분류는 대표적인 지도학습(Supervised Learning) 방법의 하나
  • 지도학습
    • 학습을 위한 다양한 피처와 분류 결정값인 레이블 데이터로 모델을 학습한 뒤, 별도의 테스트 데이터 세트에서 미지의 레이블을 예측
      → 지도학습은 명확한 정답이 주어진 데이터를 먼저 학습한 뒤 미지의 정답을 예측하는 방식
    • 학습 데이터 세트: 학습을 위해 주어진 데이터 세트
    • 테스트 데이터 세트: 머신러닝 모델의 예측 성능 평가를 위해 주어진 데이터 세트
1
2
3
4
5
6
7
8
# 사이킷런에서 자체적으로 제공하는 데이터 세트를 생성하는 모듈의 모임
from sklearn.datasets import load_iris

# sklearn.tree 내 모듈은 트리 기반 ML 알고리즘을 구현한 클래스 모임
from sklearn.tree import DecisionTreeClassifier

# 학습 데이터와 검증 데이터, 예측데이터로 데이터를 분리하거나 최적의 하이퍼 파라미터로 평가하기 위한 다양한 모듈의 모임
from sklearn.model_selection import train_test_split
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import pandas as pd

# 붓꽃 데이터 세트 로딩
iris = load_iris()

# iris.data는 Iris 데이터 세트에서 피처만으로 된 데이터를 numpy로 가짐
iris_data = iris.data

# iris.target은 붓꽃 데이터 세트에서 레이블(결정값) 데이터를 numpy로 가짐
iris_label = iris.target
print('iris target값:', iris_label)
print('iris target명:', iris.target_names)

# 붓꽃 데이터 세트를 자세히 보기 위해 DataFrame으로 변환
iris_df = pd.DataFrame(data=iris_data, columns=iris.feature_names)
iris_df['label'] = iris.target
iris_df.head(3)
iris target값: [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2]
iris target명: ['setosa' 'versicolor' 'virginica']

sepal length (cm) sepal width (cm) petal length (cm) petal width (cm) label
0 5.1 3.5 1.4 0.2 0
1 4.9 3.0 1.4 0.2 0
2 4.7 3.2 1.3 0.2 0
  • 피처는 총 4개, 레이블은 3개(0: setosa 품종, 1: versicolor 품종, 2: virginica 품종)

  • 학습용 데이터와 테스트용 데이터 분리하기

    • train_test_split() API를 사용하면, 학습 데이터와 테스트 데이터를 test_size 파라미터 입력 값의 비율로 쉽게 분할함
    • test_size=0.2 ← 전체 데이터 중, 테스트 데이터가 20%, 학습 데이터가 80%로 분할됨
1
2
3
4
dt_clf = DecisionTreeClassifier()
iris_data = load_iris()

X_train, X_test, y_train, y_test = train_test_split(iris_data.data, iris_data.target, test_size = 0.2, random_state=11)
  • train_test_split()
    • iris_data: 피처 데이터 세트
    • iris_label: 레이블 데이터 세트
    • test_size=0.2: 전체 데이터 세트 중 테스트 데이터 세트 비율 = 20%
    • random_state: 호출 시마다 같은 학습/테스트용 데이터 세트를 생성하기 위해 주어지는 난수 발생 값 (여기서는 값 고정을 위해 임의 숫자를 넣음)
    • train_test_split(): 호출 시 무작위로 데이터를 분리 → random_state를 지정하지 않으면 수행할 때마다 다른 학습/테스트용 데이터가 생성됨
  • train_test_split() 구분
X_train X_test y_train y_test
학습용 피처 데이터 세트 테스트용 피처 데이터 세트 학습용 레이블 데이터 세트 테스트용 레이블 데이터 세트
  • 의사결정나무로 학습과 예측 수행
    1. 사이킷런 의사결정나무 클래스인 DecisionTreeClassifier를 객체로 생성
    2. 생성된 DecisionTreeClassifier 객체의 fit() 메서드에 학습용 피처 데이터 속성과 결정값 데이터 세트를 입력해 호출
1
2
3
4
5
# DecisionTreeClassifier 객체 생성
dt_clf = DecisionTreeClassifier(random_state=11) # 같은 학습/예측 결과를 위해 random 값 임의 지정

# 학습 수행
dt_clf.fit(X_train, y_train)
DecisionTreeClassifier(random_state=11)
  • 위 코드 실행 결과, DecisionTreeClassifier 객체는 학습 데이터 기반으로 학습 완료
  • 학습된 객체를 이용해 예측 수행
    • 예측은 반드시 학습 데이터가 아닌 다른 데이터를 이용해야 하며, 일반적으로 테스트 데이터 세트를 이용
    • DecisionTreeClassifier 객체의 predict() 메서드에 테스트용 피처 데이터 세트를 입력해 호출하면, 학습된 모델 기반에서 테스트 데이터 세트에 대한 예측값을 반환
1
2
# 학습 완료된 객체에서 테스트 데이터 세트로 예측 수행
pred = dt_clf.predict(X_test)
  • 예측 결과 기반으로 DecisionTreeClassifier의 예측 성능 평가하기
    • 해당 예제에서는 정확도(예측 결과가 실제 레이블값과 얼마나 일치하는지) 평가
    • 사이킷런에서는 정확도 측정을 위해 accuracy_score() 함수 제공
    • accuracy_score: 첫 번째 파라미터로 실제 레이블 데이터 세트, 두 번째 파라미터로 예측 레이블 데이터 세트 입력
1
2
from sklearn.metrics import accuracy_score
print('예측 정확도: {0: 4f}'.format(accuracy_score(y_test, pred)))
예측 정확도:  0.933333
  • 학습한 의사결정나무 알고리즘 예측 정확도가 약 0.9333(93.33%)으로 측정됨
    1. 데이터 세트 분리: 데이터를 학습 데이터와 테스트 데이터로 분리
    2. 모델 학습: 학습 데이터를 기반으로 ML 알고리즘을 적용해 모델을 학습시킴
    3. 예측 수행: 학습된 ML 모델로 테스트 데이터의 분류(즉, 붓꽃 종류) 예측
    4. 평가: 예측된 결괏값과 테스트 데이터의 실제 결괏값을 비교해 ML 모델 성능을 평가

03. 사이킷런의 기반 프레임워크 익히기

1. Estimator 이해 및 fit(), predict() 메서드

  • 사이킷런은 API 일관성과 개발 편의성을 제공하기 위한 패키지
    • 사이킷런을 ML 모델 학습을 위해 fit()을, 학습된 모델의 예측을 위해 predict() 메서드를 제공
    • 지도학습의 주요 두 축인 분류(classification)와 회귀(regression)의 다양한 예측 결과를 반환
    • 사이킷런에서는 분류 알고리즘을 구현한 클래스를 classifier로, 회귀 알고리즘을 구현한 클래스를 regressor로 지칭
    • 사이킷런은 매우 많은 유형의 classifier와 regressor 클래스를 제공: 이를 합쳐 Estimator 클래스라고 부름
  • 사이킷런에서 비지도학습인 차원 축소, 피처 추출 등을 구현한 클래스 역시 대부분 fit()과 transform()을 적용
    • 비지도학습과 피처 추출에서 fit()은 지도학습의 fit() 같이 학습을 의미하는 것이 아니라, 입력 데이터의 형태에 맞춰 데이터를 변환하기 위한 사전 구조를 맞추는 작업
    • fit()으로 변환을 위한 사전 구조를 맞추면 이후 입력 데이터의 차원 변환, 클러스터링, 피처 추출 등의 실제 작업은 transform()으로 수행
    • 사이킷런은 fit()과 transform()을 하나로 결합한 fit_transform()도 함께 제공

2. 사이킷런의 주요 모듈

  • 교재 참고

3. 내장된 예제 데이터 세트

  • 분류나 회귀 연습용 예제 데이터
API명 설명
datasets.load_boston() 회귀 용도이며, 미국 보스턴의 집 피처들과 가격에 대한 데이터 세트
datasets.load_breast_cancer 분류 용도이며, 위스콘신 유방암 피처들과 악성/음성 레이블 데이터 세트
datasets.load_diabetes 회귀 용도이며, 당뇨 데이터 세트
datasets.load_digits() 분류 용도이며, 0에서 9까지 숫자 이미지 픽셀 데이터 세트
datasets.load_iris() 분류 용도이며, 붓꽃에 대한 피처를 가진 데이터 세트

- fetch 계열 명령은 데이터 용량이 커서 인터넷에서 내려받는 방식으로 사용 - fetch_covtype(): 회귀분석용 토지 조사 자료 - fetch_20newsgroups(): 뉴스 그룹 텍스트 자료 - fetch_lfw_people(): 얼굴 이미지 자료 - fetch_lfw_pairs(): 얼굴 이미지 자료 - fetch_rcv1(): 로이터 뉴스 말뭉치 - fetch_mldata(): ML 웹사이트에서 다운로드
- 분류와 클러스터링을 위한 표본 데이터 생성시
API명 설명
datasets.make_classifications() 분류를 위한 데이터 세트를 만듦, 높은 상관도, 불필요한 속성 등의 노이즈 효과를 위한 데이터를 무작위로 생성
datasets.make_blobs() 클러스터링을 위한 데이터 세트를 무작위로 생성, 군집 지정 개수에 따라 여러 클러스터링을 위한 데이터 세트를 쉽게 만듦

  • 사이킷런에 내장된 데이터 딕셔너리 엿보기
    • data: 피처 데이터 세트를 가리킴
    • target: 분류- 레이블값 데이터 세트, 회귀- 숫자 결괏값 데이터 세트
    • target_names: 개별 레이블 이름
    • feature_names: 피처 이름
    • DESCR: 데이터 세트에 관한 설명과 각 피처의 설명
  • 설명
    • data, target: 넘파이 배열(ndarray) 타입
    • target_names, feature_names: 넘파이 배열 또는 파이썬 리스트(list) 타입
    • DESCR: 스트링타입
    • 피처 데이터값을 반환받기 위해서는 내장 데이터 세트 API를 호출한 뒤, key 값을 지정하면 됨
1
2
3
4
5
# 붓꽃 데이터 세트 생성
from sklearn.datasets import load_iris

iris_data = load_iris()
print(type(iris_data))
<class 'sklearn.utils.Bunch'>
  • load_iris() API 반환 결과는 sklearn.utils.Bunch 클래스 (딕셔너리 자료형과 유사)
1
2
keys = iris_data.keys()
print('붓꽃 데이터 세트의 키들:', keys)
붓꽃 데이터 세트의 키들: dict_keys(['data', 'target', 'frame', 'target_names', 'DESCR', 'feature_names', 'filename'])
  • 데이터 키는 피처들의 데이터값을 가리킴
    • 데이터 세트는 딕셔너리 형태로, 데이터 세트.data(or 데이터 세트[‘data’])로 추출 가능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# load_iris()가 반환하는 객체의 키가 가리키는 값 출력
print('\n feature_names의 type:', type(iris_data.feature_names))
print('feature_names의 shape:', len(iris_data.feature_names))
print(iris_data.feature_names)

print('\n target_names의 type:', type(iris_data.target_names))
print('target_names의 shape:', len(iris_data.target_names))
print(iris_data.target_names)

print('\n data의 type:', type(iris_data.data))
print('data의 shape:', iris_data.data.shape)
print(iris_data['data'])

print('\n target의 type:', type(iris_data.target))
print('target의 shape:', iris_data.target.shape)
print(iris_data.target)
 feature_names의 type: <class 'list'>
feature_names의 shape: 4
['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']

 target_names의 type: <class 'numpy.ndarray'>
target_names의 shape: 3
['setosa' 'versicolor' 'virginica']

 data의 type: <class 'numpy.ndarray'>
data의 shape: (150, 4)
[[5.1 3.5 1.4 0.2]
 [4.9 3.  1.4 0.2]
 [4.7 3.2 1.3 0.2]
 [4.6 3.1 1.5 0.2]
 [5.  3.6 1.4 0.2]
 [5.4 3.9 1.7 0.4]
 [4.6 3.4 1.4 0.3]
 [5.  3.4 1.5 0.2]
 [4.4 2.9 1.4 0.2]
 [4.9 3.1 1.5 0.1]
 [5.4 3.7 1.5 0.2]
 [4.8 3.4 1.6 0.2]
 [4.8 3.  1.4 0.1]
 [4.3 3.  1.1 0.1]
 [5.8 4.  1.2 0.2]
 [5.7 4.4 1.5 0.4]
 [5.4 3.9 1.3 0.4]
 [5.1 3.5 1.4 0.3]
 [5.7 3.8 1.7 0.3]
 [5.1 3.8 1.5 0.3]
 [5.4 3.4 1.7 0.2]
 [5.1 3.7 1.5 0.4]
 [4.6 3.6 1.  0.2]
 [5.1 3.3 1.7 0.5]
 [4.8 3.4 1.9 0.2]
 [5.  3.  1.6 0.2]
 [5.  3.4 1.6 0.4]
 [5.2 3.5 1.5 0.2]
 [5.2 3.4 1.4 0.2]
 [4.7 3.2 1.6 0.2]
 [4.8 3.1 1.6 0.2]
 [5.4 3.4 1.5 0.4]
 [5.2 4.1 1.5 0.1]
 [5.5 4.2 1.4 0.2]
 [4.9 3.1 1.5 0.2]
 [5.  3.2 1.2 0.2]
 [5.5 3.5 1.3 0.2]
 [4.9 3.6 1.4 0.1]
 [4.4 3.  1.3 0.2]
 [5.1 3.4 1.5 0.2]
 [5.  3.5 1.3 0.3]
 [4.5 2.3 1.3 0.3]
 [4.4 3.2 1.3 0.2]
 [5.  3.5 1.6 0.6]
 [5.1 3.8 1.9 0.4]
 [4.8 3.  1.4 0.3]
 [5.1 3.8 1.6 0.2]
 [4.6 3.2 1.4 0.2]
 [5.3 3.7 1.5 0.2]
 [5.  3.3 1.4 0.2]
 [7.  3.2 4.7 1.4]
 [6.4 3.2 4.5 1.5]
 [6.9 3.1 4.9 1.5]
 [5.5 2.3 4.  1.3]
 [6.5 2.8 4.6 1.5]
 [5.7 2.8 4.5 1.3]
 [6.3 3.3 4.7 1.6]
 [4.9 2.4 3.3 1. ]
 [6.6 2.9 4.6 1.3]
 [5.2 2.7 3.9 1.4]
 [5.  2.  3.5 1. ]
 [5.9 3.  4.2 1.5]
 [6.  2.2 4.  1. ]
 [6.1 2.9 4.7 1.4]
 [5.6 2.9 3.6 1.3]
 [6.7 3.1 4.4 1.4]
 [5.6 3.  4.5 1.5]
 [5.8 2.7 4.1 1. ]
 [6.2 2.2 4.5 1.5]
 [5.6 2.5 3.9 1.1]
 [5.9 3.2 4.8 1.8]
 [6.1 2.8 4.  1.3]
 [6.3 2.5 4.9 1.5]
 [6.1 2.8 4.7 1.2]
 [6.4 2.9 4.3 1.3]
 [6.6 3.  4.4 1.4]
 [6.8 2.8 4.8 1.4]
 [6.7 3.  5.  1.7]
 [6.  2.9 4.5 1.5]
 [5.7 2.6 3.5 1. ]
 [5.5 2.4 3.8 1.1]
 [5.5 2.4 3.7 1. ]
 [5.8 2.7 3.9 1.2]
 [6.  2.7 5.1 1.6]
 [5.4 3.  4.5 1.5]
 [6.  3.4 4.5 1.6]
 [6.7 3.1 4.7 1.5]
 [6.3 2.3 4.4 1.3]
 [5.6 3.  4.1 1.3]
 [5.5 2.5 4.  1.3]
 [5.5 2.6 4.4 1.2]
 [6.1 3.  4.6 1.4]
 [5.8 2.6 4.  1.2]
 [5.  2.3 3.3 1. ]
 [5.6 2.7 4.2 1.3]
 [5.7 3.  4.2 1.2]
 [5.7 2.9 4.2 1.3]
 [6.2 2.9 4.3 1.3]
 [5.1 2.5 3.  1.1]
 [5.7 2.8 4.1 1.3]
 [6.3 3.3 6.  2.5]
 [5.8 2.7 5.1 1.9]
 [7.1 3.  5.9 2.1]
 [6.3 2.9 5.6 1.8]
 [6.5 3.  5.8 2.2]
 [7.6 3.  6.6 2.1]
 [4.9 2.5 4.5 1.7]
 [7.3 2.9 6.3 1.8]
 [6.7 2.5 5.8 1.8]
 [7.2 3.6 6.1 2.5]
 [6.5 3.2 5.1 2. ]
 [6.4 2.7 5.3 1.9]
 [6.8 3.  5.5 2.1]
 [5.7 2.5 5.  2. ]
 [5.8 2.8 5.1 2.4]
 [6.4 3.2 5.3 2.3]
 [6.5 3.  5.5 1.8]
 [7.7 3.8 6.7 2.2]
 [7.7 2.6 6.9 2.3]
 [6.  2.2 5.  1.5]
 [6.9 3.2 5.7 2.3]
 [5.6 2.8 4.9 2. ]
 [7.7 2.8 6.7 2. ]
 [6.3 2.7 4.9 1.8]
 [6.7 3.3 5.7 2.1]
 [7.2 3.2 6.  1.8]
 [6.2 2.8 4.8 1.8]
 [6.1 3.  4.9 1.8]
 [6.4 2.8 5.6 2.1]
 [7.2 3.  5.8 1.6]
 [7.4 2.8 6.1 1.9]
 [7.9 3.8 6.4 2. ]
 [6.4 2.8 5.6 2.2]
 [6.3 2.8 5.1 1.5]
 [6.1 2.6 5.6 1.4]
 [7.7 3.  6.1 2.3]
 [6.3 3.4 5.6 2.4]
 [6.4 3.1 5.5 1.8]
 [6.  3.  4.8 1.8]
 [6.9 3.1 5.4 2.1]
 [6.7 3.1 5.6 2.4]
 [6.9 3.1 5.1 2.3]
 [5.8 2.7 5.1 1.9]
 [6.8 3.2 5.9 2.3]
 [6.7 3.3 5.7 2.5]
 [6.7 3.  5.2 2.3]
 [6.3 2.5 5.  1.9]
 [6.5 3.  5.2 2. ]
 [6.2 3.4 5.4 2.3]
 [5.9 3.  5.1 1.8]]

 target의 type: <class 'numpy.ndarray'>
target의 shape: (150,)
[0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
 0 0 0 0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2
 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2
 2 2]

04. Model Selection 모듈 소개

  • 사이킷런의 model_selection 모듈은 학습 데이터와 데이터 세트를 분리하거나, 교차 검증 분할 및 평가, Estimator의 하이퍼 파라미터를 튜닝하기 위한 함수와 클래스 제공

1. 학습/테스트 데이터 세트 분리- train_test_split()

1
2
3
4
5
6
7
8
9
10
11
12
13
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score

iris = load_iris()
dt_clf = DecisionTreeClassifier()
train_data = iris.data
train_label = iris.target
dt_clf.fit(train_data, train_label)

# 학습 데이터 세트로 예측 수행
pred = dt_clf.predict(train_data)
print('예측 정확도:', accuracy_score(train_label, pred))
예측 정확도: 1.0
  • 정확도가 100%인 이유: 이미 학습한 학습 데이터 세트를 기반으로 예측했기 때문
    • 예측을 수행한 데이터 세트는 학습한 학습용 데이터 세트가 아닌, 테스트 데이터 세트여야 함
    • train_test_split() 함수로 학습 데이터/테스트 데이터 세트 분리하기
  • 파라미터 설명

    • test_size
      전체 데이터에서 테스트 데이터 세트 크기를 얼마로 샘플링할지 결정 (디폴트는 0.25, 즉 25%)
    • train_size
      전체 데이터에서 학습용 데이터 세트 크기를 얼마로 샘플링할 것인가 결정(통상적으로 test_size를 사용하고, 해당 파라미터는 잘 사용하지 않음)
    • shuffle
      데이터 분리 전, 데이터를 미리 섞을지 결정, 디폴트는 True, 데이터를 분산해서 좀 더 효율적인 학습 및 테스트 데이터 세트를 만드는 데 사용
    • random_state
      random_state는 호출할 때마다 동일한 학습/테스트용 데이터 세트를 생성하기 위해 주어지는 난수 값, train_test_split()는 호출 시 무작위로 데이터를 분리하므로 random_state를 지정하지 않으면 수행할 때마다 다른 학습/테스트용 데이터를 생성 → 연습 시에는 random_state에 일정 숫자값을 주어 변하지 않도록 함
    • train_test_split()의 반환값은 튜플 형태
      → 학습용 데이터의 피처 데이터 세트, 테스트용 데이터의 피처 데이터 세트, 학습용 데이터의 레이블 데이터 세트, 테스트용 데이터의 레이블 데이터 세트가 순차적으로 반환

  • 붓꽃 데이터 세트를 train_test_split()으로 테스트 데이터 세트를 전체의 30%, 학습 데이터 세트를 70%로 분리
1
2
3
4
5
6
7
8
9
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import train_test_split

dt_clf = DecisionTreeClassifier()
iris_data = load_iris()

X_train, X_test, y_train, y_test = train_test_split(iris_data.data, iris_data.target, test_size=0.3, random_state=121)
1
2
3
dt_clf.fit(X_train, y_train)
pred = dt_clf.predict(X_test)
print('예측 정확도: {0:4f}'.format(accuracy_score(y_test, pred)))
예측 정확도: 0.955556
  • 테스트 데이터로 예측을 수행한 결과 정확도가 약 95.56%
    • 붓꽃 데이터는 데이터 양(150개)이 크지 않아 전체 30%인 텟트 데이터로 알고리즘 예측 성능을 판단하기 적절하지 않음

3. 교차 검증

  • 과적합(Overfitting): 모델이 학습 데이터에만 과도하게 최적화되어, 실제 예측을 다른 데이터로 수행할 경우 예측 성능이 과도하게 떨어지는 것
  • 과적합을 방지하기 위해 교차 검증을 이용해 다양한 학습과 평가를 수행

K 폴드 교차 검증

  • K 폴드 교차 검증: 가장 보편적으로 사용되는 교차 검증 기법
  • K개의 데이터 폴드 세트를 만들어서 K번마늠 각 폴드 세트에 학습과 검증 평가를 반복적으로 수행하는 방법

4.PNG

  • 사이킷런에서는 K 폴드 교차 검증 프로세스를 구현하기 위해 KFold와 StratifiedKFold 클래스를 제공
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score
from sklearn.model_selection import KFold
import numpy as np

iris = load_iris()
features = iris.data
label = iris.target
df_clf = DecisionTreeClassifier(random_state=156)

# 5개의 폴드 세트로 분리하는 KFold 객체와 폴드 세트별 정확도를 담을 리스트 객체 생성
kfold = KFold(n_splits=5)
cv_accuracy = []
print('붓꽃 데이터 세트 크기:', features.shape[0])
붓꽃 데이터 세트 크기: 150
  • KFold(n_splits=5)로 KFold 객체를 생성했으니 생성된 KFold 객체의 split()을 호출해 전체 붓꽃 데이터를 5개의 폴드 데이터 세트로 분리
    • 학습용 데이터 세트는 120개
    • 검증 테스트 데이터 세트는 30개
  • KFold 객체는 split()을 호출하면 학습용/검증용 데이터로 분할할 수 있는 인덱스를 반환
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
n_iter = 0

# KFold 객체의 split()를 호출하면 폴드별 학습용, 검증용 테스트의 로우 인덱스를 array로 반환
for train_index, test_index in kfold.split(features):
# KFold.split()으로 반환된 인덱스로 학습용, 검증용 테스트 데이터 추출
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)

# 개별 iteration별 정확도를 합하여 평균 정확도 계산
print('\n## 평균 검증 정확도:', np.mean(cv_accuracy))
#1 교차 검증 정확도: 1.0, 학습 데이터 크기: 120, 검증 데이터 크기: 30
#{0} 검증 세트 인덱스: {1}.format(n_iter, test_index)

#2 교차 검증 정확도: 1.0, 학습 데이터 크기: 120, 검증 데이터 크기: 30
#{0} 검증 세트 인덱스: {1}.format(n_iter, test_index)

#3 교차 검증 정확도: 0.8333, 학습 데이터 크기: 120, 검증 데이터 크기: 30
#{0} 검증 세트 인덱스: {1}.format(n_iter, test_index)

#4 교차 검증 정확도: 0.9333, 학습 데이터 크기: 120, 검증 데이터 크기: 30
#{0} 검증 세트 인덱스: {1}.format(n_iter, test_index)

#5 교차 검증 정확도: 0.8333, 학습 데이터 크기: 120, 검증 데이터 크기: 30
#{0} 검증 세트 인덱스: {1}.format(n_iter, test_index)

## 평균 검증 정확도: 0.91998

Stratified K 폴드

  • Stratified K 폴드: 불균형한 분포도를 가진 레이블 데이터 집합을 위한 K 폴드 방식
  • 불균형한 분포도: 특정 레이블 값이 특이하게 많거나 매우 적어서 값의 분포가 한쪽으로 치우치는 것
  • e.g) 대출 사기 데이터처럼 대출 사기 1값이 매우 적고, 대부분 데이터가 정상 대출 0인 경우
1
2
3
4
5
6
import pandas as pd

iris = load_iris()
iris_df = pd.DataFrame(data=iris.data, columns=iris.feature_names)
iris_df['label']=iris.target
iris_df['label'].value_counts()
2    50
1    50
0    50
Name: label, dtype: int64
  • 레이블 값은 0, 1, 2 값 모두 50개로 동일
  • Setosa, Versicolor, Virginica 품종 모두 50개
1
2
3
4
5
6
7
8
9
10
11
12
13
# 분할 파라미터 설정 = 3개
kfold = KFold(n_splits=3)

# 교차 검증 횟수를 세기 위한 변수
n_iter=0

# 반복문을 통해 교차 검증 수행
for train_index, test_index in kfold.split(iris_df):
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)
## 교차 검증: 1
학습 레이블 데이터 분포:
 50     1
51     1
52     1
53     1
54     1
      ..
145    2
146    2
147    2
148    2
149    2
Name: label, Length: 100, dtype: int32
## 교차 검증: 2
학습 레이블 데이터 분포:
 0      0
1      0
2      0
3      0
4      0
      ..
145    2
146    2
147    2
148    2
149    2
Name: label, Length: 100, dtype: int32
## 교차 검증: 3
학습 레이블 데이터 분포:
 0     0
1     0
2     0
3     0
4     0
     ..
95    1
96    1
97    1
98    1
99    1
Name: label, Length: 100, dtype: int32
1
2
3
4
5
6
7
8
9
10
11
12
from sklearn.model_selection import StratifiedKFold

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())
## 교차 검증: 1
학습 레이블 데이터 분포:
 2    34
1    33
0    33
Name: label, dtype: int64
검증 레이블 데이터 분포:
 1    17
0    17
2    16
Name: label, dtype: int64
## 교차 검증: 2
학습 레이블 데이터 분포:
 1    34
2    33
0    33
Name: label, dtype: int64
검증 레이블 데이터 분포:
 2    17
0    17
1    16
Name: label, dtype: int64
## 교차 검증: 3
학습 레이블 데이터 분포:
 0    34
2    33
1    33
Name: label, dtype: int64
검증 레이블 데이터 분포:
 2    17
1    17
0    16
Name: label, dtype: int64
  • 출력 결과
    • 학습 레이블과 검증 레이블 데이터값 분포도가 동일하게 할당
    • 첫 번째 교차 검증: 학습 레이블은 0, 1, 2 값이 각각 33개 → 레이블별로 동일하게 할당
    • 검증 레이블: 0, 1, 2 값이 각각 17개 → 레이블별로 동일하게 할당
    • → 위와 같이 분할되어야 레이블값 모두 학습 가능하며, 이에 기반해 검증할 수 있음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
dt_clf = DecisionTreeClassifier(random_state=156)

skfold = StratifiedKFold(n_splits=3)
n_iter=0
cv_accuracy=[]

# StratifiedKFold의 split() 호출 시, 반드시 레이블 데이터 세트도 추가 입력 필요
for train_index, test_index in skfold.split(features, label):

# split()으로 반환된 인덱스를 이용해 학습용, 검증용 테스트 데이터 추출
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.mean(cv_accuracy))
#1 교차 검증 정확도 :0.98, 학습 데이터 크기: 100, 검증 데이터 크기: 50
#1 검증 세트 인덱스: [  0   1   2   3   4   5   6   7   8   9  10  11  12  13  14  15  16  50
  51  52  53  54  55  56  57  58  59  60  61  62  63  64  65  66 100 101
 102 103 104 105 106 107 108 109 110 111 112 113 114 115]

#2 교차 검증 정확도 :0.94, 학습 데이터 크기: 100, 검증 데이터 크기: 50
#2 검증 세트 인덱스: [ 17  18  19  20  21  22  23  24  25  26  27  28  29  30  31  32  33  67
  68  69  70  71  72  73  74  75  76  77  78  79  80  81  82 116 117 118
 119 120 121 122 123 124 125 126 127 128 129 130 131 132]

#3 교차 검증 정확도 :0.98, 학습 데이터 크기: 100, 검증 데이터 크기: 50
#3 검증 세트 인덱스: [ 34  35  36  37  38  39  40  41  42  43  44  45  46  47  48  49  83  84
  85  86  87  88  89  90  91  92  93  94  95  96  97  98  99 133 134 135
 136 137 138 139 140 141 142 143 144 145 146 147 148 149]

## 교차 검증별 정확도: [0.98 0.94 0.98]
## 평균 검증 정확도: 0.9666666666666667
  • 3개의 Stratified K 폴드로 교차 검증한 결과: 평균 검증 정확도가 약 96.04%로 측정
  • Stratified K 폴드의 경우, 원본 데이터의 레이블 분포도 특성을 반영한 학습/검증 데이터 세트를 만들 수 있으므로 왜곡된 레이블 데이터 세트에서는 반드시 Stratified K 폴드를 이용해 교차 검증해야 함
    • 분류(Classification)의 교차 검증은 K 폴드 대신, Stratified K 폴드로 분할되어야 함
    • 회귀(Regression)에서는 Stratified K 폴드가 지원되지 않음 ← 회귀 결정값은 이산값 형태 레이블이 아니라 연속된 숫자값이므로 결정값별로 분포를 정하는 의미가 없음

교차 검증을 보다 간편하게 - cross_val_score( )

  • 사이킷런은 교차 검증을 좀 더 편리하게 수행할 수 있게 해주는 API를 제공
    • 대표적인 API: cross_val_score()
    • KFold로 데이터를 학습하고 예측하는 코드를 보면 ① 폴드 세트를 설정하고 ② for 루프에서 반복으로 학습 및 테스트 데이터의 인덱스를 추출한 뒤 ③ 반복적으로 학습과 예측을 수행하고 예측 성능을 반환
  • cross_val_score( )는 위 과정을 한 번에 수행해주는 API
  • cross_val_score( ) API의 선언 형태
    1
    cross_val_score(estimator, X, y=None, scoring=None, cv=None, n_jobs=1, verbose=0, fit_params=None, pre_dispatch='2*n_jobs')
  • 주요 파라미터
    • esmitator : 사이킷런의 분류 알고리즘 클래스인 Classifier 또는 회귀 알고리즘 클래스인 Regressor를 의미
    • X : 피처 데이터 세트
    • y : 레이블 데이터 세트
    • scoring : 예측 성능 평가 지표를 기술
    • cv : 교차 검증 폴드 수
  • cross_val_score( ) 수행 후 반환 값은 scoring 파라미터로 지정된 성능 지표 측정값을 배열 형태로 반환
  • cross_val_score( )는 classifier가 입력되면 Stratified K 폴드 방식으로 레이블값의 분포에 따라 학습/테스트 세트를 분할
  • cf) 회귀인 경우에는 Stratified K 폴드 방식으로 분할할 수 없으므로 K 폴드 방식으로 분할
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from sklearn.tree import DecisionTreeClassifier 
from sklearn.model_selection import cross_val_score , cross_validate
from sklearn.datasets import load_iris

iris_data = load_iris()
dt_clf = DecisionTreeClassifier(random_state=156)

data = iris_data.data
label = iris_data.target

# 성능 지표는 정확도(accuracy) , 교차 검증 세트 는 3개
scores = cross_val_score(dt_clf , data , label , scoring='accuracy',cv=3)
print('교차 검증별 정확도:',np.round(scores, 4))
print('평균 검증 정확도:', np.round(np.mean(scores), 4))
교차 검증별 정확도: [0.98 0.94 0.98]
평균 검증 정확도: 0.9667
  • cross_val_score( )는 cv로 지정된 횟수만큼 scoring 파라미터로 지정된 평가 지표로 평가 결괏값을 배열로 반환, 일반적으로 이를 평균해 평가 수치로 사용

  • cross_val_score( ) API는 내부에서 Estimator를 학습(fit), 예측(predict), 평가(evaluation)시켜주므로 간단하게 교차 검증을 수행할 수 있음

  • cross_val_score( )와 앞 예제의 StratifiedKFold의 수행 결과를 비교해 보면 각 교차 검증별 정확도와 평균 검증 정확도가 모두 동일 → cross_val_score( )가 내부적으로 StratifiedKFold를 이용하기 때문

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

  • 사이킷런은 GridSearchCV API를 이용해 Classifier나 Regressor와 같은 알고리즘에 사용되는 하이퍼 파라미터를 순차적으로 입력하면서 편리하게 최적의 파라미터를 도출할 수 있는 방안을 제공(Grid는 격자라는 뜻으로, 촘촘하게 파라미터를 입력하면서 테스트를 하는 방식)
  • e.g) 결정 트리 알고리즘의 여러 하이퍼 파라미터를 순차적으로 변경하면서 최고 성능을 가지는 파라미터 조합을 찾고자 한다면 다음과 같이 파라미터의 집합을 만들고 이를 순차적으로 적용하면서 최적화를 수행하면 됨
  • cf) 데이터 핸들링 - 피처 엔지니어링, ML 모형 핸들링 - 하이퍼 파라미터 튜닝
1
2
3
grid_parameters = {'max_depth':[1, 2, 3],
'min_samples_split':[2,3]
}
  • 6회에 걸쳐 파라미터를 순차적으로 바꿔 실행하면서 최적의 파라미터와 수행 결과를 도출
순번 max_depth min_samples_split
1 1 2
2 1 3
3 2 2
4 2 3
5 3 2
6 3 3
  • GridSearchCV: 사용자가 튜닝하고자 하는 여러 종류의 하이퍼 파라미터를 다양하게 테스트하면서 최적의 파라미터를 편리하게 찾게 해주지만 동시에 순차적으로 파라미터를 테스트하므로 수행시간이 상대적으로 오래 걸림
    • 이 경우, 순차적으로 6회에 걸쳐 하이퍼 파라미터를 변경하면서 교차 검증 데이터 세트에 수행 성능을 측정
    • CV가 3회라면 개별 파라미터 조합마다 3개의 폴딩 세트를 3회에 걸쳐 학습/평가해 평균값으로 성능을 측정
    • 6개의 파라미터 조합이라면 총 CV 3회 * 6개 파라미터 조합 = 18회의 학습/평가가 이뤄짐
  • GridSearchCV 클래스의 생성자로 들어가는 주요 파라미터
    • estimator : classifier, regressor, pipeline이 사용될 수 있다.
    • param_grid : key + 리스트 값을 가지는 딕셔너리가 주어짐, estimator의 튜닝을 위해 파라미터명과 사용될 여러 파라미터 값을 지정
    • scoring : 예측 성능을 측정할 평가 방법을 지정, 보통은 사이킷런의 성능 평가 지표를 지정하는 문자열(예 : 정확도의 경우 ‘accuracy’)로 지정하나 별도의 성능 평가 지표 함수도 지정 가능
    • cv : 교차 검증을 위해 분할되는 학습 / 테스트 세트의 갯수를 지정
    • refit : 디폴트가 True이며 True로 생성 시 가장 최적의 하이퍼 파라미터를 찾은 뒤 입력된 esitmator 객체를 해당 하이퍼 파라미터로 재학습시킴
1
2
3
4
5
6
7
8
9
10
11
12
from sklearn.datasets import load_iris
from sklearn.tree import DecisionTreeClassifier
from sklearn.model_selection import GridSearchCV

# 데이터를 로딩하고 학습 데이터와 테스트 데이터 분리
iris_data = load_iris()
X_train, X_test, y_train, y_test = train_test_split(iris_data.data, iris_data.target,
test_size=0.2, random_state=121)
dtree = DecisionTreeClassifier()

## 파라미터를 딕셔너리 형태로 설정
parameters = {'max_depth': [1,2,3], 'min_samples_split': [2,3]}
  • 학습 데이터 세트를 GridSearchCV 객체의 fit(학습 데이터 세트) 메서드에 인자로 입력
    • GridSearchCV 객체의 fit 메서드를 수행하면 학습 데이터를 cv에 기술된 폴딩 세트로 분할해 param_grid에 기술된 하이퍼 파라미터를 순차적으로 변경하면서 학습/평가를 수행하고 결과를 cv_results_(gridsearchcv의 결과 세트, 딕셔너리 형태로 key값과 리스트 형태의 value값을 가짐) 속성에 기록
1
2
3
4
5
6
7
8
9
10
11
12
13
import pandas as pd

# param_grid의 하이퍼 파라미터를 3개의 train, test set fold로 나누어 테스트 수행 설정
## refit=True가 default, True인 경우 가장 좋은 파라미터 설정으로 재학습시킴
grid_dtree = GridSearchCV(dtree, param_grid=parameters, cv=3, refit=True, return_train_score=True)

# 붓꽃 학습 데이터로 param_grid의 하이퍼 파라미터를 순차적으로 학습/평가
grid_dtree.fit(X_train, y_train)

# GridSearchCV 결과를 추출해 DataFrame으로 변환
scores_df = pd.DataFrame(grid_dtree.cv_results_)
scores_df[['params', 'mean_test_score', 'rank_test_score',
'split0_test_score', 'split1_test_score', 'split2_test_score']]

params mean_test_score rank_test_score split0_test_score split1_test_score split2_test_score
0 {'max_depth': 1, 'min_samples_split': 2} 0.700000 5 0.700 0.7 0.70
1 {'max_depth': 1, 'min_samples_split': 3} 0.700000 5 0.700 0.7 0.70
2 {'max_depth': 2, 'min_samples_split': 2} 0.958333 3 0.925 1.0 0.95
3 {'max_depth': 2, 'min_samples_split': 3} 0.958333 3 0.925 1.0 0.95
4 {'max_depth': 3, 'min_samples_split': 2} 0.975000 1 0.975 1.0 0.95
5 {'max_depth': 3, 'min_samples_split': 3} 0.975000 1 0.975 1.0 0.95
  • 총 6개의 결과를 볼 수 있으며, 하이퍼 파라미터 max_depth와 min_samples_split을 순차적으로 6번 변경하면서 학습 및 평가를 수행했음을 나타냄
    • 맨 마지막에서 두 번째 행을 보면, 평가 결과 예측 성능이 1위라는 의미
    • split0_test_score, split1_test_score, split2_test_score는 CV가 3인 경우, 즉 3개의 폴딩 세트에서 각각 테스트한 성능 수치
    • mean_test_score는 이 세 개 성능 수치를 평균화한 것
  • GridSearchCV 객체의 fit()을 수행하면, 최고 성능을 나타낸 하이퍼 파라미터 값과 그때의 평가 결괏값이 best_params, best_score_ 속성에 기록
1
2
print('GridSearchCV 최적 파라미터:', grid_dtree.best_params_)
print('GridSearchCV 최고 정확도: {0:4f}'.format(grid_dtree.best_score_))
GridSearchCV 최적 파라미터: {'max_depth': 3, 'min_samples_split': 2}
GridSearchCV 최고 정확도: 0.975000
  • GridSearchCV 객체의 생성 파라미터로 refit=True가 default
    • refit = True면 GridSearchCV가 최적 성능을 나타내는 하이퍼 파라미터로 Estimator를 학습해 best_estimator_로 저장
1
2
3
4
5
6
# GridSearchCV refit으로 이미 학습된 estimator 반환
estimator = grid_dtree.best_estimator_

# GridSearchCV의 best_estimator_는 이미 최적 하이퍼 파라미터로 학습됨
pred = estimator.predict(X_test)
print('테스트 데이터 세트 정확도: {0:4f}'.format(accuracy_score(y_test, pred)))
테스트 데이터 세트 정확도: 0.966667

05. 데이터 전처리

  • 사이킷런 ML 알고리즘 적용 전, 알아야 할 사항
    • 결손값(NaN, Null 값은 허용되지 않음) → 고정된 다른 값으로 변환해야 함
    • Null 값이 얼마 되지 않으면 피처의 평균값 등으로 간단히 대체 가능
    • 단, Null 값이 대부분이라면 해당 피처를 드롭하는 편이 좋음
  • 사이킷런 머신러닝 알고리즘은 문자열 값을 입력 값으로 허용하지 않음
    • 모든 문자열 값은 인코딩하여 숫자형으로 변환해야 함
    • 문자열 피처: 카테고리형 피처, 텍스트형 피처

1. 데이터 인코딩

  • 레이블 인코딩(Label encoding)
    • 카테고리 피처를 코드형 숫자 값으로 변환하는 것
    • 01, 02(문자형)이 아닌 1, 2와 같은 숫자형 값으로 변환해야 함
  • 사이킷런의 레이블 인코딩은 LabelEncoder 클래스로 구현
1
2
3
4
5
6
7
8
9
from sklearn.preprocessing import LabelEncoder

items=['TV', '냉장고', '전자레인지', '컴퓨터', '선풍기', '선풍기', '믹서', '믹서']

# LabelEncoder를 객체로 생성한 후, fit()과 transform()으로 레이블 인코딩 수행
encoder = LabelEncoder()
encoder.fit(items)
labels = encoder.transform(items)
print('인코딩 변환값:',labels)
인코딩 변환값: [0 1 4 5 3 3 2 2]
1
2
3
4
5
# 문자열 값이 어떤 숫자 값으로 인코딩됐는지 알고 싶을 경우
print('인코딩 클래스:',encoder.classes_)

# 인코딩된 값을 디코딩하기
print('디코딩 원본 값:',encoder.inverse_transform([4, 5, 2, 0, 1, 1, 3, 3]))
인코딩 클래스: ['TV' '냉장고' '믹서' '선풍기' '전자레인지' '컴퓨터']
디코딩 원본 값: ['전자레인지' '컴퓨터' '믹서' 'TV' '냉장고' '냉장고' '선풍기' '선풍기']
  • 데이터 인코딩 단점

    • 숫자 값으로 변환되어 몇몇 ML 알고리즘에서 크고 작음의 특성이 반영될 수 있음
  • 원-핫 인코딩(One-Hot Encoding)

    • 피처 값 유형에 따라 새로운 피처를 추가하여, 고유값에 해당하는 칼럼에만 1을 표시하고 나머지 칼럼에는 0을 표시하는 방식

    • 사이킷런에서 OneHotEncoder 클래스로 쉽게 변환 가능

  • 주의할 점
    1. OneHotEncoder로 변환하기 전에 모든 문자열 값이 숫자형 값으로 변환되어야 함
    2. 입력값으로 2차원 데이터가 필요하다는 점
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from sklearn.preprocessing import OneHotEncoder
import numpy as np

items=['TV', '냉장고','전자렌지','컴퓨터','선풍기','선풍기','믹서','믹서']

# 숫자값 변환을 위해 LabelEncoder로 변환
encoder = LabelEncoder()
encoder.fit(items)
labels = encoder.transform(items)

# 2차원 데이터로 변환
labels = labels.reshape(-1, 1)

# 원-핫 인코딩 적용
oh_encoder = OneHotEncoder()
oh_encoder.fit(labels)
oh_labels = oh_encoder.transform(labels)
print('원-핫 인코딩 데이터')
print(oh_labels.toarray())
print('원-핫 인코딩 데이터 차원')
print(oh_labels.shape)
원-핫 인코딩 데이터
[[1. 0. 0. 0. 0. 0.]
 [0. 1. 0. 0. 0. 0.]
 [0. 0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 0. 1.]
 [0. 0. 0. 1. 0. 0.]
 [0. 0. 0. 1. 0. 0.]
 [0. 0. 1. 0. 0. 0.]
 [0. 0. 1. 0. 0. 0.]]
원-핫 인코딩 데이터 차원
(8, 6)
1
2
3
4
5
# get_dummies()를 이용하면 바로 변환 가능
import pandas as pd

df = pd.DataFrame({'item':['TV', '냉장고','전자렌지','컴퓨터','선풍기','선풍기','믹서','믹서']})
pd.get_dummies(df)

item_TV item_냉장고 item_믹서 item_선풍기 item_전자렌지 item_컴퓨터
0 1 0 0 0 0 0
1 0 1 0 0 0 0
2 0 0 0 0 1 0
3 0 0 0 0 0 1
4 0 0 0 1 0 0
5 0 0 0 1 0 0
6 0 0 1 0 0 0
7 0 0 1 0 0 0

2. 피처 스케일링과 정규화

  • 피처 스케일링(feature scaling)

    • 서로 다른 변수값 범위를 일정한 수준으로 맞추는 작업
    • 표준화(Standardization)와 정규화(Normalization)가 있음
  • 표준화

    • 데이터 피처 각각 평균이 0이고 분산이 1인 가우시안 정규 분포를 가진 값으로 변환
    • xi_new = $\frac{xi - mean(x)}{stdev(x)}$
  • 정규화

    • 서로 다른 피처 크기를 통일하기 위해 크기 변환

    • xi_new = $\frac{xi - min(x)}{max(x) - min(x)}$

    • 사이킷런 전처리에서 제공하는 Normalizer 모듈과 일반적인 정규화는 약간의 차이가 있음

      • 선형대수에서의 정규화 개념이 적용, 개별 백터 크기를 맞추기 위해 변환
      • xi_new = $\frac{xi}{xi^2^ + yi^2^ + zi^2^}$ (분모 루트, 아래 사진 참고)

      10.PNG

3. StandardScaler

  • 표준화를 쉽게 지원하기 위한 클래스
  • 개별 피처를 평균이 0이고 분산이 1인 값으로 변환해줌
    • 사이킷런에서 구현한 RBF 커널을 이용하는 서포트 백터 머신, 선형 회귀, 로지스틱 회귀는 가우시안 분포를 가지고 있다는 가정 하에 구현됐기 때문에 사전에 표준화를 적용하는 것이 예측 성능 향상에 중요한 요소가 됨
1
2
3
4
5
6
7
8
9
10
11
12
from sklearn.datasets import load_iris
import pandas as pd

# 붓꽃 데이터 세트를 로딩하고 DataFrame으로 변환
iris = load_iris()
iris_data = iris.data
iris_df = pd.DataFrame(data=iris_data, columns=iris.feature_names)

print('feature들의 평균값')
print(iris_df.mean())
print('\nfeature들의 분산값')
print(iris_df.var())
feature들의 평균값
sepal length (cm)    5.843333
sepal width (cm)     3.057333
petal length (cm)    3.758000
petal width (cm)     1.199333
dtype: float64

feature들의 분산값
sepal length (cm)    0.685694
sepal width (cm)     0.189979
petal length (cm)    3.116278
petal width (cm)     0.581006
dtype: float64
  • StandardScaler를 이용해 각 피처를 한 번에 표준화해 변환
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from sklearn.preprocessing import StandardScaler

# StandardScaler 객체 생성
scaler = StandardScaler()

# StandardScaler로 데이터 세트 변환, fit()과 transform() 호출
scaler.fit(iris_df)
iris_scaled = scaler.transform(iris_df)

# transform() 시, 스케일 변환된 데이터 세트가 Numpy ndarray로 반환되 이를 DataFrame으로 변환
iris_df_scaled = pd.DataFrame(data=iris_scaled, columns=iris.feature_names)
print('feature들의 평균값')
print(iris_df_scaled.mean())
print('\nfeature들의 분산값')
print(iris_df_scaled.var())
feature들의 평균값
sepal length (cm)   -1.690315e-15
sepal width (cm)    -1.842970e-15
petal length (cm)   -1.698641e-15
petal width (cm)    -1.409243e-15
dtype: float64

feature들의 분산값
sepal length (cm)    1.006711
sepal width (cm)     1.006711
petal length (cm)    1.006711
petal width (cm)     1.006711
dtype: float64
  • 모든 칼럼 값 평균이 0에 아주 가까운 값으로, 분산은 1에 아주 가까운 값으로 변환됨을 확인할 수 있음

3. MinMaxScaler

  • MinMaxScaler
    • 데이터값을 0과 1 사이 범위 값으로 변환
    • 음수 값이 있으면 -1에서 1값으로 변환
    • 데이터 분포가 가우시안 분포가 아닐 경우에는 Min, Max Scale 적용 가능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from sklearn.preprocessing import MinMaxScaler

# MinMaxScaler 객체 생성
scaler = MinMaxScaler()

# MinMaxScaler로 데이터 세트 변환, fit()과 transform()호출
scaler.fit(iris_df)
iris_scaled=scaler.transform(iris_df)

# transform() 시 스케일 변환된 데이터 세트가 NumPy ndarry로 변환돼 이를 DataFrame으로 변환
iris_df_scaled=pd.DataFrame(data=iris_scaled, columns=iris.feature_names)
print('feature 최솟값')
print(iris_df_scaled.min())
print('\nfeature 최댓값')
print(iris_df_scaled.max())
feature 최솟값
sepal length (cm)    0.0
sepal width (cm)     0.0
petal length (cm)    0.0
petal width (cm)     0.0
dtype: float64

feature 최댓값
sepal length (cm)    1.0
sepal width (cm)     1.0
petal length (cm)    1.0
petal width (cm)     1.0
dtype: float64

4. 학습 데이터와 테스트 데이터의 스케일링 변환 시 유의점

  • StandardScaler나 MinMaxScaler와 같은 Scaler 객체로 데이터 스케일링 변환 시, fit(), transform(), fit_transform() 메소드를 이용
    • fit(): 데이터 변환을 위한 기준 정보 설정(최댓값, 최솟값 등)
    • transform(): 설정된 정보를 이용해 데이터 변환
    • fir_transform(): fit()과 transform()을 한 번에 적용하는 기능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 테스트 데이터에 fit()을 적용할 때 발생하는 문제 알아보기

from sklearn.preprocessing import MinMaxScaler
import numpy as np

# 학습 데이터는 0부터 10까지, 테스트 데이터는 0부터 5까지 값을 가지는 데이터 세트로 생성
# Scaler 클래스의 fit(), transform()은 2차원 이상 데이터만 가능하므로 reshape(-1, 1)로 차원 변경
train_array = np.arange(0,11).reshape(-1,1)
test_array = np.arange(0,6).reshape(-1,1)


# 학습 데이터인 train_array부터 MinMaxScaler를 이용해 변환
## MinMaxScaler 객체에 별도 feature_range 파라미터 값을 지정하지 않으면 0~1 값으로 변환
scaler = MinMaxScaler()

## fit()하면 train_array 데이터의 최솟값이 0, 최댓값이 10으로 설정됨
scaler.fit(train_array)

## 1/10 scale로 train_arrau 데이터 변환, 원본 10 → 1로 변환
train_scaled = scaler.transform(train_array)

print('원본 train_array 데이터:', np.round(train_array.reshape(-1),2))
print('Scale된 train_array 데이터:', np.round(train_scaled.reshape(-1),2))


# 테스트 데이터 세트를 변환: fit()을 호출해 스케일링 기준 정보를 적용한 뒤, transform()을 수행한 결과 확인
## MinMaxScaler에 test_array를 fit()하게 되면 원본 데이터 최솟값이 0, 최댓값이 5로 설정
scaler.fit(test_array)

## 1/5 scale로 test_array 데이터 변환. 원본 5 → 1로 변환
test_scaled = scaler.transform(test_array)

## test_array의 scale 변환 출력
print('\n원본 test_array 데이터:', np.round(test_array.reshape(-1),2))
print('Scale된 test_array 데이터:', np.round(test_scaled.reshape(-1),2))
원본 train_array 데이터: [ 0  1  2  3  4  5  6  7  8  9 10]
Scale된 train_array 데이터: [0.  0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1. ]

원본 test_array 데이터: [0 1 2 3 4 5]
Scale된 test_array 데이터: [0.  0.2 0.4 0.6 0.8 1. ]
  • 출력 결과를 확인하면 학습 데이터와 테스트 데이터 스케일링이 맞지 않음을 알 수 있음
    • 학습 데이터와 테스트 데이터의 서로 다른 원본값이 동일한 값으로 변환되는 결과 초래
  • 머신러닝 모델은 학습 데이터를 기반으로 학습됨
    • 테스트 데이터는 학습 데이터의 스케일링 기준에 따라야 하며, 테스트 데이터의 1값은 학습 데이터와 동일하게 0.1값으로 변환되어야 함
    • 따라서, 테스트 데이터에 다시 fit()을 적용해서는 안 되며, 학습 데이터로 이미 fit()이 적용된 Scaler 객체를 이용해 transform()으로 변환해야 함
1
2
3
4
5
6
7
8
9
10
11
# 재시도: 테스트 데이터에 fit() 호출하지 않음
scaler = MinMaxScaler()
scaler.fit(train_array)
train_scaled = scaler.transform(train_array)
print('원본 train_array 데이터:', np.round(train_array.reshape(-1),2))
print('Scale된 train_array 데이터:', np.round(train_scaled.reshape(-1),2))

# test_array에 Scale 변환할 때는 반드시 fit() 호출하지 않고 transform()만으로 변환
test_scaled = scaler.transform(test_array)
print('\n원본 test_array 데이터:', np.round(test_array.reshape(-1),2))
print('Scale된 test_array 데이터:', np.round(test_scaled.reshape(-1),2))
원본 train_array 데이터: [ 0  1  2  3  4  5  6  7  8  9 10]
Scale된 train_array 데이터: [0.  0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1. ]

원본 test_array 데이터: [0 1 2 3 4 5]
Scale된 test_array 데이터: [0.  0.1 0.2 0.3 0.4 0.5]

06. 사이킷런으로 타이타닉 생존자 예측하기

  • 타이타닉 탑승자 데이터
    • Passengerid : 탑승자 번호
    • survived : 생존 여부 0 : 사망 / 1 : 생존
    • pclass : 티켓의 선실 등급
    • sex : 성별
    • name :이름
    • Age : 나이
    • sibsp : 같이 탑승한 형제자매 또는 배우자 인원수
    • parch : 같이 탑승한 부모님 또는 어린이 인원수
    • ticket : 티켓 번호
    • fare : 요금
    • cabin : 선실 번호
    • embarked : 중간 정착 항구 C = Cherbourg, Q = Queenstown, S = Southampton
1
2
3
4
5
6
7
8
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
%matplotlib inline

titanic_df = pd.read_csv('./titanic_train.csv')
titanic_df.head(3)

PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris male 22.0 1 0 A/5 21171 7.2500 NaN S
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... female 38.0 1 0 PC 17599 71.2833 C85 C
2 3 1 3 Heikkinen, Miss. Laina female 26.0 0 0 STON/O2. 3101282 7.9250 NaN S
1
2
print('\n ### 학습 데이터 정보 ### \n')
print(titanic_df.info())
 ### 학습 데이터 정보 ### 

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 891 entries, 0 to 890
Data columns (total 12 columns):
 #   Column       Non-Null Count  Dtype  
---  ------       --------------  -----  
 0   PassengerId  891 non-null    int64  
 1   Survived     891 non-null    int64  
 2   Pclass       891 non-null    int64  
 3   Name         891 non-null    object 
 4   Sex          891 non-null    object 
 5   Age          714 non-null    float64
 6   SibSp        891 non-null    int64  
 7   Parch        891 non-null    int64  
 8   Ticket       891 non-null    object 
 9   Fare         891 non-null    float64
 10  Cabin        204 non-null    object 
 11  Embarked     889 non-null    object 
dtypes: float64(2), int64(5), object(5)
memory usage: 83.7+ KB
None
  • 데이터 확인
    • Range Index: DataFrame 인덱스의 범위, 891개의 로우로 구성
    • Column: 열 12개(object는 string 타입으로 보아도 무방)
  • 사이킷런 머신러닝 알고리즘은 Null 값을 허용하지 않으므로, Null값 처리가 필요
    • DataFrame의 fillna() 함수로 Null 값을 평균 또는 고정값으로 변경
    • Age는 평균 나이, 나머지 칼럼은 ‘N’값으로 변경
1
2
3
4
titanic_df['Age'].fillna(titanic_df['Age'].mean(), inplace=True)
titanic_df['Cabin'].fillna('N', inplace=True)
titanic_df['Embarked'].fillna('N', inplace=True)
print('데이터 세트 Null 값 개수:', titanic_df.isnull().sum().sum())
데이터 세트 Null 값 개수: 0
1
2
3
4
# 남은 문자열 피처 값 분류 살펴보기
print('Sex 값 분포:\n', titanic_df['Sex'].value_counts())
print('\nCabin 값 분포 :\n', titanic_df['Cabin'].value_counts())
print('\nEmbarked 값 분포 :\n', titanic_df['Embarked'].value_counts())
Sex 값 분포:
 male      577
female    314
Name: Sex, dtype: int64

Cabin 값 분포 :
 G6             4
B96 B98        4
C23 C25 C27    4
E101           3
F2             3
              ..
C32            1
C90            1
D49            1
B73            1
C46            1
Name: Cabin, Length: 147, dtype: int64

Embarked 값 분포 :
 S    644
C    168
Q     77
Name: Embarked, dtype: int64
  • 결과 해석
    • Cabin(선실): Ndl 687건으로 가장 많은 것과 속성값이 정리되지 않은 것으로 확인
      • e.g) 여러 Cabin이 한 번에 표기된 값이 4건이나 됨
      • Cabin의 경우 선실 번호 중, 선실 등급을 나타내는 첫 번째 알파벳이 중요해 보임
1
2
3
# Cabin 속성 앞 문자만 추출하기
titanic_df['Cabin'] = titanic_df['Cabin'].str[:1]
print(titanic_df['Cabin'].head(3))
0    N
1    C
2    N
Name: Cabin, dtype: object
  1. Women and Children First 시절로 성별에 따른 생존 비율 비교
1
titanic_df.groupby(['Sex', 'Survived'])['Survived'].count()
Sex     Survived
female  0            81
        1           233
male    0           468
        1           109
Name: Survived, dtype: int64
  • Survived 칼럼: 레이블로서 결정 클래스 값(0: 사망, 1: 생존)
    • 여성 314명 중, 233명(약 74.2%) 생존
    • 남성 577명 중, 109명(약 18.8%) 생존
1
2
# Seaborn 패키지를 이용하여 성별 생존 비율 비교하기
sns.barplot(x='Sex', y='Survived', data=titanic_df)
<matplotlib.axes._subplots.AxesSubplot at 0x188fdd03610>

png

  1. 부자와 가난한 사람 생존 확률 확인
    • 객실 등급으로 부를 추출해보기
    • 성별도 추가
1
2
# 객실 등급별 생존 확률 확인하기
sns.barplot(x='Pclass', y='Survived', hue='Sex', data=titanic_df)
<matplotlib.axes._subplots.AxesSubplot at 0x188fe470130>

png

  • 결과 해석
    • 여성의 경우, 일/이등실에 따른 생존 확률 차이는 적으나, 삼등실의 경우 생존 확률이 상대적으로 많이 떨어짐
    • 남성의 경우, 일등실 생존 확률이 이/삼등실 생존 확률보다 월등히 높음
  1. Age에 따른 생존 확률 알아보기
    • 0 - 5: Baby
    • 6 - 12: Child
    • 13 - 18: Teenager
    • 19 - 25: Student
    • 26 - 35: Young Adult
    • 36 - 60: Adult
    • 61 - : Elderly
    • ~ -1: Unknown (오류값)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 입력 age에 따라 구분값을 반환하는 함수 설정
# DataFrame의 apply lambda 식에 사용
def get_category(age):
cat = ''
if age <= -1: cat = 'Unknown'
elif age <= 5: cat = 'Baby'
elif age <= 12: cat = 'Child'
elif age <= 18: cat = 'Teenager'
elif age <= 25: cat = 'Student'
elif age <= 35: cat = 'Young Adult'
elif age <= 60: cat = 'Adult'
else: cat = 'Elderly'

return cat

# 막대그래프 크기 figure를 더 크게 설정
plt.figure(figsize=(10,6))

# X축의 값을 순차적으로 표시하기 위한 설정
group_names= ['Unknown', 'Baby', 'Child', 'Teenager', 'Student', 'Young Adult', 'Adult', 'Elderly']

# lambda 식에 위에서 생성한 get_category() 함수를 반환값으로 지정
titanic_df['Age_cat'] =titanic_df['Age'].apply(lambda x : get_category(x))
sns.barplot(x='Age_cat', y='Survived',hue ='Sex', data=titanic_df,order=group_names)
titanic_df.drop('Age_cat',axis=1,inplace=True)

png

  • 결과 해석

    • 여자 baby의 경우 비교적 생존 확률 높음
    • 여자 child는 다른 연령대에 비해 생존 확률 낮음
    • 여자 elderly의 경우 생존 확률이 매우 높음
  • 남은 문자열 카테고리 피처를 숫자형 카테고리 피처로 변환

    • 인코딩: 사이킷런의 LabelEncoder 클래스를 이용하여 레이블 인코딩 적용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 여러 칼럼을 encode_features() 함수를 생성해 한 번에 변환하기
from sklearn import preprocessing

def encode_features(dataDF):
features = ['Cabin', 'Sex', 'Embarked']
for feature in features:
le = preprocessing.LabelEncoder()
le = le.fit(dataDF[feature])
dataDF[feature] = le.transform(dataDF[feature])

return dataDF

titanic_df = encode_features(titanic_df)
titanic_df.head()

PassengerId Survived Pclass Name Sex Age SibSp Parch Ticket Fare Cabin Embarked
0 1 0 3 Braund, Mr. Owen Harris 1 22.0 1 0 A/5 21171 7.2500 7 3
1 2 1 1 Cumings, Mrs. John Bradley (Florence Briggs Th... 0 38.0 1 0 PC 17599 71.2833 2 0
2 3 1 3 Heikkinen, Miss. Laina 0 26.0 0 0 STON/O2. 3101282 7.9250 7 3
3 4 1 1 Futrelle, Mrs. Jacques Heath (Lily May Peel) 0 35.0 1 0 113803 53.1000 2 3
4 5 0 3 Allen, Mr. William Henry 1 35.0 0 0 373450 8.0500 7 3
  • Sex, Cabin, Embarked 속성이 숫자형으로 바뀜
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# Null 처리 함수
def fillna(df):
df['Age'].fillna(df['Age'].mean(),inplace = True)
df['Cabin'].fillna('N',inplace=True)
df['Embarked'].fillna('N', inplace = True)
df['Fare'].fillna(0,inplace = True)
return df

#머신러닝 알고리즘에 불필요한 속성 제거
def drop_features(df):
df.drop(['PassengerId', 'Name', 'Ticket'],axis=1,inplace=True)
return df

#레이블 인코딩 수행
def format_features(df):
df['Cabin'] = df['Cabin'].str[:1]
features = ['Cabin','Sex','Embarked']
for feature in features:
le = LabelEncoder()
le = le.fit(df[feature])
df[feature] = le.transform(df[feature])
return df

# 앞에서 설정한 데이터 전처리 함수 호출
def transform_features(df):
df = fillna(df)
df = drop_features(df)
df= format_features(df)
return df
  • 원본 데이터 가공 위해 원본 csv 파일 재로딩
    • Survived 속성만 별로도 분리해 클래스 결정값 데이터 세트로 만들기
    • Survived 속성을 드롭해 피처 데이터 세트 만들기
    • 생성된 데이터 세트에 transform_features()를 적용해 데이터 가공
1
2
3
4
5
6
#원본 데이터 재로딩하고, 피처 데이터 세트와 레이블 데이터 세트 추출.
titanic_df = pd.read_csv("./titanic_train.csv")
y_titanic_df=titanic_df['Survived']
X_titanic_df=titanic_df.drop('Survived',axis=1)

X_titanic_df = transform_features(X_titanic_df)
1
2
3
4
# train_test_split() API를 이용해 별도의 테스트 데이터 세트 추출, 세트 크기는 전체의 20% 설정
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test=train_test_split(X_titanic_df, y_titanic_df,
test_size=0.2, random_state=11)
  • ML 알고리즘 결정 트리, 랜덤 포레스트, 로지스틱 회귀로 타이타닉 생존자 예측하기

    • 결정 트리: DecisionTreeClassifier 클래스
    • 랜덤 포레스트: RandomForestClassifier 클래스
    • 로지스틱 회귀: LogisticRegression 클래스 제공
  • 사이킷런 클래스를 이용해 train_test_split()으로 분리한 학습 데이터와 테스트 데이터를 기반으로 머신러닝 모델을 학습하고(fit) 예측할 것(predict)

    • 예측 성능 평가 기준: 정확도 → accuracy_score() API 사용
    • random_state=11의 숫자는 예제 수행 시마다 같은 결과를 출력하기 위한 용도
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score

#결정트리, Random Forest, 로지스틱 회귀를 위한 사이킷런 Classifier 클래스 생성
dt_clf = DecisionTreeClassifier(random_state=11)
rf_clf = RandomForestClassifier(random_state=11)
lr_clf = LogisticRegression()

#DecisionTreeClassifier 학습/예측/평가
dt_clf.fit(X_train, y_train)
dt_pred = dt_clf.predict(X_test)
print('DecisionTreeClassifier 정확도: {0:.4f}'.format(accuracy_score(y_test, dt_pred)))

#RandomForestClassifier 학습/예측/평가
rf_clf.fit(X_train, y_train)
rf_pred = rf_clf.predict(X_test)
print('RandomForestClassifier 정확도:{0:.4f}'.format(accuracy_score(y_test, rf_pred)))

# LogisticRegression 학습/예측/평가
lr_clf.fit(X_train, y_train)
lr_pred = lr_clf.predict(X_test)
print('LogisticRegression 정확도: {0:.4f}'.format(accuracy_score(y_test, lr_pred)))
DecisionTreeClassifier 정확도: 0.7877
RandomForestClassifier 정확도:0.8547
LogisticRegression 정확도: 0.8492
  • 최적화 작업을 수행하지 않았고, 데이터 양도 충분하지 않아 어떤 알고리즘이 가장 성능이 좋은지 평가할 수 없음
    • 교차 검증을 위해 KFold 클래스(폴드 개수 5개로 설정), cross_val_score, GridSearchCV 클래스 모두 사용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from sklearn.model_selection import KFold

def exec_kfold(clf, folds=5):
# 폴드 세트를 5개인 KFold객체를 생성, 폴드 수만큼 예측결과 저장을 위한 리스트 객체 생성.
kfold = KFold(n_splits=folds)
scores = []

# KFold 교차 검증 수행.
for iter_count , (train_index, test_index) in enumerate(kfold.split(X_titanic_df)):
# X_titanic_df 데이터에서 교차 검증별로 학습과 검증 데이터를 가리키는 index 생성
X_train, X_test = X_titanic_df.values[train_index], X_titanic_df.values[test_index]
y_train, y_test = y_titanic_df.values[train_index], y_titanic_df.values[test_index]

# Classifier 학습, 예측, 정확도 계산
clf.fit(X_train, y_train)
predictions = clf.predict(X_test)
accuracy = accuracy_score(y_test, predictions)
scores.append(accuracy)
print("교차 검증 {0} 정확도: {1:.4f}".format(iter_count, accuracy))

# 5개 fold에서의 평균 정확도 계산.
mean_score = np.mean(scores)
print("평균 정확도: {0:.4f}".format(mean_score))

# exec_kfold 호출
exec_kfold(dt_clf , folds=5)
교차 검증 0 정확도: 0.7542
교차 검증 1 정확도: 0.7809
교차 검증 2 정확도: 0.7865
교차 검증 3 정확도: 0.7697
교차 검증 4 정확도: 0.8202
평균 정확도: 0.7823
  • 평균 정확도: 약 78.23%

  • cross_val_score() API로 교차 검증 수행

1
2
3
4
5
6
7
from sklearn.model_selection import cross_val_score

scores = cross_val_score(dt_clf, X_titanic_df , y_titanic_df , cv=5)
for iter_count,accuracy in enumerate(scores):
print("교차 검증 {0} 정확도: {1:.4f}".format(iter_count, accuracy))

print("평균 정확도: {0:.4f}".format(np.mean(scores)))
교차 검증 0 정확도: 0.7430
교차 검증 1 정확도: 0.7753
교차 검증 2 정확도: 0.7921
교차 검증 3 정확도: 0.7865
교차 검증 4 정확도: 0.8427
평균 정확도: 0.7879
  • KFold와 평균 정확도가 다른 이유: cross_val_score가 stratifiedKFold를 이용해 폴드 세트를 분할하기 때문

- GridSearchCV를 이용해 DecisionTreeClassifier의 최적 하이퍼 파라미터를 찾고 예측 성능 측정하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from sklearn.model_selection import GridSearchCV

parameters = {'max_depth':[2,3,5,10],
'min_samples_split':[2,3,5], 'min_samples_leaf':[1,5,8]}

grid_dclf = GridSearchCV(dt_clf , param_grid=parameters , scoring='accuracy' , cv=5)
grid_dclf.fit(X_train , y_train)

print('GridSearchCV 최적 하이퍼 파라미터 :',grid_dclf.best_params_)
print('GridSearchCV 최고 정확도: {0:.4f}'.format(grid_dclf.best_score_))
best_dclf = grid_dclf.best_estimator_

# GridSearchCV의 최적 하이퍼 파라미터로 학습된 Estimator로 예측 및 평가 수행.
dpredictions = best_dclf.predict(X_test)
accuracy = accuracy_score(y_test , dpredictions)
print('테스트 세트에서의 DecisionTreeClassifier 정확도 : {0:.4f}'.format(accuracy))
GridSearchCV 최적 하이퍼 파라미터 : {'max_depth': 3, 'min_samples_leaf': 5, 'min_samples_split': 2}
GridSearchCV 최고 정확도: 0.7992
테스트 세트에서의 DecisionTreeClassifier 정확도 : 0.8715
  • 하이퍼 파라미터인 max_depth=3, min_samples_leaf=1, min_samples_split=2로 DecisionTreeClassifier를 학습시킨 뒤 예측 정확도가 약 87.15%로 향상
    → 테스트용 데이터 세트가 작기 때문에 수치상으로 예측 성능이 많이 증가한 것으로 보임

07. 정리

  • 사이킷런
    • 많은 머신러닝 알고리즘 제공
    • 쉽고 직관적인 API 프레임워크
    • 편리하고 다양한 모듈 지원
  • 머신러닝 애플리케이션
    • 데이터의 가공 및 변환 과정의 처리
    • 데이터를 학습 대이타와 테스트 데이터로 분리하는 데이터 시트 분리 작업
    • 학습 데이터를 기반으로 머신러닝 알고리즘을 적용해 모델 학습
    • 학습된 모델을 기반으로 테스트 데이터에 대한 예측 수행
    • 예측된 결과값을 실제 결과값과 비교해 머신러닝 모델에 대한 평가 수행
  • 데이터 전처리 작업
    • 오류 데이터의 보정이나 결손(Null) 처리 등의 다양한 데이터 클렌징 작업
    • 레이블 인코딩이나 원-핫 인코딩 같은 인코딩 작업
    • 데이터의 스케일링/정규화 작업 등으로 머신러닝 알고리즘이 최적으로 수행되도록 사전에 데이터 처리
  • 추가
    • 머신러닝 모델은 학습 데이터 세트로 학습한 뒤 반드시 별도의 테스트 데이터 세트로 평가해야 함
    • 테스트 데이터의 건수 부족이나 고정된 테스트 데이터 세트를 이용한 반복적인 모델의 학습과 평가는 해당 테스트 데이터 세트에만 치우치는 빈약한 머신러닝 모델을 만들 가능성이 높음
    • 해결 방안
      • 학습 데이터 세트를 학습 데이터와 검증 데이터로 구성된 여러 개의 폴드 세트로 분리해 교차검증 수행 (교차검증은 데이터셋이 적을 때, 많으면 시간이 너무 오래 걸림)
      • 사이킷런은 교차 검증을 지원하기 위해 KFord, StratifiedKFold, cross_val_score 등의 다양한 클레스 함수를 제공
      • 머신러닝 모델의 최적의 하이퍼 파라미터를 교차 검증을 통해 추출하기 위해 GridSearchCV를 제공
Share