Study/머신러닝

머신러닝 Advanced_ 심화 ML 모델

김 도경 2024. 11. 6. 19:12

[2024.11.06] 필수 온라인 강의 Part16 Machine Learning Advanced CH06 심화 ML 모델

이론

 

  • Bagging vs Boosting
    - Bagging (Recap.)
       - 부트스트랩을 통해 표본을 여러번 뽑아 모델을 학습시키고, 결과를 집계(Aggregation) 하는 앙상블 방법

    - Boosting
      - Boosting은 성능이 약한 모델의 예측값에 대한 오차를 이용해 모델을 더욱 최적값으로 보완하며 성능을 높이는 방법
      - 이전 모델이 잘 예측하지 못한 부분에 집중하여 찾아낸 오차를 최적화에 사용하는 점이 가장 큰 차이

  • Boosting Algorithms
    - 이전 모델의 예측 결과를 다음 모델 구축에 어떻게 활용하는지에 따라 아래의 두방법이 존재
    AdaBoost (Adaptive Boosting)
             - 이전 모델이 틀리게 예측한 값에 가중치를 부여하여, 다음 모델의 조정에 사용
             - 매 스텝마다 오차가 큰 데이터에 대해서는 큰 가중치를, 오차가 작은 데이터에 대해서는 낮은 가중치를 부여

    GBM (Gradient Boosting Model)
    - 오차를 계산하는 함수 (Loss function)을 두고, 함수의 Gradient를 사용하여 오차가 최소가 되도록 다음 모델의 조정에 사용
    - 정답과 예측값의 차이를 통해 데이터에 가중치를 두어 다음 모델이 조정되었던 이전의 Adaboost와는 다르게, 정답과 이전 모델의 예측값으로 Gradient를 계산하여 다음 모델을 조정하는 Boosting 알고리즘
    - GBM을 기반으로 훈련 및 추론 속도와 메모리 효율성, 정확도를 향상시킨 다양한 알고리즘
    - 목적에 따라 오차(Loss)를 계산하는 손실함수(Loss function)을 두고, 손실 함수로부터 Gradient를 계산하여 모델을 업데이트하는 Gradient Descent를 사용
             - 대표적으로 XGBoost, LightGBM, CatBoost으로 구분

    - Gradient Descent (with Regression)
       - 오차함수의 최소값을 찾기 위한 반복 최적화 알고리즘
       - 오른쪽 그림은 몸무게를 통해 키를 예측하는 예시 데이터의 모습
       - 녹색 선은 현재 모델의 예측 값 (Prediction Value)
       - 파란 점은 관측 값 (True Value)
       - 실제 값과 예측 값의 차이를 잔차 (Residual)라고 하며 아래와 같이 표현
       - “실제 값” - “현재 모델의 예측값” = “다음 모델이 예측해야 할 값”
       - “실제 값” = “현재 모델의 예측값” + “다음 모델의 예측값”
       - 점진적으로 잔차를 줄이는 방향으로 학습되며, 실제 값에 최적화
       - 앞선 잔차를 계산하는데에 목적에 맞는 다양한 오차함수(Loss Function)이 존재 (아래 예시는 MSE Loss를 사용)
       - 초기 모델의 결과가 아래 왼쪽의 그림과 같을때, 오른쪽 그림의 MSE Loss에서 빨간점이 현재 모델의 Loss값으로 적용
        - 모든 모델의 변화에 대한 Loss의 값을 집계하게 된다면 Loss값이 최소가 되는 지점을 찾아내는 것이 가능
        - Loss값이 최소가 되는 모델은 최적
        - 하지만 사전에 모든 모델을 찾아낼 수 없을 뿐만 아니라, 모든 모델에 대한 Loss를 계산해야하는 점이 비효율
        - 앞선 문제를 오차함수에서 해당 지점 접선의 기울기(Gradient)를 이용한 경사하강법(Gradient Descent)으로 해결
        - 즉, 접선의 기울기는 “X값인 함수 F(x)가 변할 때, Y값인 Loss가 얼마나 변하는지”로 표현
        - 따라서 오차 함수 미분을 통한 기울기(Gradient)를 계산한다면, 다음의 모델이 어느 방향(+, -)으로 얼마나(값) 이동해야 하는지를 추측 가능
        - 현재 모델의 Negative Gradient를 통해 다음의 모델을 추론하는 과정을 Loss가 최소가 되는 지점이 될때까지 반복

  • Regularization : 과적합을 줄이는 방법
    - Overfitting
        - 학습 데이터셋으로 학습한 모델의 예측값을 통한 잔차가 0이 되며, 학습세트에 과하게 적합(Overfitting)되는 문제 발생 
        - 과적합을 완화시켜주는 Regularization방법으로 Subsampling, Shrinkage, Early Stopping을 제안

    - Subsampling
        - 원본 데이터의 일부분만을 추출해서 다음 학습에 사용
               ○ Sample A -> 첫번째 모델 학습
               ○ Sample B -> 두번째 모델 학습
               ○ Sample C -> 세번째 모델 학습
        - 데이터를 sampling하면서 조금씩 다른 분포의 데이터에 대해 학습이 이루어지도록 하여 과적합을 방지

    - Shrinkage
        - 추가로 추정되어지는 모델의 영향력을 줄이는 방법
        - Learning rate (학습률) 파라미터를 주어 강도를 조절
        - 즉, 모델을 어느 정도로 Update할 것인지를 결정하여 과적합을 방지하는 방법

    - Early stopping
        - 학습 데이터에 대한 모델학습이 더 이상 검증 세트에 대해 개선되어지지 않을 때, 훈련을 중지하는 정규화 기술
        - 모델 학습을 진행하면서 검증 세트에 대해 최고성능을 가지는 때의 모델의 상태를 저장해두어, 일정 기준이상 성능 개선이 이루지지 않을 경우 저장해두었던 모델을 마지막 최상의 모델로 사용

  • LightGBM
    - GBM은 속도가 너무 느려서, GBM을 기반으로 성능의 감소없이 더욱 빠른 학습을 이루는 LightGBM 사용
    - 훈련 속도, 효율성과 메모리 사용량을 줄이면서 성능을 높인 Gradient Boosting Machine
    - GBM보다 대규모 데이터 처리 및 학습에 적합
    - LightGBM: A Highly Efficient Gradient Boosting Decision Tree (2017)

    GBM의 문제점
    - 앞선 GBM에서는 모든 Feature, 모든 Data instance의 Scan을 통해 Gradient를 측정
    - 계산복잡성이 Feature, Instance의 수에 비례하게 되어 데이터의 크기가 커질수록 시간이 많이 소요
    - 위 문제들을 GOSS, EFB라는 방법을 통해 개선
          - 전체 데이터를 사용하는데에서 발생하는 시간/연산적인 비용을 효율적으로 개선

    - GOSS : Gradient-based One-Side Sampling
          - Gradient를 기준으로 인스턴스를 정렬 후, down sampling하는 방법
          - Gradient가 작은 인스턴스 = 잘 훈련된 것
          - Gradient가 큰 인스턴스 = 덜 훈련된 것    : 학습에 더 참여 시켜야함
          -  topSet과 randSet을 합쳐서 다음의 학습에 사용
                - topSet : k개의 큰 Gradient를 가지는 인스턴스
                - randSet : 나머지 (n-k)개의 인스턴스에서 무작위로 샘플링

    - EFB : Exclusive Feature Bundling
          -  입력 Feature의 수를 줄이는 방법 
              - Feature bundling을 통해 복잡도를 더욱 낮추는 방법
          - GOSS를 통해 Data관점의 개선으로 O(Down sampled data * feature)의 복잡도
          - GOSS+EFB를 통해 O(Down sampled data * feature bundle)으로 복잡도를 낮추어 더욱 빠른 계산 가능
         - 번들로 묶을 수 있는 Feature를 식별하는 단계와 Feature들을 병합하는 단계, 총 2단계로 구성

    - Part 1 of EFB : 번들로 묶을 수 있는 Feature 식별
          - 오른쪽 그림에서 각 차원은 Feature, 각 점들은 데이터가 되며 차원이 늘어남에 따라 전체 공간에서의 빈 공간이 늘어나는 형태
                   - 빈 공간은 수치적으로 0을 의미
          - 일반적으로 Feature가 많을 수록 차원이 높아지면서 0의 값들이 많아지게되고 Sparse한 데이터가 구성
                   - 각 Feature들이 서로 동시에 0이 아닌 값을 가질 확률이 낮아짐
          - 여러 Feature들을 하나의 Bundle로 묶었을 때에 모두 0의 값을 가질 확률이 높음
          - Feature의 값이 모두 0일 경우 Bundle의 값은 손실없이 0으로 둘 수있기에, 원본 데이터의 손실이 적은 접근이 가능
          - 따라서, Bundling을 위해서는 각 Feature간에 0이 아닌 값을 가지는 경우에 집중한 효율적인 방법이 필요
          - 각 Bundle에서 허용하는 최대 Conflict count를 정의
                - 최대 Conflict count가 클수록 더욱 많은 Feature가 묶이고 Bundle은 줄어들면서, 학습시간은 감소하지만 값의 손실은 증가

    - Part 2 of EFB : Feature 병합
          - Bundle내에서 큰 Degree를 가진 Feature가 기준 Feature로 선정
          - 다른 Feature와 가장 많은 Conflict를 발생한 Feature
          - Bundle내의 값들을 조합해 하나의 값으로 변환하는데에 있어 가장 큰 손실을 초래할 수 있음
          - 해당 feature를 기준으로 최소한의 손실로 값을 변환할 수 있는 방법을 사용
          - 기준 Feature의 가장 많은 Conflict가 발생한 경우(최댓값)를 찾고, Bundle내 값의 변환에 사용할 Offset으로 지정
          - 기준 Feature가 0의 값을 가지고 Bundle내의 다른 Feature들이 0이 아닐 경우, 다른 Feature의 값에 기준 Feature가 가질 수 있는 최대 Offset을 추가하면서 사용
          - 해당 열의 Data에서는 Conflict가 0이지만, 다른 데이터에 대해서 Offset만큼 기준 Feature에서 Conflict가 발생할 수 있기 때문

    - LightGBM 라이브러리를 이용해 사용 가능
       : https://lightgbm.readthedocs.io/en/stable/index.html
    - 100개 이상의 Parameter를 제공
        : https://github.com/microsoft/LightGBM/blob/master/docs/Parameters.rst
        - 학습을 위한 핵심적인 Parameter를 인지하는 것이 중요

  • Control Strategy
    - boosting Tree
         - 구축 방식을 정하는 Parameter.
         - Default값은 gbdt로, gbdt(gradient boosting decision tree), rf(random forest), dart(dropouts meet multiple additive regression trees) 중 선택 가능
    - data_sample_strategy
         - data sampling의 방식을 정하는 Parameter.
         - default값은 bagging으로, bagging과 goss중 선택가능
    - objective
         - 학습하고자 하는 Task를 정하는 Parameter.
         - default값은 regression으로, regression, regression_l1, huber, fair, poisson, quantile, mape, gamma, tweedie, binary, multiclass, multiclassova, cross_entropy, cross_entropy_lambda, lambdarank, rank_xendcg 중 선택 가능

  • Control Overfitting
    - max_depth
         - 트리의 최대 깊이를 제어하는 Parameter로 Default값은 20 max_depth를 낮추면서 모델의 과적합을 줄일 수 있음
    - num_leaves
         - 트리의 leaf 수를 조절하는 Parameter로 Default값은 31
         - 2^(max_depth)보다 낮은 수를 사용가능하며 복잡성을 제어하여 과적합을 줄일 수 있음
    - min_data_in_leaf
         - 트리의 leaf가 가질 수 있는 최소 인스턴스 수를 조절하는 Parameter로 Default값은 20
         - 큰값을 주면 너무 깊은 Tree가 구성되는 것을 피하면서 과적합을 방지할 수 있지만, 과소적합(Underfitting)이 발생
    - feature_fraction
         - 1.0보다 작은경우 매 iteration에서 Feature들에 대한 하위집합을 Parameter의 비율로 무작위로 선택
         - Default값은 1.0이며, 매 Iteration마다 학습에 사용할 데이터의 양을 조절하여 학습속도를 향상
    - bagging_fraction
         - feature_fraction 과 유사한 Parameter지만, Feature가 아닌 Data(row)에 대한 하위집합을 무작위로 선택한다는 차이가 존재
         - Default값은 1.0
    - device_type
         - 학습을 진행할 장비를 정하는 parameter. default는 cpu로 cpu, gpu, cuda 중 선택 가능
         - cuda옵션 선택시 gpu와 cpu의 학습보다 빠르며, CUDA가 지원되는 gpu환경에서만 사용 가능
         - gpu옵션 선택시 cpu보다 빠르며, CUDA가 지원되지 않는 gpu환경까지 조໿더 넓은 범위에서 사용 가능
         - Note: 더 빠른 학습을 위해서는 max_bin값을 더욱 낮춰주는것이 필요
실습


1. 실습 데이터 준비
데이터 불러오기 및 전처리


2. LightGBM 실습
LightGBM Training API를 활용한 학습/저장/불러오기/추론

# Training API를 사용하기 위해 LightGBM에서 제공하는 Dataset으로 변환
lgb_train = lgb.Dataset(X_train, Y_train)
lgb_valid = lgb.Dataset(X_valid, Y_valid)

# Training API에서는 Parameter를 별도의 Dictionary 형태로 입력
# 간단한 실습을 위해, 가장 기본적인 Parameter들을 사용
params = {
    'boosting_type': 'gbdt', # boosting 방법을 지정합니다. gbdt, rf(random forest), dart중 선택할 수 있습니다.
    'objective': 'regression', # 학습의 목적을 지정합니다. regression, binary, multiclass, lambdarank 등 학습의 목적에 따라 지정할 수 있습니다..
    'metric': {'rmse'}, # 평가과정에서의 평가함수를 지정합니다. l1, l2, rmse, auc 등 다양한 metric을 제공합니다.
    'metric_freq': 10, # 몇번의 반복마다 평가를 진행할지 지정합니다.
    'verbosity': 0 # 학습 중 출력할 로그의 레벨을 지정합니다. "< 0"은 Fatal, "0"은 Error(Warning), "1"은 Info, "> 1"은 Debug 레벨의 로그를 출력합니다.
}

# 위에서 선언한 데이터셋과 parameter를 학습을 위한 함수에 적용
%%time
gbm = lgb.train(
    params,
    lgb_train,
    valid_sets=[lgb_train, lgb_valid], # 평가과정을 진행할 데이터셋을 지정
    callbacks=[lgb.log_evaluation(period=params['metric_freq'], show_stdv=True)] # 평가과정을 parameter의 반복마다 진행하고 출력
    )

 

- 특정 반복마다 평가점수를 확인하며 모델의 학습이 잘 이루어지는지 확인

- rmse로그를 보면, training rmse는 지속적으로 감소하지만, valid rmse는 감소하다가 다시 증가하는 모습

- Parameter 튜닝에서 해당 부분을 해결

# 학습한 모델을 저장
joblib.dump(gbm, 'lightgbm_training_api.pkl')

# 저장한 모델
gbm_trained = joblib.load('lightgbm_training_api.pkl')

# 불러온 모델을 통해 추론을 진행
predicts = gbm_trained.predict(X_valid)
display(f"추론 결과 샘플 : {predicts[:4]}")

# 학습에서 평가셋으로 사용한 세트를 이용한 추론성능 평가를 진행
%%time
RMSE = mean_squared_error(Y_valid, predicts)**0.5

display(f"추론 결과 rmse : {RMSE}")

 

 

LightGBM Scikit-learn API를 활용한 학습/저장/불러오기/추론

# Regression모델을 지정
gbm = lgb.LGBMRegressor(n_estimators=100) # 총 반복 횟수를 지정

# 학습을 진행.
# scikit-learn api를 사용할 시, 별도의 lightgbm Dataset으로 변환을 거치지 않고 원데이터 형태로 사용
%%time
gbm.fit(
    X_train, Y_train, # 학습 데이터를 입력
    eval_set=[(X_train, Y_train), (X_valid, Y_valid)], # 평가셋을 지정
    eval_metric ='rmse', # 평가과정에서 사용할 평가함수를 지정
    callbacks=[lgb.log_evaluation(period=10, show_stdv=True)] # 앞서 지정했던 callback함수와 동일하게 지정.
    )
# 학습한 모델을 저장
joblib.dump(gbm, 'lightgbm_sklearn_api.pkl')

# 저장한 모델
gbm_trained = joblib.load('lightgbm_sklearn_api.pkl')

# 불러온 모델을 통해 추론을 진행
predicts = gbm_trained.predict(X_valid)
display(f"추론 결과 샘플 : {predicts[:4]}")

# 학습에서 평가셋으로 사용한 세트를 이용한 추론성능 평가를 진행
%%time
RMSE = mean_squared_error(Y_valid, predicts)**0.5
display(f"추론 결과 rmse : {RMSE}")

 

Parameters
- LightGBM에서는 100개가 넘는 수 많은 Parameter를 지정 : 이 중 주요 parameter을 적용

# 이전의 LightGBM 학습의 로그에서 확인했던 과적합을 방지하기 위한 튜닝을 진행
# num_leaves, min_data_in_leaf 모두 기존보다 높은 값을 주고, max_depth는 기존보다 낮은 값
# early stopping을 적용하여 학습의 종료 조건을 주고, n_estimators는 가능한 큰 수
%%time

gbm = lgb.LGBMRegressor(n_estimators=100000,                # early stopping을 적용하기에 적당히 많은 반복 횟수를 지정.
                        metric="rmse",
                        data_sample_strategy='goss',        # sampling 방법을 goss로 적용합니다.
                        max_depth=12,                       # default값인 20에서 12로 변경합니다.
                        num_leaves=62,                      # default값인 31에서 62으로 변경합니다.
                        min_data_in_leaf=40                 # default값인 20에서 40으로 변경합니다.
                        )
gbm.fit(X_train, Y_train,
        eval_set=[(X_train, Y_train), (X_valid, Y_valid)],
        eval_metric ='rmse',
        categorical_feature="auto",
        callbacks=[lgb.early_stopping(stopping_rounds=50),         # early stopping을 적용합니다. 50번동안 metirc의 개선이 없다면 학습을 중단
                   lgb.log_evaluation(period=10, show_stdv=True)]  # 10번의 반복마다 평가점수를 로그에 나타남
)
# 학습한 모델을 저장
joblib.dump(gbm, 'tuning_lightgbm_sklearn_api.pkl')

# 저장한 모델을 불러옴
gbm_trained = joblib.load('tuning_lightgbm_sklearn_api.pkl')

# 불러온 모델을 통해 추론을 진행
predicts = gbm_trained.predict(X_valid)
print(f"추론 결과 샘플 : {predicts[:5]}")

# 학습에서 평가셋으로 사용한 세트를 이용한 추론성능 평가를 진행
%%time
RMSE = mean_squared_error(Y_valid, predicts)**0.5
print(f"추론 결과 rmse : {RMSE}")

 

시각화

# 학습된 gbm의 parameter들을 불러옴
gbm_params = gbm_trained.get_params()
print(json.dumps(gbm_params, indent=4)) # 보기 편하게 나타내기 위해, json 형태로 변환

# 학습결과를 시각화
fig = plt.figure(figsize=(8, 8))
plt.subplot(1, 1, 1)
plt.plot(np.arange(len(list(gbm_trained.evals_result_['training']['rmse']))), list(gbm_trained.evals_result_['training']['rmse']), 'b-',
         label='Train Set')
plt.plot(np.arange(len(list(gbm_trained.evals_result_['training']['rmse']))), list(gbm_trained.evals_result_['valid_1']['rmse']), 'r-',
         label='Valid Set')
plt.legend(loc='upper right')
plt.xlabel('Boosting Iterations')
plt.ylabel('Rmse')
fig.tight_layout()
plt.show()

 

-  종목별 rmse 시각화

# 종목별로 valid set에 대한 rmse를 측정

code_li = list(X_valid['LEncodedCode'].unique())  # 종목리스트를 만듬.

valid_rmse_li = []                                # 종목별 rmse를 저장할 리스트를 선언
for code in code_li:                              # 종목별로 rmse측정을 진행
  valid_sets = X_valid.loc[X_valid['LEncodedCode']==code] # 해당 종목의 input

  valid_predicts = gbm_trained.predict(valid_sets, verbosity=-1) # 추론을 진행.
  valid_rmse_li.append(mean_squared_error(Y_valid[valid_sets.index], valid_predicts)**0.5) # 추론결과로 rmse를 계산하고 저장.
  
# 종목별 rmse결과를 dataframe형태로 바꾸고, 오름차순으로 정렬.
valid_rmse_df = pd.DataFrame({'code':code_li, 'rmse':valid_rmse_li})
valid_rmse_df = valid_rmse_df.sort_values(by='rmse', ascending=True).reset_index(drop=True)
# 시각화를 진행

plt.figure(figsize=(15,10))
barplot = sns.barplot(data=valid_rmse_df,         # 종목별 rmse를 barplot으로 시각화합니다.
                      x="code",
                      y="rmse",
                      order=valid_rmse_df['code'])
    barplot.set(xticklabels=[])                       # 종목명을 그래프에 모두 표현하기에는 너무 좁기에, 종목명은 시각화에서 제외합니다.
barplot.set(xlabel=None)
sns.lineplot(x=valid_rmse_df['code'].index, y=[RMSE] * len(valid_rmse_df), label="Total_Valid_RMSE")  # 전체 validset에 대한 rmse를 lineplot으로 시각화합니다.
plt.legend(loc='upper center')
plt.grid(True, axis='y')
plt.show()

 

- 종목 별 예측을 시각화

# 높게 나타난 3개의 종목을 추출.
high_rmse_sample = valid_rmse_df.tail(3) # 이전의 rmse기준으로 오름차순으로 정렬된 dataframe에서 끝부분 3개


# 각 종목별로 예측결과와 실제결과를 함께 시각화.

for idx, row in high_rmse_sample.iterrows():
  code_input = X_valid.index[X_valid['LEncodedCode'] == row['code']].tolist()  # 검증세트의 입력에서 해당 종목의 인덱스

  plt.figure(figsize=(15,6))
  sns.lineplot(x=range(0, len(code_input)), y=np.asarray(Y_valid)[code_input], label="Actual", alpha=1.0)  #  실제 결과들 중 앞선 인덱스를 통해 해당 종목의 실제값만 가져옵니다.
  sns.lineplot(x=range(0, len(code_input)), y=predicts[code_input], label="Predict", alpha=1.0, linestyle='--') # 예측 결과들 중 앞선 인덱스를 통해 해당 종목의 예측값만 가져옵니다.
  origin_code = code_label_encoder.inverse_transform([int(row['code'])])[0] # 종목을 label encoding하기 전의 원본코드값으로 변환합니다.
  code_name = company_data.loc[company_data['code']==origin_code]['company'].values[0] # company data에서 원본코드값에 매칭되는 종목명을 가져옵니다.
  plt.title(f"Code : {code_name}, RMSE : {row['rmse']:.2f} - Prediction Close Price")
  plt.ylabel('Close')
  plt.legend()
  plt.grid(True)
  plt.show()
# 낮게 나타난 3개의 종목을 추출
low_rmse_sample = valid_rmse_df.head(3) # 이전의 rmse기준으로 오름차순으로 정렬된 dataframe에서 앞부분 3개.

# 각 종목별로 예측결과와 실제결과를 함께 시각화.

for idx, row in low_rmse_sample.iterrows():
  code_input = X_valid.index[X_valid['LEncodedCode'] == row['code']].tolist()  # 검증세트의 입력에서 해당 종목의 인덱스.

  plt.figure(figsize=(15,6))
  sns.lineplot(x=range(0, len(code_input)), y=np.asarray(Y_valid)[code_input], label="Actual", alpha=1.0)  #  실제 결과들 중 앞선 인덱스를 통해 해당 종목의 실제값.
  sns.lineplot(x=range(0, len(code_input)), y=predicts[code_input], label="Predict", alpha=1.0, linestyle='--') # 예측 결과들 중 앞선 인덱스를 통해 해당 종목의 예측값.

  origin_code = code_label_encoder.inverse_transform([int(row['code'])])[0] # 종목을 label encoding하기 전의 원본코드값으로 변환.
  code_name = company_data.loc[company_data['code']==origin_code]['company'].values[0] # company data에서 원본코드값에 매칭되는 종목명을 가져옴.
  plt.title(f"Code : {code_name}, RMSE : {row['rmse']:.2f} - Prediction Close Price")
  plt.ylabel('Close')
  plt.legend()
  plt.grid(True)
  plt.show()

 

- 산업군 별 rmse 시각화

# 산업군별로 valid set에 대한 rmse를 측정

industry_li = list(X_valid['LEncodedIndustry'].unique())  # 산업군리스트.

valid_rmse_li = []                                # 산업군별 rmse를 저장할 리스트를 선언.
for industry in industry_li:                              # 산업군별로 rmse측정을 진행
  valid_sets = X_valid.loc[X_valid['LEncodedIndustry']==industry] # 해당 산업군의 input

  valid_predicts = gbm_trained.predict(valid_sets, verbosity=-1) # 추론을 진행

  valid_rmse_li.append(mean_squared_error(Y_valid[valid_sets.index], valid_predicts)**0.5) # 추론결과로 rmse를 계산하고 저장.
  
# 산업군별 rmse결과를 dataframe형태로 바꾸고, 오름차순으로 정렬.
valid_rmse_df = pd.DataFrame({'industry':industry_li, 'rmse':valid_rmse_li})
valid_rmse_df = valid_rmse_df.sort_values(by='rmse', ascending=True).reset_index(drop=True)

# 시각화를 진행
plt.figure(figsize=(15,10))
barplot = sns.barplot(data=valid_rmse_df,         # 산업군별 rmse를 barplot으로 시각화.
                      x="industry",
                      y="rmse",
                      order=valid_rmse_df['industry'])
barplot.set(xticklabels=[])                       # 산업군명을 그래프에 모두 표현하기에는 너무 좁기에, 산업군명은 시각화에서 제외
barplot.set(xlabel=None)
sns.lineplot(x=valid_rmse_df['industry'].index, y=[RMSE] * len(valid_rmse_df), label="Total_Valid_RMSE")  # 전체 validset에 대한 rmse를 lineplot으로 시각화.
plt.legend(loc='upper center')
plt.grid(True, axis='y')
plt.show()

 

- 산업군 별 예측 시각화

# 높게 나타난 3개의 산업군을 추출
high_rmse_sample = valid_rmse_df.tail(3) # 이전의 rmse기준으로 오름차순으로 정렬된 dataframe에서 끝부분 3개

# 각 산업군별로 예측결과와 실제결과를 함께 시각화.

for idx, row in high_rmse_sample.iterrows():
  Industry_input =   X_valid.loc[X_valid['LEncodedIndustry'] == row['industry']].sort_values(by=['LEncodedCode'], ascending=True).index.tolist()

  plt.figure(figsize=(15,5))
  sns.lineplot(x=range(0, len(Industry_input)), y=np.asarray(Y_valid)[Industry_input], label="Actual", alpha=1.0)  #  실제 결과들 중 앞선 인덱스를 통해 해당 산업군의 실제값.
  sns.lineplot(x=range(0, len(Industry_input)), y=predicts[Industry_input], label="Predict", alpha=1.0, linestyle='--') # 예측 결과들 중 앞선 인덱스를 통해 해당 산업군의 예측값.

  origin_industry = industry_label_encoder.inverse_transform([int(row['industry'])])[0] # 산업군을 label encoding하기 전의 원본산업군으로 변환
  plt.title(f"Industry : {origin_industry}, RMSE : {row['rmse']:.2f} - Prediction Close Price")
  plt.ylabel('Close')
  plt.legend()
  plt.grid(True)
  plt.show()
# 낮게 나타난 3개의 산업군을 추출
low_rmse_sample = valid_rmse_df.head(3) # 이전의 rmse기준으로 오름차순으로 정렬된 dataframe에서 앞부분 3개.

# 각 산업군별로 예측결과와 실제결과를 함께 시각화.

for idx, row in low_rmse_sample.iterrows():
  Industry_input =   X_valid.loc[X_valid['LEncodedIndustry'] == row['industry']].sort_values(by=['LEncodedCode'], ascending=True).index.tolist()

  plt.figure(figsize=(15,5))
  sns.lineplot(x=range(0, len(Industry_input)), y=np.asarray(Y_valid)[Industry_input], label="Actual", alpha=1.0)  #  실제 결과들 중 앞선 인덱스를 통해 해당 산업군의 실제값만 가져옵니다.
  sns.lineplot(x=range(0, len(Industry_input)), y=predicts[Industry_input], label="Predict", alpha=1.0, linestyle='--') # 예측 결과들 중 앞선 인덱스를 통해 해당 산업군의 예측값만 가져옵니다.

  origin_industry = industry_label_encoder.inverse_transform([int(row['industry'])])[0] # 산업군을 label encoding하기 전의 원본산업군으로 변환합니다.
  plt.title(f"Industry : {origin_industry}, RMSE : {row['rmse']:.2f} - Prediction Close Price")
  plt.ylabel('Close')
  plt.legend()
  plt.grid(True)
  plt.show()

 

- Feature Importance

# 학습된 모델에서 feature importance를 불러옴.
feat_imp = gbm_trained.feature_importances_
print(f"GBM Feature Importance \n\n {feat_imp}")

# 불러온 feature importance를 feature의 이름과 함께 dataframe의 형태로 저장.
# 저장 후, 가장 영향력이 강한 feature순으로 정렬
sorted_feat_imp = pd.Series(feat_imp, input_cols).sort_values(ascending=False)
print(sorted_feat_imp)

# 구성된 feature importance 결과를 시각화.
plt.figure(figsize=(16,6))
sorted_feat_imp.plot(kind='bar', title='Feature Importances')
plt.ylabel('Feature Importance Score')

 

- plot tree
    - Tree계열의 모델학습에 있어서, Tree의 각 노드가 어떻게 연결되어 있는지 한눈에 확인
    - 각 분기점들의 Feature와 수치, Tree의 깊이와 넓이 등을 보다 쉽게 파악할 수 있으며, 이를 통해 추가적인 Feature Engineering이나 max depth의 조절과 같은 Parameter 튜닝의 방향으로 모델의 다음 개선과정을 탐색

lgb.plot_tree(gbm_trained,
              figsize=(50,20))