Programming

Bike Demand 예측 프로젝트 : 회귀 기반 수요 예측 실전 프로젝트 정리

Lucas.Kim 2026. 1. 9. 14:01
반응형

1. 프로젝트 개요

Bike Demand 데이터셋은 시간·날씨·계절 정보를 기반으로
자전거 대여 수요(count) 를 예측하는 전형적인 회귀(Regression) 문제입니다.

이 프로젝트의 목표는 다음과 같습니다.

  • 데이터 특성을 이해하고 적절한 전처리 전략을 적용
  • RMSLE / RMSE / MAE 등 회귀 평가 지표를 정확히 이해
  • 선형 회귀 → 규제 회귀 → 트리 기반 앙상블 모델까지 단계적으로 성능 개선
  • 실제 캐글(Kaggle) Bike Demand 문제 접근 방식 체득

2. 데이터 로드 및 기본 확인

bike_df = pd.read_csv('./bike/train.csv')
print(bike_df.shape)
display(bike_df.head(3))
  • 데이터 크기: 10,886 rows × 12 columns
  • 타겟 변수: count (자전거 대여 횟수)

주요 컬럼 설명

  • datetime : 날짜 + 시간
  • season : 계절 (1~4)
  • holiday : 공휴일 여부
  • workingday : 근무일 여부
  • weather : 날씨 상태
  • temp, atemp : 온도 / 체감온도
  • humidity, windspeed
  • casual, registered : 사용자 유형별 대여 수
  • count : 예측 대상

3. 날짜 데이터 처리 (Feature Engineering)

머신러닝 모델은 문자열 형태의 날짜를 직접 이해하지 못하므로
datetime → 수치형 피처 분해가 필요합니다.

bike_df['datetime'] = bike_df.datetime.apply(pd.to_datetime)

bike_df['year'] = bike_df.datetime.apply(lambda x : x.year)
bike_df['month'] = bike_df.datetime.apply(lambda x : x.month)
bike_df['day'] = bike_df.datetime.apply(lambda x : x.day)
bike_df['hour'] = bike_df.datetime.apply(lambda x : x.hour)

#불필요 컬럼 제거
drop_columns = ['datetime','casual','registered']
bike_df.drop(drop_columns, axis=1, inplace=True)

casual, registered는 count를 직접 구성 → 데이터 누수(Data Leakage) 방지

4. 범주형 피처와 수요의 관계 시각화

fig, axs = plt.subplots(
    figsize=(16,8),
    ncols=4,
    nrows=2
)

cat_features = ['year','month','season','weather', 'day', 'hour','holiday','workingday']
#위 리스트에 있는 컬럼별 합을 barplot으로 그림
for i, feature in enumerate(cat_features):
    row = int(i/4)
    col = i%4
    #시본의 barplot을 이용해 컬럼값에 따른 count 평균값 표현
    sns.barplot(x=feature,
                y='count',
                data=bike_df,
                ax=axs[row][col])

5. 회귀 평가 지표 정의

왜 RMSLE가 중요한가?

  • Bike Demand는 수요 예측
  • 실제 값이 큰 경우 오차도 자연히 커짐
  • RMSLE는 상대적 오차를 완화해 줌
from sklearn.metrics import mean_squared_error, mean_absolute_error

#log 변환시 NaN등의 이슈로 log() 가 아닌 log1p()를 이용하여 RMSLE 계산
def rmsle(y, pred):
    log_y = np.log1p(y)
    log_pred = np.log1p(pred)
    squared_error = (log_y-log_pred)**2
    rmsle = np.sqrt(np.mean(squared_error))
    return rmsle

#사이킷런의 mean_square_error()를 이용하여 RMSE 계산
def rmse(y,pred):
    return np.sqrt(mean_squared_error(y, pred))

#MSE, RMSE, RMSLE 모두 계산
def evaluate_regr(y,pred):
    rmsle_val = rmsle(y,pred)
    rmse_val = rmse(y,pred)
    #MSE > sickit learn 의 mean_absolute_error()로 계산
    mae_val = mean_absolute_error(y,pred)
    print(f'RMSLE : {rmsle_val}\nRMSE : {rmse_val}\nMAE : {mae_val}')

log1p / expm1 사용하는 이유

  • log(0) → -inf 오류 발생
  • log1p(x) = log(x+1) → 안전한 로그 변환
  • expm1(x) → 원래 스케일 복원

6. 기본 선형 회귀 모델 (Baseline)

lr_reg = LinearRegression()
lr_reg.fit(X_train,y_train)
pred = lr_reg.predict(X_test)
evaluate_regr(y_test, pred)

결과 해석

  • RMSLE ≈ 1.16
  • 큰 오차 발생 → 과소적합(Underfitting)
get_top_error_data(y_test, pred)
  • 실제 수요가 매우 큰 구간에서 예측 실패

7. 타겟값 로그 변환 (핵심 개선 포인트)

y_target_log = np.log1p(y_target)

 

  • 수요 데이터는 오른쪽으로 긴 분포(skewed)
  • 로그 변환 → 분포 안정화
pred_exp = np.expm1(pred)

 

  • 평가 전 반드시 원래 스케일로 복원

RMSLE 개선 확인

8. One-Hot Encoding 적용

연, 월, 시(hour) 등은 수치형이지만 실제로는 범주형

X_features_ohe = pd.get_dummies(
    X_features,
    columns=['year','month','day','hour','holiday','workingday','season','weather']
)

9. 선형 / 규제 회귀 비교

lr_reg = LinearRegression()
ridge_reg = Ridge(alpha=10)
lasso_reg = Lasso(alpha=0.01)

결과 요약

모델 RMSLE
Linear 0.589
Ridge 0.590
Lasso 0.634

10. 트리 기반 앙상블 모델 적용

rf_reg = RandomForestRegressor(n_estimators=500)
gbm_reg = GradientBoostingRegressor(n_estimators=500)
xgb_reg = XGBRegressor(n_estimators=500)
lgbm_reg = LGBMRegressor(n_estimators=500)
모델 RMSLE
Linear 0.589
Ridge 0.590
Lasso 0.634
LightGBM 0.318

11. 프로젝트 결론

  • Bike Demand는 비선형 + 시간 패턴이 강한 문제
  • 단순 선형 회귀 → 한계 명확
  • 로그 변환 + One-Hot Encoding 필수
  • 최종적으로 LightGBM이 가장 우수한 성능
  • 실전 캐글 회귀 문제의 전형적인 해결 흐름
반응형