이번주에는 지난주에 이어서 '코스피 방향 예측을 위한 하이브리드 머신러닝 모델' 논문에서 사용한 모델들에 대해 알아보자. RF, XGB, LGBM을 소개하기전에 우리는 먼저 배깅과 부스팅에 대한 개념부터 정리해야 한다. 랜덤 포레스트는 배깅, XGBoost와 LightGBM은 부스팅 계열의 모델이기 때문이다. 배깅과 부스팅은 앙상블 학습 방법의 일종으로, 여러 개의 모델을 합쳐서 더 강력한 예측 모델을 만드는 기법을 말한다. 우선, 배깅에 대해 알아보자.
배깅(Bagging)
배깅은 Bootstrap Aggregating의 줄임말로, 여러 개의 훈련 데이터 세트를 부트스트랩 방법을 사용해 만들고, 각각의 훈련 세트로 여러 개의 모델을 학습시키는 방법을 의미한다. 여기서 부트스트랩이란 원래 데이터에서 복원 추출을 통해 새로운 데이터 세트를 만드는 과정을 의미한다. 즉, 샘플링의 방법 중 하나이다. 배깅은 다양한 데이터를 통해 학습된 각 모델의 예측을 평균으로 산출하거나, 투표(voting)를 통해 최종 예측을 결정한다. 투표에는 다음과 같은 두 가지 형태가 있다.
- Hard Voting : 각 모델의 예측 결과 중 가장 많이 나온 클래스를 최종 예측 결과로 선택한다. 다수결에 기반하는 방식이며, 주로 분류 문제에서 주로 사용된다.
- Soft Voting : 각 모델이 예측한 클래스의 확률을 평균 내어, 확률이 가장 높은 클래스를 최종 예측 결과로 분류한다. 확률 정보를 활용하므로 모델의 확신도(confidence)를 반영할 수 있으며, 주로 분류 문제에서 사용된다. 여기서 확신도는 모델이 예측한 결과에 대한 신뢰도나 확신의 정도를 의미하며, 분류 문제에서는 각 클래스에 속할 확률을 뜻한다.
랜덤 포레스트(Random Forest)는 배깅의 대표적인 예시로, 여러 개의 의사결정 나무(Decision Tree)를 만들고 그 결과를 평균내거나 다수결로 결정한다. 특성을 무작위로 선택하기 때문에 overfitting을 방지하고 안정적인 성능을 기대할 수 있다. 랜덤 포레스트는 밑에서 조금 더 자세히 알아보도록 하고, 다음으로는 부스팅에 대해 알아보자.
부스팅(Boosting)
부스팅은 여러 개의 약한 학습기를 순차적으로 학습시켜 강한 학습기를 만드는 방법이다. 각 학습기는 이전 학습기의 오차를 보완하도록 설계를 진행한다. 아래에는 부스팅의 과정을 순서대로 적어보았다.
- 초기 가중치 할당 : 데이터의 각 샘플에 동일한 가중치를 부여하여 첫 번째 약한 학습기를 학습시킨다.
- 가중치 갱신 : 첫 번째 약한 학습기의 예측 오차를 기반으로 데이터 샘플의 가중치를 갱신한다. 잘못 예측된 샘플의 가중치는 증가시키고, 올바르게 예측된 샘플의 가중치는 감소시킨다. 이렇게 가중치를 조정하면 다음 학습기는 잘못 예측된 샘플에 대해 더 집중하여 학습하게 된다.
- 순차 학습 : 갱신된 가중치를 사용하여 다음 약한 학습기를 학습시키고, 이 과정을 여러 번 반복한다. 연속된 학습기들이 각가의 오차를 보완해 나가면서 전체적인 예측 성능을 향상시키는 것이 부스팅의 핵심이다.
- 최종 예측 : 각 약한 학습기의 예측을 결합하여 최종 예측을 생성한다.
주요 부스팅 알고리즘으로는 Adaboost, Gradient Boosting, XGBoost, LightGBM이 있으며, 논문에서는 XGB와 LGBM을 사용하였다. 부스팅을 사용하면 개별 학습기보다 높은 성능을 달성할 수 있으며, 분류와 회귀 문제 모두에 사용할 수 있다. XGB와 LGBM 역시 밑에서 조금 더 자세히 알아보도록 하자.
랜덤 포레스트(Random Forest)
랜덤 포레스트는 여러 개의 의사결정 나무(Decision Tree)를 결합한 앙상블 학습 방법이다. 의사결정 나무를 간략하게 설명하자면, 데이터를 분류하거나 값을 예측(회귀)하는 데 사용되는 지도 학습 모델을 뜻한다. 특징과 목표 변수 간의 관계를 나무 구조로 표현하며, 규칙을 기반으로 예측을 수행한다. 이렇게 독립적으로 학습된 나무들을 보아서, 각 나무의 예측을 평균으로 하거나(soft voting) 다수결로 결합(hard voting)하는 배깅을 사용하는 모델을 랜덤 포레스트라고 한다. 아래에는 랜덤 포레스트의 학습과정과 하이퍼 파라미터에 대해 정리해보았다.
학습과정
- 배깅(sampling) : 전체 데이터 세트에서 무작위로 중복을 허용하여 학습에 사용할 샘플을 추출한다. 이렇게 만들어진 새로운 데이터 세트로 개별 나무를 훈련시킨다.
- 나무 생성 : 각각의 나무는 위에서 만들어진 샘플로 학습되며, 개별 노드에서 무작위로 선택된 일부 특징만을 사용하여 분할 기준을 탐색한다. 여기서 노드란, 의사결정 나무의 각 단계에서 데이터를 분할하는 역할을 수행한다. 노드의 종류는 다음과 같이 3가지로 구분된다.
- 루트 노드(root node) : 나무의 가장 꼭대기에 있는 노드로, 첫 번째 분할이 일어나는 곳이다. 해당 노드는 전체 데이터 세트를 2개 이상의 하위 그룹으로 분할한다.
- 내부 노드(internal node) : root node와 leaf node 사이에 있는 노드로, 추가적인 분할을 수행한다. Internal node에서는 특정 변수와 임계값(threshold)을 기반으로 데이터를 분할하며, 이 과정을 계속 반복한다.
- 리프 노드(leaf node) : 나무의 가장 아래쪽에 있는 노드로, 더 이상 분할되지 않는다. leaf node에 도달하면 최종 예측이 이뤄지며, 분류 문제에서는 다수 클래스를 예측 결과로, 회귀 문제에서는 leaf node에 속한 sample의 평균 값을 결과로 사용한다.
- 예측 결합 : 분류 문제의 경우 각 나무의 투표를 집계하여 최종 예측을 만들고, 회귀 문제의 경우 나무들의 예측 값을 평균치로 환산한다.
하이퍼파라미터
n_estimators : 앙상블에 사용되는 나무의 수로, 너무 작으면 부정확하고 너무 많으면 계산 비용이 증가한다. 일반적으로 나무의 수가 많을수록 예측의 안정성이 향상된다.
max_features : 각 노드에서 분할에 사용될 특징(변수)의 수를 최대 수를 지정한다. 특징을 무작위로 선택함으로써 나무 간의 다양성을 증가시키며, overfitting을 방지할 수 있다.
max_depth : 나무의 최대 깊이를 지정한다. 단, 너무 깊은 나무는 학습 데이터에 overfitting 할 수 있으므로, 적절한 깊이를 설정하는것이 중요하다.
min_samples_split : 노드를 분할하기 위해 필요한 최소 샘플 수를 의미한다. 값을 크게 설명하면 나무의 깊이가 얕아져 underfitting 될 수 있다.
min_samples_leaf : leaf node가 가져야 할 최소 샘플 수를 지정한다.
bootstrap : 위에서 소개한 bootstrap sampling 여부를 결정하는 hyperparameter이다. 사용 시, 나무 간의 다양성을 높이고 overfitting을 줄일 수 있다.
하이퍼파라미터는 랜덤 포레스트의 성능과 복잡도를 직접 조절하며, 문제와 데이터 특성에 따라 적절히 조정해야 최적의 결과를 달성할 수 있다. 설명만으로는 이해가 부족할 수 있기 때문에, python을 사용해서 간단한 Random Forest 분류 모형을 제작해 보았다.
from sklearn.datasets import load_breast_cancer
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import train_test_split
from sklearn.tree import export_graphviz
import graphviz
from sklearn.metrics import accuracy_score
# 데이터 로드
data = load_breast_cancer()
X = data.data
y = data.target
# 트레이닝 및 테스트 데이터 분리
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 랜덤 포레스트 모델 생성
model = RandomForestClassifier(n_estimators=100, random_state=42)
# 모델 학습
model.fit(X_train, y_train)
# 예측 수행
y_pred = model.predict(X_test)
# 정확도 출력
accuracy = accuracy_score(y_test, y_pred)
print(f'Accuracy: {accuracy * 100:.2f}%') # 96.77%
# 첫 번째 나무 시각화
dot_data = export_graphviz(model.estimators_[0], out_file=None, filled=True, feature_names=data.feature_names, class_names=[str(i) for i in data.target_names], rounded=True)
graph = graphviz.Source(dot_data)
graph.render("tree") # 파일로 저장
graph.view("tree")

위의 모델은 총 100개의 나무를 사용한 Random Forest 모델로 유방암 환자 분류 결과를 시각화 한 것이다. 모든 나무의 그림을 가져올 수 없기 때문에, 제일 처음으로 사용한 나무의 그림만 가져왔다. 해석해보면, 제일 위의 root node에서는 'worst concave point' 라는 특성을 기준으로 값이 0.13 이하(threshold)이면 왼쪽, 그렇지 않으면 오른쪽으로 이동하는 것을 확인할 수 있다. 이러한 패턴이 트리의 모든 레벨에서 반복되며, 각 분기점에서 특정 변수에 대한 threshold를 기준으로 데이터를 분할하여 최종적으로 leaf node에 도달하는 것이다.
GBM(Gradient Boosting Machine)
지금까지 배깅을 기반으로 하는 랜덤포레스트에 대해 살펴봤다면, 이제부터는 부스팅 계열 모델에 대해 살펴보자. XGB와 LGBM을 알기전, 반드시 알아야하는 개념이 있다. 바로 Gradient Boost(경사 하강법) 이다. XGB와 LGBM은 기본적으로 GBM을 기반으로 모델을 개선/확장한 알고리즘이기 때문에, Graident Boost와 GBM의 원리에 대해 먼저 살펴보자.
- Graident Boost
- Graident Boost는 여러 기본 추정기의 예측을 결합하여 강력한 예측 모델을 구축하는 앙상블 학습 방법
- 이전 모델의 잔차(residual)에 순차적으로 약한 학습기를 적합시키고 손실 함수(loss function)을 최소화하는 방식을 반복해서 최종 모델을 구축
- 분류나 회귀 문제를 해결하기 위해 예측 성능을 향상시키는 것이 목표
- GBM의 작동 원리
- GBM은 상수 값을 예측하는 간단한 모델로 시작해서, 약한 학습기를 순차적으로 구축하면서 현재 모델의 잔차를 계산
- 잔차에 약한 학습기(ex - shallow decision tree)를 적합시키고, learning rate로 weight가 부여된 weak learner의 예측을 추가하여 모델을 업데이트
- 최종 모델은 weak learner의 weight를 합산시킨 것으로, gradient descent(경사 하강법)를 사용하여 지정된 loss function(ex - RMSE)를 최소화 한다. gradient descent는 정말 중요한 개념이기 때문에, 추후 별도로 포스팅하도록 하겠다.
- 예측 성능이 높지만, Greddy Algorithm으로 overfitting이 빠르게 되고, 시간이 오래 걸릴 수 있다는 단점이 있다.
- Greddy Algorithm(탐욕 알고리즘) : 미래를 생각하지 않고 각 단계에서 가장 최선의 선택을 하는 것이 전체적으로도 최선이길 바라는 알고리즘을 의미한다. 하지만, 모든 경우에서 유용하지는 않다.
간단한 예제를 보면서 GBM에 대해 확실하게 정리해보자.
from sklearn.ensemble import GradientBoostingRegressor
import numpy as np
import matplotlib.pyplot as plt
# 임의의 데이터 생성
np.random.seed(42)
X = np.sort(5 * np.random.rand(80, 1), axis=0)
y = np.sin(X).ravel() + np.random.normal(0, 0.1, X.shape[0])
# Gradient Boosting Regressor 생성
n_estimators = 10
reg = GradientBoostingRegressor(n_estimators=n_estimators, max_depth=1, learning_rate=1.0, random_state=42)
# 각 약한 학습기의 예측을 저장할 리스트
predictions = []
# 약한 학습기를 하나씩 추가하면서 학습
for i in range(1, n_estimators + 1):
reg.n_estimators = i
reg.fit(X, y)
predictions.append(reg.predict(X))
# 2x2 형태로 그림 그리기
fig, axes = plt.subplots(2, 2, figsize=(10, 6))
axes = axes.ravel()
# 각 단계별로 예측 시각화
for i, ax in enumerate(axes):
step = i * (n_estimators // 4)
ax.scatter(X, y, c='k', label='Data')
ax.plot(X, predictions[step], lw=2, label=f'Step {step + 1}', color=plt.cm.viridis(step / n_estimators))
ax.legend(loc='best')
ax.set_xlabel('X')
ax.set_ylabel('y')
ax.set_title(f'Step {step + 1}')
plt.suptitle('GBM Weak to Strong Learners (2x2 Visualization)', fontsize=15)
plt.tight_layout(rect=[0, 0, 1, 0.96])
plt.show()

그림을 살펴보면, GBM에서 weak learner에서 strong learner로 어떻게 변화하는지 확인할 수 있다. 각 단계에서 Step이 증가할수록 예측이 점차 실제 데이터에 가까워지는 것을 볼 수 있다. GBM을 살펴봤으니, 이제 우리의 목적인 XGB와 LGBM에 대해 알아보자.
XGBoost(eXtreme Gradient Boosting)
XGBoost는 앞서 설명한 Gradient Boosting의 확장된 형태로, 효율성, 유연성, 확장성을 제공한다. 최근 kaggle 등 다양한 대회에서 우승한 모델의 일부로 자주 등장하고 있으며, 분류 및 회귀 문제 해결에 널리 사용된다. XGB는 많은 특징과 학습 과정이 GBM과 유사하지만, 몇 가지 중요한 차이점이 있다. 아래는 차이점에 대한 설명을 찾아보았다.
특징
- 규제화(Regularization) : L1과 L2 Regularization을 통해 모델의 복잡성을 제어하고 overfitting을 방지한다. 이러한 Regularization은 Tree가 너무 복잡해지는 것을 방지하고 일반화 성능을 향상시킨다.
- L1 Regularization(Lasso) : L1을 적용하면 weight가 sparse(희소)하게 되는 경우가 많다. 즉, 작은 weight 값이 0이 된다. 따라서 weight 수를 줄이고 small set을 만들고 싶으면 L1을 사용하는데, 이러한 성질을 feature selection 이라고 한다.
- L2 Regularization(Ridge) : L2는 모든 가중치를 균등하고 작게 유지하려고 하므로, 일반적으로 학습 시 더 좋은 결과를 보여준다.
- 가지치기(Pruning) : GBM은 일반적으로 Tree를 Greedy Algorithm으로 성장시키지만, XGBoost는 Tree를 성장시킨 후 필요에 따라 pruning Algorithm을 사용한다.
- 병렬 처리 : XGBoost는 병렬 학습이 가능하도록 설계되어, 데이터셋이 큰 경우 GBM보다 더 빠른 학습을 제공한다.
- 결측값 처리 : XGBoost는 missing value를 자동으로 처리하는 방법을 제공한다.
- 하이퍼 파라미터 : L1, L2의 weight 설정과 colsample_byree로 Tree에서 사용할 특성을 sampling 하는 등 기존 GBM의 하이퍼 파라미터에서 추가된 몇 가지를 사용해 더 유연하고 효율적으로 모델을 구성할 수 있다.
- 교차 검증(Cross Validation) : XGB는 자체적으로 Train data와 Validation Data에 대한 교차 검증을 수행하고, 이를 기반으로 loss 값이 일정 수치에서 더 이상 감소하지 않거나 올라가는 경우 설정한 반복 횟수보다 조기에 학습을 중단시켜 최적화된 값을 탐색 할 수 있다.
아래에는 Random Forest와 동일한 Data로 분류 모델을 만들어 보았다.
from xgboost import XGBClassifier, plot_tree
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import matplotlib.pyplot as plt
# 데이터 로드
data = load_breast_cancer()
X = data.data
y = data.target
# 트레이닝 및 테스트 데이터 분리
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# XGBoost 모델 생성
xgb_model = XGBClassifier(n_estimators=100, random_state=42, use_label_encoder=False, eval_metric='mlogloss')
# 모델 학습
xgb_model.fit(X_train, y_train)
# 예측 수행
xgb_y_pred = xgb_model.predict(X_test)
# 정확도 출력
xgb_accuracy = accuracy_score(y_test, xgb_y_pred)
xgb_accuracy * 100 # 95.62%
# 첫 번째 나무 출력
fig, ax = plt.subplots(figsize=(15, 15))
plot_tree(xgb_model, num_trees=0, ax=ax)
plt.savefig('xgb_plot.png')
plt.show()

XGBoost는 랜덤 포레스트와 같이 트리 구조를 사용하며, 각각의 트리가 데이터를 분할한다. 그러나 XGBoost와 Random Forest의 주요 차이점은 XGBoost가 Gradient Boosting을 사용한다는 것이다. 이로 인해 XGBoost의 각 트리는 연속적인 학습 과정에서 오차를 수정하며, 노드 분할을 최적화한다다. 이러한 접근 방식은 모델의 성능을 향상시키며, 각 트리가 전체 예측에 기여하는 방식을 조정한다. 또한 Tree가 독립적인 Random Forest와 달리 XGB는 Tree간의 연관성이 있어서 깊이가 비교적 얕은 것을 확인할 수 있다. 다음으로는 LightGBM에 대해 알아보자.
LightGBM(Light Gradinet Boosting Mechine)
LightGBM은 Dataset이 크고, 분산 컴퓨팅 환경이면 높은 효율성과 빠른 속도를 제공한다. 특히, 기존의 GBM 기법들과 달리 leaf 중심의 Tree 분할 알고리즘을 사용하는 독특한 특징을 가지고 있다. 아래에는 LGBM의 특징에 대해 정리해보았다.
특징
- 리프 중심 분할(Leaf Wise) : 대부분의 Tree 기반 알고리즘이 Depth 중심으로 Tree를 성장시키는 반면, LightGBM은 leaf 중심으로 분할한다. 이는 Tree의 균형을 맞추지 않고, loss를 최대한 줄이는 leaf를 계속 분할시키는 방식이다. 즉, 최대 손실 감소를 우선시하므로 중요한 분할부터 수행하게 되어 더 정확한 예측이 가능하다.
- 범주형 특성 처리 : LGBM은 범주형 특성을 수치형으로 변환할 필요가 없다. 최적의 분할을 찾기 위해 자체적으로 Encoding을 수행하여 성능을 향상시키고, 이를 학습에 사용한다.
- 빠른 속도 : LGBM은 XGBoost보다 더 빠른 학습 속도를 제공한다. 또한 메모리를 적기 차지하고 GPU를 활용할 수 있어 대용량 데이터 학습에도 유리한 조건을 가지고 있다. 하지만, 빠른 속도만큼 overfitting에 민감하기 때문에 데이터의 size가 작은 경우 다른 알고리즘을 선택하는 것이 유리하다.
LGBM도 간단한 예시를 통해 살펴보자.
from lightgbm import LGBMClassifier, plot_tree
from sklearn.datasets import load_breast_cancer
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
import matplotlib.pyplot as plt
# 데이터 로드
data = load_breast_cancer()
X = data.data
y = data.target
# 트레이닝 및 테스트 데이터 분리
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# LightGBM 모델 생성
lgbm_model = LGBMClassifier(n_estimators=100, random_state=42)
# 모델 학습
lgbm_model.fit(X_train, y_train)
# 예측 수행
lgbm_y_pred = lgbm_model.predict(X_test)
# 정확도 출력
lgbm_accuracy = accuracy_score(y_test, lgbm_y_pred)
print(f'Accuracy: {lgbm_accuracy * 100:.2f}%') # 96.49%
# 첫 번째 나무 출력
fig, ax = plt.subplots(figsize=(15, 15))
plot_tree(lgbm_model,ax=ax)
plt.savefig('lgbm_plot.png')
plt.show()

이번 주에는 배깅과 부스팅에 대해 설명하고, 논문에서 사용된 3가지 모델을 동일한 데이터를 사용해서 비교해 보았다. 약간의 차이가 있지만, 전반적으로 나쁘지 않은 성능을 보여줌을 확인할 수 있다. 선행연구의 방법론에 대해 알아보았으니, 이제부터는 주제를 생각해보고 어떤 데이터로 어떤 분석을 해볼지 정해보자.
출처
황희수,「코스피 방향 예측을 위한 하이브리드 머신러닝 모델」,한국융합학회,한국융합학회논문지 제12권 제6호, 9-16 쪽(2021)
'데이터 분석 > 주식 선행연구 분석' 카테고리의 다른 글
| [주식] 기계학습을 활용한 주식 데이터 분석 - 6주차 (0) | 2023.08.14 |
|---|---|
| [주식] 기계학습을 활용한 주식 데이터 분석 - 5주차 (0) | 2023.08.08 |
| [주식] 기계학습을 활용한 주식 데이터 분석 - 3주차 (2) | 2023.08.05 |
| [주식] 기계학습을 활용한 주식 데이터 분석 - 2주차 (0) | 2023.08.04 |
| [주식] 기계학습을 활용한 주식 데이터 분석 - 1주차 (0) | 2023.08.04 |