<jupyter_start><jupyter_text>앙상블들의 앙상블 (모델 Stacking : Stacked generalization) - 서로 다른 분류기 형태 간의 앙상블들의 앙상블 - 동일 형식 분류기를 사용한 부트스트랩 표본을 통한 앙상블들의 앙상블<jupyter_code>import pandas as pd
pd.options.display.max_columns=None<jupyter_output><empty_output><jupyter_text>---  데이터 로딩 IBM에서 제공했던 HR 데이터를 활용하겠습니다.IBM kaggle 데이터 : https://www.kaggle.com/pavansubhasht/ibm-hr-analytics-attrition-dataset<jupyter_code>datasets = pd.read_csv('./inputs/HR-Employee-Attrition.csv')
datasets.head()
datasets.shape<jupyter_output><empty_output><jupyter_text>데이터를 살펴보면 categorycal features, numerical features가 함께 있습니다.<jupyter_code>datasets.dtypes<jupyter_output><empty_output><jupyter_text>Taget variable : *Attrition* Yes / No -> 1 / 0으로 변경합니다- 1 : 퇴직 Yes- 0 : 퇴직 No<jupyter_code>datasets['Attrition_idx'] = datasets['Attrition']\
    .apply(lambda x: 1 if x == 'Yes' else 0)
datasets.head()<jupyter_output><empty_output><jupyter_text>Column 전처리<jupyter_code>col_names = datasets.columns
col_names<jupyter_output><empty_output><jupyter_text>필요없는 변수들이 있다 : *EmployeeCount*, *EmployeeNumber*, *Over18*, *StandardHours*<jupyter_code>print(datasets.Over18.value_counts())
print(datasets.EmployeeCount.value_counts())
print(datasets.StandardHours.value_counts())
# Target은 feature에서 제외한다.
col_names = col_names\
    .drop(['Attrition_idx', 'Attrition', 'Over18', 
           'EmployeeCount', 'EmployeeNumber', 'StandardHours'])<jupyter_output><empty_output><jupyter_text>Categorical column을 다루어보자.Catagorical column을 numerical column을 나누어보자.<jupyter_code>categorical_features = []
numerical_features = []
target = 'Attrition_idx'

# feature를 2가지 형태로 구분한다.
for col in col_names:
    if datasets[col].dtype == 'O':
        categorical_features.append(col)
    else:
        numerical_features.append(col)
print('Categorical feature의 수 :', len(categorical_features))
print('Numerical feature의 수 :', len(numerical_features))
categorical_features
numerical_features<jupyter_output><empty_output><jupyter_text>Categorical 데이터를 one-hot vector로 변경하자. Pandas에서 `get_dummies`를 이용하자.- Train, test set을 구분하지 않고 원핫벡터를 만드는 경우 : 해당 feature의 모든 원소들을 아는 경우, 예를 들어 회사 부서, 국가 코드의 경우에 해당한다.- Train, test set을 구분하고 train set으로만 원핫벡터를 만드는 경우 : 해당 feature의 원소가 test set에 없는 경우가 존재할 수 있다.<jupyter_code>categorical_datasets = pd.get_dummies(datasets[categorical_features])
categorical_datasets.head()
numerical_datasets = datasets[numerical_features]
numerical_datasets.head()<jupyter_output><empty_output><jupyter_text>Categorical dataset과 numerical dataset을 합친다. 모델의 input으로 사용할 feature이다.<jupyter_code>X = pd.concat([categorical_datasets, numerical_datasets], axis=1)
X.head()
y = datasets[target]
y.head()<jupyter_output><empty_output><jupyter_text>Train set과 test set을 구분<jupyter_code>from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = \
    train_test_split(X, y, test_size=0.3, random_state=42)<jupyter_output><empty_output><jupyter_text>모델의 성능을 평가하기 위한 metric들을 정의한 helper 함수 정의<jupyter_code>from sklearn.metrics import accuracy_score, classification_report
# Help 함수
def get_metric(y_label, pred, set_type):
    # 1. Confusion Matrix
    print('\n {} Confusion Matrix :'.format(set_type))
    display(pd.crosstab(y_label, pred, rownames=['Actual'], colnames=['Predict']))

    # 2. Accuracy
    print('\n {} accuracy :'.format(set_type), accuracy_score(y_label, pred))

    # 3. Classification Report
    print('\n {} Classification Report : \n'.format(set_type), classification_report(y_label, pred))<jupyter_output><empty_output><jupyter_text>Out-of-fold를 사용한 Stacking을 위한 helper 함수 정의<jupyter_code>from sklearn.model_selection import StratifiedKFold

splitter = StratifiedKFold(n_splits=5, shuffle=False, random_state=42)
print('cross validation의 길이', len(list(splitter.split(x_train, y_train))))
# cv에서 순서가 보장되는지 확인!
list(splitter.split(x_train, y_train))[0]
# Out-of-fold
def get_oof(classifier, x_train, y_train, x_test):
    oof_val = []
    oof_test = pd.DataFrame()
    clf_name = classifier.__class__.__name__
    
    for i, (train_indice, test_indice) in enumerate(splitter.split(x_train, y_train)):
        x_cv_train = x_train.iloc[train_indice]
        y_cv_train = y_train.iloc[train_indice]
        x_cv_val = x_train.iloc[test_indice]
        
        # 4개의 fold로 학습 (첫 번째 layer 학습)
        classifier.fit(x_cv_train, y_cv_train)
        # 1개의 fold로 예측 (두 번째 layer의 input)
        oof_val.extend(classifier.predict_proba(x_cv_val)[:,1])
        # 최종 성능 측정을 위해서 각 cross validation classifier 별로 예측값을 모은다.
        oof_test[i] = classifier.predict_proba(x_test)[:,1]
        
    oof_train = pd.DataFrame({clf_name: oof_val})
    oof_test[clf_name + '_mean'] = oof_test.mean(axis=1)
    
    return oof_train, oof_test<jupyter_output><empty_output><jupyter_text>---  서로 다른 분류기 형태 간의 앙상블들의 앙상블 - 훈련 데이터에 각 네 가지 분류기를 별도로 적용(로지스틱 회귀, 의사결정 트리, 램덤 포레스트, 에이다 부스트)- 네 가지 분류기에 관한 확률을 계산한다. 그리고 부류 1에 관한 확률만 메타 분류기에 이용한다. 부류 0의 확률 + 부류 1의 확률 = 1이므로 하나의 확률만 계산해도 충분하다. 그렇지 않으면 다중공선성 문제가 발생한다.- 최종 0/1 출력에 관한 4개의 확률(각 분류기에서 얻은 값) 간의 관계를 모델링하기 위해 로지스틱 회귀를 메타 분류기로 사용했다.- 메타 분류기에 사용된 4개 변수 모두에 관한 계수를 계산하고 새로운 데이터에 적용해 관측값을 최종 부류로 분류하기 위한 계산을 한다.  1. 분류기에 대하여 학습을 진행 (Out_of_fold 기법을 활용하여)<jupyter_code>from sklearn.model_selection import cross_val_predict
# Decision tree 강의에서 클래스 가중치를 구했습니다.
c_weight = {0: 0.3, 1: 0.7}<jupyter_output><empty_output><jupyter_text>> 로지스틱 회귀 분류기 out-of-fold<jupyter_code>from sklearn.linear_model import LogisticRegression
lr_classifier = LogisticRegression(fit_intercept=True, class_weight=c_weight, random_state=42)
lr_train_oof_pred, lr_test_oof_pred = get_oof(lr_classifier, x_train, y_train, x_test)
print(len(lr_train_oof_pred))
lr_train_oof_pred.head()
print(len(lr_test_oof_pred))
lr_test_oof_pred.head()
# 로지스틱 회귀 분류기 모델만 이용하여 예측값을 구한다.
lr_classifier.fit(x_train, y_train)
train_pred = lr_classifier.predict(x_train)
test_pred = lr_classifier.predict(x_test)

# 로지스틱 회귀 분류기 모델의 성능을 구한다.
get_metric(y_train, train_pred, 'Train')
print('=' * 60)
get_metric(y_test, test_pred, 'Test')<jupyter_output>Train Confusion Matrix :<jupyter_text>> 결정 트리 분류기 out-of-fold<jupyter_code>from sklearn.tree import DecisionTreeClassifier
dt_classifier = DecisionTreeClassifier(
    max_depth=5,
    min_samples_split=2,
    min_samples_leaf=1,
    class_weight=c_weight,
    random_state=42
)
dt_train_oof_pred, dt_test_oof_pred = get_oof(dt_classifier, x_train, y_train, x_test)
print(len(dt_train_oof_pred))
dt_train_oof_pred.head()
print(len(dt_test_oof_pred))
dt_test_oof_pred.head()
# 결정 트리 분류기 모델만을 이용하여 예측값을 생성한다.
dt_classifier.fit(x_train, y_train)
train_pred = dt_classifier.predict(x_train)
test_pred = dt_classifier.predict(x_test)

# 결정 트리 분류기 모델의 성능을 평가해보자.
get_metric(y_train, train_pred, 'Train')
print('=' * 60)
get_metric(y_test, test_pred, 'Test')<jupyter_output>Train Confusion Matrix :<jupyter_text>> 랜덤 포레스트 분류기 out-of-fold<jupyter_code>from sklearn.ensemble import RandomForestClassifier
rf_classifier = RandomForestClassifier(
    n_estimators=10000,
    max_depth=6,
    min_samples_split=2,
    min_samples_leaf=1,
    class_weight=c_weight,
    random_state=42)
rf_train_oof_pred, rf_test_oof_pred = get_oof(rf_classifier, x_train, y_train, x_test)
# 랜덤 포레스트 분류기만을 이용하여 예측값을 구합니다.
rf_classifier.fit(x_train, y_train)
train_pred = rf_classifier.predict(x_train)
test_pred = rf_classifier.predict(x_test)

# 랜덤 포레스트 분류기의 성능을 측정합니다.
get_metric(y_train, train_pred, 'Train')
print('=' * 60)
get_metric(y_test, test_pred, 'Test')<jupyter_output>Train Confusion Matrix :<jupyter_text>> 에이다 부스트 분류기<jupyter_code>from sklearn.ensemble import AdaBoostClassifier
ab_classifier = AdaBoostClassifier(
    DecisionTreeClassifier(max_depth=1, class_weight=c_weight),
    n_estimators=5000,
    learning_rate=0.05,
    random_state=42
)
ab_train_oof_pred, ab_test_oof_pred = get_oof(ab_classifier, x_train, y_train, x_test)
# 에이다 부스트 분류기만을 이용하여 예측값을 생성합니다.
ab_classifier.fit(x_train, y_train)
train_pred = ab_classifier.predict(x_train)
test_pred = ab_classifier.predict(x_test)

# 에이다 부스트 분류기의 성능을 측정합니다.
get_metric(y_train, train_pred, 'Train')
print('=' * 60)
get_metric(y_test, test_pred, 'Test')<jupyter_output>Train Confusion Matrix :<jupyter_text>2. 분류기의 결과들을 결합<jupyter_code>ensemble = pd.DataFrame()<jupyter_output><empty_output><jupyter_text>부류 1(퇴직자)에 관한 확률을 이용하여 앙상블을 수행합니다. > 로지스틱 회귀 분류기<jupyter_code># 로지스틱 회귀 분류기에 대한 결과값을, 부류 1이 될 확률만 리턴받습니다.
ensemble['lr_output_one'] = lr_train_oof_pred
ensemble.head()<jupyter_output><empty_output><jupyter_text>> 결정 트리 분류기<jupyter_code># 결정 트리 분류기에 대한 결과값을, 부류 1이 될 확률만 리턴받습니다.
ensemble['dt_output_one'] = dt_train_oof_pred
ensemble.head()<jupyter_output><empty_output><jupyter_text>> 랜덤 포레스트 분류기<jupyter_code># 랜덤 포레스트 분류기에 대한 결과값을, 부류 1이 될 확률만 리턴받습니다.
ensemble['rf_output_one'] = rf_train_oof_pred
ensemble.head()<jupyter_output><empty_output><jupyter_text>> 에이다 부스트 분류기<jupyter_code># 에이다 부스트 분류기에 대한 결과값을, 부류 1이 될 확률만 리턴받습니다.
ensemble['ab_output_one'] = ab_train_oof_pred
ensemble.head()
ensemble = pd.concat([ensemble, pd.DataFrame(y_train).reset_index(drop=True)], axis=1)
ensemble.head(5)<jupyter_output><empty_output><jupyter_text>3. 메타 분류기 학습<jupyter_code>meta_classifier = LogisticRegression(fit_intercept=False)
meta_classifier.fit(
    ensemble[['lr_output_one', 'dt_output_one', 'rf_output_one', 'ab_output_one']],
    ensemble['Attrition_idx'])<jupyter_output><empty_output><jupyter_text>4. 테스트 셋에 적용 가능하도록 테스트 셋에 대한 예측값 구하기<jupyter_code>ensemble_test = pd.DataFrame()
# 로지스틱회귀 분류기에 대한 결과값을, 부류 1이 될 확률만 리턴받습니다.
ensemble_test['lr_output_one'] = lr_test_oof_pred['LogisticRegression_mean']
ensemble_test['dt_output_one'] = dt_test_oof_pred['DecisionTreeClassifier_mean']
ensemble_test['rf_output_one'] = rf_test_oof_pred['RandomForestClassifier_mean']
ensemble_test['ab_output_one'] = ab_test_oof_pred['AdaBoostClassifier_mean']
ensemble_test.head()<jupyter_output><empty_output><jupyter_text>5. 메타분류기 성능 측정<jupyter_code># 학습 셋에 대한 예측값
train_pred = meta_classifier.predict(
    ensemble[['lr_output_one', 'dt_output_one', 'rf_output_one', 'ab_output_one']],)
# 테스트 셋에 대한 예측값
test_pred = meta_classifier.predict(
    ensemble_test[['lr_output_one', 'dt_output_one', 'rf_output_one', 'ab_output_one']],)
get_metric(y_train, train_pred, 'Train')
print('=' * 60)
get_metric(y_test, test_pred, 'Test')<jupyter_output>Train Confusion Matrix :<jupyter_text>6. 개별 분류기에 대한 평가 각각의 분류기에 대해서 평가 : 로지스틱 회귀 분류기를 메타 분류기로 사용했으면 각 계수(coefficients)를 살펴본다.<jupyter_code>meta_classifier.coef_<jupyter_output><empty_output><jupyter_text>에이다부스트의 경우 성능을 끌어내리고 있는것처럼 보인다.따라서, 에이다 부스트의 파라미터를 조절해보고 성능을 살펴보거나, 에이다 부스트를 제거하고 성능을 살펴본다.  동일 형식 분류기를 사용한 부트스트랩 표본을 통한 앙상블들의 앙상블 - 훈련 데이터에서 부트스트랩 표본을 추출한다.- 각 표본에 관해 매번 에이다 부스트 모델을 학습한다.- 개별 에이다 부스트 모델의 결과는 bagging 분류기를 통해 합쳐진다.분산의 축소가 여전히 성능을 향상시키는 매우 유연한 모델에 관해 적절하다(의사 결정 트리, 랜덤 포레스트 등).  1. 기본 분류기 및 부트스트랩 샘플을 학습시킬 모델 생성<jupyter_code># 에이다 부스트 모델에 사용할 기본 분류기(의사결정 그루터기)
base_learner = DecisionTreeClassifier(max_depth=1, class_weight=c_weight)
# 부트스트랩 샘플을 학습시킬 에이다 부스트 모델
ab_classifier = AdaBoostClassifier(base_estimator=base_learner,
                                   n_estimators=500,
                                   learning_rate=0.05,
                                   random_state=42)
ab_classifier.fit(x_train, y_train)<jupyter_output><empty_output><jupyter_text>에이다 부스트의 기본성능을 살펴보자.<jupyter_code>train_pred = ab_classifier.predict(x_train)
test_pred = ab_classifier.predict(x_test)
get_metric(y_train, train_pred, 'Train')
print('=' * 60)
get_metric(y_test, test_pred, 'Test')<jupyter_output>Train Confusion Matrix :<jupyter_text>2. Bootstrapping samples를 생성하고 각 샘플에 분류기를 학습 `BaggingClassifier`는 내부에 bootstrap 기능이 있고, 학습시킬 분류기를 1개 지정할 수 있다. 따라서 이것을 사용하면 된다.<jupyter_code>from sklearn.ensemble import BaggingClassifier

bag_classifier = BaggingClassifier(
    base_estimator=ab_classifier,
    n_estimators = 50,
    max_samples=1.0,
    max_features=1.0,
    bootstrap=True,
    bootstrap_features=False,
    n_jobs=-1,
    random_state=42)
bag_classifier.fit(x_train, y_train)<jupyter_output><empty_output><jupyter_text>3. 성능 측정<jupyter_code>train_pred = bag_classifier.predict(x_train)
test_pred = bag_classifier.predict(x_test)
get_metric(y_train, train_pred, 'Train')
print('=' * 60)
get_metric(y_test, test_pred, 'Test')<jupyter_output>Train Confusion Matrix :
{'fraction_non_alphanumeric': 0.10678269355888094, 'fraction_numerical': 0.009840598568640209, 'mean_word_length': 6.022844089091947, 'pattern_counts': {'":': 0, '<': 114, '<?xml version=': 0, '>': 123, 'https://': 1, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 3, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '30', 'dir': 'jupyter-structured-clean-dedup', 'id': '58358', 'license': 'MIT', 'n_tokens_mistral': 6317, 'n_tokens_neox': 7076, 'n_words': 982, 'path': 'machine-learning-lecture-notes-master/Lecture17_Stacking.ipynb', 'repo_name': 'm1016m/NN', 'size': '12296'}