Study/Data Analysis

[DACON] 중고차 가격 예측 경진대회 (2)

ChoiSenn 2022. 7. 10. 18:15

 

 

 

코랩에서 한 번에 긁어오는 법을 알았다!

 

 

4. 모델링

4-1. 변수 정의


해당 데이터 분석의 목적은 주어진 중고차 상세 정보 데이터를 이용하여 target 변수인 자동차 가격을 예측하는 것입니다.

이를 바탕으로 독립변수 X는 중고차 상세 정보 데이터, 종속변수 y는 중고차 가격인 target 변수가 될 것입니다.

X = train.drop(['id', 'target'], axis = 1) #training 데이터에서 피쳐 추출
y = train.target #training 데이터에서 중고차 가격 추출

기본적인 인덱스를 의미하는 id 칼럼과 종속변수가 될 target 변수를 제외한 나머지 데이터를 독립변수 X로 설정하였습니다.

그리고 중고차 가격인 target 칼럼만 추출하여 종속변수로 설정하였습니다.

 

 

4-2. train/validation 정의


주어진 데이터 중 train 데이터셋을 모두 이용하여 학습시킨 뒤, 바로 test 데이터셋에 넣어 예측하면 train 데이터셋으로 학습시킨 모델이 제대로 만들어졌는지, 즉 유의한지 확인할 수 없습니다.

따라서 train 데이터셋의 일부는 모델을 학습하는 데 사용하고, train의 나머지 데이터셋은 모델이 유의한지 확인하는 용도로 사용합니다.
높은 정확도가 출력되어 유의하다는 것이 확인되었을 때 train 데이터셋을 전부 이용하여 학습하고 test 데이터셋에 적용하도록 합니다.


train 데이터 셋을 각각 75%와 25%로 나누어, 학습시키는 용도로 사용될 train_data 데이터셋과 모델이 유의한지 검증을 위한 val_dat 데이터셋으로 나누어줍니다.

from sklearn.model_selection import train_test_split

data = train.drop('id', axis = 1).copy() #필요없는 id열 삭제
train_data, val_data = train_test_split(data, test_size=0.25) # 25프로로 설정
train_data.reset_index(inplace=True) #전처리 과정에서 데이터가 뒤섞이지 않도록 인덱스를 초기화
val_data.reset_index(inplace=True)

print('학습시킬 train 셋 : ', train_data.shape)
print('검증할 val 셋 : ', val_data.shape)
 
학습시킬 train 셋 :  (759, 12)
검증할 val 셋 :  (254, 12)
 

기본적인 인덱스를 나타내는 id 칼럼을 제거한 후, sklearn의 train_test_split 메소드를 이용하여 75%의 학습용 데이터셋과 25%의 검증용 데이터셋으로 나눠주었습니다.

train 데이터셋(총 761개의 데이터)으로 모델을 학습시키고 val 데이터셋(총 254개의 데이터)으로 모델의 성능이 유의한지 검증하도록 합니다.

 
 
 
train_data_X = train_data.drop(['target', 'index'], axis = 1) #training 데이터에서 피쳐 추출
train_data_y = train_data.target #training 데이터에서 target 추출

val_data_X = val_data.drop(['target', 'index'], axis = 1) #training 데이터에서 피쳐 추출
val_data_y = val_data.target #validation 데이터에서 target 추출
 

나뉜 train 데이터 셋에서 target변수와 일련번호를 뜻하는 index 칼럼을 제외하여 train_data_X로 정의하고 target 변수만 추출하여 train_data_y로 정의하였습니다.

val 데이터 역시 target변수와 일련번호를 뜻하는 index 칼럼을 제외하여 val_data_X로 정의하고 target 변수만 추출하여 val_data_y로 정의하였습니다.

 



5. XGBoost

5-1. XGBoost의 정의


우선 Boosting이란, 여러 개의 약한 의사결정나무를 조합해서 사용하는 앙상블 기법 중 하나입니다. 즉, 약한 예측 모형들을 통해 가중치를 정하고, 순차적으로 다음 학습 모델에 반영하여 강한 예측 모형을 만들어나가는 것입니다.


XGBoost란 Extreme Gradient Boosting의 약자로, Boosting 기법을 이용해 구현한 알고리즘인 Gradient Boost를 병렬 학습이 지원되도록 구현한 라이브러리입니다.

Regression, Classfication 문제를 모두 지원하며, 성능과 자원 효율이 좋아 많이 사용되고 있는 알고리즘입니다.


5-2. XGBoost의 하이퍼파라미터


XGBoost에는 다수의 하이퍼파라미터가 존재하며, 일반/부스터/학습 과정 파라미터로 나뉩니다.

  • 일반 파라미터 : 부스팅을 수행할 때 트리를 사용할지, 선형 모델을 사용할지 등을 선택합니다.
  • 부스터 파라미터 : 선택한 부스터에 따라 적용할 수 있는 파라미터 종류가 다릅니다.
  • 학습 과정 파라미터 : 학습 시나리오를 결정합니다.

일반 파라미터

  • booster : 어떤 부스터 구조를 쓸지 결정합니다. 의사결정기반모형(gbtree), 선형모형(gblinear), dart가 있습니다.
  • n_jobs : 사용되는 병렬 스레드 수를 결정합니다.
  • verbosity : 0(무음), 1(경고), 2(정보), 3(디버그)를 의미합니다.

부스터 파라미터

  • learning_rate : 기본값은 0.3이며 높을수록 과적합되기 쉽습니다.
  • n_estimators : 생성할 weak learner의 수이며 기본값은 100입니다. learning_rate가 낮을 때에는 n_estimators를 높여야 과적합이 방지됩니다.
  • max_depth : 적절한 값이 제시되어야 하며 보통 3~10 사이 값이 적용됩니다. 높아질수록 모델의 복잡도가 커져 과적합되기 쉽습니다. 기본값은 6.
  • min_child_weight : 관측치에 대한 가중치 합의 최소입니다. 높아질수록 과적합이 방지됩니다. 기본값은 1.
  • subsample : 학습에 사용하는 데이터 샘플링 비율입니다. 보통 0.5~1 정도가 사용되며, 값이 낮을수록 과적합이 방지됩니다.
  • colsample_bytree : 각 트리 별 사용된 feature의 퍼센테이지입니다. 보통 0.5~1 정도로 사용됩니다. 값이 낮을수록 과적합이 방지됩니다.

학습 과정 파라미터

  • objective : reg:squarederror는 제곱 손실이 있는 회귀를 뜻하며 기본 값입니다. binary:logistic은 이항 분류 문제 로지스틱 회귀 모형을 예측 확률을 반환합니다. multi:softmax는 다항 분류 문제에 사용됩니다. multi:softprob는 각 클래스 범주에 속하는 예측확률을 반환합니다.
  • eval_metric : 모델의 평가 함수를 조정하는 함수입니다. rmse, mae, logloss 등으로 설정할 수 있습니다.
  • seed : 재현 가능하도록 난수를 고정시킵니다.

과적합 방지를 위해서는 learning rate, max_depth 등을 낮추고 n_estimators, min_child_weight, gamma 등은 높이는 조정을 할 수 있습니다.




6. 모델링

6-1. 모델링 및 하이퍼파라미터 튜닝


일반적으로 하이퍼파라미터 튜닝은

  1. high learning rate(0.05 - 0.3)를 선택하고 이 학습률에 맞는 tree 개수를 선정한다.
  2. tree-specific parameter를 수정한다.
  3. regularization parameter를 수정한다.
  4. 학습률을 낮추고 다시 반복한다.

의 순서로 진행됩니다.


우선, Learning rate 와 estimator의 수를 설정합니다.

초기값으로는 각 파라미터마다 적정값에 따라 max_depth는 4~6, min_child_weight는 1, subsample는 0.9, colsample_bytree는 0.8, scale_pos_weight는 1로 시작하여 튜닝해나갈 것입니다.

import xgboost as xgb
from sklearn.metrics import mean_squared_error

xgb_reg = xgb.XGBRegressor(objective ='reg:squarederror', 
                           colsample_bytree=0.8, 
                           learning_rate=0.3, 
                           max_depth=6, 
                           n_estimators=100, 
                           subsample=0.9, 
                           min_child_weight=1,
                          scale_pos_weight=1,
                           seed=1)
 

objective(목적 함수)는 기본 값을 설정해주었고, seed 값은 결괏값 고정을 위해 1로 지정해주었습니다.

파라미터들은 우선 위처럼 적정값의 평균 정도에 맞추어 설정한 뒤, XGBRegressor()를 이용해 XGBoost 회귀 모델로 모델을 정의하였습니다.

 
xgb_reg.fit(train_data_X, train_data_y, eval_set=[(train_data_X, train_data_y), (val_data_X, val_data_y)], early_stopping_rounds=300, verbose=False)
 
XGBRegressor(colsample_bytree=0.86, eta=0.01, learning_rate=0.3, max_depth=6,
             objective='reg:squarederror', seed=1, subsample=0.9)
 

fit()을 이용하여 train 데이터로 모델을 학습하였습니다. eval_set은 val 데이터로 지정하여, 검증 세트를 val 데이터로 하여 예측 오류값을 줄이고 오버 피팅은 줄일 수 있도록 하였습니다.

import numpy as np
from sklearn.metrics import mean_squared_error

def nmae(true, pred):

    mae = np.mean(np.abs(true-pred))
    score = mae / np.mean(np.abs(true))
    
    return score

y_hat = xgb_reg.predict(val_data_X) # y예측
print(f'모델 NMAE: {nmae(val_data_y,y_hat)}')
 
모델 NMAE: 0.35751279308806594

예측한 값을 NMAE 평가산식으로 출력하자, 0.3575 정도의 값이 나온 모습입니다.

하이퍼파라미퍼 튜닝을 하지 않았을 때의 XGBoost를 이용한 예측 결과의 오차값은 35.7% 정도로, 의사결정나무나 앙상블 기법을 사용했을 때보다 약간 낮은 오차값을 보이는 모습입니다.


오차값을 줄이고 정확도를 높이기 위하여 하이퍼파라미터를 튜닝합니다.

우선, learning_rate와 n_estimators를 조정하여 오차를 줄입니다.

learning_rate는 낮은 값일수록 모델이 견고해지고 높을수록 과적합의 위험이 있습니다.
n_estimators는 높을수록 과적합을 예방할 수 있습니다.

두 파라미터를 조정해가며 최적의 오차값이 나오는 값을 찾고, 최종적으로 learning_rate는 0.000997, n_estimators는 60000로 설정해주었습니다.

 
xgb_reg = xgb.XGBRegressor(
    objective ='reg:squarederror', 
    colsample_bytree=0.8, 
    learning_rate=0.000997, 
    max_depth=6, 
    n_estimators=60000, 
    subsample=0.9, 
    min_child_weight=1,
    scale_pos_weight=1,
    seed=1)
xgb_reg.fit(train_data_X, train_data_y, eval_set=[(train_data_X, train_data_y), (val_data_X, val_data_y)], early_stopping_rounds=300, verbose=False)
y_hat = xgb_reg.predict(val_data_X) # y예측
print(f'모델 NMAE: {nmae(val_data_y,y_hat)}')
 
모델 NMAE: 0.3289287196312952
 

예측한 값을 NMAE 평가산식으로 출력하자, 0.3289 정도의 값이 나온 모습입니다.

오차값은 33% 정도로, 튜닝 전보다 줄어들은 모습입니다.


다음으로는 max_depth와 min_child_weight 하이퍼파라미터를 조정합니다.

max_depth는 값이 높아질수록 과적합 될 위험이 있습니다. 통상적인 범위(3~10) 내에서 오차값이 충분히 줄어들 정도로 높여서 10으로 설정하였습니다.

min_child_weight는 높아질수록 과적합이 방지됩니다. 오차값이 크게 상승하지 않는 한에서 높여서 3으로 설정하였습니다.

xgb_reg = xgb.XGBRegressor(
    objective ='reg:squarederror', 
    colsample_bytree=0.8, 
    learning_rate=0.000997, 
    max_depth=10, 
    n_estimators=60000, 
    subsample=0.9, 
    min_child_weight=3,
    scale_pos_weight=1,
    seed=1)
xgb_reg.fit(train_data_X, train_data_y, eval_set=[(train_data_X, train_data_y), (val_data_X, val_data_y)], early_stopping_rounds=300, verbose=False)
y_hat = xgb_reg.predict(val_data_X) # y예측
print(f'모델 NMAE: {nmae(val_data_y,y_hat)}')
 
모델 NMAE: 0.306278773499749
 

예측한 값을 NMAE 평가산식으로 출력하자, 0.3062 정도의 값이 나온 모습입니다.

오차값은 30.6% 정도로, max_depth와 min_child_weight를 튜닝하기 전보다 줄어든 모습입니다.


다음으로는 colsample_bytree와 subsample 하이퍼파라미터를 조정합니다.

두 파라미터 모두 범위값(0~1) 이내에서 오차값이 줄어들도록 조정해주었습니다.

최종적으로 colsample_bytree는 0.8, subsample은 0.94로 설정하였습니다.

xgb_reg = xgb.XGBRegressor(
    objective ='reg:squarederror', 
    colsample_bytree=0.8, 
    learning_rate=0.000997, 
    max_depth=10, 
    n_estimators=60000, 
    subsample=0.94, 
    min_child_weight=3,
    scale_pos_weight=1,
    seed=1)
xgb_reg.fit(train_data_X, train_data_y, eval_set=[(train_data_X, train_data_y), (val_data_X, val_data_y)], early_stopping_rounds=300, verbose=False)
y_hat = xgb_reg.predict(val_data_X) # y예측
print(f'모델 NMAE: {nmae(val_data_y,y_hat)}')
 
모델 NMAE: 0.30484334905127464
 

예측한 값을 NMAE 평가산식으로 출력하자, 0.3062 정도의 값이 나온 모습입니다.

오차값은 30.4% 정도로, XGBoost의 하이퍼파라미터를 튜닝하기 전보다 약 5.3% 정도 감소한 것을 확인할 수 있습니다.


 

7. train 데이터셋 + val 데이터셋 100% 활용하는 모델 만들기


학습한 모델의 성능이 어느 정도인지 파악하였으니, 나눠두었던 train 데이터셋과 val 데이터셋을 100% 모두 이용하여 모델을 학습시킵니다.

train_X = train.drop(['id', 'target'], axis = 1) #training 데이터에서 피쳐 추출
train_y = train.target #training 데이터에서 target 추출
 
xgb_reg.fit(train_X, train_y, eval_set=[(train_X, train_y)], early_stopping_rounds=300, verbose=False)
 
XGBRegressor(colsample_bytree=0.8, eta=0.01, learning_rate=0.000997,
             max_depth=10, min_child_weight=3, n_estimators=60000,
             objective='reg:squarederror', seed=1, subsample=0.5)
 

 

8. 예측하기


학습한 모델을 이용하여 test 데이터셋을 예측해봅니다.

변수의 형태 및 개수를 동일하게 해주기 위해, test 데이터셋에도 앞서 train 데이터셋에 진행하였던 전처리 과정을 동일하게 진행해줍니다.

 
check_missing_col(test) # 결측치 확인

test = test.drop('id', axis = 1) #분석에 필요없는 열 삭제

test['brand'] = test['title'].apply(lambda x : x.split(" ")[0])

temp = clean_text(test['paint']) #메소드 적용
test['paint'] = temp
test['paint'] = test['paint'] = test['paint'].apply(lambda x : 'blue' if x.find('blue') >= 0 else x)
test['paint'] = test['paint'] = test['paint'].apply(lambda x : 'red' if x.find('red') >= 0 else x)
test['paint'] = test['paint'] = test['paint'].apply(lambda x : 'green' if x.find('green') >= 0 else x)
test['paint'] = test['paint'] = test['paint'].apply(lambda x : 'white' if x.find('white') >= 0 else x)
test['paint'] = test['paint'] = test['paint'].apply(lambda x : 'grey' if x.find('grey') >= 0 else x)
test['paint'] = test['paint'] = test['paint'].apply(lambda x : 'grey' if x.find('gery') >= 0 else x)
test['paint'] = test['paint'] = test['paint'].apply(lambda x : 'grey' if x.find('gray') >= 0 else x)
test['paint'] = test['paint'] = test['paint'].apply(lambda x : 'ash' if x.find('ash') >= 0 else x)
test['paint'] = test['paint'] = test['paint'].apply(lambda x : 'brown' if x.find('brown') >= 0 else x)
test['paint'] = test['paint'] = test['paint'].apply(lambda x : 'silver' if x.find('silver') >= 0 else x)
test['paint'] = test['paint'] = test['paint'].apply(lambda x : 'silver' if x.find('sliver') >= 0 else x)
test['paint'] = test['paint'] = test['paint'].apply(lambda x : 'black' if x.find('black') >= 0 else x)
test['paint'] = test['paint'] = test['paint'].apply(lambda x : 'gold' if x.find('gold') >= 0 else x)
test['paint'] = test['paint'] = test['paint'].apply(lambda x : 'wine' if x.find('whine') >= 0 else x)

test = label_encoder(test, le)
 
결측치가 존재하지 않습니다
 

전처리를 완료한 test 데이터셋을 이용하여 앞서 만들어두었던 XGBoost 모델로 예측합니다.

y_pred = xgb_reg.predict(test)
y_pred[0:5]
 
array([12399618. ,  4556846.5,  6437247. ,   761937.8,  2391824.2],
      dtype=float32)



9. 파일 저장 및 제출


학습한 모델을 이용한 예측 결과를 csv 파일로 제작하여 제출합니다.

# 제출용 sample 파일을 불러옵니다.
submission = pd.read_csv('/content/drive/MyDrive/Colab Notebooks/sample_submission.csv')
submission.head()
 

제출용으로 제시된 sample 파일을 pandas 모듈의 read_csv를 통해 읽어옵니다.

# 위에서 구한 예측값을 그대로 넣어줍니다.
submission['target'] = y_pred
submission.to_csv('/content/drive/MyDrive/Colab Notebooks/XGBoost3.csv', index=False)

# 데이터가 잘 들어갔는지 확인합니다.
submission.head()
 
 

앞서 모델을 이용해 예측했던 데이터 값들을 sample 데이터프레임의 target 칼럼에 넣어줍니다.

예측값을 넣은 데이터프레임을 to_csv()를 이용하여 csv 파일로 저장합니다. 이때, index=False를 지정하여 추가적인 id를 지정하지 않도록 합니다.