머신러닝

[4장] 파이썬 머신러닝 완벽 가이드_분류_1

zsun 2023. 5. 20. 02:21

01. 분류의 개요

분류(Classification)

- 지도학습의 대표적인 유형

  (*지도학습 :  레이블(Label)처럼 명시적인 답이 있는 데이터가 주어진 상태에서 학습하는 머신러닝 방식)

- 학습 데이터로 주어진 데이터의 피처와 레이블값(결정 값, 클래스 값)을 머신러닝 알고리즘으로 학습하여 모델을 생성

- 생성된 모델에 새로운 데이터 값이 주어졌을때 미지의 레이블 값을 예측하는 것

(= 기존 데이터가 어떤 레이블에 속하는지 패턴을 알고리즘으로 인지 -> 새롭게 관측된 데이터에 대한 레이블 판별)

 

분류를 구현할 수 있는 다양한 머신러닝 알고리즘

  • 베이즈(Bayes) 통계와 생성 모델에 기반한 나이브 베이즈(Naive Bayes)
  • 독립변수와 종속변수의 선형 관계성에 기반한 로지스틱 회귀(Logistic Regression)
  • 데이터 균일도에 다른 규칙 기반의 결정 트리(Decision Tree)
  • 개별 클래스 간의 최대 분류 마진을 효과적으로 찾아주는 서포트 벡터 머신(Support Vector Machine)
  • 근접 거리를 기준으로 하는 최소 근접(Nearest Neighbor) 알고리즘
  • 심층 연결 기반의 신경망(Neural Network)
  • 서로 다른/같은 머신러닝 알고리즘을 결합한 앙상블(Ensemble)

 

앙상블

- 일반적으로 배깅(Bagging)과 부스팅(Boosting) 방식으로 나뉨

 

- 배깅 (Bagging)

  • 랜덤 포레스트 (Random Forest) : 배깅 방식의 대표
  • 뛰어난 예측 성능, 상대적으로 빠른 수행시간, 유연성으로 애용되는 알고리즘

 

- 부스팅 (Boosting)

  • 그래디언트 부스팅 : 뛰어난 예측 성능, 수행 시간 너무 오래걸려 최적화 모델 튜닝 어려움
  • XgBoost(eXtra Gradient Boost)와 LightGBM : 성능 좋고 수행 시간 단축시키는 알고리즘 등장하며 정형 데이터 분류 영역에서 가장 활용도 높은 알고리즘으로 자리잡음

 

 

02. 결정 트리 (Decision Tree)

결정 트리 (Decision Tree)

- 데이터에 있는 규칙을 학습을 통해 자동으로 찾아내 트리(Tree) 기반의 분류 규칙 생성

- if/else 기반으로 나타냄, if/else를 자동으로 찾아내 예측을 위한 규칙을 만드는 알고리즘

- 어떤 기준을 바탕으로 규칙을 만들어야 가장 효율적인 분류가 될 것인가가 알고리즘의 성능 좌우함

  • 규칙 노드 : 규칙 조건, 데이터셋의 피처가 결합해 규칙 조건을 만들때마다 생성됨
  • 리프 노드 : 결정된 클래스 값
  • 서브 트리 : 새로운 규칙 조건마다 서브 트리 생성

 

  • 많은 규칙이 있다 -> 분류를 결정하는 방식이 복합해진다 -> 과적합으로 이어질 수 있다
  • 즉, 트리의 깊이(depth)가 깊어질수록 결정 트리의 예측 성능이 저하될 가능성이 높다

 

* 가능한 적은 결정 노드로 높은 예측 정확도를 가지려면

- 데이터를 분류할 때 최대한 많은 데이터셋이 해당 분류에 속할 수 있도록 결정 노드의 규칙이 정해져야 함

- 최대한 *균일한 데이터셋을 구성할 수 있도록 분할(split)해야 함

 

* 균일도 측정 방법: 엔트로피를 이용한 정보 이득(Information Gain)지수와 지니 계수

엔트로피 : 주어진 데이터 집합의 혼잡도

 

정보 이득 지수
  • 1- 엔트로피 지수
  • 결정 트리는 정보 이득 지수로 분할 기준 정함
  • 정보 이득이 높은 속성을 기준으로 분할 함

 

지니 계수
  • 불평등 지수를 나타낼 때 사용하는 계수
  • 0이 가장 평등, 1로 갈수록 불평등
  • 머신러닝에 적용시, 지니 계수가 낮을수록 데이터 균일도가 높은 것으로 해석해 지니 계수가 낮은 속성을 기준으로 분할

 

 

결정 트리 모델의 특징

 

- 균일도라는 룰을 기반으로 알고리즘이 쉽고 직관적

- 특별한 경우를 제외하고는 각 피처의 스케일링과 정규화 같은 전처리 작업이 필요 없음

- 가장 큰 단점은 과적합으로 정확도가 떨어짐

  -> 극복위해 트리의 크기를 사전에 제한하는 튜닝 필요

 

결정 트리 파라미터

 

사이킷런의 결정 트리 알고리즘 구현

- CART(Classification And Regression Trees) 알고리즘 기반

- DecisionTreeClassifier 와 DecisionTreeRegressor 제공

- 둘 다 다음과 같은 동일한 파라미터 사용

 

 

결정 트리 모델의 시각화 (Decision Tree Visualization)

- 붓꽃 데이터셋 사용

 

< Mac에서 grahpviz 설치 방법 >

(* jupyter terminal 에서 실행)

 

1. homebrew 설치

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

2. homebrew를 이용한 graphviz 설치

brew install graphviz

3. 설치 완료 후 jupyter 껐다가 다시 실행

 

 

결정트리 시각화

 

 

- petal length(cm) <=2.45와 같이 피처의 조건이 없으면 리프노드

- samples는 현 규칙에 해당하는 데이터 건수

- 각 노드의 색깔은 붓꽃 데이터의 레이블 값 의미(주황:0, 초록:1, 보라:2)

- 색이 짙어질수록 지니 계수가 낮고 해당 레이블에 속하는 샘플 데이터가 많음

 

위의 예시처럼 규칙 생성 로직을 미리 제어하지 않으면,

완벽하게 클래스 값을 구별하기 위해 트리노드를 계속해서 생성

-> 모델이 과적합되기 쉬움

 

결정트리 알고리즘을 제어하는 하이퍼 파라미터 변경

  • max_depth
  • min_samples_split
  • min_samples_leaf

 

결정 트리(Decision TREE) 과적합(Overfitting)

 

 

결정 트리 실습 - Human Activity Recognition

 

# 데이터 로드 후 중복된 피처가 몇개 있는지 확인

# 중복된 피처명 확인
# 총 42개의 피처명이 중복되어 있음
feature_dup_df = feature_name_df.groupby('column_name').count()
print(feature_dup_df[feature_dup_df['column_index'] > 1].count())
feature_dup_df[feature_dup_df['column_index'] > 1].head()

 

# 원본 데이터에 중복된 Feature 명으로 인하여 신규 버전의 Pandas에서 Duplicate name 에러 발생
# 중복 feature명에 대해서 원본 feature 명에 '_1(또는2)'를 추가로 부여하는 함수인 get_new_feature_name_df() 생성

def get_new_feature_name_df(old_feature_name_df):
    feature_dup_df = pd.DataFrame(data=old_feature_name_df.groupby('column_name').cumcount(),
                                  columns=['dup_cnt'])
    feature_dup_df = feature_dup_df.reset_index()
    new_feature_name_df = pd.merge(old_feature_name_df.reset_index(), feature_dup_df, how='outer')
    new_feature_name_df['column_name'] = new_feature_name_df[['column_name', 'dup_cnt']].apply(lambda x : x[0]+'_'+str(x[1]) 
                                                                                         if x[1] >0 else x[0] ,  axis=1)
    new_feature_name_df = new_feature_name_df.drop(['index'], axis=1)
    return new_feature_name_df

 

'''

train 디렉터리에 있는 학습용 피처 데이터셋과 레이블 데이터셋

test 디렉터리에 있는 테스트용 피처 데이터파일과 레이블 데이터 파일을
각각 학습/테스트용 Dataframe에 로드

각 데이터 파일은 공백으로 분리돼 있으므로 read_csv()의 sep 인자로 공백 입력
레이블 칼럼은 action 으로 명명

DataFrame을 생성하는 로직은 함수로 생성
함수명:get_human_dataset()

앞서 생성한 get_new_feature_name_df()는 get_human_dataset() 내에서 적용돼 중복된 피처명을 새로운 피처명으로 할당

'''

import pandas as pd

def get_human_dataset( ):
    
    # 각 데이터 파일들은 공백으로 분리되어 있으므로 read_csv에서 공백 문자를 sep으로 할당.
    feature_name_df = pd.read_csv('./human_activity/features.txt',sep='\s+',
                        header=None,names=['column_index','column_name'])
    
    # 중복된 피처명을 수정하는 get_new_feature_name_df()를 이용, 신규 피처명 DataFrame생성. 
    new_feature_name_df = get_new_feature_name_df(feature_name_df)
    
    # DataFrame에 피처명을 컬럼으로 부여하기 위해 리스트 객체로 다시 변환
    feature_name = new_feature_name_df.iloc[:, 1].values.tolist()
    
    # 학습 피처 데이터 셋과 테스트 피처 데이터을 DataFrame으로 로딩. 컬럼명은 feature_name 적용
    X_train = pd.read_csv('./human_activity/train/X_train.txt',sep='\s+', names=feature_name )
    X_test = pd.read_csv('./human_activity/test/X_test.txt',sep='\s+', names=feature_name)
    
    # 학습 레이블과 테스트 레이블 데이터을 DataFrame으로 로딩하고 컬럼명은 action으로 부여
    y_train = pd.read_csv('./human_activity/train/y_train.txt',sep='\s+',header=None,names=['action'])
    y_test = pd.read_csv('./human_activity/test/y_test.txt',sep='\s+',header=None,names=['action'])
    
    # 로드된 학습/테스트용 DataFrame을 모두 반환 
    return X_train, X_test, y_train, y_test


X_train, X_test, y_train, y_test = get_human_dataset()

 

# 로드한 학습용 데이터셋 살펴보기

# 7352개의 레코드로 561개의 피처 가지고 있음

print('## 학습 피처 데이터셋 info()')
print(X_train.info())
## 학습 피처 데이터셋 info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 7352 entries, 0 to 7351
Columns: 561 entries, tBodyAcc-mean()-X to angle(Z,gravityMean)
dtypes: float64(561)
memory usage: 31.5 MB
None

# 레이블 값은 1~6, 비교적 고르게 분포되어 있음

print(y_train['action'].value_counts())
6    1407
5    1374
4    1286
1    1226
2    1073
3     986
Name: action, dtype: int64

 

# DecisionTreeClassifier을 이용해 동작 예측 분류를 수행
# DecisionTreeClassifier의 하이퍼 파라미터는 모두 디폴트 값으로 설정해 수행, 이때 하이퍼파라미터 값 추출

from sklearn.tree import DecisionTreeClassifier
from sklearn.metrics import accuracy_score

# 예제 반복 시 마다 동일한 예측 결과 도출을 위해 random_state 설정
dt_clf = DecisionTreeClassifier(random_state=156)
dt_clf.fit(X_train , y_train)
pred = dt_clf.predict(X_test)
accuracy = accuracy_score(y_test , pred)
print('결정 트리 예측 정확도: {0:.4f}'.format(accuracy))

# DecisionTreeClassifier의 하이퍼 파라미터 추출
print('DecisionTreeClassifier 기본 하이퍼 파라미터:\n', dt_clf.get_params())

# 약 85.48%의 정확도
결정 트리 예측 정확도: 0.8548
DecisionTreeClassifier 기본 하이퍼 파라미터:
 {'ccp_alpha': 0.0, 'class_weight': None, 'criterion': 'gini', 'max_depth': None, 'max_features': None, 'max_leaf_nodes': None, 'min_impurity_decrease': 0.0, 'min_impurity_split': None, 'min_samples_leaf': 1, 'min_samples_split': 2, 'min_weight_fraction_leaf': 0.0, 'random_state': 156, 'splitter': 'best'}

 

# 결정 트리의 깊이(Tree Depth)가 예측 정확도에 주는 영향 살펴보기

# GridSearchCV를 이용해 사이킷런 결정 트리의 깊이를 조절할 수 있는 max_depth 값을 변화시키면서 예측 성능을 확인

# min_samples_split는 16으로 고정, max_depth를 6,8,10,12,16,20,24)로 계속 늘리면서 측정
# 교차 검증은 5개 세트 (cv=5)

from sklearn.model_selection import GridSearchCV

params = {
    'max_depth' : [ 6, 8 ,10, 12, 16 ,20, 24]
}

grid_cv = GridSearchCV(dt_clf, param_grid=params, scoring='accuracy', cv=5, verbose=1 )
grid_cv.fit(X_train , y_train)
print('GridSearchCV 최고 평균 정확도 수치:{0:.4f}'.format(grid_cv.best_score_))
print('GridSearchCV 최적 하이퍼 파라미터:', grid_cv.best_params_)
Fitting 5 folds for each of 7 candidates, totalling 35 fits
GridSearchCV 최고 평균 정확도 수치:0.8549
GridSearchCV 최적 하이퍼 파라미터: {'max_depth': 8}

# 5개의 cv세트에서 max_depth 값의 증가에 따라 예측 성능이 어떻게 변했는지 확인
# mean_test_score는 5개 CV 세트에서 검증용 데이터셋의 정확도 평균 수치

# GridSearchCV객체의 cv_results_ 속성을 DataFrame으로 생성. 
cv_results_df = pd.DataFrame(grid_cv.cv_results_)

# max_depth 파라미터 값과 그때의 테스트(Evaluation)셋, 학습 데이터 셋의 정확도 수치 추출
cv_results_df[['param_max_depth', 'mean_test_score']]

- 결과 : param_max_depth가 8일때 mean_test_score가 0.854879로 가장 높음

 

# 별도의 테스트셋에서 min_samples_split은 16으로 고정하고 max_depth의 변화에 따른 값 측정

# 8일때 가장 높은 정확도, 깊어질수록 정확도 떨어지고 과적합 영향력 커지므로 하이퍼파라미터를 이용해 깊이를 제어할 수 있어야 함
max_depths = [ 6, 8 ,10, 12, 16 ,20, 24]
# max_depth 값을 변화 시키면서 그때마다 학습과 테스트 셋에서의 예측 성능 측정
for depth in max_depths:
    dt_clf = DecisionTreeClassifier(max_depth=depth, min_samples_split=16, random_state=156)
    dt_clf.fit(X_train , y_train)
    pred = dt_clf.predict(X_test)
    accuracy = accuracy_score(y_test , pred)
    print('max_depth = {0} 정확도: {1:.4f}'.format(depth , accuracy))
max_depth = 6 정확도: 0.8551
max_depth = 8 정확도: 0.8717
max_depth = 10 정확도: 0.8599
max_depth = 12 정확도: 0.8571
max_depth = 16 정확도: 0.8599
max_depth = 20 정확도: 0.8565
max_depth = 24 정확도: 0.8565

 

# max_depth와 min_samples_split을 같이 변경하면서 정확도 성능 튜닝

params = {
    'max_depth' : [ 8 , 12, 16 ,20], 
    'min_samples_split' : [16, 24],
}

grid_cv = GridSearchCV(dt_clf, param_grid=params, scoring='accuracy', cv=5, verbose=1 )
grid_cv.fit(X_train , y_train)
print('GridSearchCV 최고 평균 정확도 수치: {0:.4f}'.format(grid_cv.best_score_))
print('GridSearchCV 최적 하이퍼 파라미터:', grid_cv.best_params_)
Fitting 5 folds for each of 8 candidates, totalling 40 fits
GridSearchCV 최고 평균 정확도 수치: 0.8549
GridSearchCV 최적 하이퍼 파라미터: {'max_depth': 8, 'min_samples_split': 16}

 

#  별도 분리된 테스트 데이터셋에 해당 하이퍼 파라미터 적용

best_df_clf = grid_cv.best_estimator_
pred1 = best_df_clf.predict(X_test)
accuracy = accuracy_score(y_test , pred1)
print('결정 트리 예측 정확도:{0:.4f}'.format(accuracy))
결정 트리 예측 정확도:0.8717

 

# 결정 트리에서 각 피처의 중요도를 feature_importance_ 속성을 이용하여 알아보기

# 중요도가 높은 순으로 Top 20 피처를 막대그래프로 표현
import seaborn as sns

ftr_importances_values = best_df_clf.feature_importances_
# Top 중요도로 정렬을 쉽게 하고, 시본(Seaborn)의 막대그래프로 쉽게 표현하기 위해 Series변환
ftr_importances = pd.Series(ftr_importances_values, index=X_train.columns  )
# 중요도값 순으로 Series를 정렬
ftr_top20 = ftr_importances.sort_values(ascending=False)[:20]
plt.figure(figsize=(8,6))
plt.title('Feature importances Top 20')
sns.barplot(x=ftr_top20 , y = ftr_top20.index)
plt.show()

 

 

03. 앙상블 학습

  • 앙상블 학습(Ensemble Learning)을 통한 분류는 여러개의 분류기를 생성하고 그 예측을 결합함으로써 보다 정확한 최종 예측을 도출하는 기법을 말함
  • 앙상블 알고리즘의 대표 : 랜덤포레스트, 그래디언트 부스팅 알고리즘

  • 앙상블 학습의 유형은 대표적으로 보팅(Voting), 배깅(Bagging), 부스팅(Booosting)의 세가지로 나뉨

  • 보팅과 배깅은 여러개의 분류기가 투표를 통해 최종 예측 결과를 결정하는 방식
  • 보팅의 경우 일반적으로 서로 다른 알고리즘을 가진 분류기를 결합하는것이고
  • 배깅의 경우 각각 분류기가 모두 같은 유형의 알고리즘 기반이지만, 데이터 샘플링을 서로 다르게 가져가면서 학습을 수행함, 대표적인 배깅 방식이 랜덤 포레스트 알고리즘

  • 교차 검증이 데이터셋 간의 중첩을 허용하지 않는 것과 다르게 배깅 방식은 중첩을 허용함

  • 부스팅은 여러 개의 분류기가 순차적으로 학습을 수행하되, 앞에서 학습한 분류기가 예측이 틀린 데이터에 대해서는 올바르게 예측할 수 있도록 다음 분류기에게는 가중치를 부여하면서 학습과 예측을 진행함
  • 대표적인 부스팅 모듈 : 그래디언트 부스트, XGBoost(eXtra Gradient Boost), LightGBM(Light Gradient Boost)

  • 스태킹은 여러 가지 다른 모델의 예측 결괏값을 다시 학습 데이터로 만들어서 다른 모델(메타 모델)로 재학습 시켜 결과 예측

 

 

보팅의 유형 - 하드 보팅과 소프트 보팅

 

  • 하드보팅
    다수결 원칙과 비슷함
    예측 결괏값 중 다수의 분류기가 결정한 예측값을 최종 보팅 결과로 선정

  • 소프트 보팅
    분류기의 레이블 값 결정 확률을 모두 더하고 이를 평균해서 이 중 확률이 가장 높은 레이블 값을 최종 보팅 결괏값으로 선정
    일반적으로 소프트 보팅이 보팅 방법으로 적용됨 (예측 성능이 더 좋음)

 

 

보팅 분류기(Voting Classifier)

 

  • 보팅 방식의 앙상블을 이용해 위스콘신 유방암 데이터셋 예측 분석
    위스콘신 유방암 데이터셋은 유방암의 악성종양, 양성종양 여부를 결정하는 이진 분류 데이터셋이며 
    종양의 크기, 모양 등의 형태와 관련한 많은 피처를 가지고 있음
    사이킷런은 load_breast_cancer() 함수를 통해 자체에서 위스콘신 유방암 데이터셋 생성 가능

 

# 필요한 모듈과 데이터 로딩 후 데이터셋 살펴보기

import pandas as pd

from sklearn.ensemble import VotingClassifier
from sklearn.linear_model import LogisticRegression
from sklearn.neighbors import KNeighborsClassifier
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score

cancer = load_breast_cancer()

data_df = pd.DataFrame(cancer.data, columns=cancer.feature_names)
data_df.head(3)

 

# 로지스틱 회귀와 KNN을 기반으로 하여 소프트 보팅 방식으로 새롭게 보팅 분류기 생성

# 사이킷런은 VotingClassifier 클래스를 이용해 보팅 분류기를 생성할 수 있음
# VotingClassifier 클래스는 주요 생성 인자로 estimators와 voting 값을 입력 받음
# estimators는 리스트 값으로 보팅에 사용될 여러 개의 Classifier객체들을 튜플 형식으로 입력받으며 voting은 하드 소프트 선택

# 개별 모델은 로지스틱 회귀와 KNN 임. 
lr_clf = LogisticRegression(solver='liblinear')
knn_clf = KNeighborsClassifier(n_neighbors=8)

# 개별 모델을 소프트 보팅 기반의 앙상블 모델로 구현한 분류기 
vo_clf = VotingClassifier( estimators=[('LR',lr_clf),('KNN',knn_clf)] , voting='soft' )

X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, 
                                                    test_size=0.2 , random_state= 156)

# VotingClassifier 학습/예측/평가. 
vo_clf.fit(X_train , y_train)
pred = vo_clf.predict(X_test)
print('Voting 분류기 정확도: {0:.4f}'.format(accuracy_score(y_test , pred)))

# 개별 모델의 학습/예측/평가.
classifiers = [lr_clf, knn_clf]
for classifier in classifiers:
    classifier.fit(X_train , y_train)
    pred = classifier.predict(X_test)
    class_name= classifier.__class__.__name__
    print('{0} 정확도: {1:.4f}'.format(class_name, accuracy_score(y_test , pred)))
Voting 분류기 정확도: 0.9561
LogisticRegression 정확도: 0.9474
KNeighborsClassifier 정확도: 0.9386