데이터사이언스/머신러닝

📈Regression 중고차 거래가 예측하기

ghtis1798 2020. 12. 19. 07:45

중고차 거래가 예측

📊데이터 전처리 및 시각화

Kaggle에 있는 데이터로 중고차 거래가를 예측하는 프로젝트를 해보았습니다.

https://www.kaggle.com/adityadesai13/used-car-dataset-ford-and-mercedes

사용한 라이브러리는 Scikit-learn, Pandas, Numpy와 시각화 라이브러리인 Matplotlib, Seaborn, Plotly를 사용하였고 실행환경은 Colab에서 진행하였습니다.

우선 여러 개의 파일을 UK_car.csv라는 하나의 파일로 합쳐 주었습니다.

import pandas as pd
import numpy as np
import dill as pickle
import matplotlib.pyplot as plt
import plotly.express as px
import plotly.graph_objects as go
import plotly.figure_factory as ff
import seaborn as sns
import dill as pickle
import warnings

from sklearn.linear_model import LinearRegression, Ridge, Lasso, ElasticNet
from lightgbm import LGBMRegressor  
from xgboost import XGBRegressor
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score
from sklearn.model_selection import cross_val_score, KFold
from sklearn.model_selection import GridSearchCV
from scipy.stats import skew

warnings.filterwarnings('ignore')
pd.set_option('display.max_rows', 400)

UK_car의 데이터 정보입니다.

data = pd.read_csv(r'UK_car.csv')
used_car = data.copy()
used_car.head()

데이터를 살펴보면 다음과 같습니다.

brand - 자동차 브랜드

model - 모델명

year - 연식

price - 가격

transmission - 구동방식

mileage - 주행거리 (mile)

fuelType - 연료 형태

mpg - 연비 (miles per gallon)

engineSize - 엔진 크기

영국에서 수집한 데이터기 때문에 주행거리와 연비가 mile과 gallon으로 된 점이 다릅니다.

전체 데이터를 보니 Row수가 108540인데 mpg값 중 non-null인 것이 99187개입니다.

나머지는 null값을 갖기 때문에 해당 값을 삭제해주던가 대체해주는 전처리 과정이 필요해보입니다.

# Feature간 상관관계 파악
used_car.corr()

연속형 변수를 대상으로 회귀 분석을 진행할 것입니다.

따라서 Feature들 간 상관관계를 먼저 확인해 주었습니다.

Regression의 경우 독립변수들 간 상관관계가 강할 경우,

분산이 커지면서 오류가 증가할 가능성이 높기 때문입니다.

이런 변수들이 많을 경우 PCA분석을 통해 변수들을 압축하거나 Feature Selection 과정이 필요해보입니다.

다행히 아래 데이터는 변수들간 큰 상관관계는 없습니다.

price와 engineSize간 약한 상관관계가 존재합니다.

price에 직접적인 영향을 줄 수 있으므로 enginzeSize에 이상치가 있는지 확인이 필요해 보입니다.

## 다중공선성 문제 발생 대비 상관관계 파악
## price에는 engineSize가 약한 상관을 보임, 피처들간 상관은 문제가 되지 않는다.
corr = used_car.corr()
sns.heatmap(corr, cmap='RdBu')

이제 데이터를 시각화해보며 모델링 성능에 영향을 줄 수 있는 Outlier들을 찾아보겠습니다.

mileage가 높음에도 불구하고, 현대차의 I10의 경우 실제 중고거래가보다 월등히 높습니다.

각 브랜드별로 존재하는 Outlier들을 모두 삭제해 주었습니다.

이제 Brand별 Price에는 Outlier가 존재하지 않습니다.

다음은 Target값과 Feature값들 간 그래프를 모두 그려보면서 각 Feature별 Outlier를 확인후 삭제해주려고합니다.

각 Column별 이상치를 삭제한 뒤 mpg의 경우 null값을 평균값으로 대체하였습니다.

데이터 전처리한 결과입니다.

price값의 분포가 왼쪽에 치우쳐져 있습니다. 회귀 모형의 경우 타겟값이 정규분포일 경우 성능이 더 잘 나오는 경향이 있으므로, 로그 변환을 통해 변경해 주도록 하겠습니다.

🔍모델링 및 평가

모델링의 경우 총 4가지 모델로 회귀 분석을 진행한 뒤, Stacking Ensemble 메타모델로 예측하였습니다.

  1. 일반 선형회귀

  2. 릿지(Ridge)

  3. 라소(Lasso)

  4. LightGBM

--> Stacking Ensemble

이제 타겟값과 학습데이터를 나누고 학습을 진행하겠습니다.

일반 선형 회귀를 Cross Validation한 결과입니다.

# linear_reg : cross_val_score()함수로 K-Fold 검증
neg_mse = cross_val_score(linear_reg, X_features, y_target, scoring='neg_mean_squared_error',
                          cv=5)
r2_score = cross_val_score(linear_reg, X_features, y_target, scoring='r2', cv=5)
rmse = np.sqrt(-1*neg_mse)
avg_rmse = np.mean(rmse)
avg_r2_score = np.mean(r2_score)

# cross_val_score 값 출력
print('Linear_reg cv = 5의 평균 RMSE : {0:.3f}'.format(avg_rmse))
print('Linear_reg cv = 5의 r2_score : {0:.3f}'.format(avg_r2_score))

규제를 적용한 Ridge와 Lasso로 진행해보겠습니다.

각 alpha값에 따라 학습을 진행한 뒤 GridSearchCV를 통해 교차검증과 하이퍼 파라미터 튜닝을 같이 수행했습니다.

GridSearchCV의 경우 확실히 시간이 오래걸리는 경향이 있습니다.

# GridSearchCV를 이용해 하이퍼파라미터 튜닝 및 교차검증
ridge_params = {'alpha':[0.001, 0.01, 0.05, 0.1, 0.5, 1, 3, 5, 10, 50, 70, 90, 100]}
ridge_grid = GridSearchCV(ridge, param_grid=ridge_params, scoring='neg_mean_squared_error',cv=5)
ridge_grid.fit(X_features, y_target)
rmse = np.sqrt(-1*ridge_grid.best_score_)
# ridge의 최적 파라미터로 세팅
ridge = ridge_grid.best_estimator_
print('Ridge RMSE : {0:.3f}, best_alpha : {1}'.format(np.round(rmse,3), ridge_grid.best_params_))

lasso_params = {'alpha':[0.001, 0.01, 0.05, 0.1, 0.5, 1, 5]}
lasso_grid = GridSearchCV(lasso, param_grid=lasso_params, scoring='neg_mean_squared_error',cv=5)
lasso_grid.fit(X_features, y_target)
rmse = np.sqrt(-1*lasso_grid.best_score_)
# lasso의 최적 파라미터로 세팅
lasso = lasso_grid.best_estimator_
print('Lasso RMSE : {0:.3f}, best_alpha : {1}'.format(np.round(rmse,3), lasso_grid.best_params_))

전체 회귀계수를 살펴보았을 때는 Lasso의 경우 규제가 강하게 적용되어 대부분의 Feature들의 회귀계수가 0이되면서 Feature Selection의 효과가 있었습니다.

두 모델 모두 공통적으로 높은 price 가격에 영향을 주는 것은 year(연식)과 특정 고가의 브랜드, engineSize로 보여집니다.

반면 낮은 가격에 영향을 주는 것은 저렴한 모델이거나 연식이 오래된 모델의 영향이 있습니다.

이번엔 회귀 트리 앙상블 모델인 LGBM으로 진행해 보겠습니다.

# GridSearchCV를 이용해 LGBM모델의 하이퍼파라미터 튜닝 및 교차검증
lgbm_params = {'n_estimators':[1000],
               'learning_rate':[0.01, 0.05, 0.1],
               'max_depth':[6, 16, 32],
              'subsample':[0.6,0.8]}
lgbm_model = LGBMRegressor(n_jobs=-1)
lgbm_grid = GridSearchCV(lgbm_model, param_grid=lgbm_params, 
                         scoring='neg_mean_squared_error', cv=5, verbose=2)
lgbm_grid.fit(X_features, y_target)
rmse = np.sqrt(-1*lgbm_grid.best_score_)
lgbm_model = lgbm_grid.best_estimator_
print('lgbm RMSE : {0:.3f}, best_parameters : {1}'.format(np.round(rmse,3), 
                                                          lgbm_grid.best_params_))

결과로 LightGBM RegressorRMSE값은 0.211이 나왔습니다. 이제 Ridge, Lasso, LGBM을 이용해 스태킹 모델을 적용하여 최종 예측을 수행하겠습니다.

스태킹 모델의 코드는 https://www.lsjsj92.tistory.com/559?category=853217를 참고하였습니다.

# 모델들을 바탕으로 최종 스태킹 앙상블 구현

def get_stacking_data(model, X_train, y_train, X_test, n_folds=5):
    kfold = KFold(n_splits = n_folds, random_state=0)

    # 최종 모델에서 사용할 데이터 셋 세팅(0 값으로)
    # 만약 shape가 (100, 10)이었으면 폴드의 검증에 사용할 저장할 데이터는 (100,1) 모양
    train_fold_predict = np.zeros((X_train.shape[0], 1))
    # test는 X_test값을 이용해서 매 폴드마다 예측을 하므로, (100, fold 수)만큼의 shape을 갖는다.
    # 그래서 폴드마다 X_test의 예측 값을 해당 fold의 열에 삽입
    test_predict = np.zeros((X_test.shape[0], n_folds))
    print('model : ', model.__class__.__name__)

    for cnt,(train_index, valid_index) in enumerate(kfold.split(X_train)):
        X_train_ = X_train[train_index]
        y_train_ = y_train[train_index]
        X_validation = X_train[valid_index]

        model.fit(X_train_, y_train_)

        # 해당 폴드에서 학습된 모델에 검증 데이터(X_validation)으로 예측 후 저장
        train_fold_predict[valid_index, :] = model.predict(X_validation).reshape(-1,1)

        # 해당 폴드에서 생성된 모델에 원본 테스트 데이터(X_test)를 이용해서 예측후 저장
        test_predict[:, cnt] = model.predict(X_test)

    # for문 끝나면 test_pred는 평균을 내서 하나로 합친다.
    test_predict_mean = np.mean(test_predict, axis=1).reshape(-1,1)

    return train_fold_predict, test_predict_mean

X_train_ = X_train.values
X_test_ = X_test.values
y_train_ = y_train.values

ridge_train, ridge_test = get_stacking_data(ridge_model, X_train_, y_train_, X_test_, 5)
lasso_train, lasso_test = get_stacking_data(lasso_model, X_train_, y_train_, X_test_, 5)
lgbm_train, lgbm_test = get_stacking_data(lgbm_model, X_train_, y_train_, X_test_, 5)

# 개별 모델이 반환한 학습 및 테스트용 데이터 세트를 스태킹 형태로 결합
Stack_final_X_train = np.concatenate((ridge_train, lasso_train, lgbm_train), axis=1)
Stack_final_X_test = np.concatenate((ridge_test, lasso_test, lgbm_test), axis=1)

meta_model = Lasso(alpha=0.001)

# 개별 모델 예측값을 기반으로 새롭게 만들어진 학습/테스트 데이터로 메타 모델 예측
meta_model.fit(Stack_final_X_train, y_train)
final = meta_model.predict(Stack_final_X_test)
mse = mean_squared_error(y_test, final)
rmse = np.sqrt(mse)
print('Stacking meta 모델의 RMSE 값 : ', rmse)

다음은 결과입니다. RMSE값은 일반 선형회귀를 사용했을 때보다 0.16정도 향상되었고, 오차 금액은 180만원 정도 나왔습니다.

각각 학습한 모델을 pickle을 사용하여 Binary 파일로 저장해보겠습니다.

# 학습한 모델을 Binary 형태로 저장
with open('meta_model.txt', 'wb') as file:
    pickle.dump(meta_model, file)

with open('ridge_model.txt', 'wb') as file:
    pickle.dump(ridge_model, file)

with open('lasso_model.txt', 'wb') as file:
    pickle.dump(lasso_model, file)

with open('lgbm_model.txt', 'wb') as file:
    pickle.dump(lgbm_model, file)

저장한 파일을 다시 로드하는 방법입니다.

# 모델 load
with open('ridge_model.txt', 'rb') as f:
    ridge_model = pickle.load(f)

with open('lasso_model.txt', 'rb') as f:
    lasso_model = pickle.load(f)

with open('lgbm_model.txt', 'rb') as f:
    lgbm_model = pickle.load(f)

with open('meta_model.txt', 'rb') as f:
    meta_model = pickle.load(f)

생각보다 오차 금액이 크게 나와 추가적인 성능 예측을 위한 자료조사를 실시하였습니다.

그 결과 중고 거래차 가격에 영향을 미치는 다른 변수로 보험 이력, 최대 출력 토크, 그리고 신차가격 등이 있었습니다.

특히 중고 거래가에는 신차 가격이 꽤 큰 영향을 미치는 것을 발견하였습니다.

데이터상에는 신차 가격이 없었기 때문에 이러한 오차가 발생한 것으로 보이며, 신차를 비롯한 추가적인 정보들을 수집한 뒤 예측하면 성능 향상이 기대됩니다.

 

참고문헌

윤대권, 김용현, 이해택, 하성용. (2015). 중고자동차 가격산정을 위한 평가요인 연구. 한국자동차공학회 춘계학술대회, (), 1095-1100.

윤대권, 이해택, 김용현, 남일우, 김흥곤, 김주동, 윤재곤, 하성용. (2015). 중고자동차 가격산정 평가 시스템 개발. 한국자동차공학회 추계학술대회 및 전시회, (), 1340-1345.

조수진. "국산 중고 자동차 가격 예측 및 영향요인 분석." 국내석사학위논문 이화여자대학교 대학원, 2019. 서울