앙상블 :  프랑스어로 '함께, 동시에, 한꺼번에, 협력하여' 등을 의미하는 부사

1. 앙상블 학습이란?

앙상블 학습(Ensemble Learning)은 여러 개의 분류기를 생성하고, 그 예측을 결합함으로써 보다 정확한 예측을 도출하는 기법을 말합니다.

강력한 하나의 모델을 사용하는대신 보다 약한 모델 여러개를 조합하여 더 정확한 예측에 도움을 주는 방식입니다.

현실세계로 예를 들면, 어려운 문제를 해결하는데 한 명의 전문가보다 여러명의 집단지성을 이용하여 문제를 해결하는 방식을 앙상블 기법이라 할 수 있습니다.

 

2. 앙상블 학습 유형

앙상블 학습은 일반적으로 보팅(Voting), 배깅(Bagging), 부스팅(Boosting) 세 가지의 유형으로 나눌 수 있습니다.

  • 보팅(Voting)
    • 여러 개의 분류기가 투표를 통해 최종 예측 결과를 결정하는 방식
    • 서로 다른 알고리즘을 여러 개 결합하여 사용
    • 보팅 방식
      • 하드 보팅(Hard Voting)
        • 다수의 분류기가 예측한 결과값을 최종 결과로 선정
      • 소프트 보팅(Soft Voting)
        • 모든 분류기가 예측한 레이블 값의 결정 확률 평균을 구한 뒤 가장 확률이 높은 레이블 값을 최종 결과로 선정

  • 배깅(Bootstrap AGGregatING, Bagging)
    • 데이터 샘플링(Bootstrap) 을 통해 모델을 학습시키고 결과를 집계(Aggregating) 하는 방법
    • 모두 같은 유형의 알고리즘 기반의 분류기를 사용
    • 데이터 분할 시 중복을 허용
    • Categorical Data : 다수결 투표 방식으로 결과 집계
    • Continuous Data : 평균값 집계
    • 과적합(Overfitting) 방지에 효과적
    • 대표적인 배깅 방식 : 랜덤 포레스트 알고리즘

 

  • 부스팅(Boosting)
    • 여러개의 분류기가 순차적으로 학습을 수행
    • 이전 분류기가 예측이 틀린 데이터에 대해서 올바르게 예측할 수 있도록 다음 분류기에게 가중치(weight)를 부여하면서 학습과 예측을 진행
    • 계속하여 분류기에게 가중치를 부스팅하며 학습을 진행하기에 부스팅 방식이라고 불림
    • 예측 성능이 뛰어나 앙상블 학습을 주도
    • 대표적인 부스팅 모듈 – XGBoost, LightGBM, SKlearn
    • 보통 부스팅 방식은 배깅에 비해 성능이 좋지만, 속도가 느리고 과적합이 발생할 가능성이 존재하므로 상황에 따라 적절하게 사용해야 함.

 

정형 데이터 / 비정형 데이터

여태까지 사용했던 데이터들 (도미/빙어/와인 등..)은 길이,무게,높이 등 csv파일에 가지런한 데이터로 되어있었다. 이런 데이터들을 정형 데이터라고 부른다.[어떠한 구조로 되어있다는 뜻] CSV / XLSX 등에 저장하기 쉽다. 반대로 비정형 데이터는 책과 같은 텍스트 데이터, 사진 , 음악, 영상 등이 있다.

 

정형 데이터들을 다루는 데 가장 뛰어난 성과를 내는 알고리즘이 앙상블 학습 이라고한다.

그렇다면 비정형 데이터는 어떤 알고리즘을 사용해야할까? (+어떻게 정리?) > 7장

 

랜덤 포레스트 알고리즘

랜덤 포레스트는 앙상블 학습의 대표격으로, 안정적 성능을 자랑한다.

좋은 이유 ==>  1. 배깅의 이점을 살리고, 2. 변수를 랜덤으로 선택하는 과정을 추가함으로 개별나무들의 상관성을 줄여 예                                측력이 좋기 때문

1 : 배깅 : 데이터 샘플링(Bootstrap) 을 통해 모델을 학습시키고 결과를 집계(Aggregating) 하는 방법

2 : 변수를 랜덤 으로 선택

 

랜덤 포레스트는 결정 트리를 랜덤하게 만들어 결정 트리의 숲을 만들고, 각 트리의 예측을 사용해 최종 예측을 만듦.

 

  • 트리의 훈련 데이터를 랜덤하게 설정 : 부트스트랩 샘플 (노드 분할시 계속 전체 무작위로)

예측력이  좋은 이유 : 다른 알고리즘과 달리 변수들의 상관관계 확연히 줄어듦.개별 나무 분리할 때마다 계속해서 랜덤한 데이터가 뽑히므로!

 

와인 데이터셋으로 해보자

import numpy as np
import pandas as pd
from sklearn.model_selection train_test_split
wine = pd.read_csv('https://bit.ly/wine_csv_data')
data = wine[['alcohol','sugar','pH']].to_numpy()

target = wine['class'].to_numpy()
train_input, test_input, train_target, test_target = train_test_split(data,target, test_size=0.2, random_state=42)

 

cross_validate()으로 교차검증을 해보자!랜덤포레스트 기본 결정트리 개수 = 100개n_jobs = -1 (오래걸리니까?)

return_train_score = True : 검증점수 뿐만아니라 훈련 세트에 대한 점수도 같이 반환한다

(과대 /과소 파악하는데 쉬우므로)

from sklearn.model_selection import cross_validate
from sklearn.ensemble import RandomForestClassifier

rf = RandomForestClassifier(n_jobs=-1, random_state=42)
scores = corss_validate(rf, train_input, train_target,
						return_train_score=True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))

0.9973541965122431 0.8905151032797809

훈련세트에 과대적합

                                                            도수  /   당도      /      pH

특성 중요도 < 단순 결정 트리에서는 0.123 /    0.868    /    0.00791  > 였다.!

rf.fit(train_input, train_target)
print(rf.feature_importances_)

[0.23167441 0.50039841 0.26792718]

 랜덤 포레스트 알고리즘에서의 특성 중요도는 당도가 줄어들고 도수 pH가 모두 증가

        >>>랜덤하게 선택하여 개별 나무들의 상관성이 줄어들었기 때문 ( 예측력 상승)<<<

 

 

OOB 샘플 (out of bag) 샘플

말그대로 사용되지 않고 남은 샘플을 의미,

이 샘플들을 이용해서 부트스트랩 샘플로 훈련한 트리를 평가할 수 있다!! ( 검증 세트의 역할과 비슷)

 

rf = RandomForestTreeClassifier(oob_score=True, n_jobs=-1, random_state=42)
rf.fit(train_input, train_target)
print(rf.oob_score)

0.8934000384837406

 

 

엑스트라 트리

엑스트라 트리는 랜덤 포레스트와 비슷하게 동작한다.

차이점: 부트스트랩 샘플 사용 X , 결정트리를 만들 때 전체 훈련 세트를 사용한다. 대신 노드를 분할할 때 가장 좋은 분할을 찾는 것이 아니라 무작위로 분할한다. 기존의 결정 트리에서 splitter='random' 사용한것과 같.

 

성능은 낮아지지만 검증 세트의 점수를 높일 수 있다.(왜?) + 속도 빠르다 ( 기준이 랜덤) +

 

from sklearn.ensemble import ExtraTreesClassifier
et = ExtraTreesClassifier(n_jobs=-1, random_state=42)
scores = cross_validate(et, train_input, train_target,
						return_train_score=True, n_jobs=-1)
print(np.mean(scores['train_scores']), np.mean(scores['test_score']))

0.9974503966084433 0.8887848893166506

비슷하다 (특성 많지않아서)  특성 중요도를 살펴보자.

ef.fit(train_input, train_target)
print(ef.feature_importances_)

[0.20183568 0.52242907 0.27573525]

오 비슷하다.

 

 

다음은 랜덤포레스트 / 엑스트라 트리와는 다른 앙상블 학습을 알아보자.

 

그레이디언트 부스팅

그레디언트 부스팅(Gradient boosting)

  • 깊이가 얕은 결정 트리 ( 이전 트리의 오차를 보완해 나가는 방식)
  • 경사하강법을 사용
  • 분류- 로지스틱 손실 함수를 사용  /  회귀 - 평균 제곱 오차 함수를 사용

( 경사하강법 조금씩 이동 ) <=> ( 그레디언트 - 길이 얕은 트리)

https://www.youtube.com/watch?v=3CC4N4z3GJc

그레디언트 부스팅
from sklearn.ensemble import GradientBoostingClassifier
gb = GradientBoostingClassifier(random_state=42)
scores = cross_validate(gb, train_input, train_target,
                        return_train_score = True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_scores']))

0.8881086892152563 0.8720430147331015

굿! 개수를  더 높여보자 (개수 500)

from sklearn.ensemble import GradientBoostingClassifier
gb = GradientBoostingClassifier(n_estimators= 500, learning_rate=0.2,random_state=42)
scores = cross_validate(gb, train_input, train_target,
                        return_train_score = True, n_jobs=-1)
print(np.mean(scores['train_score']), np.mean(scores['test_scores']))

0.9464595437171814 0.8780082549788999

특성 중요도

gb.fit(train_input,train_target)
print(gb.feature_importances_)

[0.15872278 0.68010884 0.16116839]

 

 

+새로운 매개변수 subsample : 트리 훈련에 사용할 훈련 세트 비율을 정함 (default=1 [전체])

subsample을 낮추면????   <<< 미니배치 경사 하강법과 비슷함 >>>

 

그레디언트 부스팅은 랜덤 포레스트보다 더 높은 성능! 하지만 속도 느리고 과적합 가능성 높다.

 

그레디언트 부스팅의 속도와 성능을 개선한 모델

히스토그램 기반 그레이디언트 부스팅

정형 데이터를 다루는 머신러닝 알고리즘 중 가장 인기가 높은 알고리즘이래.

 

입력 특성을 256개의 구간으로 나눈다. 따라서 노드를 분할할 때 최적의 분할을 빠르게 찾을 수 있다.

히스토그램 기반 그레디언트 부스팅은 256개의 구간 중에서 하나를 떼어 놓고 누락된 값을 위해서 사용한다.

따라서 입력에 누락된 특성이 있더라도 이를 따로 전처리할 필요가 없다! (왜?)

 

from sklearn.experimental import enable_hist_gradient_boosting
from sklearn.ensemble import HistGradientBoostingClassifier
hgb = HistGradientBoostingClassifier(random_state=42)
scores = cross_validate(hgb, train_input, train_target,
						return_train_score=True)
print(np.mean(scores['train_score']),np.mean(scores['test_score']))

0.9321723946453317 0.8801241948619236

과대적합 더 억제하면서 그레디언트부스팅보다 성능이 좋다.

 

여기서 특성중요도는 feature_importnaces_가 아니라 permutation_importance 라고 한다.

from sklearn.inspection import permutatin_importance

hgb.fit(train_input,train_target)
result = permutation_importance(hgb, train_input,train_target,
				n_repeats=10, random_state=42, n_jobs=-1)
print(result.importances_mean)

[0.08876275 0.23438522 0.08027708]

 

n_repeats는 랜덤하게 섞을 횟수 (default=5)

 

성능을 확인해보자.

hgb.score(test_input,test_target)

0.8723076923076923

사이킷런 말고 그레디언트 부스팅 알고리즘을 쓰고싶다면 XGBoost 가 있다.

from XGBoost import XGBClassifier
xgb = XGBClassifier(tree_method='hist', random_state=42)
scores = cross_validate(xgb, train_input, train_target,
					   return_train_score=True)
print(np.mean(scores['train_score']), np.mean(scores['test_score']))
0.8824322471423747 0.8726214185237284

 

 

 

 

여태까지 배웠던 최근접 이웃, 로지스틱 회귀, SGD, 트리 등 다양한 머신러닝 모델들이 있었지만,

모든 모델들은 결국 실전에 투입시키기 위해 사용한다. 따라서 성능을 높이기 위해 반복적으로 테스트 세트를 활용하여 점수를 높여가야하는데, 이는 목적과 다르게 모델이 테스트 세트에 더 적합해지는 불상사가 생길 수 있다. 

이를 해결하기 위해 검증 세트를 활용해보자.

 

검증 세트 (validation set)

테스트 세트를 마지막에 한 번 사용하기위해 훈련 세트를 또 나누어서 검증세트를 만들고 이 세트를 테스트 세트와 똑같이 사용할 수 있다.

 import pandas as pd
 wine = pd.read_csv("https://bit.ly/wine_csv_data")
 
 # class 열을 타겟으로 설정하고 나머지 열은 특성으로 저장 ( .to_numpy() )

data = wine[["alcohol","sugar","pH"]].to_numpy()
target = wine["class"].to_numpy()

# 이제 훈련 테스트 세트 나누기 
from sklearn.model_selection import test_train_split
train_input , test_input, train_target, test_target = test_train_split(data,target,test_size=0.2, random_state=42)

# 검증 세트 만들기
sub_input,val_input, sub_target,val_target = test_train_split(train_input,train_target,test_size=0.2, random_state=42)

print(sub_input.shape, val_input.shape)
(4157, 3) (1040, 3)  # <최종 훈련세트수 4157개, 검증세트 수 1040개>

이제 평가해보자.

from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier( random_state=42 )
df.fit(sub_input, sub_target)

print(dt.score(sub_input,sub_target))
print(dt.score(val_input,val_target))

0.9971133028626413
0.864423076923077

 

교차 검증

 

검증세트를 배워 보았는데, 확실히 성능 향상에 좋은 모델을 만들 수 있을것 같다!

하지만 훈련에 많은 데이터를 활용할수록 좋은데, 검증세트로 인해 훈련세트가 적어지는 단점이 있다.

그렇다고 검증세트를 적게하면 그만큼 점수가 불안정하다.

 

이럴 때 교차검증을 이용하면 안정적인 검증 점수를 얻고 훈련에 더 많은 데이터를 사용할 수 있다.

 

교차검증은 검증 세트를 떼어 내어 평가하는 과정을 여러 번 반복한다. 그 다음 이 점수를 평균하여 최종 검증 점수를 얻는다. 위 그림과 같이 3개로 나누는것을 3-폴드 교차 검증이라 한다.

보통 실전에서는 5-폴드나 10-폴드 교차검증을 주로 사용한다. 이렇게 하면 데이터의 8~90%까지 훈련에 사용할 수 있다.

 

사이킷런의 cross_validate() 교차 검증 함수를 사용하여 코드를 실행해보자.

from sklearn.model_selection import cross_validate
scores = cross_validate(dt, train_input, train_target)
print(scores)

{'fit_time': array([0.01978898, 0.01216435, 0.01753545, 0.01066256, 0.03117847]), 'score_time': array([0.00559211, 0.00121975, 0.00127912, 0.00102139, 0.00114012]), 'test_score': array([0.86923077, 0.84615385, 0.87680462, 0.84889317, 0.83541867])}

cross_validate() 는 fit_time , score_time, test_score 각각의 키를 가진 딕셔너리를 반환한다.

fit_time:          모델을 훈련하는 시간

score_time:    모델을 검증하는 시간

test_score:     교차 검증 폴드의 점수들

 

교차 검증의 최종점수는 교차 검증 폴드의 점수들 (test_score) 의 평균이다.

print(np.mean(scores['test_score']))

0.855300214703487

※ 교차 검증은 데이터를 섞을 필요가 없다. ※

※ 교차 검증을 수행하면 모델에서 얻을 수 있는 최상의 검증 점수를 얻을 수 있다. ※

 

훈련 세트를 섞으려면 분할기를 지정해야 한다.

cross_validate()에서 사용하는 분할기- 회귀일 때는 KFold / 분류일 때는 StrarifiedKFold

여기에서는 분류이므로

from sklearn.model_selection import StratifiedKFold
scores = cross_validate(dt, train_input, train_target, cv=StratifiedKFold())
print(np.mean(scores['test_score']))

0.855300214703487

위 두개는 결과가 같다 == cross_validate 할 때는 따로 섞지 않아도 된다! 

 

만약 훈련 세트를 섞고 10-폴드 교차 검증을 하고 싶으면

from sklearn.model_selection import StratifiedKFold
scores = cross_validate(dt, train_input, train_target, cv=splitter)
print(np.mean(scores['test_score']))

0.8574181117533719

 

하이퍼파라미터 튜닝

모델파라미터      :  모델이 학습하는 파라미터 

하이퍼파라미터  :  모델이 학습할 수 없어서 사용자가 지정해야 하는 파라미터

 

사이킷런과 같은 머신러닝 라이브러리를 사용할 때 하이퍼파라미터는 모두 클래스/메소드의 매개변수로 표현됨

< 하이퍼파라미터 : 머신러닝 클래스/함수의 파라미터(변수)>

 

머신러닝 모델에서 최적의 파라미터를 찾는다고 가정해보자. DecisionTreeClassifier을 예를들면,

최적의 파라미터들을 찾는다고 할 때, 먼저 최적의 max_depth를 찾고, 고정한 후 최적의 min_samples_split를 찾고.. 이런 방법으로 하이퍼파라미터 튜닝을 할 수 있을까?

 

불행하게도 이런식으로 찾을 수 없다. max_depth의 최적값은 min_samples_split 변수의 값이 바뀌면 따라 바뀐다.즉, 변수들을 동시에 바꿔가며 최적의 값을 찾아야 한다.  ( 변수의 값이 많을 수록 매우 복잡해진다.)

 

이렇게 동시에 변수들의 값을 바꿔가며 최적의 값을 찾아주는 기능이 있다.

그리드 서치 (Grid Search)   (하이퍼파라미터 튜닝)

그리드 서치는 위에 설명한 최적값을 교차 검증을 통해 찾아내 준다!! 

따라서 cross_validate()를 따로 호출할 필요가 없다.

from sklearn.model_selection import GridSearchCV
params = {'min_impurity_decrease' : [0.0001, 0.0002, 0.0003, 0.0004, 0.0005]}

#그리드서치는 클래스에 탐색 대상 모델과 params 변수(딕셔너리로)를 전달하여 실행

gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params , n_jobs= -1)
# n_jobs =  사용할 CPU 코어 수 { -1은 제한없이 최대로 }

gs.fit(train_input,train_target)    (그리드 서치를 통해 25개의 모델을 훈련시킨다)

dt = gs.best_estimator_             # 24개 모델 중 최고의 모델! 이 모델로 학습시키면된다 (dt)
print( dt.score(train_input, train_target))
0.9615162593804117

위 코드를 실행하면 그리드 서치는

prarams 변수 (5개)를 바꿔가며 5번(default=5) 실행한다. 따라서 총 25가지의 경우의 수가 나온다.

그 중 최적의 하이퍼파라미터를 찾으면 best_estimator_를 통해서 그 모델로 학습시키면 될 것 같다.

또한 최적의 매개변수는 best_params_에 저장되어 있다.

 # best_ estimator_

 # best_params_

 

print(dt.best_params_)

{'min_impurity_decrease': 0.0001}

  여기서는 0.0001 ( 첫 번째 변수) 변수가 최적이다.

 

각 매개변수에서 수행한 교차 검증의 평균 점수또한 cv_results_속성의 mean_test_score에 저장되어 있다.

 

a = gs.cv_results_['mean_test_score']
print(a)
[0.86819297 0.86453617 0.86492226 0.86780891 0.86761605]

# gs.cv_results_['mean_test_score']  : 평균 점수 나열

변수 개수가 많거나 찾기 힘들때는 

np.argmax() 메소드를 통해 가장 큰 값의 인덱스를 추출할 수 있다.

 

best_index = np.index[np.argmax(gs.cv_results_['mean_test_score']]  # 인덱스 번호 반환
print(gs.cv_results_['params'][best_index])                         # 키 값을 통해 params value 출력

{'min_impurity_decrease': 0.0001}

정리하자면

  1. 탐색할 매개변수를 지정 ex) max_depth, min_impurity_decrease
  2. 훈련 세트에서 그리드 서치를 하여 최상의 평균 검증 점수가 나오는 매개변수 조합을 찾음.
  3.  최상의 매개변수에서 전체 훈련 세트를 사용해 최종 모델을 훈련

 

더 복잡하게 해보자.       <<min_impurity_decrease = 노드를 분할하기 위한 불순도 감소 최소량을 지정>>

params = {'min_impurity_decrease': np.arange(0.0001,0.001, 0.0001),
		  'max_depth' = range(5,20,1),
          'min_samples_split' : range(2,100,10)
          }
          
  # 총 경우의 수 (?) = 9 x 15 x 10 x 5(default =5) = 6,750 개

최적의 매개변수 조합을 확인해보자.

gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_hobs = -1)
gs.fit(train_input, train_target)
print(gs.best_estimator_)

DecisionTreeClassifier(max_depth=14, min_impurity_decrease=0.0004,
                       min_samples_split=12, random_state=42)

max_depth= 14, min_impurity=0.0004, min_samples_split= 12 일 때가 최적이다!

print(np.max(gs.cv_results_['mean_test_score']))
0.8683865773302731

 

랜덤 서치

위 코딩처럼 매개 변수처럼 범위나 간격을 일일히 설정해주기 힘들 수 있다. 변수가 많거나 할 수 없는 환경일 때 !

따라서 랜덤 서치를 이용할 수 있다. by using 싸이파이

 

랜덤 서치에는 매개변수 값의 목록이 아닌 매개변수를 샘플링할 수 있는 확률 분포 객체를 전달한다.

싸이파이에서 2개의 확률 분포 클래스를 임포트해보자.

from scipy.stats import uniform, randint

randint = 정수 / uniform = 실수

scipy.stats 에 있는 uniform과 randint 클래스는 모두 주어진 범위에서 고르게 값을 뽑는데, 이를 

"균등 분포에서 샘플링한다"라고 한다.

 

 

rgen = randint(0,10)
rgen.rvs(10)

array([6, 9, 4, 9, 5, 1, 0, 3, 6, 1])

데이터 10개 중에서 10개를 뽑아서 균일하게 뽑혔는지 잘 모른다.

rgen = randint(0,10)
a = rgen.rvs(1000)
np.unique(a, return_counts = True)

(array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
 array([ 98, 107, 107,  93,  88,  95,  98, 102, 106, 106]))

대략적으로 골고루 뽑히는것을 확인할 수 있다.

일종의 난수 발생기와 유사하다고 볼 수 있다.

 

이제 균등 분포에서 샘플링을 해보자!

 

params = { 'min_impurity_decrease': uniform(0.0001, 0.001),
  	   'max_depth' : randint(20,50),
           'min_samples_split' : randint(2,25),
           'min_samples_leaf' : randint(1,25),
          }

샘플링 횟수는 사이킷런의 랜덤 서치 클래스인 RandomizedSearchCV의 n_iter = 에 지정 가능

from sklearn.model_selection import RandomizedSearchCV
gs = RandomizedSearchCV(DecisionTreeClassifier(random_state=42),params,  n_iter = 100, n_jobs= -1, random_state=42)
gs.fit(train_input, train_target)

print(gs.best_params_)
{'max_depth': 39, 'min_impurity_decrease': 0.00034102546602601173, 'min_samples_leaf': 7, 'min_samples_split': 13}

위 params에서 정의된 매개변수 범위에서 총 100번을 샘플링하여 교차 검증을 수행하고 최적의 매개변수 조합을 찾는다.

 

앞서 그리드 서치보다 훨씬 교차 검증 수를 줄이면서 넓은 영역을 효과적으로 탐색할 수 있다.

 

최고의 교차 검증 점수도 확인해보자.

print(np.max(gs.cv_results_['mean_test_score']))

0.8695428296438884

최적의 모델은 이미 전체 훈련세트로 훈련되어 best_estimator_ 속성에 저장되어 있다. 

이 모델을 최종 모델로 결정하고 이제 테스트 세트로 성능을 확인해보자!!!!

dt = gs.best_estimator_
print(dt.score(test_input,test_target))

0.86

 

'머신러닝 & 딥러닝 기초 > 5장 - 트리 알고리즘' 카테고리의 다른 글

5 - 3 : 트리의 앙상블  (0) 2023.01.17
5 - 1 결정 트리  (1) 2023.01.09

로지스틱 회귀 모델을 적용하여 이진 분류를 해보자.

import pandas as pd
wine = pd.read_csv("https://bit,ly/wine_csv_data")

 

wine.head()

 

데이터를 보면 첫 번째 열부터 차례대로 도수, 당도, pH를 나타내며, 마지막 열인 class는 타깃값이 0이면 레드와인, 1이면 화이트 와인이라고 한다.

wine.info()

<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6497 entries, 0 to 6496
Data columns (total 4 columns):
 #   Column   Non-Null Count  Dtype  
---  ------   --------------  -----  
 0   alcohol  6497 non-null   float64
 1   sugar    6497 non-null   float64
 2   pH       6497 non-null   float64
 3   class    6497 non-null   float64
dtypes: float64(4)
memory usage: 203.2 KB

 

describe() 메소드를 이용하여 간략한 통계를 출력해보자.

wine.describe()


alcohol	sugar	pH	class
count	6497.000000	6497.000000	6497.000000	6497.000000
mean	10.491801	5.443235	3.218501	0.753886
std	1.192712	4.757804	0.160787	0.430779
min	8.000000	0.600000	2.720000	0.000000
25%	9.500000	1.800000	3.110000	1.000000
50%	10.300000	3.000000	3.210000	1.000000
75%	11.300000	8.100000	3.320000	1.000000
max	14.900000	65.800000	4.010000	1.000000

describe()를 통해 평균 / 표준편차 / 최솟값/ 쿼터값 / 최대값을 볼 수 있습니다.

 

이제 데이터 전처리 ( 표준화 ) 를 해보자.

 

data = wine[['alcohol', 'sugar', 'pH']].to_numpy()
target = wine['class'].to_numpy()

from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = train_test_split(data,target,test_size=0.2, random_state = 42)

훈련 세트와 테스트 세트의 크기를 확인해 보자.

print(train_input.shape, test_input.shape)

(5197, 3) (1300, 3)

 이제 표준화를 해보자.

from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)

이제 훈련을 해보자.

from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target))
print(lr.score(test_scaled, test_target))

from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_scaled, train_target)
print(lr.score(train_scaled, train_target))
print(lr.score(test_scaled, test_target))

0.7808350971714451
0.7776923076923077

낮은 score() + 학습원리 설명하기 어려움

--결정 트리 이용

 

결정 트리 모델

 

from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(random_state = 42)
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target))
print(dt.score(test_scaled, test_target))

0.996921300750433
0.8592307692307692

점수가 높아졌다. !

 

이 트리를 시각화해보자. by using plot tree()

import matplotlib.pyplot as plt
from sklearn.tree import plot_tree
plt.figure(figsize=(10,7))
plt_tree(dt)
plt.show()

노드의 길이가 너무 길어서 제한해보자 by using max_depth = 1 하면 하나의 노드를 더 그림

plt.figure(figsize=(10,7))
plot_tree(dt, max_depth = 1 , filled = True, feature_names = ['alcohol','sugar','pH'])
plt.show()

 

그림이 담고 있는 정보는 조건 (sugar), 불순도(gini), 샘플 수, 클래스 별 샘플 수로 이루어져 있다.

루트 노드는 당도가 -0.239이하인지...yes <---   /   No --->

불순도

  • 해당 범주 안에 서로 다른 데이터가 얼마나 섞여 있는지를 뜻함.

gini는 지니 불순도를 의미하는데,

<<<<<클래스개수에 따른 케이스들의 불순한 정도를 나타내는 척도라고 생각하면 될 것 같다.

지니불순도가 필요한 이유는 의사결정을 하는데 있어서 최적의 분류를 위한 결정을 계속해서 맞이하는데 이 결정에 사용되기 때문이다. 이 변수로 인해서 분류를 거쳤을 때 지니불순도가 얼마나 되는가? 를 생각할 수 있게된다. 또다른 불순도지표로는 엔트로피가 있다.>>>>>>

DecisionTreeClassifier 클래스의 criterion 매개변수의 기본값이 gini이다.

 

지니 불순도(gini) = 1 - ( 음성 클래스 비율^2 + 양성 클래스 비율^2 )

결정 트리 모델은 부모와 자식 노드의 불순도 차이가 커지도록 트리를 성장시킨다.

 

(자식 노드의 불순도를 샘플 개수에 비례하여 모두 더하고 그 이후 부모 노드의 불순도에서 뺀다.)

 

=부모 gini - (왼쪽 샘플 수 / 부모 샘플 수) x 왼쪽 gini - (오른쪽 샘플 수 / 부모 샘플 수) x 오른쪽 gini

 

= 0.367 - [(2922/5197) x 0.481] - [(2275/5197) x 0.069] = 0.066

 

부모와 자식 노드간의 불순도 차이를 정보 이득이라고 한다.

따라서 정보 이득이 최대가 되도록 데이터를 나누고, 그 기준으로 불순도 (gini)를 사용한다.

 

한편, 사이킷런에서는 또 다른 불순도 기준이 존재한다. ( 엔트로피 불순도)

 

DeicisionTreeClassifier 클래스에서 criterion='entropy' 

엔트로피 불순도 기준은 아래에 log 2를 붙여서 사용한다.

 

가지치기

끝도 없이 자라나는 트리가 생길 수 있기 때문에 가지치기는 필수적이다.

가장 간단한 방법은 아까 했던 것처럼  max_depth = 으로 지정할 수 있다.

dt = DecisionTreeClassifier( max_depth = 3,  random_state=42 )
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target))
print(dt.score(test_scaled, test_target))

0.8454877814123533
0.8415384615384616

 

plt.figure(figsize=(20,15))
plot_tree(dt, filled= True, feature_names=['alcohol', 'sugar','pH'])
plt.show()

위 그림을 보면 깊이 0~3 중 깊이 1은 당도를 기준, 2는 당도,도수,pH 순으로 기준을 사용했다.

최종 노드를 보면 왼쪽에서 세번째 노드만 음성클래스가 더 많은것을 볼 수 있다. 

따라서 이 노드 (주황)에 도착해야만 레드와인으로 분류하는것 같다.

즉, 당도는 -0.802~-0.239 사이, 도수는 0.454보다 작아야 레드와인으로 분류한다.

 

그런데 당도가 음수라는것을 어떻게 해석해야할까.( 문제는 전처리)

 

결정 트리는 표준화 전처리 과정을 하지 않아도 된다!!!

 

dt = DecisionTreeClassifier( max_depth = 3, random_state = 42)
dt.fit(train_input, train_target)
print(dt.score(train_input, train_target))
print(dt.score(test_input, test_target))

0.8454877814123533
0.8415384615384616

트리를 그려보자 (no 전처리)

plt.figure(figsize=(20,15))
plot_tree(dt, filled=True, feature_names=['alcohol','sugar','pH'])
plt.show()

이제 가시적으로 해석하기 더 쉽다. 

마지막으로, 결정 트리에는 어떤 특성이 가장 유용한지 나타내는 특성 중요도를 계산해 줍니다. 이 트리의 루트 노드와 깊이1에서 당도를 사용했기 때문에 당도가 가장 유용한 특성일 것 같다.

메서드는 DecisionTreeClassifier.feature_importances_

print(dt.feature_importances_)

[0.12345626 0.86862934 0.0079144 ]

 

앞의 데이터와는 달리 만약 훈련 데이터들이 준비되어 있지 않고,

시간에 걸쳐 조금씩 추가가된다면 어떻게 학습을 시켜야할까?

 

기존에 훈련한 모델을 버리지 않고 새로운 데이터에 대해서만 조금씩 훈련시킬 수는 없을까?

점진적 학습을 통해 이를 해결할 수 있다.

대표적인 점진적 학습법은 확률적 경사 하강법이라고 한다. (stochastic Gradient Descent)

 

확률적 경사 하강법에서 확률적이다 라는 말은 '무작위하게' 혹은 '랜덤하게'의 기술적인 표현이다.

경사 = 기울기

하강법 = 내려가는 방법

 

경사하강법은 기본적으로 가장 빠르게 내려가는 방법을 고수한다. (산에서 내려오는것을 생각)

하지만 만약 발걸음이 너무 크다면 경사를 따라 내려가는게 아니라 오히려 올라갈 수 도있다.

 경사하강법은 훈련 세트를 사용해 훈련할 때, 전체 샘플을 사용하지 않고, 딱 하나의 샘플을 훈련 세트에서 랜덤하게 골라 가장 가파른 길을 찾는다. 이처럼 훈련 세트에서 랜덤하게 하나의 샘플을 고르는것이 바로 률적 경사 하강법이라고 한다.

 < 훈련세트에서 랜덤하게 하나의 샘플을 택해 조금 내려간 후 다음 샘플 선택해서 내려가고, 다음,..다음...이런방식 >

 이렇게 모든 샘플을 사용할때 까지 반복한다.

 

하지만 만약 그래도 산을 다 내려오지 못한다면? 

다시 처음부터! 훈련세트에 모든 샘플을 다시 채워넣은 후 다시 랜덤하게 하나의 샘플을 선택해 이어서 경사를 내려간다.

이렇게 만족할 때 까지 계속 내려가면 된다.

확률적 경사 하강법에서 훈련 세트를 한 번 모두 사용하는 과정을 에포크라고 부른다.

일반적으로 경사하강법은 수십,수 백번의 에포크를 수행한다고 한다..

 

경사하강법은 무작위 샘플을 선택해서 산에서 내려가는건데 너무 무책임한거 아니야??라고 생각할 수 있다.

원래는 걱정하는것과 달리 이 알고리즘은 매우 정상적으로 작동하지만, 그래도 걱정이 된다면

1개씩 말고 몇 개씩 샘플을 선택해서 따라 내려갈 수 있다. 이를

미니배치 경사 하강법이라고 부른다. 이 방법또한 실전에서 많이 쓰인다.

 

그 밖에도 배치 경사 하강법이라는 방법도 있는데, 이는 한 번 경사를 따라 이동하는데에 모든 샘플을 사용한다.

이 방법이 가장 안전,정확하지만 그만큼 전체데이터를 사용하기 때문에 시간,자원이 많이 소모된다.

 

이 확률적 경사 하강법을 통해 매일매일 데이터가 업데이트 되어도 학습을 계속 이어나갈 수 있을것이다!

 

그런데 문제가 있다. 어디서 내려가야하는걸까? + 산은 도대체 무엇인걸까?? 이 산은 손실함수라고 부른다.

 

손실 함수 ( loss function )

손실함수란 어떤 문제에서 머신러닝 알고리즘이 얼마나 엉터리인지를 측정하는 기준이다.

그렇다면 손실함수의 값이 작을수록 좋다. 다행히도 우리가 다루는 많은 문제에 필요한 손실함수는 이미 정의가 되어있다.

 

이제 더 자세히 알아보도록 하자.

분류에서 손실은 확실하다. 못맞추는것 !

 

그럼 이를 어떻게 표현할 수 있지 않을까?

만약 4번의 예측 중에 두 번만 맞췄다면 정확도는 1/2 ( 0.5 ) 이다.

정확도를 손실함수로 사용한다면? 음수를 취해 -1이 가장 낮고, -0이 가장 높다. 이렇게 정확도를 손실함수로 사용할 수 있다.

하지만 정확도에는 치명적 단점이 있는데, 앞의 4개의 샘플만 있다면 가능한 정확도는 0, 0.25, 0.5 , 0.75, 1 다섯가지 뿐이다. 이는 경사하강법에서 연속적이지 않고 듬성듬성하게 내려오면 부적절할 수 있다.

 

그렇다면 손실함수를 연속적으로 만들려면 앞의 로지스틱과 연관지어보면된다.

 

로지스틱 손실 함수

우선 예시로 샘플 4개의 예측 확률을 각각 0.9, 0.3, 0.2, 0.8 이라고 가정해보자.

그리고 첫번째 두번째의 타겟은 1,   세 번째, 네 번째의 타겟은 0 이다. (불리안)

첫 번째 샘플의 예측은 0.9이므로 양성 클래스의 타깃인 1과 곱한다음 음수로 바꿀 수 있다. 이 경우 예측이 1에 가까울 수록 좋은 모델이라고 할 수 있다. 반대로 예측이 1에 가까울수록 음수는 점점 작아진다. 이 값을 손실함수로 사용해보자.

 

두 번째 샘플의 예측은 0.3이다. 거리가 멀다. 첫 번째 샘플처럼  - (예측 x 타겟)을 해보자. -0.3이다. 첫 번째보다 높은 손실.

 

세 번째 샘플에서 타겟 0을 곱하면 무조건 0이되기 때문에 문제가 생긴다.

★★★따라서 타겟을 양성클래스와 같이 1로 바꿔준다. 대신에 예측값도 양성클래스에 대한 예측으로 바꿔준다!!! ★★★

그렇다면 1-0.2 = - (0.8 x 1) = -0.8이다. 꽤 낮은 손실이다.

 

네 번째 샘플또한 세번째처럼 양성처럼 바꿔준다. -0.2이다. 높은 손실이다.

1,3번째 샘플 = 낮은 손실, 2,4번째 샘플  = 높은 손실

 

 

예측 확률을 사용해 이런 방식으로 계산한다면 연속적인 손실 함수를 얻을 수 있을 것 같다. 여기에서 예측 확률에 로그함수를 적용한다면 더욱 보기 좋을 수 있다.

로그함수의 특성 ( 0에 가까울 수록 큰 음수가 되어 손실을 크게 만들어줆.)

위와 같은  손실 함수를 로지스틱 손실 함수 또는 <이진or다중>크로스엔트로피 손실 함수라고 부른다.

 

이제 손실함수까지 알아봤으니, 경사 하강법을 이용한 분류 모델을 보자.

import pandas as pd
fish = pd.read_csv("https://bit.ly/fish_csv_data")

fish_input = fish[['Weight','Length','Diagonal','Height','Width']].to_numpy()
fish_target = fish['Species'].to_numpy()

from sklearn.model_selection import train_test_split
train_input, test_input,train_target,test_target = train_test_split(fish_input,fish_target,random_state=42)

from sklearn.preprocessing import StandardScaler
ss = StandardScaler()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)

 

SGD Classifier

★SGD Classifier클래스는 사이킷런에서 확률적 경사 하강법을 제공하는 대표적인 분류용 클래스이다. !!!!★

from sklern.linear_model import SGDClassifier

SGDClassifier는 두개의 매개변수가 필요하다. loss= 와 max_iter= 이다.

loss=는 손실함수의 종류, max_iter = 은 에포크 횟수

여기서는 loss='log', max_iter = 10으로 해보자.

sc = SGDClassifier(loss='log', max_iter=10 , random_state=42)
sc.fit(train_scaled,train_target)

sc.score(train_scaled, train_target)
sc.score(test_scaled, test_target)

0.773109243697479
0.775

0.7로 정확도가 그리 높지 않은것을 볼 수 있다.

아마도 에포크가 부족한 것일 수도 있다.

 

SGDClassifier의 장점?특성에 따라 다시 만들 필요없이 추가 훈련을 해보자.

모델 추가 훈련할 때에는 partial_fit() 메소드를 사용하면 된다.

 

이 메소드는 fit()과 같지만 호출할 때마다 에포크를 1씩 이어서 훈련할 수 있다는 특징이 있다.

partial_fit() 메소드를 호출하고 다시 훈련세트와 테스트세트의 점수를 확인해보자.

 

sc.partial_fit(train_scaled, train_target)
sc.score(train_scaled, train_target)
sc.score(test_scaled, test_target)

0.8151260504201681
0.85

오 더 올라감. 그런데 얼마나 올려야할까????? 기준이 필요하다.

 

에포크의 과대/과소 적합

확률적 경사 하강법을 사용한 모델은 에포크 횟수에 따라 과소적합/과대적합이 될 수 있다.

에포크 횟수가 적으면 모델이 훈련 세트를 덜 학습한다. 마치 산을 다 내려오지 못하고 훈련이 끝난 상황.

에포크 횟수가 많으면 훈련 세트를 완전히 학습할 것이다.

바꿔 말하면 적은 에포크 횟수 동안 훈련한 모델은 훈련/테스트 세트에 잘 맞지 않는 과소적합된 모델일 것이고,  많은 에포크 횟수 동안 훈련한 모델은 훈련세트에 잘맞아 테스트세트에는 점수가 나쁜 과대적합된 모델일 가능성이 높다.

이를 그림으로 표현하면

 

이 그래프는 에포크가 진행됨에 따라 모델의 정확도를 나타낸 것이다. 훈련 세트 점수는 에포크가 진행될수록 꾸준히 증가하지만 테스트 세트 점수는 어느 순간 감소하기 시작한다. 이 지점이 바로 모델이 과대적합되기 시작하는 지점이다.

과대적합이 시작하기전에 훈련을 멈추는것을 조기종료라고 한다.

 

우리가 가진 데이터으로 위와 같은 그래프를 만들어보자.

이제 fit()이 아닌 partial_fit()만 사용할건데, 이를 위해서는 훈련세트에 있는 전체 클래스의 레이블을 partial_fit()메소드에 전달해줘야 한다. 이를 위해 np.unique()함수로 train_target 에 있는 7개 생선 목록을 만들고, 에포크마다 훈련세트와 테스트 세트의 점수를 보기 위해 2개의 리스트를 준비해보자.

 

import numpy as np
sc = SGDClassifier(loss='log', random_state= 42)
train_score = []
test_score = []
classes = np.unique(train_target)		# unique() = 오름차순으로 고유값 정렬시킴(중복제거)

300번의 에포크를 해보자.

for _ in range(0,300):
	sc.partial_fit(train_scaled, train_target, classes=classes)
    train_score.append(sc.score(train_scaled,train_target))
    train_score.append(sc.score(test_scaled,test_target))

이제 그래프를 그려보자!

import matplotlib.pyplot as plt
plt.plot(train_score)
plt.plot(test_score)
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.show()

약 백 번째 에포크 이후에는 훈련 세트의 테스트 세트가 점점 벌어진다!!

 

이제 반복 횟수를 100에 맞춰볼까?

sc = SGDClassifier(loss='log', max_iter = 100, tol=None, random_state=42)
sc.fit(train_scaled, train_target)

print(sc.score(train_scaled,train_target))
print(sc.score(test_scaled,test_target))

0.957983193277311
0.925

점수가 매우 좋다!!!

SGDClassifier는 일정 에포크 동안 성능이 향상되지 않으면 더 훈련하지 않고 자동으로 멈춘다.

tol 매개변수에서 향상될 최솟값을 지정한다. 앞의 코드에서는 tol 매개변수를 None으로 지정하여 자동으로 멈추지 않고 max_iter = 100만큼 무조건 반복하도록 하였다.

 

판다스를 이용하여 csv파일을 읽어보자!

import pandas as pd
fish = pd.read_csv("https://bit.ly/fish_csv_data")
fish.head()

species에는 어떠한 종류의 생선들이 있는지 확인하기 위해 unique() 함수를 사용해보자.

pd.unique(fish["Species"])

도미,빙어를 비롯해 총 7가지 종류의 생선이 있다.

이제 Species 열을 타겟으로 만들고 나머지 5개 열을 입력 데이터로 사용해보자.

fish_input = fish[["Weight","Length","Diagonal","Height","Width"]].to_numpy()

잘 입력되었는지 처음 5개 행을 출력해보자!

print(fish_input[:5])

array([[242.    ,  25.4   ,  30.    ,  11.52  ,   4.02  ],
       [290.    ,  26.3   ,  31.2   ,  12.48  ,   4.3056],
       [340.    ,  26.5   ,  31.1   ,  12.3778,   4.6961],
       [363.    ,  29.    ,  33.5   ,  12.73  ,   4.4555],
       [430.    ,  29.    ,  34.    ,  12.444 ,   5.134 ]])

이제 타겟 데이터를 준비하자. ( 종류 ) 

fish_target = fish["Species"].to_numpy()

이제 훈련,테스트 세트로 나눠보자.

from sklearn.model_selection import train_test_split()
train_input, train_target, test_input, test_target = train_test_split(fish_input,fish_target,random_state=42)

 

이제 데이터 전처리 (표준화)를 하고 훈련시켜보자 !

from sklearn.preprocessing import StandardScaler
ss = StandardSclaer()
ss.fit(train_input)
train_scaled = ss.transform(train_input)
test_scaled = ss.transform(test_input)

K-최근접 이웃 분류기로 확률 예측

from sklearn.neighbors import KNeighborsClassifier
Kn = KNeighborsClassifier()
Kn.fit(train_scaled,train_target)
print(Kn.score(train_scaled,train_target))
print(kn.score(test_scaled,test_target))

0.8907563025210085
0.85

이제 타겟값을 어떻게 예측하는지 알아보자.

print(kn.classes_)

array(['Bream', 'Parkki', 'Perch', 'Pike', 'Roach', 'Smelt', 'Whitefish'],
      dtype=object)

'Bream'이 첫 번째 클라스, 'Parkki'가 두 번째 클라스.. 방식 predict()메소드를 이용해서 타깃값으로 예측을 출력해보자.

Kn.predict(test_scaled[:5])

array(['Perch', 'Smelt', 'Pike', 'Perch', 'Perch'], dtype=object)

predit_proba()메소드클래스별 확률값을 반환한다.

import numpy as np
proba = Kn.predict_proba(test_scaled[:5])
print(np.round(proba, decimals=4)     #np.round() = 반올림 , decimals = n >>소수 n번째 자리까지 표시

해석 => 첫 번째 열 : 'Bream'일 확률, 두 번째 열 : 'Parkki'일 확률 ,,,,,,,이다.

확률을 봤으니 직접 샘플의 최근접 이웃 (3개)가 무엇인지 살펴보자.

distances, indexes = Kn.kneighbors(test_scaled[3:4])
print(train_scaled[indexes])

[['Roach' 'Perch' 'Perch']]

1/3 = 0.33333333      2/3 = 0.66666667 

기준 샘플 수가 3개이다 보니 확률이 0, 0.3, 0.6, 1 네 개의 숫자 밖에 나오지 않는다...  더 좋은 방법은 없을까?

 

로지스틱 회귀

로지스틱 회귀 (logistic regression)는 이름은 회귀이지만 분류 모델이다.

또한 로지스틱 회귀는 선형 회귀와 마찬가지로 선형 방정식을 학습한다.

 

변수 a,b,c,d,e는 가중치/계수.

z의 값은 0~1로 나오면 확률로 표현할 수 있다. 이를 위해 시그모이드 함수를 사용하면 된다!

import numpy as np
import matplot.pyplot as plt
z = np.arange( -5, 5 , 0.1)
phi = 1 / ( 1 + np.exp(-z))
plt.plot(z , phi)
plt.xlabel('z')
plt.ylabel('phi')
plt.show()

정말 0 ~ 1로 출력된다!

 

# 로지스틱 회귀로 이진 분류 수행해보자

이진 분류이므로 불리언 인덱싱을 통해 해결 ( T / F)

char_arr = np.array(['A','B','C','D','E'])
print(char_arr[[True,False,True,False,False]])

['A' 'C']

위와 같은 방식을 통해 훈련 세트에서 도미,빙어의 행만 골라 내자

bream_smelt_indexes = (train_target == "Bream" ) | (train_target == "Smelt")
train_bream_smelt = train_scaled[bream_smelt_indexes]
target_bream_smelt = train_scaled[bream_smelt_indexes]

# | = or 과 같음

위 코딩 => 도미,빙어에는 True값이 들어가있고 나머지는 모두 False가 들어가 있다.

따라서 이 배열을 사용해 train_scaled와 train_target 배열에 불리언 인덱싱을 적용하면

손쉽게 도미와 빙어 데이터만 골라낼 수 있을것 같다.

 

이제 준비된 데이터로 로지스틱 회귀 모델을 훈련해보자.

from sklearn.linear_model import LogisticRegression
lr = LogisticRegression()
lr.fit(train_bream_smelt, target_bream_smelt)

print(lr.predict(train_bream_smelt[:5]))

['Bream' 'Smelt' 'Bream' 'Bream' 'Bream']

두 번째 샘플을 빼고 모두 도미로 예측했다.! 

KNeighborsClassifier()과 마찬가지로 예측확률은 preidct_proba() 메소드에서 제공한다.

lr.predict_proba(train_bream_smelt[:5])

array([[0.99759855, 0.00240145],
       [0.02735183, 0.97264817],
       [0.99486072, 0.00513928],
       [0.98584202, 0.01415798],
       [0.99767269, 0.00232731]])

샘플마다 두개의 확률이 출력되었다. !! 첫 번째 열 = 음성 클래스(0), 두 번째 열 = 양성 클래스(1)

무엇이 0, 1인지는 classes_ 를 통해 알아보자.

lr.classes_

array(['Bream', 'Smelt'], dtype=object)

빙어가 양성이다 ! 

 

이제 계수까지 확인해보자.

print( lr.coef_, lr.intercept_ )

[[-0.4037798  -0.57620209 -0.66280298 -1.01290277 -0.73168947]] [-2.16155132]

이 계수를 통해 보면 방정식은 다음과 같다.

위 방정식에서 z값을 구해보고싶다!!

= decision_function()메소드로 출력할 수 있다.

lr.decision_function(train_bream_smelt[:5])

array([-6.02927744,  3.57123907, -5.26568906, -4.24321775, -6.0607117 ])

이 z값을 시그모이드 함수에 통과시키면 확률을 얻을 수 있다!!

파이썬의 사이파이 라이브러리를 통해 시그모이드함수(expit()) == np.exp()함수를 이용할 수 있다.

 

from scipy.special import expit
expit(decision)

[0.00240145 0.97264817 0.00513928 0.01415798 0.00232731]

predict_proba()메소드를 사용했을 때의 두 번째 열과 동일하다. 

즉, decision_function() 메소드는 양성 클래스(빙어)에 대한 z값을 반환함을 알 수 있다.

 

이제 다중 분류 문제로 넘어가보자.

 

로지스틱 회귀로 다중 분류 수행하기

 

lr = LogisticRegression(C=20, max_iter = 1000)
lr.fit(train_scaled, train_target)

lr.score(train_scaled, train_target)
lr.score(test_scaled, test_target)

0.9327731092436975
0.925

처음 5개 샘플에 대한 예측을 출력해보자.

lr.predict(test_scaled[:5])

array(['Perch', 'Smelt', 'Pike', 'Roach', 'Perch'], dtype=object)

이번에는 테스트 세트의 처음 5개 샘플에 대한 예측확률을 출력해보자!

proba = lr.predict_proba(test_scaled[:5])
np.round(proba, decimals = 3)

array([[0.   , 0.014, 0.841, 0.   , 0.136, 0.007, 0.003],
       [0.   , 0.003, 0.044, 0.   , 0.007, 0.946, 0.   ],
       [0.   , 0.   , 0.034, 0.935, 0.015, 0.016, 0.   ],
       [0.011, 0.034, 0.306, 0.007, 0.567, 0.   , 0.076],
       [0.   , 0.   , 0.904, 0.002, 0.089, 0.002, 0.001]])

5개 샘플에 대한 예측이므로 5개의 행이 출력, 7개의 생선에 대한 확률을 계산했으므로 7개의 열이 출력되었다.

 

첫 번째 샘플을 보면  세 번째 열이 가장 높다. (Perch:농어)

lr.classes_

array(['Bream', 'Parkki', 'Perch', 'Pike', 'Roach', 'Smelt', 'Whitefish'],
      dtype=object)

3번째 열이 농어임을 확인!!!

 

 

이제 다중 분류일 경우의 선형방정식을 살펴보자.

소프트맥스함수

시그모이드함수는 하나의 선형방정식의 출력값을 0~1로 반환하는 반면,

소프트맥스함수는 여러개의 방정식의 출력값을 0~1로 압축하고 전체합이 1이 되게한다.

소프트맥스함수를 정규화된 지수함수라고 부르기도 한다.

 

 

먼저 decision_function()메소드를 통해 z1 ~ z7까지의 값을 구하고,

소프트맥스함수를 이용해 확률로 바꿔보자.

decision = lr.decision_function(test_scaled[:5])
print(np.round(decision, decimals=2))

[[ -6.5    1.03   5.16  -2.73   3.34   0.33  -0.63]
 [-10.86   1.93   4.77  -2.4    2.98   7.84  -4.26]
 [ -4.34  -6.23   3.17   6.49   2.36   2.42  -3.87]
 [ -0.68   0.45   2.65  -1.19   3.26  -5.75   1.26]
 [ -6.4   -1.99   5.82  -0.11   3.5   -0.11  -0.71]]

 

from scipy.special import softmax
proba = softmax(decision, axis = 1)
print(np.round(proba, decimals = 3))

[[0.    0.014 0.841 0.    0.136 0.007 0.003]
 [0.    0.003 0.044 0.    0.007 0.946 0.   ]
 [0.    0.    0.034 0.935 0.015 0.016 0.   ]
 [0.011 0.034 0.306 0.007 0.567 0.    0.076]
 [0.    0.    0.904 0.002 0.089 0.002 0.001]]

앞서 구한 decision()배열을 softmax함수에 전달했다.

axis= 의 매개변수는 소프트맥스를 계산할 축을 지정한다.

여기서는 각 행(샘플)에 대해 소프트맥스를 계산한다. !

+ Recent posts