그래요 이제 절반 완주했어요 🥳🥳🥳🥳🥳
힘을 내서 완주까지 열심히 달려봅시다 🔥🔥🔥
6,497개의 와인 샘플 데이터를 가지고 와인을 분류해보는 작업을 진행할 것이다
import pandas as pd
wine = pd.read_csv("https://bit.ly/wine_csv_data")
wine.head()
처음 3개의 열 (alcohol, sugar, pH) 은 각각 알코올 도수, 당도, pH 값을 나타낸다
네 번째 열 (class)은 타깃값으로 0이면 레드 와인, 1이면 화이트 와인이라고 한다
레드 와인과 화이트 와인을 구분하는 이진 분류 문제이고,
화이트 와인이 양성 클래스이다
정리하면 전체 와인에서 화이트 와인을 골라내는 문제이다
로지스틱 회귀 모델을 훈련하기 전에
판다스 데이터 프레임의 유용한 메서드 2개를 먼저 알아보자
info()
이 메서드는 데이터프레임의 각 열의 데이터 타입과 누락된 데이터가 있는지를 확인한다
wine.info()
출력결과를 보면 6,497개의 샘플이 있고, 4개의 열은 모두 실수값이다
그리고 Non-Null Count가 모두 6497이므로 누락된 값은 없다
describe()
이 메서드는 열에 대한 간략한 통계를 출력해준다
최대, 최소, 평균값 등을 볼 수 있다
wine.describe()
우리는 이를 통해 알코올과 당도, pH 값의 스케일이 다르다는 것을 확인할 수 있다
그래서 이전에 했던 것처럼 사이킷런의 StandardScaler 클래스를
사용해서 특성을 표준화해야 한다
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)
그 전에 먼저 판다스 데이터 프레임을 넘파이 배열로 바꾸고
훈련 데이터와 테스트 데이터로 나누는 작업을 진행했다
train_test_split()
함수는 설정하지 않으면 25%를 테스트 데이터로 설정한다
샘플 개수가 충분히 많음으로 우리는 20% 정도만 테스트 데이터로 나눴다
test_size = 0.2
가 이런 의미이다
print(train_input.shape,test_input.shape) # (5197, 3) (1300, 3)
이제 StandardSclaer 클래스를 통해 전처리를 진행하고
로지스틱 회귀 모델에 학습을 진행해보자
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)) # 0.7808350971714451
print(lr.score(test_scaled,test_target)) # 0.7776923076923077
결과를 확인해보니 생각보다 점수가 높지 않았다
훈련 데이터와 테스트 데이터의 점수가 모두 낮은걸을 확인해보니
과소적합이라는 생각도 들 수 있다
그러면 이 문제를 해결하기 위해서 규제 매개변수인 C의 값을 변경해볼 수도 있고,
아니면 solver 매개변수에서 다른 알고리즘을 선택해볼 수도 있다
print(lr.coef_,lr.intercept_) # [[ 0.51268071 1.67335441 -0.68775646]] [1.81773456]
쉽게 이야기를 하면 이 모델은 알코올 도수 값에 0.51268071
을 곱하고
당도에 1.67335441
를 곱하고 마지막으로 pH값에 -0.68775646
을 곱한 다음 모두 더한다
그 값이 0보다 크면 화이트 화인 작으면 레드와인이다
그리고 지금 정확도는 77% 정도이다
음...
이런 것들을 봐서는 우리가 이 모델이 왜 저런 계수를 학습했는지 잘 모른다
그저 추측할 뿐이다
이럴 때 쓰면 좋은 것이 있다 바로 결정 트리(Decision Tree)이다
결정 트리 모델은 마치 스무고개와 같다
질문을 하나씩 던져서 정답과 맞춰가는 것이다
데이터를 잘 나눌 수 있는 질문을 찾는다면
계속 질문을 추가해서 분류 정확도를 높일 수 있다
이 역시 사이킷런에서 결정 트리 알고리즘을 제공하고 있다
그러면 사이컷런의 DecisionTreeClassifier 클래스를 사용해서 훈련해보자
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(random_state=42)
dt.fit(train_scaled,train_target)
print(dt.score(train_scaled,train_target)) # 0.996921300750433
print(dt.score(test_scaled,test_target)) # 0.8592307692307692
보면 훈련 데이터에 대한 점수가 매우 높다
하지만 테스트 데이터의 성능은 그에 비해 낮은 걸 확인할 수 있다
일종의 과대적합된 모델이라고 볼 수 있을 것 같은데
한 번 그림으로 표현을 해보자
import matplotlib.pyplot as plt
from sklearn.tree import plot_tree
plt.figure(figsize=(10,7))
plot_tree(dt)
plt.show()
사이킷런에서는 plot_tree()
함수를 통해서
결정 트리를 이해하기 쉬운 그림으로 출력해준다
엄청난 트리가 만들어졌다
진짜 나무는 아래에서부터 위로 자라지만 결정트리는 위에서부터 아래로 거꾸로 자란다
우리는 맨 위의 노드를 루트 노드라고 부르고
맨 아래 끝에 달린 노드를 리프 노드라고 부른다
너무 복잡하니까 plot_tree()
함수에서 트리의 깊이를 제한해서 출력을 진행해볼 것이다
max_depth
매개변수를 1로 주면 루트 노드를 제외하고 하나의 노드를
더 확장하여 그려주고 filled
매개변수에서 클래스에 맞게 노드의 색깔을 칠할 수 있다
feature_names
매개변수에는 특성의 이름을 전달 할 수 있다
plt.figure(figsize=(10,7))
plot_tree(dt, max_depth=1, filled = True, feature_names=['alcohol','sugar','pH'])
plt.show()
그림을 이제 좀 살펴보자
먼저 루트 노드는 당도(sugar)가 -0.239 이하인지 질문을 하고 있다
만약 어떤 샘플의 당도가 -0.239보다 같거나 작으면
왼쪽 가지로 그렇지 않으면 오른쪽 가지로 가게된다
즉 왼쪽이 yes, 오른쪽이 No이다
또 확인할 수 있 것은
루트 노드의 총 샘플 수는 5197개이고 이 중에서 음성 클래스(레드 와인)는 1258개,
양성 클래스(화이트 와인)은 3939개이다 이 모든 값은 value에 나타나 있다
왼쪽노드에서는 추가적으로 어떤 당도가 더 낮은지 물어보고 있다
루트 노드보다 양성 클래스, 즉 화이트 와인의 비율이 크게 줄었다
그 이유는 오른쪽 노드를 보면 알 수 있다
오른쪽 노드는 음성 클래스가 81개, 양성 클래스가 2194개로
대부분의 화이트 와인 샘플이 이 노드로 이동을 했다
또 루트의 노드 색깔을 보면 이 쪽이 더 진한 것을 알 수 있는데
plot_tree()
함수에서 filled=True
로 지정하면 클래스마다 색깔을 부여하고
어떤 클래스의 비율이 높아지면 점점 진한 색으로 표시를 한다
이를 활용하면 매우 직관적으로 확인할 수 있다
결정 트리에서 예측하는 방법은 간단하다
리프 노드에서 가장 많은 클래스가 예측 클래스가 된다
앞에서 보았던 k-최근접 이웃과 매우 비슷하다
만약 이 결정 트리의 성장을 여기서 멈추면 왼쪽 노드에 도달한 샘플과
오른쪽 노드에 도달한 샘플은 모두 양성 클래스로 예측이 된다
두 노드 모두 양성 클래스가 많기 때문이다
gini는 지니 불순도를 의미한다 DecisionTreeClassifier 클래스의
criterion 매개변수의 기본값이 'gini'다
criterion 매개변수의 용도는 노드에서 데이터를 분할할 기준을 정하는 것이다
앞서 루트 노드는 어떻게 당도 -0.239를 기준으로 왼쪽과 오른쪽 노드로 나눴을까?
바로 criterion 매개변수에 지정한 지니 불순도를 사용했다
지니 불순도는 클래스의 비율을 제곱해서 더한 다음 1에서 빼면 된다
지니불순도 = 1 - (음성 클래스 비율^2 + 양성 클래스 비율^2)
예를 구체적으로 들어보자
루트 노드는 5197개의 샘플이 있었고,
그 중에 1258개가 음성 클래스이고 3939개가 양성 클래스였다.
따라서 다음과 같이 지니 불순도를 작성할 수 있다
1 - ((1258/5197^2) + (3939/5197^2)) = 0.367
만약 100개의 샘플이 있는 어떤 노드의 두 클래스의 비율이 정확히 1/2이면
지니 불순도는 0.5가 되어 최악이 된다
다른 경우로 노드에 하나의 클래스만 있다면 지니 불순도는 0이 되어 가장 작다
이런 노드를 우리는 순수 노드라고 한다
결정 트리 모델은
부모 노드와 자식 노드의 불순도 차이가 가능한 크도록 트리를 성장시킨다
그러면 부모 노드와 자식 노드의 불순도 차이를 계산하는 방법을 알아보자
먼저 자식 노드의 불순도를 샘플 개수에 비례하여 모두 더한다
그 다음 부모 노드의 불순도에 빼면 된다
부모의 불순도 - (왼쪽 노드의 샘플 수 / 부모의 샘플 수)
x
왼쪽 노드 불순도 - ( 오른쪽 노드 샘플 수 / 부모의 샘플 수)
x
오른쪽 노드 불순도
=
이런 부모와 자식 노드 사이의 불순도 차이를
우리는 정보 이득(information gain)이라고 한다
해당 알고리즘은 이 정보 이득이 최대가 되도록 데이터를 나눈다
이때 이 지니 불순도를 기준으로 한다
그런데 사이킷런에는 또 다른 불순도 기준이 있다
바로 DecisionTreeClassifier
클래스에서
criterion='entropy'
를 지정하여 사용하는 엔트로피 불순도이다
엔트로피 불순도도 노드의 클래스 비율을 사용하지만
지니 불순도처럼 제곱이 아니라 밑이 2인 로그을 사용하여 곱한다
- 음성 클래스 비율
x log2(음성 클래스 비율)
- 양성 클래스 비율
x log2(양성 클래스 비율)
보통 기본값인 지니 불순도와 엔트로피 불순도가 만든 결과는 큰 차이가 없다
정리를 해보면 불순도 기준을 사용해 정보 이득이 최대가 되도록
노드를 분할한다 이 때에 노드를 순수하게 나눌수록 정보 이득이 커진다새로운 샘플에 대해 예측할 때에는 노드의 질문에 따라 트리를 이동한다
그리고 마지막에 도달한 노드의 클래스 비율을 보고 예측을 만든다
열매를 잘 맺기 위해 과수원에서 가지치기를 하는 것처럼
결정 트리도 가지치기를 해야한다 그렇지 않으면 무한정으로 트리가 만들어진다
그러면 훈련 데이터에는 아주 잘 맞겠지만 테스트 데이터 점수는 그에 못 미친다
그래서 우리는 가지치기가 필요하다
결정 트리에서 가지치기를 하는 가장 간단한 방법은 트리의 최대 깊이를 지정하는 것이다
먼저 DecisionTreeClassifier 클래스의 max_depth 매개변수를 3으로 지정해보자
dt = DecisionTreeClassifier(max_depth =3, random_state=42)
dt.fit(train_scaled, train_target)
print(dt.score(train_scaled, train_target)) # 0.8454877814123533
print(dt.score(test_scaled, test_target)) # 0.8415384615384616
훈련 데이터의 성능은 낮아졌지만 테스트 데이터의 성능은 거의 그대로이다
한 번 트리 그래프로 그려보자
plt.figure(figsize=(20,15))
plot_tree(dt, filled=True, feature_names=['alchol','sugar','pH'])
plt.show()
해당 그래프를 통해 샘플이 어떻게 나눠지는지를 잘 확인할 수 있다
그런데 -0.802라는 음수로 된 당도를 어떻게 설명을 해야할까?
또 이상한 점이 있다
앞서 불순도를 기준으로 샘플을 나눈다고 했는데
불순도는 클래스별 비율을 가지고 계산했다
샘플을 어떤 클래스 비율로 나누는지 계산할 때
특성값의 스케일이 계산에 영향을 미칠까?
-> 아니다
특성값의 스케일은 결정 트리 알고리즘에 아무런 영향을 미치지 않는다
따라서 표준화 전처리를 할 필요가 없다
이것이 결정 트리 알고리즘의 또 다른 장점 중 하나이다
dt = DecisionTreeClassifier(max_depth=3, random_state =42)
dt.fit(train_input,train_target)
print(dt.score(train_input,train_target)) # 0.8454877814123533
print(dt.score(test_input, test_target)) # 0.8415384615384616
plt.figure(figsize=(20,15))
plot_tree(dt, filled=True, feature_names=['alcohol','sugar','pH'])
plt.show()
결과를 보면 같은 트리이지만, 특성값을 표준 점수로 바꾸지 않아서 이해하기가 더 쉽다
당도 1.625보다 크고 4.325보다 작은 와인 중에
알코올 도수가 11.025와 같거나 작은 것이 레드 와인으로 예측을 했고
그 외에는 모두 화이트 와인으로 예측을 했다
마지막으로 결정 트리는 어떤 특성이 가장 유용한지 나타내는 특성 중요도를 계산해준다
이 트리의 루트 노드와 깊이 1에서 당도를 사용했기 때문에
아마도 당도가 가장 유용한 특성 중 하나일 것이다
특성 중요도는 결정 트리 모델의 feature_importances_
속성에 저장되어 있다
print(dt.feature_importances_) # [0.12345626 0.86862934 0.0079144 ]
결과값을 보면 두 번째 특성인 당도가 0.87 정도로 특성 중요도가 가장 높았다
그 다음 알코올 도수, pH 순이다 이 값을 모두 더하면 1이된다
특성 중요도는 각 노드의 정보 이득과 전체 샘플에 대한 비율을 곱한 후
특성 별로 더하여 계산한다 이를 이용하면 결정트리 모델을 특성 선택에 활용할 수 있다
이것이 결정 트리 알고리즘의 또 다른 장점 중 하나이다
지금까지 우리는 훈련 데이터에서 모델을 훈련하고 테스트 데이터에서 모델을 평가했다
그리고 테스트 데이터에서 얻은 점수를 보고 일반화 성능을 가늠해왔다
그런데 테스트 데이터를 사용해서 자꾸 성능을 확인하다보면
점점 테스트 데이터에 맞춰지게 된다 그래서 테스트 데이터로 일반화 성능을
올바르게 예측하려면 모델을 만들고 나서 마지막에 딱 한 번만 사용하는 것이 좋다
그러면 max_depth
매개변수를 사용한 하이퍼파라미터 튜닝은 어떻게 해야할까?
게다가 결정 트리는 테스트 해볼 매개변수가 많다
테스트 데이터를 사용하지 않으면 과소적합인지 과대적합인지 알기가 힘들다
테스트 데이터를 사용하지 않고 이를 측정하는 방법은 훈련 데이터를 또 나누는 것이다
이러한 데이터를 우리는 검증 데이터라고 한다
이 방법은 실제 많이 사용하는 방법이며 보통 데이터의
60%는 훈련 데이터, 20%는 검증 데이터, 또 20%는 테스트 데이터로 많이 사용한다
이렇게 훈련 데이터를 통해 모델을 훈련하고 검증 데이터로 모델을 평가한다
이런식으로 테스트하고 싶은 매개변수를 바꿔가며 가장 좋은 모델을 고른다
그리고 그 매개변수를 사용해 훈련 데이터와 검증 데이터를 합쳐
전체 훈련 데이터에서 모델을 다시 훈련한다
그리고 마지막에 테스트 데이터에서 최종 점수를 평가한다
그러면 실전에 투입했을 때 테스트 데이터의 점수와 비슷한 성능을 기대할 수 있다
import pandas as pd
wine = pd.read_csv('https://bit.ly/wine_csv_data')
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)
데이터를 받아와서 alcohol
, sugar
, pH
만 따로 빼서 인풋으로 설정하고
우리가 예측할 맞출 class
는 타겟으로 설정해서 train_test_split
을 통해 나눠준다
sub_input, val_input, sub_target, val_target = train_test_split(train_input, train_target, test_size =0.2, random_state = 42)
print(sub_input.shape, val_input.shape) # (4157, 3) (1040, 3)
나눈 훈련 데이터에서 다시 20%를 검증 데이터로 나눴다
원래 5197개였던 훈련 데이터가 4157개로 줄고, 검증 데이터는 1040개가 되었다
그러면 이제 sub_input
과 val_input
을 사용해서 모델을 만들고, 평가해보자
from sklearn.tree import DecisionTreeClassifier
dt = DecisionTreeClassifier(random_state=42)
dt.fit(sub_input, sub_target)
print(dt.score(sub_input, sub_target)) # 0.9971133028626413
print(dt.score(val_input,val_target)) # 0.864423076923077
결과를 보면 이 모델은 과대적합이 되어있다는 것을 확인할 수 있다
매개변수를 바꿔서 더 좋은 모델을 한 번 찾아보자
검증 데이터를 만드느라 훈련 데이터가 줄었다
보통 많은 데이터를 훈련에 사용할수록 좋은 모델이 만들어진다
그렇다고 검증 데이터를 적게하면 검증 점수가 들쭉날쭉되고 불안정될 것이다
이럴 때 교차검증이 있다
교차검증은 검증 데이터를 떼어 내어 평가하는 과정을 여러 번 반복하고
이 점수를 평균하여 최종 검증 점수를 얻는 방법을 이야기한다
3-폴드 교차 검증
훈련 데이터를 세 부분으로 나눠서
교차 검증을 수행하는 것을 3-폴드 교차 검증이라고 한다통칭 k-폴드 교차 검증(k-fold cross validation)이라고 하며
훈련 데이터를 몇 부분으로 나누냐에 따라 다르게 부른다
보통 k-겹 교차 검증이라고 부른다
보통은 3-폴드 교차 검증보다는 5나 10 교차 검증을 많이 사용한다
이렇게 하면 보통 데이터의 80~90%를 훈련에 사용할 수 있다
검증 데이터가 줄어들지만 계산한 검증 점수를 평균하기에 안정적인 점수로 생각할 수 있다
사이킷런에는 cross_validate()
라는 교차 검증 함수가 있다
먼저 평가할 모델 객체를 첫 번째 매개변수로 전달하고 직접 떼어내지 말고
훈련 데이터 전체를 cross_validate()
함수에 전달한다
from sklearn.model_selection import cross_validate
scores = cross_validate(dt, train_input, train_target)
print(scores)
# {'fit_time': array([0.0086596 , 0.00798011, 0.00838995, 0.00853205, 0.00781655]), 'score_time': array([0.00127149, 0.00116587, 0.00118089, 0.00114131, 0.00110841]), 'test_score': array([0.86923077, 0.84615385, 0.87680462, 0.84889317, 0.83541867])}
해당 함수는 fit_time
, score_time
, test_score
키를 가진 딕셔너리를 반환한다
처음 2개의 키는 각각 모델을 훈련하는 시간과 검증하는 시간을 의미한다
각 키마다 5개의 숫자가 담겨져 있다
보통 일반적으로 cross_validate()
함수는 5-폴드 교차 검증을 수행한다
cv 매개변수를 통해서 폴드 수를 변경할 수 있다
교차 검증에 최종점수는 test_score 키에 담긴 5개의 점수를 평균하여 구할 수 있다
이름은 test_score이지만 검증 폴드의 점수이다
import numpy as np
print(np.mean(scores['test_score'])) 0.855300214703487
교차검증을 수행하면 입력한 모델에서 얻을 수 있는 최상의 검증 점수를 가늠해볼 수 있다
한 가지 우리가 알아둬야 하는 점은 cross_validate()
는
훈련 데이터를 섞어서 폴드를 나누지는 않는다
하지만 우리는 앞서 데이터를 나누면 train_test_split()
을
사용했기에 따로 섞을 필요는 없다
하지만 만약 교차 검증을 할 때 훈련 데이터를 섞으려면 분할기를 지정해야 한다
사이킷런의 분할기는 교차 검증에서 폴드를 어떻게 나눌지 결정해주는데
cross_validate()
함수는 기본적으로 회귀 모델일 경우 KFold 분할기를 사용하고
분류 모델일 경우 타깃 클래스를 골고루 나누기 위해 StratifiedKFold를 사용한다
앞서 수행한 코드는 다음과 같다
from sklearn.model_selection import StratifiedKFold
scores = cross_validate(dt, train_input, train_target, cv=StratifiedKFold())
print(np.mean(scores['test_score'])) # 0.855300214703487
만약 훈련 데이터를 섞은 후 10-폴드 교차 검증을 수행한다면 다음과 같다
splitter = StratifiedKFold(n_splits=10, shuffle=True, random_state =42)
scores = cross_validate(dt, train_input, train_target, cv=splitter)
print(np.mean(scores['test_score']))
KFold 클래스도 동일한 방식으로 바꿔서 사용할 수 있다
그러면 이제는 결정 트리의 매개변수 값을 바꿔가며 가장 좋은 성능을 보이는 모델을 찾아보자
머신러닝 모델이 학습하는 파라미터를 모델 파라미터라고 부르고,
반면에 모델이 학습할 수 없어 사용자가 지정해야만 하는 파라미터를 하이퍼파라미터라고 한다
사이킷런과 같은 머신러닝 라이브러리를 사용할 때
이런 하이퍼파라미터는 모두 클래스나 메서드의 매개변수로 표현이 된다
그렇다면 이런 하이퍼파라미터를 튜닝하는 작업은 어떻게 진행이 될까?
먼저 라이브러리가 제공하는 기본값을 그대로 사용하여 모델을 훈련한다
그 다음 검증 데이터의 점수나 교차 검증을 통해서 매개변수를 조금씩 바꿔본다
모델마다 적게는 1~2개에서 많게는 5~6개의 매개변수를 제공한다
이 매개변수를 바꿔가면서 모델을 훈련하고 교차 검증을 수행해야 한다
결정 트리 모델에서 최적의 max_depth
값을 찾았다고 가정해보자
그러면 보통 max_depth
는 고정하고 min_sample_split
을 바꿔가며 최적의 값을 찾는다
그런데 이렇게 한 매개변수의 최적값을 찾고 다른 매개변수의 최적값을 찾아도 될까?
왜냐하면 max_depth
의 최적값은 min_sample_split
매개변수의 값이 바뀌면 함께 달라진다
그래서 이 두 매개변수를 동시에 바꿔가면 최적의 값을 찾아야 한다
그렇다면 매개변수가 많아지면 문제가 더 복잡해진다
파이썬의 for문을 통해서도 구현할 수 있겠지만 사이킷런에서는 그리드 서치를 사용한다
사이킷런의 GridSearchCV 클래스는 친절하게도 하이퍼파라미터 탐색과
교차 검증을 한 번에 수행을 해준다 사용하는 방법은 기본 매개변수를
사용한 결정 트리 모델에서 min_impurity_decrease
매개변수의 최적값을 찾는다
먼저 GridSearchCV 클래스를 임포트하고 탐색할 매개변수와
탐색할 값의 리스트를 딕셔너리로 만든다
from sklearn.model_selection import GridSearchCV
params = {'min_impurity_decrease' : [0.0001, 0.0002,0.0003,0.0004,0.0005]}
0.0001부터 0.0005까지 0.0001씩 증가하는 5개의 값을 시도했다
GridSearchCV 클래스에 탐색 대상 모델과 params 변수를 전달하여 그리드 서치 객체를 만든다
gs = GridSearchCV(DecisionTreeClassifier(random_state=42),params, n_jobs=-1)
gs.fit(train_input, train_target)
dt = gs.best_estimator_
print(dt.score(train_input, train_target)) # 0.9615162593804117
GridSearchCV의 cv 매개변수 기본값은 5이다
따라서 min_impurity_decrease
값마다 5-폴드 교차 검증을 수행한다
결국 5 x 5 = 25개의 모델을 훈련한다
많은 모델을 훈련하기 때문에 GridSearchCV 클래스의 n_jobs 매개변수에서
병렬 실행에 사용할 CPU 코어 수를 지정하는 것이 좋다
(해당 매개변수의 기본값은 1로 -1로 지정하면 시스템에 있는 모든 코어를 사용한다)
교차 검증에서 최적의 하이퍼파라미터를 찾으면 전체 훈련 데이터로 모델을 다시 만들어야 했다
하지만 아주 편리하게도 사이킷런의 그리드 서치는 훈련이 끝나면 25개의 모델 중
검증 점수가 가장 높은 모델의 매개변수 조합으로 전체 훈련 데이터에서 자동으로 모델을 훈련한다
이 모델은 gs 객체의 best_estimator_
속성에 저장되어 있다
또한 그리드 서치로 찾은 최적의 매개변수는 best_params
속성에 저장되어 있다
print(gs.best_params_) # {'min_impurity_decrease': 0.0001}
각 매개변수에서 수행한 교차 검증의 평균 점수는
cv_results_
속성의 mean_test_score
키에 저장되어 있다
print(gs.cv_results_['mean_test_score'])
# [0.86819297 0.86453617 0.86492226 0.86780891 0.86761605]
값을 보면 첫 번째 값이 가장 큰 것을 확인할 수 있다
이렇게 수동으로 고르는 것보다
넘파이 argmax()
함수를 사용하면 가장 큰 값의 인덱스를 추출할 수 있다
그 다음 이 인덱스를 사용해 params 키에 저장된 매개변수를 출력할 수 있다
best_index = np.argmax(gs.cv_results_['mean_test_score'])
print(gs.cv_results_['params'][best_index])
# {'min_impurity_decrease': 0.0001}
정리를 해보면
- 먼저 탐색할 매개변수를 지정한다
- 그 다음 훈련 데이터에서 그리드 서치를 수행하여 최상의 평균 검증 점수가
나오는 매개변수 조합을 찾는다 이 조합은 그리드 서치 객체에 저장된다- 그리드 서치는 최상의 매개변수에서 전체 훈련 데이터를 사용해 최종 모델을 훈련한다
이 모델도 그리드 서치 객체에 저장된다
그러면 조금 더 복잡한 매개변수 조합을 탐색해보자
결정 트리에서min_impurity_decrease
는 노드를 분할하기 위한 불순도 감소 최소량을 지정한다
여기에다가 max_depth
로 트리의 깊이를 제한하고
min_samples_split
으로 노드를 나누기 위한 최소 샘플 수도 골라봤다
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)
}
넘파이 arrange()
함수는 첫 번째 매개변수 값에서 시작하여
두 번째 매개변수에 도달 할 때까지 세 번째 매개변수를 계속 더한 배열을 만든다
-> 두 번째 매개변수는 포함되지 않으니 총 9개가 된다
파이썬 range()
함수도 비슷하다 이 함수는 정수만 사용할 수 있기에 때문에
max_depth
를 5에서 20까지 1씩 증가시키면서 15개의 값을 만들고 있다
min_sample_split
은 2에서 100까지 10씩 증가시켜 총 10개의 값을 만든다
따라서 이 매개변수로 수행할 교차 검증 횟수는
9 x 15 x 10 = 1350 이다
그리고 기본 5-폴드 교차 검증을 수행하므로 모델의 수는 6750개가 될 것이다
그러면 이제 그리드 서치를 시행해보자
gs = GridSearchCV(DecisionTreeClassifier(random_state=42), params, n_jobs=-1)
gs.fit(train_input, train_target)
print(gs.best_params_)
# 최상의 매개변수 조합
# {'max_depth': 14, 'min_impurity_decrease': np.float64(0.0004), 'min_samples_split': 12}
# 최상의 교차 검증 점수
print(np.max(gs.cv_results_['mean_test_score'])) # 0.8683865773302731
GridSearchCV를 사용하면 이렇게 매개변수를 일일이 바꿔가며 교차 검증을 수행하지 않고
매개변수 값을 나열하면 자동으로 교차 검증을 수행하여 최상의 매개변수를 찾을 수 있다
그런데 아쉬운 점이 있다 앞에서 탐색할 매개변수의 간격을 우리가 정해줬는데
이렇게 간격을 둔 것에 특별한 근거가 없다
그래서 이보다 더 좁거나 넓은 가격으로 시도해보자
매개변수의 값이 수치일 때 값의 범위나 간격을 미리 정하기가 어려울 때가 있다
또 너무 많은 매개변수 조건이 있어 그리드 서치 수행 시간이 오래걸릴 수도 있다
이럴 때 랜덤 서치 (Random Search)를 사용하면 좋다
랜덤 서치에는 매개변수 값의 목록을 전달하는 것이 아니라
매개변수를 샘플링할 수 있는 확률 분포 객체를 전달한다
먼저 싸이파이에서 2개의 확률 분포 클래스를 임포트 해보자
싸이파이는 파이썬의 핵심 과학 라이브러리 중 하나이다
적분, 보간, 선형 대수, 확률 등을 포함한 수치 계산 전용 라이브러리이다
from scipy.stats import uniform, randint
싸이파이의 stats 서브 패키지에 있는 uniform과 randint 클래스는
모두 주어진 범위에서 고르게 값을 뽑는다
이를 '균등 분포에서 샘플링한다'라고 말한다
randint는 정숫값을 뽑고, uniform은 실수값을 뽑는다
예시로 한 번 이제 0에서 10사이의 범위를 갖는
randint 객체를 만들고 10개의 숫자를 샘플링해보자
rgen = randint(0,10)
rgen.rvs(10) # array([0, 0, 7, 4, 7, 3, 6, 6, 5, 0])
10개 밖에 되지 않기 때문에 고르게 샘플링 되는 것 같지 않지만
샘플링 숫자를 늘리면 쉽게 확인 할 수 있다
np.unique(rgen.rvs(100),return_counts=True)
# (array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9]),
# array([105, 98, 103, 95, 92, 109, 91, 92, 111, 104]))
uniform 클래스의 사용법도 동일하다
ugen = uniform(0,1)
ugen.rvs(10)
# array([0.50693613, 0.9677498 , 0.7778074 , 0.57100099, 0.9749155 ,
# 0.30245407, 0.54323033, 0.47886582, 0.53833216, 0.60641081])
일종의 난수 발생기랑 유사하다고 생각하면 될 것 같다
우리는 이를 이용해서 랜덤 서치에 randint과 uniform 클래스 객체를 넘겨주고
총 몇 번을 샘플링해서 최적의 매개변수를 찾으라고 명령할 수 있다
샘플링 횟수는 시스템 자원이 허락하는 한에서 최대한 크게 하는 것이 좋다
그러면 탐색할 매개변수의 딕셔너리를 만들어보자
여기에서는 min_sample_leaf
매개변수를 탐색대상에 추가했다
이 매개변수는 리프 노드가 되기 위한 최소 샘플의 개수이다
어떤 노드가 분할하여 만들어질 자식 노드의 샘플 수가
이 값보다 작을 경우 분할하지 않는다
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)}
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)
샘플링 횟수는 n_iter
매개변수에 저장을 한다
그러면 위 params에 정의된 매개변수 범위에서 총 100번을 샘플링하여
교차검증을 수행하고 최적의 매개변수 조합을 찾아낸다
앞서 그리드 서치보다 훨씬 교차 검증 수를 줄이면서
넓은 영역을 효과적으로 탐색할 수 있다
# 최적의 매개변수 조합
print(gs.best_params_)
{'max_depth': 39, 'min_impurity_decrease': np.float64(0.00034102546602601173), 'min_samples_leaf': 7, 'min_samples_split': 13}
# 최고의 교차 검증 점수
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등분 혹은 10등분 한다
나누어진 한 덩어리를 폴드라고 부르며 한 폴드씩 돌아가면서 검증 데이터의 역할을 한다
최종 검증 점수는 모든 폴드의 검증 점수를 평균하여 계산한다
교차 검증을 사용해 다양한 하이퍼파미터를 탐색할 수 있다
이 때 테스트하고 싶은 매개변수 리스트를 만들어 이 과정을 자동화하는
그리드 서치를 사용할 수 있고 사용하면 편하다
매개변수 값이 수치형이고 특히 연속적인 실수값이라면
싸이파이의 확률 분포 객체를 전달하여 특정 범위 내에서 지정된 횟수만큼
매개변수 후보 값을 샘플링하여 교차 검증을 시도할 수 있다
일반적으로 지금까지 우리가 다뤘던 데이터는 CSV 파일 형식의 데이터인데
이런 형태의 데이터를 정형 데이터(structured data)라고 부른다
이런 데이터는 CSV나 데이터베이스, 혹은 엑셀에 저장하기 쉽다
하지만 이와 반대되는 데이터도 있다
바로 비정형 데이터(unstructured data)로
데이터베이스나 엑셀로 표현하기 어려운 것들을 이야기한다
보통은 텍스트나 사진, 음악등이 있다
이런 비정형 데이터는 규칙성을 찾기가 어려워서 전통적인 머신러닝 방법으로 힘들다
그래서 우리는 추후에 신경망 알고리즘을 배울 것이다
그래서 다시 돌아와서 정형 데이터에 대해서 이야기를 해보면
지금까지 우리가 배운 머신러닝 알고리즘은 정형 데이터에 잘 맞는다
그중에 정형 데이터를 다루는 데 가장 뛰어난 성과를 내는 알고리즘이
앙상블 학습(ensemble learning)이다
그래서 이번 시간에는 사이킷런에서 제공하는 정형 데이터의 끝판왕인
앙상블 학습 알고리즘에 대해서 알아보자!!
랜덤 포레스트는 앙상블 학습의 대표 주자 중 하나로
안정적인 성능 덕분에 가장 많이 사용되고 있다
이름에서 알 수 있듯이 랜덤 포레스트는
결정 트리를 랜덤하게 만들어 결정 트리(나무)의 숲을 만든다
그리고 각 결정 트리의 예측을 사용해서 최종 예측을 하게 된다
먼저 랜점 포레스트는 각 트리를 훈련하기 위한 데이터를 랜덤하게 만드는데
이 데이터를 만드는 방법이 독특하다
우리가 입력한 훈련 데이터에서 랜덤하게 샘플을 추출하여 훈련 데이터를 만든다
이때 한 샘플이 중복되어 추출될 수도 있다
예시를 들면 1000개의 샘플이 들어있는 가방에서 100개의 샘플을 뽑는다면
먼저 1개를 뽑고, 뽑았던 1개를 다시 가방에 넣는다 그리고 또 뽑는다
이렇게 만들어지는 샘플을 우리는 부트스트랩 샘플이라고 부른다
일반적으로 부트스트랩 샘플은 훈련 데이터의 크기와 같게 만든다
1000개의 샘플이 들어있는 가방에서 중복해서 1000개의 샘플을 뽑는다
또한 각 노드를 분할할 때 전체 특성 중에서 일부 특성을
무작위로 고른 다음 이 중에서 최선의 분할을 찾는다
분류 모델인 RandomForestClassifier는 기본적으로
전체 특성 개수의 제곱근만큼의 특성을 선택한다
(즉 4개의 특성이 있다면 2개를 랜덤하게 선택해서 사용)
사이킷런의 랜덤 포레스트는 기본적으로 100개의 결정 트리를 이런 방식으로 훈련한다
각 트리의 클래스 별 확률을 평균하여 가장 높은 확률을 가진 클래스를 예측으로 삼는다
회귀일 때는 단순히 각 트리의 예측을 평균한다
랜덤 포레스트는 랜덤하게 선택한 샘플과 특성을 사용하기 때문에
훈련 데이터에 과대적합되는 것을 막아주고
검증 데이터와 테스트 데이터에서 안정적인 성능을 보여준다
그러면 이제 본격적으로 코드로 실험을 해보자
import numpy as np
import pandas as pd
from sklearn.model_selection import 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)
기존에 해왔던 것처럼 데이터를 불러와서 넘파이로 바꿔주고
데이터를 훈련 데이터와 테스트 데이터로 나누었다
from sklearn.model_selection import cross_validate
from sklearn.ensemble import RandomForestClassifier
rf = RandomForestClassifier(n_jobs=-1, random_state=42)
scores = cross_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
그 다음에는 cross_validate()
함수를 이용해서 교차 검증을 수행한다
RandomForestClassifier는 기본적으로 100개의 결정트리를 사용하기에
n_jobs 매개변수를 -1로 지정하여 모든 CPU 코어를 사용하도록 한다
또, retrun_train_score 매개변수를 True로 지정하면 검증 점수뿐만 아니라
훈련 데이터에 대한 점수도 같이 반환된다
훈련 데이터와 검증 데이터의 점수를 비교하면 과대적합을 파악하는데 용이하다
결과를 보면 훈련 데이터가 과적합이 되어있는 것처럼 보인다
랜덤 포레스트는 결정 트리의 앙상블이기 때문에 DecisionTreeClassifier가
제공하는 중요한 매개변수를 모두 제공한다
(criterion, max_depth, max_feature, min_sample_split,
min_inpurity_decrease, min_samples_leaf 등)
또한 결정트리의 큰 장점 중 하나인 특성 중요도를 계산해준다
랜덤 포레스트의 특성 중요도는 각 결정 트리의 특성 중요도를 취합한 것이다
rf.fit(train_input,train_target)
print(rf.feature_importances_) # [0.23167441 0.50039841 0.26792718]
각각 알코올 도수, 당도, pH 인데 전에 우리가 전에 했던 결정트리의 결과와
비교를 하면 조금 당도가 하락하고 양쪽의 도수와 pH가 올랐다
그 이유는 랜덤 포레스트가 특성의 일부를 랜덤하게
선택하여 결정트리를 훈련했기 때문이다
그래서 하나의 특성에 과도하게 집중하지 않고 좀 더 많은 특성이
훈련에 기여할 기회를 얻었고 이는 과대적합을 줄이고 일반화의 성능을 높였다
또 RandomForestClassifier에는 재미있는 기능이 있는데
바로 자체적으로 모델을 평가한 점수를 얻을 수 있다
랜덤 포레스트는 훈련 데이터에서 중복을 허용해서
부트스트랩 샘플을 만들어 결정 트리를 훈련한다
이 때 부트스트랩에 포함되지 않고 남는 샘플이 있다
이런 샘플을 OOB(out of bag)샘플이라고 한다
이 남는 샘플을 사용하여 부트스트랩 샘플로 훈련한 결정트리를 평가하는 것이다
일종의 검증 세트 같은 느낌으로 보면 될 것 같다
이 점수를 얻으려면 RandomForestClassifier 클래스의
oob_score 매개변수를 True로 지정해야 한다 (이 매개변수의 기본값은 False다)
그러면 랜덤 포레스트는 각 결정트리의 OOB 점수를 평균하여 출력하게 된다
rf = RandomForestClassifier(oob_score=True, n_jobs=-1, random_state=42)
rf.fit(train_input, train_target)
print(rf.oob_score_) # 0.8934000384837406
교차검증에서 얻은 점수와 매우 비슷한 결과를 얻었다
정리하면 OOB 점수를 이용하면 교차 검증을 대신할 수 있어서
결과적으로 훈련 데이터에 더 많은 샘플을 사용할 수 있다
엑스트라 트리(Extra Tree)는 기본적으로 100개의 결정 트리를 훈련하며
결정 트리가 제공하는 대부분의 매개변수를 지원하고 있다
또한 전체 특성 중 일부 특성을 랜덤하게 선택하여 노드를 분할한다
랜덤 포레스트와 엑스트라 트리의 가장 큰 차이점은 부트스트랩 샘플의 사용여부다
엑스트라 트리는 결정 트리를 만들 때 전체 훈련 데이터를 사용하고
노드를 분할할 때 가장 좋은 분할을 찾는 것이 아니라 무작위로 분할한다
하나의 결정 트리에서 특성을 무작위로 분할한다면 성능은 낮아지겠지만
많은 트리를 앙상블 하기 때문에 과대적합을 막고 검증 데이터의 점수를 높인다
정리를 하면
랜덤 포레스트는 훈련 데이터에서 일부를 복원 추출(중복 포함)해서 트리를 만들고
노드를 나눌 때도 여러 특성 중에서 정확도가 제일 좋아지는 방향으로 나눈다엑스트라 트리는 모든 훈련 데이터를 그대로 사용하고
대신 노드를 나눌 때도 무작위로 한다
이제 한 번 코드를 통해서 만나보자
사이킷런에서 제공하는 엑스트라 트리는 ExtraTreeClassifier이다
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_score']),np.mean(scores['test_score']))
# 0.9974503966084433 0.8887848893166506
랜덤 포레스트와 비슷한 결과를 얻었다
보통 엑스트라 트리가 무작위성이 더 크기 때문에
랜덤 포레스트보다 더 많은 결정 트리를 훈련해야 한다
하지만 랜덤하게 노드를 분할하기 때문에 빠르게 계산을 하게 된다
이것 또한 엑스트라 트리의 장점이라고 볼 수 있다
엑스트라 트리도 랜덤 포레스트와 마찬가지로 특성 중요도를 제공한다
et.fit(train_input, train_target)
print(et.feature_importances_) # [0.20183568 0.52242907 0.27573525]
결과를 보면 엑스트라 트리도 당도에 대한 의존성이 적은 것을 확인할 수 있다
엑스트라 트리의 회귀 버전은 ExtraTreeRegressor 클래스이다
그레이디언트 부스팅(gradient boosting)은 깊이가 얕은 결정 트리를 사용하여
이전 트리의 오차를 보완하는 방식으로 앙상블 하는 방법이다
사이킷런의 GradientBoostingClassifier는 기본적으로
깊이가 3인 결정트리 100개를 사용한다
깊이가 얕은 결정트리를 사용하기에 과대적합에 강하고
일반적으로 높은 일반화 성능을 보여준다
그레이디언트란 이름에서 봤듯이 경사 하강법을 사용하여 트리를 앙상블에 추가한다
분류에서는 로지스틱 손실 함수를 사용하고 회귀에서는 평균 제곱 오차 함수를 사용한다
그레이디언트 부스팅은 결정 트리를 조금씩 추가하면서 가장 낮은 곳을 찾아 이동한다
경사하강법이 손실 함수의 낮은 곳으로 천천히 움직였던 것처럼
그레이디언트 부스팅도 깊이가 얕은 트리를 사용하고, 매개변수로 속도를 조절한다
그러면 이제 바로 코드를 통해서 알아보자
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_score']))
# 0.8881086892152563 0.8720430147331015
이번에 결과값을 보면 과대적합이 되지 않았다는 것을 잘 알 수 있다
그레이디언트 부스팅은 결정 트리의 개수를 늘려도 과대적합에 매우 강하다
학습률을 증가시키고 트리의 개수를 높이면 성능이 더 향상될 수도 있다
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_score']))
# 0.9464595437171814 0.8780082549788999
결정 트리 개수를 500개로 5배나 늘렸지만 과대적합을 잘 억제하고 있다
학습률 learning_rate의 기본값은 0.1 이지만 변경해줬다
gb.fit(train_input, train_target)
print(gb.feature_importances_)
# [0.15887763 0.6799705 0.16115187]
여기에서 재미있는 매개변수가 하나 있다
바로 트리 훈련에 사용할 훈련 데이터의 비율을 정하는 subsample이다
이 매개변수의 기본값은 1.0으로 전체 훈련 데이터를 사용한다
하지만 subsample이 1보다 작으면 훈련 데이터의 일부를 사용한다
이는 경사 하강법 단계마다 일부 샘플을 랜덤하게 선택하여 진행하는
확률적 경사 하강법이나 미니배치 경사 하강법과 비슷하다
일반적으로 그레이디언트 부스팅이 랜덤 포레스트보다 조금 더 높은 성능을 보일 수 있다
하지만 순서대로 트리를 추가하기 때문에 훈련 속도가 느리다
즉 GradientBoostingClassifier에는 n_jobs 매개변수가 없다
그레이디언트 부스팅의 회귀 버전은 GradientBoostingRegressor이다
히스토그램 기반 그레이디언트 부스팅 (Histogram-based Gradient Boosting)은
정형 데이터를 다루는 머신러닝 알고리즘 중에 가장 인기가 높은 알고리즘이다
히스토그램 기반 그레이디언트 부스팅은 입력 특성을 256개의 구간으로 나눈다
따라서 노드를 분할할 때 최적의 분할을 매우 빠르게 찾을 수 있다
히스토그램 기반 그레이디언트 부스팅은 256개의 구간 중에서
하나를 떼어 놓고 누락된 값을 위해서 사용한다
따라서 입력에 누락된 특성이 있더라도 따로 전처리할 필요가 없다
사이킷런의 히스트로그램 기반 그레이디언트 부스팅 클래스는
HisGradientBoostingClassifier로 트리의 개수를 지정하는데
n_estimators 대신에 부스팅 반복횟수를 지정하는 max_iter를 사용한다
성능을 높이려면 max_iter 매개변수를 바꿔서 테스트하면 된다
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
과대 적합을 억제하면서도 그레이디언트 부스팅보다 높은 성능을 보여준다
이번에는 특성 중요도를 확인해보자
히스토그램 기반 그레이디언트 부스팅의 특성 중요도를 계산하기 위해서는
permutation_importance()
함수를 사용한다
이 함수는 특성을 하나씩 랜덤하게 섞어서
모델의 성능이 변화하는지를 관찰하여 어떤 특성이 중요한지를 계산한다
훈련 데이터뿐만 테스트 데이터에도 적용할 수 있고
사이킷런에서 제공하는 추정기 모델에 모두 사용할 수 있다
from sklearn.inspection import permutation_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 매개변수는 랜덤하게 섞을 횟수를 지정한다
기본값은 5인데 우리는 여기에서 10으로 지정을 했다
permutation_importance()
함수가 반환하는 객체는 반복하여 얻은
특성 중요도(importances), 평균(importances_mean),
표준 편차(importance_std)를 담고 있다
평균을 출력해보면 랜덤 포레스트와 비슷한 비율임을 알 수 있다
이번에는 테스트 데이터에서 특성 중요도를 파악해보겠다
result = permutation_importance(hgb,test_input, test_target, n_repeats=10, random_state=42, n_jobs=-1)
print(result.importances_mean) # [0.05969231 0.20238462 0.049 ]
테스트 데이터의 결과를 확인해보면 그레이디언트 부스팅과 비슷하게
당도에 집중하고 있다는 것을 확인해볼 수 있다
그러면 최종적으로 성능을 확인해보자
hgb.score(test_input,test_target) # 0.8723076923076923
테스트 데이터에서는 약 87%의 정확도를 얻었다
이렇게 볼 수 있듯이
앙상블 모델은 확실하게 단일 결정 트리보다 좋은 결과를 얻을 수 있다
히스토그램 기반 그레이디언트 부스팅의 회귀버전은
HistGradientBoostingRegressor 클래스에 구현되어 있다
하지만 사이킷런 말고도
그레이디언트 부스팅 알고리즘을 구현한 라이브러리가 여럿있다
가장 대표적인 라이브러리는 XGBoost이다
해당 라이브러리는 다양한 부스팅 알고리즘을 지원한다
tree_method 매개변수를 "hist"로 지정하면 히스토그램 기반
그레이디언트 부스팅을 사용할 수 있다
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.9567059184812372 0.8783915747390243
또 널리 사용하는 또 다른 히스토그램 기반
그레이디언트 부스팅 라이브러리는 LightGBM이다
from lightgbm import LGBMClassifier
lgb = LGBMClassifier(random_state=42)
scores = cross_validate(lgb, train_input, train_target, return_train_score=True, n_jobs = -1)
print(np.mean(scores['train_score']),np.mean(scores['test_score']))
# 0.935828414851749 0.8801251203079884
우리는 이번에 앙상블 학습에 대해서 배웠다
앙상블 학습은 정형 데이터에서 가장 뛰어난 성능을 내는
머신러닝 알고리즘 중 하나로 대표적인 앙상블 학습은 다음과 같다
랜덤 포레스트는 가장 대표적인 앙상블 학습 알고리즘으로
성능이 좋고 안정적이기 때문에 처음 시도할 때 많이 사용한다
랜덤 포레스트는 결정 트리를 훈련하기 위해 부트스트랩 샘플을 만들고
전체 특성 중 일부를 랜덤하게 선택하여 결정 트리를 만든다
엑스트라 트리는 랜덤 포레스트랑 비슷하지만 부트스트랩 샘플을 사용하지 않고
노드를 분할 할 때 최선이 아니라 랜덤하게 분할한다
이런 특징 때문에 랜덤 포레스트보다 훈련 속도가 빠르지만
보통 더 많은 트리가 필요하다
그레이디언트 부스팅은 깊이가 얕은 트리를 연속적으로 추가하여
손실함수를 최소화하는 앙상블 방법이다 성능이 뛰어나지만
병렬로 훈련할 수 없기에 랜덤 포레스트나 엑스트라 트리보다 훈련 속도가 느리다
그레이디언트 부스팅에서 학습률 매개변수를 조정하여 모델의 복잡도를 제어할 수 있다
학습률 매개변수가 크면 복잡하고 훈련 데이터에 과대적합된 모델을 얻을 수 있다
마지막으로 가장 뛰어난 앙상블 학습이라고 불리는
히스토그램 기반 그레이디언트 부스팅은 훈련 데이터를 256개의 구간으로 변환하여
사용하기에 노드 분할 속도가 매우 빠르고 높은 성능을 보여준다
사이컷뿐만 아니라 XGBoost, lightGMB 등의 라이브러리도 존재한다
교차 검증을 그림으로 설명이라 한 번 피카소를 보여줘야겠네요 ㅋㅋ
이 과제는 위에서 했기에 PASS
그러면 다들 다음번에 만나요 ^^