본문 바로가기

Lecture ML

머신러닝 강좌 #15] 랜덤 포레스트 (Random Forest)

반응형

배깅(Bagging)은 여러 개의 분류기를 만들어서 보팅으로 최종 결정하는 알고리즘으로 대표적인 알고리즘으로 랜덤 포레스트가 있습니다. 

 

앙상블 알고리즘 중 비교적 빠른 수행 속도를 가지고 있으며, 다양한 영역에서 높은 예측 성능을 보이고 있습니다. 랜덤포세스트의 기반 알고리즘은 결정 트리입니다. (부스팅 기반의 다양한 앙상블 알고리즘 역시 대부분 결정 트리 알고리즘을 기반 알고리즘으로 채택하고 있습니다.)

 

랜덤 포레스트는 여러 개의 결정 트리 분류기가 전체 데이터에서 배깅 방식으로 각자의 데이터를 샘플링해 개별적으로 학습을 수행한 뒤 최종적으로 모든 분류기가 보팅을 통해 예측 결정을 하게 됩니다.

 

 

랜덤 포레스트는 개별적인 분류기의 기반 알고리즘은 결정 트리이지만 개별 트리가 학습하는 데이터 세트는 전체 데이터에서 일부가 중첩되게 샘플링된 데이터 세트입니다. 이렇게 여러 개의 데이터 세트를 중첩되게 분리하는 것을 부트 스트래핑(bootstrapping) 분할 방식이라고 합니다.

 

부트스트랩은 통계학에서 여러 개의 작은 데이터 세트를 임의로 만들어 개별 평균의 분포도를 측정하는 등의 목적을 위한 샘플링 방식을 지칭합니다. 랜덤 포레스트의 서브세트(Subset)데이터는 이러한 부트 스트래핑으로 데이터가 임의로 만들어집니다. 서브 세트의 데이터 건수는 전체 데이터 건수와 동일하지만, 개별 데이터가 중첩되어 만드러 집니다. 원본 데이터의 건수가 10개인 학습 데이터 세트에 랜덤 포레스트를 3개의 결정 트리 기반으로 학습하려면 n_estomator=3으로 하이퍼 파라미터를 부여하면 다음과 같이 데이터 서브 세트가 만들어집니다.

 

 

이렇게 데이터가 중첩된 개별 데이터 세트에  결정 트리 분류기를 각각 적용하는 것이 랜덤 포레스트입니다. 

 

from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
import pandas as pd
import warnings
warnings.filterwarnings('ignore')

def get_new_feature_name_df(old_feature_name_df):
    feature_dup_df = pd.DataFrame(data=old_feature_name_df.groupby('coulmn_name').cumcount(), columns=['dup_cnt'])
    feature_dup_df = feature_dup_df.reset_index()

    new_feature_name_df = pd.merge(old_feature_name_df.reset_index(), feature_dup_df, how='outer')
    # print(old_feature_name_df.info())
    # print(feature_dup_df.info())
    # print(new_feature_name_df.info())

    new_feature_name_df['coulmn_name'] = new_feature_name_df[['coulmn_name', 'dup_cnt']].apply(lambda x : x[0]+'_'+str(x[1]) if x[1] > 0 else x[0], axis=1)
    # new_feature_name_df = new_feature_name_df.drop(['index'], axis=1)
    # print(new_feature_name_df.info())
    return new_feature_name_df

def get_human_dataset():

    #각 데이터 파일은 공백으로 분리되어 있으므로 read_csv에서 공백 문자를 sep으로 할당.
    feature_name_df = pd.read_csv('dataset/ML_DecisionTree_UCI/ML_DecisionTree_UCI.txt', sep='\s+', header=None, names=['column_index', 'coulmn_name'])
    new_feature_name_df = get_new_feature_name_df(feature_name_df)

    #DataFrame에 피처명을 칼럼으로 부여하기 이해 리스트 객체로 다시 변환
    feature_name = new_feature_name_df.iloc[:,1].values.tolist()
    # print(new_feature_name_df.iloc[:,1])
    # print(new_feature_name_df.iloc[:,1].values)
    # print(new_feature_name_df.iloc[:,1].values.tolist())

    X_train = pd.read_csv('dataset/ML_DecisionTree_UCI/X_train.txt', sep='\s+', names=feature_name)
    X_test = pd.read_csv('dataset/ML_DecisionTree_UCI/X_test.txt', sep='\s+', names=feature_name)

    y_train = pd.read_csv('dataset/ML_DecisionTree_UCI/y_train.txt', sep='\s+', header=None, names=['action'])
    y_test = pd.read_csv('dataset/ML_DecisionTree_UCI/y_test.txt', sep='\s+', header=None, names=['action'])

    return X_train, X_test, y_train, y_test

X_train, X_test, y_train, y_test = get_human_dataset()

rf_clf = RandomForestClassifier(random_state=0)
rf_clf.fit(X_train, y_train)
pred = rf_clf.predict(X_test)
print(accuracy_score(y_test,pred))

결과는 아래와 같습니다.

0.9253478113335596

 

트리 기반의 앙상블 알고리즘의 단점을 굳이 뽑자면 하이퍼 파라미터가 너무 많고, 그로 인해서 튜닝을 위한 시간이 많이 소모된다는 것입니다. 더구나 많은 시간을 소모했음에도 튜닝 후 예측 성능이 크게 향상되는 경우가 많지 않아서 더욱 아쉽습니다. 

 

  • n_estimators: 랜덤 포레스트에서 결정 트리의 개수를 지정합니다. 디폴트는 10개입니다. 많이 설정할수록 좋은 성능을 기대할 수 있지만 계속 증가시킨다고 성능이 무조건 향상되는 것은 아닙니다. 또한 늘릴수록 학습 수행 시간이 오래 걸리는 것도 감안해야 합니다.

 

랜덤 포레스트는 CPU병렬 처리도 효과적으로 수행되어 빠른 학습이 가능하기 때문에 뒤에 그래디언트 부스팅 예측 성능이 약간 떨어지더라도 랜덤 포레스트로 일단 기반 모델을 먼저 구축하는 경우가 많습니다. 멀티 코어 환경에서는 n_jobs=-1 파라미터를 추가하면 모든 CPU 코어를 이용해 학습할 수 있습니다.

 

from sklearn.model_selection import GridSearchCV
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score
import pandas as pd
import warnings
warnings.filterwarnings('ignore')

def get_new_feature_name_df(old_feature_name_df):
    feature_dup_df = pd.DataFrame(data=old_feature_name_df.groupby('column_name').cumcount(), columns=['dup_cnt'])
    feature_dup_df = feature_dup_df.reset_index()
    new_feature_name_df = pd.merge(old_feature_name_df.reset_index(), feature_dup_df, how='outer')
    new_feature_name_df['column_name'] = new_feature_name_df[['column_name', 'dup_cnt']].apply(lambda x : x[0]+'_'+str(x[1])
                                                                                           if x[1] >0 else x[0] ,  axis=1)
    new_feature_name_df = new_feature_name_df.drop(['index'], axis=1)
    return new_feature_name_df

def get_human_dataset():
    # 각 데이터 파일들은 공백으로 분리되어 있으므로 read_csv에서 공백문자를 sep으로 할당
    feature_name_df = pd.read_csv('dataset/UCI HAR Dataset/features.txt', sep='\s+', header=None, names=['column_index', 'column_name'])
    # 데이터프레임에 피처명을 컬럼으로 뷰여하기 위해 리스트 객체로 다시 반환

    new_feature_name_df = get_new_feature_name_df(feature_name_df)
    feature_name = new_feature_name_df.iloc[:, 1].values.tolist()

    # 학습 피처 데이터세트와 테스트 피처 데이터를 데이터프레임으로 로딩
    # 컬럼명은 feature_name 적용
    X_train = pd.read_csv('dataset/UCI HAR Dataset/train/X_train.txt', sep='\s+', names=feature_name)
    X_test = pd.read_csv('dataset/UCI HAR Dataset/test/X_test.txt', sep='\s+', names=feature_name)

    # 학습 레이블과 테스트 레이블 데이터를 데이터 프레임으로 로딩, 컬럼명은 action으로 부여
    y_train = pd.read_csv('dataset/UCI HAR Dataset/train/y_train.txt', sep='\s+', names=['action'])
    y_test = pd.read_csv('dataset/UCI HAR Dataset/test/y_test.txt', sep='\s+', names=['action'])

    # 로드된 학습/테스트용 데이터프레임을 모두 반환
    return X_train, X_test, y_train, y_test

X_train, X_test, y_train, y_test = get_human_dataset()

params = {
    'n_estimators':[100],
    'max_depth':[6,8,10,12],
    'min_samples_leaf':[8,12,18],
    'min_samples_split':[8,16,20]
}

rf_clf = RandomForestClassifier(random_state=0, n_jobs=-1)
grid_cv = GridSearchCV(rf_clf, param_grid=params, cv=2, n_jobs=-1)
grid_cv.fit(X_train, y_train)

print(grid_cv.best_params_)
print(grid_cv.best_score_)

상기 프로그램을 통해 하기와 같이 91%의 정확도가 나오는 하이퍼 파라미터를 구한다.

{'max_depth': 10, 'min_samples_leaf': 8, 'min_samples_split': 8, 'n_estimators': 100}
0.9179815016322089

 

최고 성능의 하이퍼파라미터를 기준으로 다시 모델을 실행합니다.

rf_clf1 = RandomForestClassifier(n_estimators=300, max_depth=10, min_samples_leaf=8, min_samples_split=8, random_state=0)
rf_clf1.fit(X_train, y_train)
pred = rf_clf1.predict(X_test)
print(accuracy_score(y_test, pred))

하기와 같이 예측성능이 나옵니다.

0.9165252799457075

 

랜덤 포레스트는 트리 결정 방식을 사용하기 때문에 feature_importances_속성을 이용해 알고리즘이 선택한 피처의 중요도를 알 수 있습니다.

import matplotlib.pyplot as plt
import seaborn as sns

ftr_importances_values = rf_clf1.feature_importances_
ftr_importances = pd.Series(ftr_importances_values, index=X_train.columns)
ftr_top20 = ftr_importances.sort_values(ascending=False)[:20]

plt.figure(figsize=(8,6))
plt.title('Fature importances Top 20')
sns.barplot(x=ftr_top20, y=ftr_top20.index)
plt.show()

반응형