전기차 가격 예측 해커톤 - Prediction Process
안녕하세요! 이번에는 지난 EDA에 이어서 데이터를 불러오는 것부터 예측까지의 모든 과정에 대한 글입니다. 지난 번 EDA에서 데이터의 이상치를 제거했다면 이번 글을 통해서는 Null 값을 어떻게 처리했는지, 어떤 Scaler를 사용했고 어떤 컬럼을 Drop했으며, 마지막으로 학습을 위해 어떤 모델을 사용했는 지에 대해 알아보도록 하겠습니다. 전반적인 과정을 요약하면 아래와 같습니다.
- 보증기간(년), 제조사, 모델, 구동방식에 따른 평균
- Scaler 선택
- 학습 모델 선택
데이콘 링크 : https://dacon.io/
데이터사이언티스트 AI 컴피티션
10만 AI 팀이 협업하는 데이터 사이언스 플랫폼. AI 경진대회와 대상 맞춤 온/오프라인 교육, 문제 기반 학습 서비스를 제공합니다.
dacon.io
전체 과정 & Code Review
개발 환경 : Google Colab
사용 언어 : Python
1. 한글 폰트 설치
!apt-get -qq install fonts-nanum*
2. 한글 폰트 설정
# 한글 폰트 설정하기
font_path = '/usr/share/fonts/truetype/nanum/NanumGothic.ttf'
fm.fontManager.addfont(font_path)
plt.rc('font', family='NanumGothic') # 기본 폰트를 나눔고딕으로 설정
3. 라이브러리 import 및 Data Load
import pandas as pd
import numpy as np
import math
import seaborn as sns
import matplotlib.font_manager as fm
import matplotlib.pyplot as plt
from sklearn.preprocessing import LabelEncoder
# 데이터 불러오기
train = pd.read_csv('./train.csv')
test = pd.read_csv('./test.csv')
4. 모델 컬럼 확인
train.columns
5. 데이터 전처리 (EDA를 통한 이상치 제거)
# 'IONIQ' 모델의 보증기간(년)이 8년인 경우 제거
train = train[~((train['모델'] == 'IONIQ') & (train['보증기간(년)'] == 8))]
# 차량상태가 'New Nearly'이고 주행거리가 50000 초과인 데이터 제거
train = train[~((train['차량상태'] == 'Nearly New') & (train['주행거리(km)'] > 50000))]
# 조건에 맞는 행 제거
train = train[~((train['모델'] == 'IONIQ') & (train['차량상태'] == 'Pre-Owned') & (train['주행거리(km)'] < 50000))]
train = train[~((train['모델'] == 'IONIQ') & (train['배터리용량'] >= 80))]
train = train[~((train['모델'] == 'TayGTS') & (train['배터리용량'] >= 77) & (train['배터리용량'] <= 88))]
6. 배터리용량 Null 값 채우기
# train 데이터프레임에서 배터리용량의 NULL 값을 보증기간, 제조사, 모델, 구동방식 별로 그룹의 평균으로 채우기
train['배터리용량'] = train['배터리용량'].fillna(
train.groupby(['보증기간(년)', '제조사', '모델', '구동방식'])['배터리용량'].transform('mean'))
# train에서 평균값 계산
mean_battery_capacity = train.groupby(['보증기간(년)', '제조사', '모델', '구동방식'])['배터리용량'].mean()
# test 데이터프레임에서 NULL 값을 보증기간, 제조사, 모델, 구동방식 그룹의 평균으로 채우기
def fill_battery_capacity(row):
return mean_battery_capacity.get((row['보증기간(년)'], row['제조사'], row['모델'], row['구동방식']), row['배터리용량'])
test['배터리용량'] = test['배터리용량'].fillna(test.apply(fill_battery_capacity, axis=1))
해당 과정에서 배터리용량의 Null 값에 대해서 어떻게 채워넣어야할지 고민이 많았습니다. 처음에는 전체적인 평균을 넣었지만, 결과가 썩 좋지 않아서 보증기간에 따른 제조사, 모델, 구동 방식별로 그룹을 묶고 이에 대한 평균으로 Null 채워 넣었습니다. 그리고 해당 방식을 Test에 그대로 적용되게 하였습니다. 이후 나머지 Null 값은 무시하였습니다.
7. 불필요한 컬럼 Drop
# 학습과 예측을 위해 데이터를 분리합니다.
x_train = train.drop(['ID', '가격(백만원)', '연식(년)', '차량상태'], axis=1)
y_train = train['가격(백만원)']
x_test = test.drop(['ID', '연식(년)','차량상태'], axis=1)
해당 과정은 뒤에 나오는 Fearture Importance를 통해 학습 중 낮은 영향을 미치는 요소들을 제거하였습니다. 가장 마지막으로 사고이력도 낮은 점수였지만 같은 조건 아래 사고 이력의 유무가 가격에 영향을 미치므로 이에 대한 사고이력 컬럼은 별도로 Drop 하지 않았습니다.
8. 범주형 데이터 Encoding
# 범주형 변수에 대해 레이블 인코딩을 적용합니다.
categorical_features = [col for col in x_train.columns if x_train[col].dtype == 'object']
for i in categorical_features:
le = LabelEncoder()
le=le.fit(x_train[i])
x_train[i]=le.transform(x_train[i])
for case in np.unique(x_test[i]):
if case not in le.classes_:
le.classes_ = np.append(le.classes_, case)
x_test[i]=le.transform(x_test[i])
display(x_train.head(3))
숫자로 치환할 수 있는 범주형 데이터를 처리할 때에는 Label Encoder 말고도 get_dummy를 활용할 수 있지만, 둘 다 사용해본 결과, LabelEncoder를 활용한 방식이 더 좋은 성능을 냈기 때문에 해당 방식을 선택하였습니다.
9. Scaler
from sklearn.preprocessing import StandardScaler
# StandardScaler 객체 생성
scaler = StandardScaler()
# x_train 데이터에 대한 스케일링
x_train_scaled = scaler.fit_transform(x_train)
# x_test 데이터에 대한 스케일링 (train 데이터에서 변환된 스케일을 사용)
x_test_scaled = scaler.transform(x_test)
# 스케일링된 데이터를 DataFrame으로 변환
x_train_scaled = pd.DataFrame(x_train_scaled, columns=x_train.columns)
x_test_scaled = pd.DataFrame(x_test_scaled, columns=x_test.columns)
스케일러는 MinMax와 RobustScaler, MaxAbsScaler 그리고 Standard Scaler 중 저는 Standard Scaler를 선택하게 되었습니다. 해당 스케일러는 데이터의 평균을 0, 표준편차를 1로 변환하여 각 특성의 값을 평균으로부터의 표준 편차의 배수로 변환하는 성질을 가지고 있습니다. 또한 해당 스케일러는 고루 분포된 데이터에 적합하기 때문에 제 나름 기준으로 최적으로 이상치를 다 없앴다라는 가정 아래 해당 스케일러를 선택하였고, 혹시 몰라 다른 기법들을 적요해보았지만 결과를 보면 Standard Scaler가 가장 좋은 성능을 보였습니다. 해당 스케일러의 공식은 아래에서 확인하실 수 있습니다.
10. LGBM Regressor
import pandas as pd
from lightgbm import LGBMRegressor
from sklearn.model_selection import GridSearchCV
# LightGBM 모델 학습
lightgbm_model = LGBMRegressor(random_state=42)
# 하이퍼파라미터 그리드 정의
param_grid = {
'n_estimators': [200, 500],
'learning_rate': [0.01, 0.1, 0.2],
'max_depth': [5, 10, 20],
'num_leaves': [10, 20, 30],
'min_child_samples': [40, 50, 60]
}
# GridSearchCV 정의
grid_search1 = GridSearchCV(estimator=lightgbm_model, param_grid=param_grid,
scoring='neg_mean_squared_error',
cv=5, n_jobs=-1, verbose=1)
# 모델 학습
grid_search1.fit(x_train_scaled, y_train)
# 최적의 하이퍼파라미터와 점수 출력
print("최적의 하이퍼파라미터:", grid_search1.best_params_)
print("최고의 점수:", np.sqrt(-grid_search1.best_score_)) # RMSE로 변환
모델은 처음에 Random Forest, CatBoost, XGB, LGBM 등의 다양한 모델을 사용해보았습니다. 랜덤 포레스트 같은 경우는 시간이 너무 오래 걸려서 제외했고, 나머지 모델과 비교한 결과 초반에 LGBM이 가장 좋아서 나중에 전처리에 끝까지 왔을 때, 앙상블이나 해보자 하는 생각으로 진행했으나, 결과가 저 같은 경우는 압도적으로 LGBM이 좋아서 해당 모델을 단독으로 학습에 사용하였습니다. 또한 이 과정에서 어떤 파라미터가 좋을 지를 고민하기 위해 GridSearchCV를 사용하였고 이를 통해 최적의 파라미터를 찾을 수 있었습니다.
11. Feature Importance
# 피처 중요도 추출
importance = grid_search1.best_estimator_.feature_importances_
# 피처 중요도 데이터프레임 생성
features = x_train.columns
importance_df = pd.DataFrame({'Feature': features, 'Importance': importance})
# 중요도 정렬
importance_df = importance_df.sort_values(by='Importance', ascending=False)
# 피처 중요도 시각화
plt.figure(figsize=(10, 6))
plt.barh(importance_df['Feature'], importance_df['Importance'], color='skyblue')
plt.xlabel('Importance')
plt.title('Feature Importance')
plt.gca().invert_yaxis() # 상위 중요도를 위로
plt.show()
학습 이후에는 Feature Importance를 통해 가장 낮은 중요도를 보이는 요소를 제거하였습니다. 위 사진과 같이 사고이력도 낮은 중요도를 보이지만, Drop을 해보고 결과를 제출해보니 결과가 좋지 않아서 계속 가지고 있기로 하였습니다.
이와 같은 과정의 반복을 통해서 마감날까지 결과를 지속해서 넣어볼 수 있었고 최종적으로 마감 이후 공개되는 Private 데이터 순위는 5위를 기록할 수 있었습니다.
마무리
여기까지 전체 코드에 대한 내용이었습니다. 코드는 짧고 아주 쉬워보이지만, 해당 과정을 찾기까지 무수히 많은 제출 시도가 있었던 것 같습니다. 아마도... 제출 수로 따지면 제가 가장 많은 제출을 기록하지 않았나 싶습니다. 하나 하나의 새로운 인사이트가 떠오르면 이를 바로 적용하고 왜 이러는지 그리고 어떤 것을 더 시도해볼 수 있는 지 많은 고민을 했던 것 같습니다. 조금 더 컴퓨터 환경이 좋아서 Google Colab을 사용하지 않았으면 더 좋았겠지만, 그래도 나름대로 많은 인사이트를 얻을 수 있었던 과정이었습니다. 앞으로 다가올 Dacon 대회는 또 어떤 대회가 있을 지를 기대해보면서 이번 Dacon 후기는 여기서 마무리하도록 하겠습니다.

데이터 EDA 과정 :
2025.01.31 - [Personal Projects/Dacon] - [Dacon] 전기차 가격 예측 해커톤 (2) - EDA
[Dacon] 전기차 가격 예측 해커톤 (2) - EDA
전기차 가격 예측 해커톤 - Exploratory Data Analysis, EDA 안녕하세요. 이번에는 Dacon 전기차 가격 예측 해커톤에서 제공받은 데이터를 기반으로 수행한 탐색적 데이터 분석(Exploratory Data Anaysis, EDA)에
muns-da2.tistory.com
현재 진행중 다른 대회
부동산 허위매물 분류 해커톤 : https://dacon.io/competitions/official/236439/overview/description
부동산 허위매물 분류 해커톤: 가짜를 색출하라! - DACON
분석시각화 대회 코드 공유 게시물은 내용 확인 후 좋아요(투표) 가능합니다.
dacon.io
난독화된 한글 리뷰 복원 AI 경진 대회 : https://dacon.io/competitions/official/236446/overview/description
난독화된 한글 리뷰 복원 AI 경진대회 - DACON
분석시각화 대회 코드 공유 게시물은 내용 확인 후 좋아요(투표) 가능합니다.
dacon.io
'Personal Projects > Dacon' 카테고리의 다른 글
[Dacon] 부동한 허위매물 분류 해커톤 (3) - 최종 코드 (0) | 2025.02.28 |
---|---|
[Dacon] 부동산 허위매물 분류 해커톤 (2) - EDA (0) | 2025.02.28 |
[Dacon] 부동산 허위매물 분류 해커톤 (1) - 후기 (Private 43, 상위 10%) (0) | 2025.02.28 |
[Dacon] 전기차 가격 예측 해커톤 (2) - EDA (0) | 2025.01.31 |
[Dacon] 전기차 가격 예측 해커톤 (1) - 후기 (최종 3위) (0) | 2025.01.31 |