머신러닝·딥러닝 문제해결 전략 | Ch6 Bike Sharing Demand - Baseline

리혜·2022년 6월 6일
0
post-thumbnail

머신러닝·딥러닝 문제해결 전략 책을 읽으면서
Kaggle 경진대회 코드와 문제해결 전략을 정리한 글


6장 자전거 대여 수요 예측 경진대회 - 베이스라인 모델

베이스라인 모델

  • 베이스라인 모델이란 뼈대가 되는 가장 기본적인 모델을 의미
  • 베이스라인 모델에서 출발해 성능을 점차 향상시키는 방향으로 모델링
  • 사이킷런이 제공하는 기본 선형 회귀 모델을 베이스라인으로 사용

베이스라인 모델 전체 프로세스
1. 데이터 불러오기
2. (기본적인) 피처 엔지니어링
3. 평가지표 계산 함수 작성
4. 모델 훈련
5. 성능 검증
6. 제출

데이터 불러오기

import pandas as pd
# 데이터 경로
data_path = '/kaggle/input/bike-sharing-demand/'

train = pd.read_csv(data_path + 'train.csv')  
test = pd.read_csv(data_path + 'test.csv')   
submission = pd.read_csv(data_path + 'sampleSubmission.csv')

피처 엔지니어링

  • 피처 엔지니어링은 데이터를 변환하는 작업
  • 이 변환을 훈련 데이터와 테스트 데이터에 공통으로 반영해야 하기 때문에, 피처 엔지니어링 전에 두 데이터를 합쳤다가 다 끝나면 나눠줌
  • 데이터를 합치기 전에 훈련 데이터에서 이상치 하나를 제거하고 시작

이상치 제거

  • 앞서 포인트 플롯에서 확인한 결과 훈련 데이터에서 weather가 4인 데이터(폭우, 폭설이 내리는 날 저녁 6시에 대여)는 이상치였고 이를 제거하도록 함
# 훈련 데이터에서 weather가 4가 아닌 데이터만 추출
train = train[train['weather'] != 4]

데이터 합치기

  • 훈련 데이터와 테스트 데이터에 같은 피처 엔지니어링을 적용하기 위해 두 데이터를 하나로 합치기
  • 판다스의 concat() 함수를 사용하면 축을 따라 DataFrame을 이어붙일 수 있음

훈련 데이터 : 10,886 행
테스트 데이터 : 6,493 행
총 데이터 : 17,379 행
weather가 4인 데이터를 제거했으니(1개 있음) 최종적으로 17,378행

all_data_temp = pd.concat([train, test])
all_data_temp
datetime season holiday workingday weather temp atemp humidity windspeed casual registered count
0 2011-01-01 00:00:00 1 0 0 1 9.84 14.395 81 0.0000 3.0 13.0 16.0
1 2011-01-01 01:00:00 1 0 0 1 9.02 13.635 80 0.0000 8.0 32.0 40.0
2 2011-01-01 02:00:00 1 0 0 1 9.02 13.635 80 0.0000 5.0 27.0 32.0
3 2011-01-01 03:00:00 1 0 0 1 9.84 14.395 75 0.0000 3.0 10.0 13.0
4 2011-01-01 04:00:00 1 0 0 1 9.84 14.395 75 0.0000 0.0 1.0 1.0
... ... ... ... ... ... ... ... ... ... ... ... ...
6488 2012-12-31 19:00:00 1 0 1 2 10.66 12.880 60 11.0014 NaN NaN NaN
6489 2012-12-31 20:00:00 1 0 1 2 10.66 12.880 60 11.0014 NaN NaN NaN
6490 2012-12-31 21:00:00 1 0 1 1 10.66 12.880 60 11.0014 NaN NaN NaN
6491 2012-12-31 22:00:00 1 0 1 1 10.66 13.635 56 8.9981 NaN NaN NaN
6492 2012-12-31 23:00:00 1 0 1 1 10.66 13.635 65 8.9981 NaN NaN NaN

17378 rows × 12 columns

# 원래 데이터의 인덱스를 무시하고 이어붙이려면 ignore_index=True를 전달
all_data = pd.concat([train, test], ignore_index=True)
all_data
datetime season holiday workingday weather temp atemp humidity windspeed casual registered count
0 2011-01-01 00:00:00 1 0 0 1 9.84 14.395 81 0.0000 3.0 13.0 16.0
1 2011-01-01 01:00:00 1 0 0 1 9.02 13.635 80 0.0000 8.0 32.0 40.0
2 2011-01-01 02:00:00 1 0 0 1 9.02 13.635 80 0.0000 5.0 27.0 32.0
3 2011-01-01 03:00:00 1 0 0 1 9.84 14.395 75 0.0000 3.0 10.0 13.0
4 2011-01-01 04:00:00 1 0 0 1 9.84 14.395 75 0.0000 0.0 1.0 1.0
... ... ... ... ... ... ... ... ... ... ... ... ...
17373 2012-12-31 19:00:00 1 0 1 2 10.66 12.880 60 11.0014 NaN NaN NaN
17374 2012-12-31 20:00:00 1 0 1 2 10.66 12.880 60 11.0014 NaN NaN NaN
17375 2012-12-31 21:00:00 1 0 1 1 10.66 12.880 60 11.0014 NaN NaN NaN
17376 2012-12-31 22:00:00 1 0 1 1 10.66 13.635 56 8.9981 NaN NaN NaN
17377 2012-12-31 23:00:00 1 0 1 1 10.66 13.635 65 8.9981 NaN NaN NaN

17378 rows × 12 columns

파생 피처(변수) 추가

from datetime import datetime

# 날짜 피처 생성
all_data['date'] = all_data['datetime'].apply(lambda x: x.split()[0])
# 연도 피처 생성
all_data['year'] = all_data['datetime'].apply(lambda x: x.split()[0].split('-')[0])
# 월 피처 생성
all_data['month'] = all_data['datetime'].apply(lambda x: x.split()[0].split('-')[1])
# 시 피처 생성
all_data['hour'] = all_data['datetime'].apply(lambda x: x.split()[1].split(':')[0])
# 요일 피처 생성
all_data['weekday'] = all_data['date'].apply(lambda dateString: datetime.strptime(dateString, '%Y-%m-%d').weekday())
  • 훈련 데이터는 매달 1일부터 19일까지의 기록이고, 테스트 데이터는 매달 20일부터 월말까지의 기록
  • 그러므로 대여 수량을 예측할 때 일(day) 피처는 사용할 필요가 없음
  • minute와 second 피처도 모든 기록에서 값이 같으므로 예측에 사용할 필요가 없음
  • 그래서 day, minute, second는 피처로 생성하지 않았음

필요 없는 피처 제거

  • casualregistered 피처는 테스트 데이터에 없는 피처이므로 제거
  • datetime 피처는 인덱스 역할이고 date 피처가 갖는 정보들은 다른 피처들(year, month, day)에도 담겨 있기 때문에 datetime과 date 피처도 필요 없음
  • season 피처가 month의 대분류 성격이라서 month 피처도 제거
  • windspeed 피처도 타깃값과 상관관계가 약하므로 제거
drop_features = ['casual', 'registered', 'datetime', 'date', 'windspeed', 'month']

all_data = all_data.drop(drop_features, axis=1)
  • 필요 없는 피처를 제거함으로써 모델링할 때 사용할 피처를 모두 선별
  • 탐색적 데이터 분석에서 얻은 인사이트를 활용해 의미 있는 피처와 불필요한 피처를 구분
    • 이러한 과정을 피처 선택이라고 함

피처 선택(feature selection) : 모델링 시 데이터의 특징을 잘 나타내는 주요 피처만 선택하는 작업

데이터 나누기

  • 모든 피처 엔지니어링을 적용했으므로 훈련 데이터와 테스트 데이터를 다시 나누기
# 훈련 데이터와 테스트 데이터 나누기
X_train = all_data[~pd.isnull(all_data['count'])]   # 타깃값이 있으면 훈련 데이터
X_test = all_data[pd.isnull(all_data['count'])]     # 타깃값이 없으면 테스트 데이터

# 타깃값 count 제거
X_train = X_train.drop(['count'], axis=1)
X_test = X_test.drop(['count'], axis=1)

y = train['count']  # 타깃값
# 피처 엔지니어링 후의 훈련 데이터
X_train.head()
season holiday workingday weather temp atemp humidity year hour weekday
0 1 0 0 1 9.84 14.395 81 2011 00 5
1 1 0 0 1 9.02 13.635 80 2011 01 5
2 1 0 0 1 9.02 13.635 80 2011 02 5
3 1 0 0 1 9.84 14.395 75 2011 03 5
4 1 0 0 1 9.84 14.395 75 2011 04 5

평가지표 계산 함수 작성

  • 훈련이란 어떠한 능력을 개선하기 위해 배우거나 단련하는 행위
  • 따라서 훈련이 제대로 이루어졌는지 확인하려면 대상 능력을 평가할 수단, 즉 평가지표가 필요함
  • 그래서 본격적인 훈련에 앞서 본 경진대회 평가지표인 RMSLE를 계산하는 함수 만들기
import numpy as np

def rmsle(y_true, y_pred, convertExp=True):
    # 지수변환
    if convertExp:
        y_true = np.exp(y_true)
        y_pred = np.exp(y_pred)
    
    # 로그변환 후 결측값을 0으로 변환
    log_true = np.nan_to_num(np.log(y_true+1))
    log_pred = np.nan_to_num(np.log(y_pred+1))
    
    # RMSLE 계산
    output = np.sqrt(np.mean((log_true - log_pred)**2))
    return output

실제 타깃값 y_true와 예측값 y_pred를 인수로 전달하면 RMSLE 수치를 반환하는 함수

  • convertExp는 입력 데이터를 지수변환할지를 정하는 파라미터
    • 기본값인 convertExp=True를 전달하면 y_true와 y_pred를 지수변환
    • 지수변환에는 넘파이 내장 함수인 exp() 이용
    • 지수변환하는 이유는 타깃값으로 count가 아닌 log(count)를 사용하기 때문
  • y_true와 y_pred를 로그변환하고 결측값은 0으로 변환
    • np.log() 함수의 밑은 e
    • np.nan_to_num() 함수는 NaN 결측값을 모두 0으로 바꾸는 기능을 함

모델 훈련

  • 데이터와 평가 함수가 준비되었으니 본격적으로 모델을 생성한 뒤 훈련
# 사이킷런이 제공하는 LinearRegression 을 임포트하여 모델 생성
from sklearn.linear_model import LinearRegression

linear_reg_model = LinearRegression()
# 훈련 데이터로 모델 훈련
log_y = np.log(y)  # 타깃값 로그 변환
linear_reg_model.fit(X_train, log_y)  # 모델 훈련
LinearRegression()

선형 회귀 모델

  • 훈련 : 피처(독립변수)와 타깃값(종속변수)이 주어졌을 때 최적의 가중치(회귀계수)를 찾는 과정
  • 예측 : 최적의 가중치를 아는 상태(훈련된 모델)에서 새로운 독립변수(데이터)가 주어졌을 때 타깃값을 추정하는 과정
  • 탐색적 데이터 분석 : 예측에 도움이 될 피처를 추리고, 적절한 모델링 방법을 탐색하는 과정
  • 피처 엔지니어링 : 추려진 피처들을 훈련에 적합하도록, 성능 향상에 도움되도록 가공하는 과정

모델 성능 검증

  • 훈련을 마쳤으니 예측을 해본 후 RMSLE 값까지 확인
# 모델 성능 검증을 위해 예측을 수행하는 코드
preds = linear_reg_model.predict(X_train)

코드를 실행하면 훈련된 선형 회귀 모델이 X_train 피처를 기반으로 타깃값을 예측

  • 여기서는 검증 시 훈련 데이터를 사용했는데, 모델을 훈련하고 결과를 예측하고 평가지표인 RMSLE까지 구해보려고 시험 삼아 짠 것
  • 원래는 훈련 시 훈련 데이터를 사용하고, 검증 시 검증 데이터를 사용하며, 테스트시 테스트 데이터를 사용해야 함
# 예측 결과로부터 훈련이 얼마나 잘 되었는지를 평가 : 타깃값 log_y와 예측 결과 preds 사이의 RMSLE 값을 구함
print(f'선형 회귀의 RMSLE 값 : {rmsle(log_y, preds, True):.4f}')
선형 회귀의 RMSLE 값 : 1.0205

예측 및 결과 제출

  • 베이스라인 모델로 예측한 결과를 제출

주의할 점
1. 테스트 데이터로 예측한 결과를 이용해야 함. 앞서 모델 성능 검증 과정에서는 RMSLE 값을 구해보고자 훈련 데이터를 이용
2. 예측한 값에 지수변환을 해줘야 함. 현재 예측값이 count가 아니라 log(count)이기 때문

linearreg_preds = linear_reg_model.predict(X_test)  # 테스트 데이터로 예측

submission['count'] = np.exp(linearreg_preds)   # 지수변환
submission.to_csv('submission.csv', index=False)  # 파일로 저장

0개의 댓글