본문 바로가기
Data/머신러닝

[파이썬머신러닝완벽가이드]04.분류(1)

by mozi2 2023. 5. 20.
반응형

1. 분류

- 지도학습의 대표적인 유형
- 학습 데이터로 주어진 데이터의 피처와 레이블값(결정 값, 클래스 값)을 머신러닝 알고리즘으로 학습하여 모델을 생성
- 생성된 모델에 새로운 데이터 값이 주어졌을때 미지의 레이블 값을 예측하는 것

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

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

1.2 앙상블

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

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


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

2. 결정트리

: 데이터에 있는 규칙을 학습을 통해 자동으로 찾아내 트리 기반의 분류 규칙을 만드는 것
: 규칙을 가장 쉽게 표현하는 방법은 if/else 기반으로 나타내는 것으로, 룰 기반의 프로그램에 적용되는 if, else를 자동으로 찾아내 예측을 위한 규칙을 만드는 알고리즘
: 데이터의 어떤 기준을 바탕으로 규칙으로 만들어야 가장 효율적인 분류가 될 것인가가 알고리즘의 성능을 크게 좌우함

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

Q. 가능한 적은 결정 노드로 높은 예측 정확도를 가지려면 ???
- 데이터를 분류할 때 최대한 많은 데이터셋이 해당 분류에 속할 수 있도록 결정 노드의 규칙이 정해져야 함
- 최대한 *균일한 데이터셋을 구성할 수 있도록 분할(split)해야 함

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

- 정보이득지수 
:  엔트로피는 주어진 데이터 집합의 혼잡도를 의미하는데 서로 다른 값이 섞여 있으면 엔트로피가 높고, 같은 값이 섞여 있으면 엔트로피가 낮다. 정보 이득 지수는 1에서 엔트로피 지수를 뺀 값(1 - 엔트로피지수)이다. 결정 트리는 이 정보 이득 지수가 높은 속성을 기준으로 분할한다.

- 지니 계수 
 : 원래 경제학에서 불평등 지수를 나타낼 때 사용하는 계수로 0이 가장 평등하고 1로 갈수록 불평등하다. 머신러닝에서는 지니 계수가 낮을수록 데이터 균일도가 높은 것으로 해석해 지니 계수가 낮은 속성을 기준으로 분할한다

: 결정 트리는 균일도라는 룰을 기반으로 하고 있어서 알고리즘이 쉽고 직관적이다. 하지만 트리의 깊이가 커지고 복잡해지면서 과적합이 될 수 있다는 단점 또한 지닌다.

 : 결정 트리는 균일도라는 룰을 기반으로 하고 있어서 알고리즘이 쉽고 직관적이다. 하지만 트리의 깊이가 커지고 복잡해지면서 과적합이 될 수 있다는 단점 또한 지닌다.

  • min_samples_split : 노드를 분할하기 위한 최소한의 샘플 데이터 , default = 2 ( 작게 설정할 수록 과적합 가능성 증가 )
  • min_samples_leaf : 말단 노드가 되기 위한 최소한의 샘플 데이터 수, 비대칭적 데이터의 경우 특정 클래스의 데이터가 극도로 작을 수 있기 때문에 작게 설정해 주어야 한다. ( 과적합 제어 용도 )
  • max_features : 최적의 분할을 위해 고려할 최대 피처 개수, default = None ( 모든 피처를 사용해 분할 수행 ) ( int, float, sqrt = auto, log )
  • max_depth : 트리의 최대 깊이를 규정 , default = None ( 완벽하게 클래스 결정 값이 될 때까지 깊이를 계속 키움 or 노드가 가지는 데이터 개수가 min_samples_split보다 작아질 때까지 계속 깊이를 증가시킴 )
  • max_leaf_nodes : 말단 노드의 최대 개수

3. 붓꽃 데이터를 이용한 결정트리 모델의 학습과 예측

#붓꽃 데이터를 이용한 결정 트리 모델의 학습과 예측
from sklearn.tree import DecisionTreeClassifier
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
import warnings
warnings.filterwarnings('ignore')

# DecisionTree Classifier 생성
dt_clf = DecisionTreeClassifier(random_state=156)

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)

dt_clf.fit(X_train, y_train)
DecisionTreeClassifier(random_state=156)
#Graphiz 사용

from sklearn.tree import export_graphviz

# export_graphviz()의 호출 결과로 out_file로 지정된 tree.dot 파일을 생성함
export_graphviz(dt_clf, out_file = "tree.dot", class_names = iris_data.target_names, \
                feature_names = iris_data.feature_names, impurity = True, filled = True)
                
import graphviz
# 위에서 생성된 tree.dot 파일을 Graphviz가 읽어서 주피터 노트북상에서 시각화
with open("tree.dot") as f:
    dot_graph = f.read()
graphviz.Source(dot_graph)

: 생성 로직을 미리 제어하지 않으면, 완벽하게 클래스 값을 구별하기 위해 트리노드를 계속해서 생성 됨

=> 모델 과적합 되기 쉬움

  • petal length(cm) ≤ 2.45 : 자식 노드를 만들기 위한 규칙 노드
  • gini : value=[]로 주어진 데이터 분포에서의 지니 계수
  • samples : 현 규칙에 해당하는 데이터 건수
  • value=[] : 클래스 값 기반의 데이터 건수

min_samples_splits=x
자식노드로 분할할 수 있는 최소한의 샘플 데이터개수는 x개임을 의미함.
Ex) min_samples_splits=4 이면 샘플데이터가 (0,2,1)개일 때 자식노드로 분화할 수 없다.

min_samples_leaf=x
leaf노드가 될 수 있는 조건은 디폴트로 1이지만, 값을 설정하면 리프노드가 될 수 있는 샘플데이터 건수의 최소값을 말한다. 즉, 샘플 데이터개수 x개를 만족하고 한쪽으로 몰릴 때 leaf노드가 될 수 있음을 의미함.

Ex) min_sampels_leaf=2이고, sample=7이라면 2와 5개로 나누어질 수가 없다.

둘의 차이는 거의 없다. 만약 샘플수가 4인데, splits=3, leaf=4의 값을 준다면? leaf=4이므로 멈춘다.만약 샘플수가 4인데 splits=4, leaf=4라면? splits=4이므로 멈춘다.

 feature importance

결정트리 알고리즘이 학습을 통해 규칙을 정하는데 있어 피처의 중요한 역할 지표 제공

4. 결정트리 과적합

: 분류용 가상데이터를 생성해주는 make_classification( ) 에서 n_feature는 독립변수의 수
: n_informatives는 종속변수 데이터와 상관있는 독립변수의 수
: n_classes는 종속변수의 레이블데이터 개수
: n_clusters_per_class는 한 클래스에 들어간 cluster(군집)의 수를 의미한다.

from sklearn.tree import DecisionTreeClassifier

# 특정한 트리 생성 제약 없는 결정 트리의 학습과 결정 경계 시각화
dt_clf = DecisionTreeClassifier().fit(X_features, y_labels)
visualize_boundary(dt_clf, X_features, y_labels)

# 일부 이상치 데이터까지 분류하기 위한 분할이 자주 일어나서 결정 기준 경계가 너무 많아졌다.

# min_samples_leaf = 6으로 트리 생성 조건을 제약
dt_clf = DecisionTreeClassifier(min_samples_leaf = 6).fit(X_features, y_labels)
visualize_boundary(dt_clf, X_features, y_labels)

5. 결정트리 실습 -사용자 행동 인식 데이터세트

# 중복된 피처명이 얼마나 있는지 확인
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()
# 총 42개의 column이 중복되어 있음
column_index    42
dtype: int64

# 원본 피처명에 _1 or _2를 추가로 부여해 새로운 피처명을 가지는 DataFrame을 반환하는 함수

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
import pandas as pd

def get_human_dataset( ):
    
    # 공백으로 분리 -> sep인자를 \s+
    feature_name_df = pd.read_csv('./UCI_HAR_Dataset/features.txt', sep ='\s+', header = None, names=['column_index', 'column_name'])
    
    # 중복된 피처명을 수정
    new_feature_name_df = get_new_feature_name_df(feature_name_df)
    
    # DataFrame에 피처명을 column으로 부여하기 위해
    feature_name = new_feature_name_df.iloc[:, 1].values.tolist()
    
    # 학습 핓 데이터 세트, 테스트 피처 데이터
    X_train = pd.read_csv('./UCI_HAR_Dataset/train/X_train.txt', sep ='\s+', names=feature_name)
    X_test = pd.read_csv('./UCI_HAR_Dataset/test/X_test.txt', sep ='\s+', names=feature_name)
    
    # 학습 레이블, 테스트 레이블
    y_train = pd.read_csv('./UCI_HAR_Dataset/train/y_train.txt', sep ='\s+', header = None, names=['action'])
    y_test = pd.read_csv('./UCI_HAR_Dataset/test/y_test.txt', sep ='\s+', header = None, names=['action'])
    
    return X_train, X_test, y_train, y_test

X_train, X_test, y_train, y_test = get_human_dataset()

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

print('## 학습 피처 데이터셋 info() ##')
print(X_train.info())

# 7352개의 레코드로 561개의 피처를 지닌다.
## 학습 피처 데이터셋 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

: 학습 데이터 세트는 7352개의 레코드로 561개의 피처를 갖고 있음.

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

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))
print('DecisionTreeClassifier 기본 하이퍼 파라미터 : ', dt_clf.get_params())
결정 트리 예측 정확도: 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'}

- 결정 트리의  트리 깊이가 예측 정확도에 주는 영

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('최고 평균 정확도 수치' ,grid_cv.best_score_)
print('최적 하이퍼 파라미터', grid_cv.best_params_)
Fitting 5 folds for each of 7 candidates, totalling 35 fits
최고 평균 정확도 수치 0.8513444970102249
최적 하이퍼 파라미터 {'max_depth': 16}
# GridSearchCV 객체의 cv_results_ 속성을 DataFrame으로 생성
cv_results_df = pd.DataFrame(grid_cv.cv_results_)

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

# max_depth 변화에 따른 값 측정

max_depths = [6,8,10,12,16,20,24]
# max_depth 값을 변화시키면서 그때마다 학습과 테스트 세트에서의 예측 성능 측정
for depth in max_depths:
    dt_clf = DecisionTreeClassifier(max_depth=depth, 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}, accuracy = {1:.4f}'.format(depth, accuracy))
max_depth = 6, accuracy = 0.8558
max_depth = 8, accuracy = 0.8707
max_depth = 10, accuracy = 0.8673
max_depth = 12, accuracy = 0.8646
max_depth = 16, accuracy = 0.8575
max_depth = 20, accuracy = 0.8548
max_depth = 24, accuracy = 0.8548

 - min_samples_split을 활용한 grid search 생략

: grid search 후 .bestestimator 에 최적의 하이퍼 파라미터가 적용된 모델이 저장된다는 것을 알면된다.

# 피처 중요도 시각화
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_top10 = ftr_importances.sort_values(ascending = False)[:10]
plt.figure(figsize=(8,6))
plt.title('Feature importances Top10')
sns.barplot(x=ftr_top10, y = ftr_top10.index)
plt.show()

 

728x90
반응형