4주차 ML 지도학습 WIL
ml저는 어차피 머신러닝을 공부할꺼 제대로 공부해보자 다짐하고 WIL이지만 블로그 글처럼 써보려고 합니다….(스압 주의^^)
2.3.5 결정트리
- 개념
◽ 결정 트리(decision tree) : 특정 기준(질문)에 따라 데이터를 구분하는 모델을 결정 트리 모델
◽ 노드 : 질문이나 정답을 담은 네모 상자
▪ 리프(leaf) : 마지막 노드
▪ 루프 노드(root node) : 맨 위의 노드, 전체 데이터 셋과 처음 분류 기준이 포함
▪ 순수 노드(pure node) : 한 개의 타깃 값(하나의 클래스나 하나의 회귀 분석 결과)로 이루어진 리프 노드
◽ 에지(edge) : 질문의 답과 다음 질문을 연결
◽ 테스트 : 결정 트리에 있는 질문들 (테스트 세트와 혼동하지 않기)
결정트리 만들기
STEP1. 모든 테스트에서 타깃 값에 대해 가장 많은 정보를 가진 특성을 이용해 테스트(x[1]<=0.06)한다.
STEP2. 각 노드에 대해 데이터를 잘 구분할 수 있는 테스트를 추가한다. 이 때 결정 트리의 리프에 한 개의 타깃 값(여기서는 클래스)을 가질 때 즉, 순수 노드가 될 때까지 반복한다.
STEP3. 최종 분할 트리의 모습!
STEP4. 새로운 데이터 포인트에 대해 예측
◽ 분류
: 주어진 데이터 포인트가 특성을 분할한 영역 중 어디에 놓이는가?
: 영역의 타깃 값 중 다수(순수 노드라면 하나)인 것을 예측 결과로 한다
◽ 회귀
: 각 노드의 테스트 결과에 따라 트리를 탐색하고 찾은 리프 노드의 훈련 데이터 평균값을 예측 결과로 한다
결정트리의 복잡도 제어하기
1) 문제점
- 모든 리프 노드가 순수 노드가 될 때까지 진행하면 모델이 복잡해지고 훈련 데이터에 과대적합 되어 새로운 데이터에 일반화 되지 않는다.
- 결정경계가 클래스의 포인트들에서 멀리 떨어진 이상치에 민감.(클래스 0으로 결정된 영역이 클래스 1에 속한 포인트들로 둘러쌓인 이상치까지 판별하고 있음)
2) 해결방법
-
- 사전 가지치기
- 트리 생성을 일찍 중단하는 전략
- 트리의 최대 깊이나 리프의 최대 개수를 제한
- 노드가 분할하기 위한 포인트의 최소 개수를 지정
-
- 사후 가지치기(가치지기)
- 트리를 만든 후 데이터 포인트가 적은 노드 삭제 혹은 병합
참고
scikit-learn에서 결정트리는 DecisionTreeRegressor와 DecisionTreeClassifier에 구현
sckit-learn은 사전 가지치기만 지원
3) 실습- 유방암 데이터셋을 이용하여 사전 가지치기의 효과를 확인해보자!
from sklearn.tree import DecisionTreeClassifier # 결정 트리 가져오기
cancer = load_breast_cancer()
# 훈련 세트와 테스트 세트로 나누기
X_train, X_test, y_train, y_test = train_test_split(
cancer.data, cancer.target, stratify=cancer.target, random_state=42)
#기본값 설정으로 완전한 트리 모델 생성
tree = DecisionTreeClassifier(random_state=0) #random_state=0으로 하여 트리를 같은 조건으로 비교
tree.fit(X_train, y_train) # 훈련시킨다!
print("훈련 세트 정확도: {:.3f}".format(tree.score(X_train, y_train)))
print("테스트 세트 정확도: {:.3f}".format(tree.score(X_test, y_test)))
훈련 세트 정확도: 1.000
테스트 세트 정확도: 0.937
✅ Result
모든 리프 노드가 순수 노드이기 때문에 훈련 세트의 정확도는 100%
테스트 세트의 정확도는 선형 모델에서의 정확도보다 낮다
# 결정 트리의 깊이 제한으로 과대 적합 줄이기
tree = DecisionTreeClassifier(max_depth=4, random_state=0) #max_depth로 제한 -> 연속된 질문 최대 4개로 제한
tree.fit(X_train, y_train)
print("훈련 세트 정확도: {:.3f}".format(tree.score(X_train, y_train)))
print("테스트 세트 정확도: {:.3f}".format(tree.score(X_test, y_test)))
훈련 세트 정확도: 0.988
테스트 세트 정확도: 0.951
✅ Result
훈련 세트의 정확도를 떨어뜨리지만 테스트 세트의 성능은 개선!
결정 트리 시각화
1) 시각화
- tree 모듈의 export_graphviz 함수를 이용함
- 그래프 저장용 텍스트 파일 포맷인 .dot 파일 생성
- export_graphviz 함수에 filled 매개변수를 True로 지정하면 노드의 클래스가 구분되도록 칠해준다
2) 실습
from sklearn.tree import export_graphviz
# 결정 트리, 생성되는 파일이름, 클래스이름, 특성 이름, filled는 노드에 색이 칠해짐
export_graphviz(tree, out_file="tree.dot", class_names=["악성", "양성"],
feature_names=cancer.feature_names, impurity=False, filled=True)
import graphviz # graphviz 모듈을 사용해 시각화!
with open("tree.dot") as f:
dot_graph = f.read()
display(graphviz.Source(dot_graph))
# plot_tree() 함수를 사용하면 .dot 파일을 만들지 않고 바로 트리를 그릴 수 있음!
from sklearn.tree import plot_tree
plot_tree(tree, class_names=["악성", "양성"], feature_names=cancer.feature_names,
impurity=False, filled=True, rounded=True, fontsize=4)
plt.show()
트리의 특성 중요도
1) 특성 중요도(feature importance)
- 트리를 만드는 결정에 각 결정에 각 특성이 얼마나 중요한지 평가
- 0과 1 사이의 양수(0: 전혀 사용되지 않았다 / 1: 완벽하게 타깃 클래스를 예측)
- 트리가 너무 커서 전체 트리를 살펴보는게 어려울 때 확인
print("특성 중요도:\n", tree.feature_importances_)
특성 중요도:
[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.01 0.048
0. 0. 0.002 0. 0. 0. 0. 0. 0.727 0.046 0. 0.
0.014 0. 0.018 0.122 0.012 0. ]
이를 시각화 해보면
def plot_feature_importances_cancer(model):
n_features = cancer.data.shape[1]
plt.barh(np.arange(n_features), model.feature_importances_, align='center')
plt.yticks(np.arange(n_features), cancer.feature_names)
plt.xlabel("특성 중요도")
plt.ylabel("특성")
plt.ylim(-1, n_features)
plot_feature_importances_cancer(tree)
✅ 분석
worst radius(첫 번째 노드에서 사용한 특성)가 가장 중요한 특성임
-> 첫 번째 노드에서 두 클래스를 잘 나누고 있다.
2) 한계
-
특성 중요도는 해당 특성이 어떤 클래스를 지지, 결정하는지 알 수 없다.(이건 트리를 봐야 함) EX) worst radius가 중요하다고 알려주지만 높은 반지름이 양성(클래스0)인지 악성(클래스1)인지 의미할 수 없다.
cf) 특성과 클래스 사이의 관계는 단순하지 않고 복잡할 수 있음 -
회귀 결정 트리, 즉 DecisionTreeRegressor는 외삽(훈련 데이터의 범위 밖의 새로운 데이터 포인트)에 대해 예측할 수 없음
✅ 분석
선형 모델은 테스트 데이터를 꽤 정확히 예측하지만 트리 모델은 훈련 데이터를 완벽하게 예측한다. 그러나 모델이 가진 데이터 범위 밖으로 나가면 마지막 포인트를 이용해 예측..
장단점과 매개변수
👍장점
첫째, 만들어진 모델을 쉽게 시각화 가능
둘째, 데이터 스케일에 구애받지 않아 결정 트리에서 특성의 정규화나 표준화 같은 전처리 과정이 필요 없음(이진 특성과 연속적인 특성이 혼합되어도 잘 작동)
👎단점
사전 가지치기를 사용해도 과대적합되는 경향이 있어 일반화 성능이 좋지 않다.
보충
엔트로피와 불순도에 대해서 추후.. 작성하도록 하겠음..
2.3.6 결정 트리의 앙상블
앙상블 = 머신러닝 모델을 연결하여 더 강력한 모델을 만드는 기법
다음은 분류와 회귀 문제의 다양한 데이터셋에서 효과적인 두 앙상블 모델
랜덤 포레스트
1) 랜덤 🌲포레스트🌲?
- 결정 트리가 훈련 데이터에 과대적합된다는 단점을 해결!
- 조금씩 다른 여러 결정 트리의 묶음
- 서로 다른 방향으로 과대적합된 트리를 많이 만들고 그 결과를 평균냄 -> 모델의 예측 성능이 유지되면서 과대적합이 줄어듬
- 핵심 포인트!! 각 트리가 고유하게 만들어지도록 무작위한 선택을 함!!
2) 랜덤 포레스트 구축
STEP1) 생성할 트리의 개수 정하기
- RandomForestRegressor나 RandomForestClassifier의 n_estimators 매개변수
STEP2) 부트스트랩 샘플 생성
- 트리를 만들 때 사용하는 데이터 포인트를 무작위로 선택
- n_samples 개의 데이터 포인트 중에서 무작위로 테이터를 n_samples 횟수만큼 반복 추출(한 샘플이 여러 번 중복 추출될 수 있음)
- 데이터 셋은 원래 데이터셋 크기와 같지만 모든 데이터 포인트가 들어있지 않다!(중복,, 누락,,)
STEP3) 분할 테스트에서 특성을 무작위로 선택
- max_features 매개변수로 고를 특성의 개수를 조정
❌주의❌◽ max_features = n_feautres로 설정하면 트리의 각 분기에서 모든 특성을 고려하므로 특성 선택에 무작위성이 들어가지 않는다 ◽ max_features = 1로 설정하면 트리의 분기는 테스트할 특성을 고를 필요가 없고 그냥 무작위로 선택한 특성을 활용 ◽ max_features 값을 크게 하면 랜덤 포레스트의 트리는 매우 비슷해지고 가장 두드러진 특성을 이용해 데이터에 맞춰질 것임! ◽ max_features를 작게 하면 랜덤 포레스트 트리들은 많이 달라지고 각 트리는 깊이가 깊어진다(해당 특성에 데이터를 맞추기 위해서)
- 각 노드에서 후보 특성을 무작위로 선택하고 이 후보들 중에서 최선의 테스트(타깃 값에 많은 데이터를 갖는 특성을 선택)를 찾는다.
- 후보 특성을 고르는 것은 매 노드마다 반복되므로 트리의 각 노드는 다른 후보 특성들을 사용하여 테스트를 만든다.
3) 회귀 VS 분류
알고리즘이 모델에 있는 모든 트리의 예측을 만들고…
◽ 분류 : 약한 투표 전략을 사용 = 각 트리들이 예측한 확률을 평균내어 가장 높은 확률을 가진 클래스를 선정
◽회귀 : 예측들을 평균하여 최종 예측을 만듬
4) 실습
트리 5개로 구성된 랜덤 포레스트 모델을 만들어 보자!!
from sklearn.ensemble import RandomForestClassifier
from sklearn.datasets import make_moons
X, y = make_moons(n_samples=100, noise=0.25, random_state=3)
X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, random_state=42)
forest = RandomForestClassifier(n_estimators=5, random_state=2)
#트리를 5개 생성
forest.fit(X_train, y_train)
# 랜덤 포레스트 안에 만들어진 트리는 extimators_ 속성에 저장
fig, axes = plt.subplots(2, 3, figsize=(20, 10))
for i, (ax, tree) in enumerate(zip(axes.ravel(), forest.estimators_)):
ax.set_title("트리 {}".format(i))
mglearn.plots.plot_tree_partition(X, y, tree, ax=ax)
mglearn.plots.plot_2d_separator(forest, X, fill=True, ax=axes[-1, -1], alpha=.4)
axes[-1, -1].set_title("랜덤 포레스트")
mglearn.discrete_scatter(X[:, 0], X[:, 1], y)
plt.show()
5) 매개변수
-
- random_state
- 지정하거나 지정하지 않으면 전혀 다른 모델이 만들어진다.
- 랜덤 포레스트 트리가 많을 수록 random_state 값의 변화에 따른 변동이 적다
- 같은 결과를 만들고 싶으면 random_state 값을 고정
-
- n_estimators
- 클수록 좋다! 더 많은 트리를 평균하면 과대적합을 줄여 안정적인 모델을 만듬!
- 단, 더 많은 메모리와 긴 훈련 시간으로 이어짐
-
- max_features
- 각 트리가 얼마나 무작위가 될지를 결정
- 일반적으로 기본값(auto)을 쓰는 것이 좋다
- 분류 max_features=sqrt(n_features) / 회귀 max_features=n_features
그레이디언트 부스팅 회귀 트리
1) 그레이디언트 부스팅 회귀 트리?
- 이전 트리의 예측과 타깃 값 사이의 오차를 줄이는 방향으로 순차적으로 새로운 트리를 추가하는 알고리즘
❗손실 함수를 정의 & 경사 하강법을 사용하여 다음에 추가될 트리가 예측해야 할 값을 보정❗ - 무작위성이 없으며 대신 강력한 사전 가지치기가 사용
- 보통 하나에서 다섯 정도의 깊지 않은 트리를 사용 -> 메모리를 적게 사용하고 예측도 빠름
- 얕은 트리 같은 간단한 모델(약한 학습기)을 많이 연결 -> 데이터 일부에 대해서 예측을 수행
-> 트리가 많이 추가될수록 성능이 좋아짐 - 회귀와 분류 모두 사용 가능
2) 실습
from sklearn.ensemble import GradientBoostingClassifier
gbrt = GradientBoostingClassifier(random_state=0, max_depth=1) # 트리의 최대 깊이를 줄여 사전 가지치기를 강하게 함
gbrt.fit(X_train, y_train)
print("훈련 세트 정확도: {:.3f}".format(gbrt.score(X_train, y_train)))
print("테스트 세트 정확도: {:.3f}".format(gbrt.score(X_test, y_test)))
훈련 세트 정확도: 0.991
테스트 세트 정확도: 0.972
gbrt = GradientBoostingClassifier(random_state=0, learning_rate=0.01) # 학습률을 낮춘다
gbrt.fit(X_train, y_train)
print("훈련 세트 정확도: {:.3f}".format(gbrt.score(X_train, y_train)))
print("테스트 세트 정확도: {:.3f}".format(gbrt.score(X_test, y_test)))
훈련 세트 정확도: 0.988
테스트 세트 정확도: 0.965
✅ 분석
모델의 복잡도를 감소시키므로 훈련 세트의 정확도가 낮아짐
이 예시에서는 학습률을 낮추는 것이 테스트 세트의 성능을 조금밖에 개선 못함 반면, 트리의 최대 깊이를 낮추는 것은 모델 성능 향상에 크게 기여!!
3) 매개변수
-
- learning_rate
- 이전 트리의 오차를 얼마나 강하게 보정할 것인지 보정 정도를 조절
- 학습률이 크면 트리는 보정을 강하게 하여 복잡한 모델을 만든다
- 학습률을 낮추면 비슷한 복잡도의 모델을 만들기 위해 더 많은 트리를 추가해야 한다
-
- n_estimators
- 트리의 개수를 지정
- 앙상블에 트리가 많이 추가되어 모델의 복잡도가 커지고 훈련세트의 오차를 바로잡을 기회가 많아짐
- estimators의 값이 클수록 모델이 복잡해지고 과대적합될 가능성이 있음!!!
Tip! 가용한 시간과 메모리 한도에서 n_estimators를 맞추고 적절한 learning_rate를 찾는다
-
- n_iter_no_change, validation_fraction
- 조기 종료를 위한 매개변수
- 훈련 데이터에서 validation_fraction(기본값 0.1) 비율만큼 검증 데이터로 사용 -> n_iter_no_change 반복 동안 검증 점수가 향상되지 않으면 훈련 종료
- n_iter_no_change 기본값이 None이면 조기 종료를 사용하지 않는다.
2.3.7 배깅, 엑스트라 트리, 에이다부스트
sckit-learn이 제공하는 다른 앙상블 알고리즘들!
배깅
1) 배깅(Bootstrap aggregating)
- 중복을 허용한 랜덤 샘플링으로 만든 훈련 세트를 사용하여 분류기에 각기 다르게 학습
- 분류기가 predict_proba() 메서드를 지원 하면 -> 확률값을 평균하여 예측 수행
지원하지 않으면 -> 가장 빈도가 높은 클래스 레이블이 예측 결과가 됨
2) 실습
from sklearn.linear_model import LogisticRegression #로지스틱 회귀 모델을 100개 훈련해보겠음!
from sklearn.ensemble import BaggingClassifier
bagging = BaggingClassifier(LogisticRegression(), n_estimators=100,
oob_score=True, n_jobs=-1, random_state=42)
# 로지스틱 회귀 객체를 기반 분류기(LogisticRegression())로 전달, 훈련할 분류기 개수는 100개(n_estimators=100)
# oob_score=True : 부트스트래핑에 포함되지 않은 샘플을 기반으로 훈련된 모델 평가, 테스트 세트 성능 짐작 가능!
bagging.fit(Xc_train, yc_train)
print("훈련 세트 정확도: {:.3f}".format(bagging.score(Xc_train, yc_train)))
print("테스트 세트 정확도: {:.3f}".format(bagging.score(Xc_test, yc_test)))
print("OOB 샘플의 정확도: {:.3f}".format(bagging.oob_score_))
훈련 세트 정확도: 0.953
테스트 세트 정확도: 0.951
OOB 샘플의 정확도: 0.946
엑스트라 트리
1) 엑스트라 트리(Extra-Trees)
- 랜덤 포레스트와 비슷하지만 후보 특성을 무작위로 분할한 다음 최적의 분할을 찾는다.
- DecisionTreeClassifier(splitter=’random’)을 사용하고 부트스트랩 샘플링은 적용하지 않는다. (splitter=’random’은 무작위로 분할한 후보 노드 중 최선의 분할)
- 다른 방식으로 모델에 무작위성을 주입!! 예측방식은 랜덤 포레스트와 동일하게 각 트리가 만든 확률값을 평균한다.
2) 엑스트라 트리 VS 랜덤 포레스트
[출처: 끙정의 데이터 사이언스]
랜덤 포레스트는 주어진 Feature에 대한 Partition을 계산하고 비교해서 최선의 Feature를 선택하여 Node를 분할
엑스트라 트리는 Feature 중에서 아무거나 고른 후에 Feature에 대해서만 최적의 분할을 찾아 Node를 분할
에이다부스트
1) 에이다부스트(Adaptive Boosting)
- 훈련된 각 모델은 성능에 따라 가중치가 부여!
- 그레이디언트 부스팅처럼 약한 학습기를 사용
- 이전의 모델이 잘못 분류한 샘플에 가중치를 높여서 다음 모델을 훈련(그레디언트 부스팅과 차이)
- 예측할 때는 모델이 예측한 레이블을 기준으로 모델의 가중치를 합산하여 가장 높은 값을 가진 레이블을 선택!
- AdaBoostClassifier의 기본값 = DecisionTreeClassifier(max_depth=1)를 사용
AdaBoostRegressor의 기본값 = DecisionTreeRegressor(max_depth=3)을 사용 - base_estimator 매개변수로 다른 모델을 지정할 수 있음!
시간 관계상 늦게 필기를 할 것 같습니다.. 곧 업데이트 할 것이니 기다려주세요~ㅠㅠ