지도학습 결정트리 앙상블(Randomforest, Gradient Boosting)

2018. 1. 5. 00:44ML(머신러닝)/Tree Based Model

728x90

도움이 되셨다면, 광고 한번만 눌러주세요.  블로그 관리에 큰 힘이 됩니다 ^^

결정 트리 

 

1. 분류 / 2. 회귀모델 

 

결정에 다다를 때까지 예/아니오 질문 이어가면서 학습

 

구분 예제는 예/아니오 연속적인 데이터에 적용한 테스트는 " 특성 i는 값 a 보다 큰가? " 

 

계측적으로 영역을 분할해가는 알고리즘 

 

## 복잡도 제어하기

 

모든 leaf node 가 순수 노드가 될 때까지 진행하면 -> 과대적합 발생 (훈련 세트 100% 정확하게)  -> 이상치에 너무 민감

 

과대 적합 막는 전략 크게 2가지

1) 사전 가지치기 -> 일찍 중단하는 전략 2) 데이터 포인트가 적은 노드를 삭제하거나 병합하는 전략(사후 가지치기) 

1) 사전 가지치기 방법 트리의 최대 깊이나 리프의 최대 개수를 제한하거나 또는 노드를 분할하기 위한 포인트의 최소 개수를 지정하는 것

 

순수 노드

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)
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

일정 깊이 도달하면 트리의 성장 제한 -> 과대적합 줄어든다.
tree = DecisionTreeClassifier(max_depth=4, 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)))

훈련 세트 정확도: 0.988 ## 훈련의 정확도는 떨어지지만 테스트 성능은 개선시킨다. 테스트 세트 정확도: 0.951

 

# 트리의 특성 중요도 

전체 트리 보기 어려우니 -> 트리 어떻게  작동하는지 요약하는 성능 사용 -> 결정에 각 특성이 얼마나 중요한지 평가하는 특성 중요도

# 0 ~1 사이의 숫자로 -> 0은 전혀 사용 안 함 1은 완벽하기 타깃 클래스 예측 

print("특성 중요도:\n{}".format(tree.feature_importances_))

 

# 특성 중요도 시각화 

def plot_feature_importances_cancer(model):
    n_features = cancer.data.shape[1]
    plt.barh(range(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 가장 중요한 특성 

# feature importance 값이 낮다고 유용하지 안다는 것 -> 선택되지 않 았을 뿐이며 다른 특성이 동일한 정보 지니고 있어서 일 수도 있다.

# 특성 중요도는 선형과 달리 항상 양수이미 특성이 어떤 클래스 지지하는지 알 수 없다.

# worst radius 가 중요하지만 양성과 악성을 어떤 걸 의미하는지 알 수 없다. 

 

 

# DecisionTreeRegressor  // LinearRegression 비교

 

log 스케일로 바꾼 것이 영향을 LinearRegression 에서는 큰 차이가 있어서 지수함수로 적용합니다.

 

from sklearn.tree import DecisionTreeRegressor
# 2000년 이전을 훈련 데이터로, 2000년 이후를 테스트 데이터로 만듭니다
data_train = ram_prices[ram_prices.date < 2000]
data_test = ram_prices[ram_prices.date >= 2000]

# 가격 예측을 위해 날짜 특성만을 이용합니다
X_train = data_train.date[:, np.newaxis]
# 데이터와 타깃 사이의 관계를 간단하게 만들기 위해 로그 스케일로 바꿉니다
y_train = np.log(data_train.price)

tree = DecisionTreeRegressor().fit(X_train, y_train)
linear_reg = LinearRegression().fit(X_train, y_train)

# 예측은 전체 기간에 대해서 수행합니다
X_all = ram_prices.date[:, np.newaxis]

pred_tree = tree.predict(X_all)
pred_lr = linear_reg.predict(X_all)

# 예측한 값의 로그 스케일을 되돌립니다
price_tree = np.exp(pred_tree)
price_lr = np.exp(pred_lr)
plt.yticks(fontname = "Arial") # 한글 폰트가 지수에 음수를 표시하지 못하므로 ytick의 폰트를 바꾸어 줍니다.
plt.semilogy(data_train.date, data_train.price, label="훈련 데이터")
plt.semilogy(data_test.date, data_test.price, label="테스트 데이터")
plt.semilogy(ram_prices.date, price_tree, label="트리 예측")
plt.semilogy(ram_prices.date, price_lr, label="선형회귀 예측")
plt.legend()

 

선형은 매끈하게 근사해서 가지만 선형 트리는 테스트 데이터를 완벽하게 예측 -> 복잡 제한도 두지 않아서 데이터셋 모두 기억 -> 과대 적합

 

트리의 단점 -> 훈련 데이터  밖의 새로운 데이터 예측할 능력이 없다.

 

장단점과 매개변수

 

복잡도 조절 매개변수 -> 사전 가지치기(max_depth , max_lead_nodes , min_samples_leaf) 하나만 지정해도 충분

 

이전 소개한 알고리즘보다 나은 점 : 쉽게 시각화 -> 이해하기 쉬움 

각 특성이 개별적으로 처리 데어 스케일 영향받지 않음 -> 전처리나 정규화 필요 없음

이진 특성과 연속 특성 혼합되어도 잘 작동

 

주요 단점 : 사전 가지치기 사용함에도 과대 적합 경향이 있어 -> 성능이 안 좋음 


 

앙상블 

 

여러 머신러닝 모델을 연결하여 더 강력한 모델을 만드는 기법 -> 렌덤 포레스트 / 그래디언트 부스팅  효과적

 

랜덤 포레스트

결정 트리 주요 단점 -> 과대 적합 경향 -> 회피 가능 -> 조금씩 다른 여러 결정 트리의 묶음 

# 각 트리가 비교적 예측을 잘하지만 -> 데이터 일부에서 과대적합 경향 -> 예_ 잘 작동하되 서로 다른 방향 과대적합 트리 

-> 평균을 냄으로써 과대적합 줄임 -> 성능 유지되면서 과대적합 줄일 수 있음 

 

# 트리 생성 시 무작위성을 주입 -> 방법 1) 데이터 포인트를 무작위로 선택하는 방법 

    방법 2) 분할 테스트에서 특성을 무작위로 선택하는 방법

 

# 구축

1. 생성할 트리 개수 정하기 (RandomForest Regressor / RandomForestClassifier의 n_estimates 매개변수 )

2. 각 트리가 고유하게 만들어지도록 무작위 한 선택 -> 먼저 데이터의 부트스트랩 샘플 생성

 n_samples 개의 데이터 포인트 중에서 무작위로 데이터를 n_samples 횟수만큼 반복 추출 (한 샘플 중복 가능성 있음)

3. 만든 데이터 셋에서 결정 트리 생성 (각 노드에서 전체 특성을 대상으로 최선의 테스트 찾는 것이 아니고

    알고리즘이 각 노드에서 후보 특성을 무작위로 선택한 후 이 후보들 중에서 최선의 테스트를 찾습니다.)

4. 몇 개의 특성을 고를지 -> max_features 후보 특성을 고르는 것은 매 노드마다 반복되므로 트리의 각 노드는 다른 후보 특성을 사용하여 테스트를 만든다.

5. 부트스트랩 샘플링은 램던 포레스트의 트리가 조금씩 다른 데이터셋을 이용해 만들어지도록 합니다. 

  또 각 노드에서 특성의 일부만 사용하기 때문에 트리의 각 분기는 각기 다른 특성 부분 집합을 사용합니다. 

  이 2가지 메커니즘을 합쳐서 랜덤 포레스트의 모든 트리가 서로 달라지도록 만듭니다. (핵심 max_features)

      max_features를 n_features로 설정하면 -> 트리 각 분기에서 모든 특성 고려하므로 무작위성 들어가지 않는다.

        # 하지만 부스트 랩 샘플링으로 인한 무작위성은 그대로입니다.

      max_features=1 -> 트리의 분기는 테스트할 특성을 고를 필요가 없게 되며 

  그냥 무작위로 선택한 특성의 임계치를 찾기만 하면 됩니다. 

결국 max_features 값 크게 하면 랜덤 포레스트의 트리들은 매우 비슷 -> 가장 두드러진 특성을 이용해 데이터에 잘 맞춤

max_features 낮추면  랜덤 포레스트 트리들이 많이 달라지고 각 트리는 맞추기 위해 깊이가 깊어지게 됩니다.

랜덤포레스트 예측할 때는 먼저 알고리즘이 모델에 있는 모든 트리의 예측 만든다.

회귀의 경우 이 예측들을 평균하여 최종 예측을 만듭니다. 

분류의 경우 약한 투표 전략을 사용합니다 즉) 각 알고리즘이 가능성 있는 출력 레이블의 확률을 제공합으로써 간접 예측

트리들이 예측한 확률을 평균 내어 가장 높은 확률을 가진 클래스가 예측값이 됩니다. 

 

n_estimators=5 각 만들어진 트리는 estimator_ 속성에 저장되고 결정 경계와 이를 취합해 시각화

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) forest.fit(X_train, y_train)


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)

 

max_features 매개변수 조정하거나 사전 가지치기 가능하다 (안 해도 좋다) 

X_train, X_test, y_train, y_test = train_test_split(
    cancer.data, cancer.target, random_state=0)
forest = RandomForestClassifier(n_estimators=100, random_state=0)
forest.fit(X_train, y_train)

print("훈련 세트 정확도: {:.3f}".format(forest.score(X_train, y_train)))
print("테스트 세트 정확도: {:.3f}".format(forest.score(X_test, y_test)))
 
훈련 세트 정확도: 1.000
테스트 세트 정확도: 0.972
In [72]:

plot_feature_importances_cancer(forest)

단일 트리 경우보다  훨씬 많은 특성이 0 이상의 중요도 가지고 있다 단일 트리보다 더 넓은 시각 가능

 

장단점과 매개변수

회귀와 분류에 있어서 랜덤 포레스트 널리 사용됨 -> 튜닝안 해도 , 스케일링 안해도 잘 됨

트리의 단점을 보완하고 장점은 그대로 가진다 

멀티 코어 프로세스일 때는 n_jobs 매개 변수 이용하여 코어 수 정할 수 있다. 

코어에 비례하여 속도 빨라짐 2개 -> 속도 2배 n_jobs=-1 하면 모든 코어 사용함

random_state 지정하면 다른 모델이 만들어진다 -> 트리 많을수록 변동은 적어지긴 한다.

BUT 텍스트 데이터 같이 매우 차원이 높고 희소한 데이터에는 잘 작동하지 않는다. -> 선형 모델이 더 적합

또한 많은 메모리 , 훈련 성능 예측이 느리다. 

 

중요 매개변수 n_estimators , max_features max_depth 사전 가지치기 옵션이 있다

n_estimators 클수록 좋다 -> 많은 트리 -> 과적합 줄여준다. -> 긴 시간 

max_features 트리를 얼마나 무작위로 할지 -> 과대 적합 줄여준다. 

분류 :: max_features =sqrt(n_features)  RandomForestClassifier

 회귀 : max_features = n_features   RandomForest Regressor

max_features , max_leaf_nodes 추가하면 시간과 메모리 아낄 수 있고 성능 향상 기대할 수 있다.

 


 

그래디언트 부스팅 회귀 트리

 

여러 개의 결정 트리를 묶어 강력한 모델을 만드는 또 다른 방법

회귀/ 분류 모두 사용 가능 

랜덤 포레스트와는  달리  이전 트리의 오차를 보완하는 방식으로 순차적으로 트리를 만든다.

 

 무작위 성이 없습니다 -> 대신에 강력한 사전 가지치기 사용  # 보통 하나에서 다섯 정도의 깊지 않은 트리 사용 -> 메모리 적고 빠름 (장점) 

근본 아이디어 -> 약한 학습기 (weak learner)을 많이 연결하자!  트리 많이 추가할수록 성능 향상!

앙상블 방식에서 사전 가지치기나 트리 개수 이외에도 트리 오차 얼마나 강하게 보정할 것인지 = learning_rate

# 학습률이 크면 트리는 보정이 강하게 하기 때문에 -> 복잡한 모델

# n_estimater 값 키우면 -> 트리 더 추가 -> 복잡도 증가 -> 훈련 세트에서의 실수를 바로잡을 기회가 많아짐 

 from sklearn.ensemble import GradientBoostingClassifier

X_train, X_test, y_train, y_test = train_test_split( cancer.data, cancer.target, random_state=0) gbrt = GradientBoostingClassifier(random_state=0) # 기본 0.1 깊이 3 , 개수 100개 gbrt.fit(X_train, y_train) print("훈련 세트 정확도: {:. 3f}". format(gbrt.score(X_train, y_train))) print("테스트 세트 정확도: {:. 3f}". format(gbrt.score(X_test, y_test)))

훈련 세트 정확도: 1.000 -> 과대 적합 테스트 세트 정확도: 0.958

 

 

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 -> 얼마 기여 못함

 

gbrt = GradientBoostingClassifier(random_state=0, max_depth=1)
gbrt.fit(X_train, y_train)

plot_feature_importances_cancer(gbrt)

 

랜덤 포레스트와 비슷하지만 일부 특성 완전 무시 

일반적으로는 랜덤 포레스트 먼저 한다 

하지만 마지막 성능까지 쥐어짜야 할 때 그래디 언 테 부스팅 도움된다.

대규모 머신러닝 -> xgboost와 파이썬 인터페이스 검토

 

장단점과 매개변수

지도 학습에서 가장 널리 강력하게 사용하는 모델 중 하나

 

큰 단점 -> 매개변수를 잘 조정해야 하는 것과 훈련 시간이 길다 

다른 트리 기반처럼 스케일 조정하지 않아도 되고 이진 특성이 연속적인 특성에서도 잘 작동

희소한 고차원 데이터에서 잘 작동 (x)

중요 매개 변수 

n_estimators : 트리 개수 지정

learning_rate 낮추면 비슷한 복잡도의 모델을 만들기 위해서 더 많은 트리 추가해야 함

n_estimators  크게 하면 랜덤 포레스트와는 달리 -> 모델 복잡 , 과대 적합 

중요한 변수 또 하나는 max_depth (max_leaf_nodes)  통상 max_depth 작게 한다 5보다 깊지 않게 한다.

 

728x90