Machine learning 10 (자전거 공유 데이터셋)
Bike Sharing Demand
- 도시 자전거 공유 시스템 사용 예측
- 캐글의 Bike Sharing Demand에서
train.csv
와test.csv
를 다운로드 - 두 파일을 각각 datasets 디렉토리에 bike_train.csv bike_test.csv로 저장
1. 문제 정의
- 자전거 대여량을 예측하는 문제
- Evaluation : Submissions are evaluated one the Root Mean Squared Logarithmic Error (RMSLE).
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
2. 데이터 가져오기
bike_train = pd.read_csv('datasets/bike_train.csv') # 훈련 데이터
bike_test = pd.read_csv('datasets/bike_test.csv') # 테스트 데이터
submission = pd.read_csv('datasets/sampleSubmission.csv') # 제출 샘플 데이터
bike_train.shape, bike_test.shape
((10886, 12), (6493, 9))
3. 데이터 훑어보기
bike_train.head()
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.0 | 3 | 13 | 16 |
1 | 2011-01-01 01:00:00 | 1 | 0 | 0 | 1 | 9.02 | 13.635 | 80 | 0.0 | 8 | 32 | 40 |
2 | 2011-01-01 02:00:00 | 1 | 0 | 0 | 1 | 9.02 | 13.635 | 80 | 0.0 | 5 | 27 | 32 |
3 | 2011-01-01 03:00:00 | 1 | 0 | 0 | 1 | 9.84 | 14.395 | 75 | 0.0 | 3 | 10 | 13 |
4 | 2011-01-01 04:00:00 | 1 | 0 | 0 | 1 | 9.84 | 14.395 | 75 | 0.0 | 0 | 1 | 1 |
bike_test.head()
datetime | season | holiday | workingday | weather | temp | atemp | humidity | windspeed | |
---|---|---|---|---|---|---|---|---|---|
0 | 2011-01-20 00:00:00 | 1 | 0 | 1 | 1 | 10.66 | 11.365 | 56 | 26.0027 |
1 | 2011-01-20 01:00:00 | 1 | 0 | 1 | 1 | 10.66 | 13.635 | 56 | 0.0000 |
2 | 2011-01-20 02:00:00 | 1 | 0 | 1 | 1 | 10.66 | 13.635 | 56 | 0.0000 |
3 | 2011-01-20 03:00:00 | 1 | 0 | 1 | 1 | 10.66 | 12.880 | 56 | 11.0014 |
4 | 2011-01-20 04:00:00 | 1 | 0 | 1 | 1 | 10.66 | 12.880 | 56 | 11.0014 |
submission.head()
datetime | count | |
---|---|---|
0 | 2011-01-20 00:00:00 | 0 |
1 | 2011-01-20 01:00:00 | 0 |
2 | 2011-01-20 02:00:00 | 0 |
3 | 2011-01-20 03:00:00 | 0 |
4 | 2011-01-20 04:00:00 | 0 |
datetime: hourly date + timestamp
season: 1 = 봄, 2 = 여름, 3 = 가을, 4 = 겨울
holiday: 1 = 토, 일요일의 주말을 제외한 국경일 등의 휴일, 0 = 휴일이 아닌 날
workingday: 1 = 토, 일요일의 주말 및 휴일이 아닌 주중, 0 = 주말 및 휴일
weather:
• 1 = 맑음, 약간 구름 낀 흐림
• 2 = 안개, 안개 + 흐림
• 3 = 가벼운 눈, 가벼운 비 + 천둥
• 4 = 심한 눈/비, 천둥/번개
temp: 온도(섭씨)
atemp: 체감온도(섭씨)
humidity: 상대습도
windspeed: 풍속
casual: 사전에 등록되지 않는 사용자가 대여한 횟수
registered: 사전에 등록된 사용자가 대여한 횟수
count: 대여 횟수
h = bike_train.hist(bins=50, figsize=(20, 15))
- target 값(count 열) 의 분포가 오른쪽으로 꼬리가 긴 모양
bike_train.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 10886 entries, 0 to 10885
Data columns (total 12 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 datetime 10886 non-null object
1 season 10886 non-null int64
2 holiday 10886 non-null int64
3 workingday 10886 non-null int64
4 weather 10886 non-null int64
5 temp 10886 non-null float64
6 atemp 10886 non-null float64
7 humidity 10886 non-null int64
8 windspeed 10886 non-null float64
9 casual 10886 non-null int64
10 registered 10886 non-null int64
11 count 10886 non-null int64
dtypes: float64(3), int64(8), object(1)
memory usage: 1020.7+ KB
bike_test.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6493 entries, 0 to 6492
Data columns (total 9 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 datetime 6493 non-null object
1 season 6493 non-null int64
2 holiday 6493 non-null int64
3 workingday 6493 non-null int64
4 weather 6493 non-null int64
5 temp 6493 non-null float64
6 atemp 6493 non-null float64
7 humidity 6493 non-null int64
8 windspeed 6493 non-null float64
dtypes: float64(3), int64(5), object(1)
memory usage: 456.7+ KB
- 누락데이터는 없으나, datetime 의 타입이 문자열로 되어 있음
- 효과적인 분석을 위해 datetime 특성을 datetime(pandas에서 제공하는 타입)으로 바꾼 뒤
- 년, 월, 일, 시간 추출
bike_train['datetime'] = bike_train.datetime.apply(pd.to_datetime)
bike_train['year'] = bike_train.datetime.apply(lambda x : x.year)
bike_train['month'] = bike_train.datetime.apply(lambda x : x.month)
bike_train['day'] = bike_train.datetime.apply(lambda x : x.day)
bike_train['hour'] = bike_train.datetime.apply(lambda x : x.hour)
bike_train['minute'] = bike_train.datetime.apply(lambda x : x.minute)
bike_train['dayofweek'] = bike_train.datetime.apply(lambda x : x.dayofweek) # 요일
- season과 weather 컬럼을 숫자에서 실제 의미 있는 단어로 표시
- season (1 = 봄, 2 = 여름, 3 = 가을, 4 = 겨울 )
- weather (1 = 맑음, 약간 구름 낀 흐림, 2 = 안개, 안개 + 흐림, 3 = 가벼운 눈, 가벼운 비 + 천둥, 4 = 심한 눈/비, 천둥/번개)
bike_train['season'] = bike_train['season'].map({1 : 'Spring',
2 : 'Summer',
3 : 'Fall',
4 : 'Winter'})
bike_train['weather'] = bike_train['weather'].map({1 : 'Clear',
2 : 'Mist, Few clouds',
3 : 'Light Snow, Rain, Thunder',
4 : 'Heavy Snow, Rain, Thunder'})
bike_train.head()
datetime | season | holiday | workingday | weather | temp | atemp | humidity | windspeed | casual | registered | count | year | month | day | hour | minute | dayofweek | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 2011-01-01 00:00:00 | Spring | 0 | 0 | Clear | 9.84 | 14.395 | 81 | 0.0 | 3 | 13 | 16 | 2011 | 1 | 1 | 0 | 0 | 5 |
1 | 2011-01-01 01:00:00 | Spring | 0 | 0 | Clear | 9.02 | 13.635 | 80 | 0.0 | 8 | 32 | 40 | 2011 | 1 | 1 | 1 | 0 | 5 |
2 | 2011-01-01 02:00:00 | Spring | 0 | 0 | Clear | 9.02 | 13.635 | 80 | 0.0 | 5 | 27 | 32 | 2011 | 1 | 1 | 2 | 0 | 5 |
3 | 2011-01-01 03:00:00 | Spring | 0 | 0 | Clear | 9.84 | 14.395 | 75 | 0.0 | 3 | 10 | 13 | 2011 | 1 | 1 | 3 | 0 | 5 |
4 | 2011-01-01 04:00:00 | Spring | 0 | 0 | Clear | 9.84 | 14.395 | 75 | 0.0 | 0 | 1 | 1 | 2011 | 1 | 1 | 4 | 0 | 5 |
4. 데이터 탐색
4.1 타깃값(count) 의 분포도 그려보기
sns.displot(bike_train['count'])
<seaborn.axisgrid.FacetGrid at 0x17430ddff40>
- x축은 타깃값 count, y축은 빈도수(횟수)
- 오른쪽으로 꼬리가 긴 분포
- 회귀 모델이 좋은 성능 내려면 데이터가 정규분포를 따르는게 좋음
- 따라서 타깃값으 정규분포에 가깝게 만들기 위해 로그 변환 사용
sns.displot(np.log(bike_train['count']))
<seaborn.axisgrid.FacetGrid at 0x1742fd459d0>
4.2 대여 시간대(년, 월, 일, 요일, 시간, 분) 에 따른 자전거 대여율
범주형 데이터에 따른 수치형 데이터의 평균값(막대그래프)
bike_train.columns
Index(['datetime', 'season', 'holiday', 'workingday', 'weather', 'temp',
'atemp', 'humidity', 'windspeed', 'casual', 'registered', 'count',
'year', 'month', 'day', 'hour', 'minute', 'dayofweek'],
dtype='object')
figure, axes = plt.subplots(nrows=2, ncols=3)
plt.tight_layout()
figure.set_size_inches(15, 9)
sns.barplot(data=bike_train, x="year", y="count", ax=axes[0][0]) # 년도별 자전거 대여량의 평균
sns.barplot(data=bike_train, x="month", y="count", ax=axes[0][1]) # 월별 자전거 대여량의 평균
sns.barplot(data=bike_train, x="day", y="count", ax=axes[0][2]) # 날짜별 자전거 대여량의 평균
sns.barplot(data=bike_train, x="dayofweek", y="count", ax=axes[1][0]) # 요일별 자전거 대여량의 평균
sns.barplot(data=bike_train, x="hour", y="count", ax=axes[1][1]) # 시간별 자전거 대여량의 평균
sns.barplot(data=bike_train, x="minute", y="count", ax=axes[1][2]) # 분별 자전거 대여량의 평균
axes[0][0].set(title="number of rentals per year")
axes[0][1].set(title="number of rentals per month")
axes[0][2].set(title="number of rentals per day")
axes[1][0].set(title="number of rentals per dayofweek")
axes[1][1].set(title="number of rentals per hour")
axes[1][2].set(title="number of rentals per minute")
[Text(0.5, 1.0, 'number of rentals per minute')]
- 년도별 대여량은 2011년보다 2012년이 더 많음
- 월별 대여량은 6월에 가장 많고, 7~10월도 대여량이 많음. 1월이 가장 적음
- 일별 대여량은 1일부터 19일까지만 데이터가 있고 예측력도 없어 보임. 20~31일 데이터는 테스트 데이터에 있음(사용불가)
- 시간대 대여량은 오전 8시, 오후 17~18시에 에 많은것으로 보아 출퇴근 시간대로 추측(주말과 평일을 나누어 분석해보기)
4.3 대여 시간대를 제외한 다른 범주형 데이터(계절, 날씨, 공휴일여부 등)에 따른 자전거 대여율
bike_train.columns
Index(['datetime', 'season', 'holiday', 'workingday', 'weather', 'temp',
'atemp', 'humidity', 'windspeed', 'casual', 'registered', 'count',
'year', 'month', 'day', 'hour', 'minute', 'dayofweek'],
dtype='object')
figure, axes = plt.subplots(nrows=2, ncols=2)
plt.tight_layout()
figure.set_size_inches(10, 10)
sns.barplot(data=bike_train, x="workingday", y="count", ax=axes[0][0])
sns.barplot(data=bike_train, x="holiday", y="count", ax=axes[0][1])
sns.barplot(data=bike_train, x="season", y="count", ax=axes[1][0])
sns.barplot(data=bike_train, x="weather", y="count", ax=axes[1][1])
axes[0][0].set(title="number of rentals per workingday")
axes[0][1].set(title="number of rentals per holiday")
axes[1][0].set(title="number of rentals per season")
axes[1][1].set(title="number of rentals per weather")
axes[1][1].set_xticklabels(axes[1][1].get_xticklabels(), rotation=30)
[Text(0, 0, 'Clear'),
Text(1, 0, 'Mist, Few clouds'),
Text(2, 0, 'Light Snow, Rain, Thunder'),
Text(3, 0, 'Heavy Snow, Rain, Thunder')]
- 가을(Fall, 3)에 대여량이 많음 (앞의 month 특성과 겹치는 부분이 있음)
- 데이터가 지나치게 세분화 되어 있으면 분류별 데이터수가 적이지므로 month 특성 제거 고려
- 날씨가 좋을수록 대여 수량이 많음
- 그러나 heavy snow 기상상태에서 160건 이상의 대여량이 있는것이 이상
bike_train.groupby("weather").size() # countplot
weather
Clear 7192
Heavy Snow, Rain, Thunder 1
Light Snow, Rain, Thunder 859
Mist, Few clouds 2834
dtype: int64
bike_train.groupby("weather").mean()['count'] # barplot
weather
Clear 205.236791
Heavy Snow, Rain, Thunder 164.000000
Light Snow, Rain, Thunder 118.846333
Mist, Few clouds 178.955540
Name: count, dtype: float64
4.4 위의 범주형 데이터(계절, 날씨, 공휴일 여부 등)의 사분위 분포 확인
figure, axes = plt.subplots(nrows=2, ncols=2)
plt.tight_layout()
figure.set_size_inches(10, 10)
sns.boxplot(data=bike_train, x="workingday", y="count", ax=axes[0][0])
sns.boxplot(data=bike_train, x="holiday", y="count", ax=axes[0][1])
sns.boxplot(data=bike_train, x="season", y="count", ax=axes[1][0])
sns.boxplot(data=bike_train, x="weather", y="count", ax=axes[1][1])
axes[0][0].set(title="box plot on count across workingday")
axes[0][1].set(title="box plot on count across holiday")
axes[1][0].set(title="box plot on count across season")
axes[1][1].set(title="box plot on count across weather")
axes[1][1].set_xticklabels(axes[1][1].get_xticklabels(), rotation=30)
[Text(0, 0, 'Clear'),
Text(1, 0, 'Mist, Few clouds'),
Text(2, 0, 'Light Snow, Rain, Thunder'),
Text(3, 0, 'Heavy Snow, Rain, Thunder')]
- 공휴일이 아닐 때 특잇값(outlier)가 많음
- 근무일일 때 특잇값이 많음
- 가을(Fall, 3)에 대여량이 많음
- 날씨가 좋을수록 대여량이 많음
- 악천후 속 자전거 대여수 한건(특잇값)데 대해 제거 고려
4.5 시간대별 자건거 대여율을 추가 정보와 함께 보기
추가정보 : 공휴일여부
figure, (ax1, ax2) = plt.subplots(nrows=2)
sns.pointplot(data=bike_train, x="hour", y="count", ax=ax1) # 시간별 자전거 대여량의 평균
sns.pointplot(data=bike_train, x="hour", y="count", hue='workingday', ax=ax2)
<AxesSubplot:xlabel='hour', ylabel='count'>
- 근무일에는 출퇴근 시간에 대여량이 많고
- 쉬는날에는 오후 12~2시 사이에 대여량이 많음
4.6 상관관계
수치형 데이터 간 상관관계를 파악하기 위해 산점도 그래프(회귀선 포함한) 그리기
- (1) 온도, 체감온도, 습도, 풍속별 대여 수량 산점도 그래프(‘temp’, ‘atemp’, ‘humidity’, ‘windspeed’)
figure, axes = plt.subplots(nrows=2, ncols=2)
plt.tight_layout()
figure.set_size_inches(7, 6)
sns.regplot(data=bike_train, x='temp', y='count', ax=axes[0][0],
scatter_kws={'alpha':0.2}, line_kws={'color' : 'blue'})
sns.regplot(data=bike_train, x='atemp', y='count', ax=axes[0][1],
scatter_kws={'alpha':0.2}, line_kws={'color' : 'blue'})
sns.regplot(data=bike_train, x='humidity', y='count', ax=axes[1][0],
scatter_kws={'alpha':0.2}, line_kws={'color' : 'blue'})
sns.regplot(data=bike_train, x='windspeed', y='count', ax=axes[1][1],
scatter_kws={'alpha':0.2}, line_kws={'color' : 'blue'})
<AxesSubplot:xlabel='windspeed', ylabel='count'>
- 온도, 체감온도가 높을수록 대여 수량이 많음
- 습하지 않을수록 대여 수량이 많음
- 풍속이 셀수록 대여 수량이 많은지는 확실치가 않고
-
풍속이 0일 때 표시되어 있는 값을이 다수 발견되었고, 만약 결측치라면 제거하는 것 고려
- (2) 상관계수
bike_train[['temp', 'atemp', 'humidity', 'windspeed', 'count']].corr()
temp | atemp | humidity | windspeed | count | |
---|---|---|---|---|---|
temp | 1.000000 | 0.984948 | -0.064949 | -0.017852 | 0.394454 |
atemp | 0.984948 | 1.000000 | -0.043536 | -0.057473 | 0.389784 |
humidity | -0.064949 | -0.043536 | 1.000000 | -0.318607 | -0.317371 |
windspeed | -0.017852 | -0.057473 | -0.318607 | 1.000000 | 0.101369 |
count | 0.394454 | 0.389784 | -0.317371 | 0.101369 | 1.000000 |
corr = bike_train[['temp', 'atemp', 'humidity', 'windspeed', 'count']].corr()
plt.figure(figsize=(8,8)) # sns.heatmap 함수가 axes level api 이므로 figure 사이즈 조절 가능
sns.heatmap(corr, annot=True, fmt='.2f', linewidths=0.5, cmap='YlGnBu')
<AxesSubplot:>
- 온도(temp)와 대여수량(count)간 상관계수 0.39(양의 상관관계)
- 풍속(windspeed)과 대여수량(count)간 상관계수는 0.1(상관관계가 매우 약함)
- windspeed는 대여 수량 예측에 별 도움을 주지 못하면서 결측치(0인값)도 많으므로 제거 고려
선형회귀에서 다중공선성 문제(Multicollinearity in Regression)
- https://towardsdatascience.com/multi-collinearity-in-regression-fe7a2c1467ea
- temp와 atemp가 상관관계가 높으므로 원하는 coef값이 나오지 않을 수 있음
- 두 특성 모두 예측변수에 양의 상관관계가 있으므로 coef가 양수값이 나오길 기대하지만
- 다중 선형회귀에선느 이 값이 바뀔 수 있음
5. 데이터 전처리
훈련 데이터와 테스트 데이터를 준비하는 방법
- 훈련 데이터에 대한 전처리를 해준 뒤, 동일한 방법으로 테스트 데이터에 적용한다.
- 훈련 데이터와 테스트 데이터를 합쳐서 모든 전처리가 끝난 뒤, 분리한다.
from IPython.display import Image
Image('./images/img5.png')
bike_train = pd.read_csv('datasets/bike_train.csv')
bike_test = pd.read_csv('datasets/bike_test.csv')
submission = pd.read_csv('datasets/sampleSubmission.csv')
5.1 이상치 제거
(bike_train['weather'] == 4).sum()
1
bike_train.shape
(10886, 12)
bike_train = bike_train[bike_train['weather'] != 4]
bike_train.shape
(10885, 12)
5.2 훈련 데이터와 테스트 데이터 합치기
pd.concat([bike_train, bike_test], axis=0) # 행축으로 연결
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
all_data = pd.concat([bike_train, bike_test], axis=0, 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
5.3 datetime으로부터 파생특성 추가(특성 공학, feature engineering)
all_data['datetime'] = all_data.datetime.apply(pd.to_datetime)
all_data['year'] = all_data.datetime.apply(lambda x : x.year)
all_data['month'] = all_data.datetime.apply(lambda x : x.month)
all_data['hour'] = all_data.datetime.apply(lambda x : x.hour)
all_data['dayofweek'] = all_data.datetime.apply(lambda x : x.dayofweek)
5.4 필요없는 특성 제거
drop_features = ['casual', 'registered', 'datetime', 'windspeed', 'month']
all_data = all_data.drop(drop_features, axis=1)
- 탐색적 데이터 분석에서 얻은 인사트를 활용해 의미있는 특성과 불필요한 특성을 구분(특성 선택)
- 특성이 많다고 무조건 좋은게 아님
- 예측 성능을 높이려면 타기값과 관련있는 특성이 필요
- 탐색적 데이터 분석, 상관관계 매트릭스 등의 배경지식을 종합적으로 활용해 판단해야 함
- 모델링 과정에서 특성 중요도(Tree model), 회귀 계수등을 참고해 볼 수 있음
5.5 훈련 데이터와 테스트 데이터 나누기
all_data[pd.isnull(all_data['count'])]
0 False
1 False
2 False
3 False
4 False
...
17373 True
17374 True
17375 True
17376 True
17377 True
Name: count, Length: 17378, dtype: bool
# Train/Test 데이터 분리하기
X_train = all_data[~pd.isnull(all_data['count'])]
X_test = all_data[pd.isnull(all_data['count'])]
# 타깃값, 특성 분리하기
y_train = X_train['count']
X_train = X_train.drop(['count'], axis=1)
X_test = X_test.drop(['count'], axis=1)
# y_test = 없음
6. 평가지표 계산 함수 작성
Image('./images/img6.png')
from sklearn.metrics import make_scorer
# log 값 변환 시 언더플로우 영향으로 log() 가 아닌 log1p() 를 이용하여 RMSLE 계산
def rmsle(y, pred, convertExp=True):
if convertExp:
y = np.expm1(y)
pred = np.expm1(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
rmsle_scorer = make_scorer(rmsle, greater_is_better=False)
7. 모델 선택과 훈련
7.1 선형회귀 모델
from sklearn.linear_model import LinearRegression
lin_reg = LinearRegression()
log_y = np.log1p(y_train)
lin_reg.fit(X_train, log_y)
LinearRegression()
7.2 잘못된 검증 (X_train으로 학습하고, X_train으로 검증을 했으므로)
y_pred = lin_reg.predict(X_train)
rmsle(log_y, y_pred, True)
1.0183455499651857
7.3 모델 성능 검증(교차 검증)
from sklearn.model_selection import cross_val_score
scores = cross_val_score(lin_reg, X_train, log_y, scoring=rmsle_scorer, cv=5)
-scores.mean() # 5개 교차 검증한 결과의 평균 rmsle
1.0259377040159592
# 공식 문서에 나와 있는 predefined values(‘neg_mean_squared_log_error’)를 사용해도 되나
# underflow 처리가 안되어 있는 것으로 보임
# https://scikit-learn.org/stable/modules/model_evaluation.html#the-scoring-parameter-defining-model-evaluation-rules
# scores = cross_val_score(lin_reg, X_train, y_train, scoring='neg_mean_squared_log_error', cv=5)
# -scores.mean()
7.4 규제모델(릿지, 라쏘, 엘라스티넷 모델)
from sklearn.linear_model import Ridge, Lasso, ElasticNet
# 모델 생성
ridge = Ridge()
lasso = Lasso()
elastic = ElasticNet()
scores = cross_val_score(ridge, X_train, log_y, scoring=rmsle_scorer, cv=5)
-scores.mean() # 릿지모델을 5개 교차 검증한 결과의 평균 rmsle
1.0259457332657096
scores= cross_val_score(lasso, X_train, log_y, scoring=rmsle_scorer, cv=5)
-scores.mean() # 라쏘모델을 5개 교차 검증한 결과의 평균 rmsle
1.1142719258697973
scores = cross_val_score(elastic, X_train, log_y, scoring=rmsle_scorer, cv=5)
-scores.mean() # 엘라스틱넷 모델을 5개 교차 검증한 결과의 평균 rmsle
1.1035342525816867
8. 모델 세부 튜닝
from sklearn.model_selection import GridSearchCV
8.1 릿지 모델
ridge = Ridge()
ridge_params = {'alpha' : [0.1, 1, 2, 3, 4, 10, 30, 100, 200, 300, 400, 800, 900, 1000]} # 14개
gridsearch_ridge = GridSearchCV(ridge, ridge_params, scoring=rmsle_scorer, cv=5, n_jobs=-1) # 14 * 5
%time gridsearch_ridge.fit(X_train, log_y)
Wall time: 6.33 s
GridSearchCV(cv=5, estimator=Ridge(), n_jobs=-1,
param_grid={'alpha': [0.1, 1, 2, 3, 4, 10, 30, 100, 200, 300, 400,
800, 900, 1000]},
scoring=make_scorer(rmsle, greater_is_better=False))
gridsearch_ridge.best_params_
{'alpha': 0.1}
cvres = gridsearch_ridge.cv_results_
for mean_score, params in zip(cvres['mean_test_score'], cvres['params']):
print(-mean_score, params) # rmsle와 그 때의 하이퍼 파라미터
1.0259385051524947 {'alpha': 0.1}
1.0259457332657098 {'alpha': 1}
1.0259538020203387 {'alpha': 2}
1.0259619099095543 {'alpha': 3}
1.0259700565712468 {'alpha': 4}
1.0260197314469994 {'alpha': 10}
1.0261944516002468 {'alpha': 30}
1.0268953328557835 {'alpha': 100}
1.0280667404301447 {'alpha': 200}
1.0293631168653716 {'alpha': 300}
1.0307297219760279 {'alpha': 400}
1.036310230958598 {'alpha': 800}
1.0376557036987102 {'alpha': 900}
1.0389657418631324 {'alpha': 1000}
coef = pd.Series(gridsearch_ridge.best_estimator_.coef_, index= X_train.columns)
coef_sort = coef.sort_values(ascending=False)
sns.barplot(x=coef_sort.values, y=coef_sort.index)
<AxesSubplot:>
8.2 랜덤포레스트
from sklearn.ensemble import RandomForestRegressor
rf_params = {'n_estimators' : [100, 120, 140]}
rnd_forest = RandomForestRegressor(random_state=42)
gridsearch_forest = GridSearchCV(rnd_forest, rf_params, scoring=rmsle_scorer, cv=5, n_jobs=-1)
%time gridsearch_forest.fit(X_train, log_y)
Wall time: 37.8 s
GridSearchCV(cv=5, estimator=RandomForestRegressor(random_state=42), n_jobs=-1,
param_grid={'n_estimators': [100, 120, 140]},
scoring=make_scorer(rmsle, greater_is_better=False))
gridsearch_forest.best_params_
{'n_estimators': 140}
cvres = gridsearch_forest.cv_results_
for mean_score, params in zip(cvres['mean_test_score'], cvres['params']):
print(-mean_score, params) # rmsle와 그 때의 하이퍼 파라미터
0.4624358747986295 {'n_estimators': 100}
0.461783777710962 {'n_estimators': 120}
0.46092376487614306 {'n_estimators': 140}
# best_model = gridsearch_forest.best_estimator_
sorted(zip(gridsearch_forest.best_estimator_.feature_importances_, X_train.columns), reverse=True)
[(0.7595180825712268, 'hour'),
(0.04792802447771889, 'temp'),
(0.03752619933445052, 'workingday'),
(0.03460172064058957, 'season'),
(0.03269870315942869, 'year'),
(0.029982293239748867, 'dayofweek'),
(0.022842240593619653, 'humidity'),
(0.021451786722442186, 'atemp'),
(0.011859502465998137, 'weather'),
(0.001591446794776728, 'holiday')]
8.3 그레디언트 부스팅 모델
from sklearn.ensemble import GradientBoostingRegressor
gbrt_params = { 'learning_rate' : [0.01, 0.02, 0.03, 0.04], # 각 트리의 기여도
'n_estimators' : [1000, 1500],
'subsample' : [0.9, 0.5, 0.2],
'max_depth' : [2, 4, 6, 8]
}
gbrt = GradientBoostingRegressor()
gridsearch_gbrt = GridSearchCV(gbrt, gbrt_params, scoring=rmsle_scorer, cv=5, n_jobs=-1)
%time gridsearch_gbrt.fit(X_train, log_y)
Wall time: 33min 23s
GridSearchCV(cv=5, estimator=GradientBoostingRegressor(), n_jobs=-1,
param_grid={'learning_rate': [0.01, 0.02, 0.03, 0.04],
'max_depth': [2, 4, 6, 8],
'n_estimators': [1000, 1500],
'subsample': [0.9, 0.5, 0.2]},
scoring=make_scorer(rmsle, greater_is_better=False))
gridsearch_gbrt.best_params_
{'learning_rate': 0.02, 'max_depth': 4, 'n_estimators': 1500, 'subsample': 0.2}
cvres = gridsearch_gbrt.cv_results_
for mean_score, params in zip(cvres['mean_test_score'], cvres['params']):
print(-mean_score, params) # rmsle와 그 때의 하이퍼 파라미터
0.5906089141705806 {'learning_rate': 0.01, 'max_depth': 2, 'n_estimators': 1000, 'subsample': 0.9}
0.579921762585159 {'learning_rate': 0.01, 'max_depth': 2, 'n_estimators': 1000, 'subsample': 0.5}
0.5663741039667025 {'learning_rate': 0.01, 'max_depth': 2, 'n_estimators': 1000, 'subsample': 0.2}
0.5471539011441585 {'learning_rate': 0.01, 'max_depth': 2, 'n_estimators': 1500, 'subsample': 0.9}
0.5311834646856217 {'learning_rate': 0.01, 'max_depth': 2, 'n_estimators': 1500, 'subsample': 0.5}
0.5145898623173469 {'learning_rate': 0.01, 'max_depth': 2, 'n_estimators': 1500, 'subsample': 0.2}
0.4118950190737608 {'learning_rate': 0.01, 'max_depth': 4, 'n_estimators': 1000, 'subsample': 0.9}
0.3996996455607074 {'learning_rate': 0.01, 'max_depth': 4, 'n_estimators': 1000, 'subsample': 0.5}
0.3905148703809438 {'learning_rate': 0.01, 'max_depth': 4, 'n_estimators': 1000, 'subsample': 0.2}
0.39833687563844705 {'learning_rate': 0.01, 'max_depth': 4, 'n_estimators': 1500, 'subsample': 0.9}
0.38808278161602433 {'learning_rate': 0.01, 'max_depth': 4, 'n_estimators': 1500, 'subsample': 0.5}
0.3795914616843881 {'learning_rate': 0.01, 'max_depth': 4, 'n_estimators': 1500, 'subsample': 0.2}
0.39842096753383477 {'learning_rate': 0.01, 'max_depth': 6, 'n_estimators': 1000, 'subsample': 0.9}
0.3869577673518602 {'learning_rate': 0.01, 'max_depth': 6, 'n_estimators': 1000, 'subsample': 0.5}
0.3824838450407437 {'learning_rate': 0.01, 'max_depth': 6, 'n_estimators': 1000, 'subsample': 0.2}
0.3968372102507972 {'learning_rate': 0.01, 'max_depth': 6, 'n_estimators': 1500, 'subsample': 0.9}
0.39148892207077896 {'learning_rate': 0.01, 'max_depth': 6, 'n_estimators': 1500, 'subsample': 0.5}
0.38106460343540627 {'learning_rate': 0.01, 'max_depth': 6, 'n_estimators': 1500, 'subsample': 0.2}
0.41987668866867167 {'learning_rate': 0.01, 'max_depth': 8, 'n_estimators': 1000, 'subsample': 0.9}
0.4035587622239323 {'learning_rate': 0.01, 'max_depth': 8, 'n_estimators': 1000, 'subsample': 0.5}
0.39139526613132203 {'learning_rate': 0.01, 'max_depth': 8, 'n_estimators': 1000, 'subsample': 0.2}
0.42247997388135533 {'learning_rate': 0.01, 'max_depth': 8, 'n_estimators': 1500, 'subsample': 0.9}
0.403033968363641 {'learning_rate': 0.01, 'max_depth': 8, 'n_estimators': 1500, 'subsample': 0.5}
0.39254128948034345 {'learning_rate': 0.01, 'max_depth': 8, 'n_estimators': 1500, 'subsample': 0.2}
0.5154467384447439 {'learning_rate': 0.02, 'max_depth': 2, 'n_estimators': 1000, 'subsample': 0.9}
0.49609546286244033 {'learning_rate': 0.02, 'max_depth': 2, 'n_estimators': 1000, 'subsample': 0.5}
0.48872050896328895 {'learning_rate': 0.02, 'max_depth': 2, 'n_estimators': 1000, 'subsample': 0.2}
0.47444137116187884 {'learning_rate': 0.02, 'max_depth': 2, 'n_estimators': 1500, 'subsample': 0.9}
0.4632210788301265 {'learning_rate': 0.02, 'max_depth': 2, 'n_estimators': 1500, 'subsample': 0.5}
0.4511927968064321 {'learning_rate': 0.02, 'max_depth': 2, 'n_estimators': 1500, 'subsample': 0.2}
0.3940615219421288 {'learning_rate': 0.02, 'max_depth': 4, 'n_estimators': 1000, 'subsample': 0.9}
0.3907714604259203 {'learning_rate': 0.02, 'max_depth': 4, 'n_estimators': 1000, 'subsample': 0.5}
0.38265131183936385 {'learning_rate': 0.02, 'max_depth': 4, 'n_estimators': 1000, 'subsample': 0.2}
0.39509294536118206 {'learning_rate': 0.02, 'max_depth': 4, 'n_estimators': 1500, 'subsample': 0.9}
0.38500914270823694 {'learning_rate': 0.02, 'max_depth': 4, 'n_estimators': 1500, 'subsample': 0.5}
0.37793852547802886 {'learning_rate': 0.02, 'max_depth': 4, 'n_estimators': 1500, 'subsample': 0.2}
0.40214516345353346 {'learning_rate': 0.02, 'max_depth': 6, 'n_estimators': 1000, 'subsample': 0.9}
0.393040935756728 {'learning_rate': 0.02, 'max_depth': 6, 'n_estimators': 1000, 'subsample': 0.5}
0.3919363193335945 {'learning_rate': 0.02, 'max_depth': 6, 'n_estimators': 1000, 'subsample': 0.2}
0.4080759521079864 {'learning_rate': 0.02, 'max_depth': 6, 'n_estimators': 1500, 'subsample': 0.9}
0.40018541504501376 {'learning_rate': 0.02, 'max_depth': 6, 'n_estimators': 1500, 'subsample': 0.5}
0.39243648284144406 {'learning_rate': 0.02, 'max_depth': 6, 'n_estimators': 1500, 'subsample': 0.2}
0.426434870907884 {'learning_rate': 0.02, 'max_depth': 8, 'n_estimators': 1000, 'subsample': 0.9}
0.4102075452980422 {'learning_rate': 0.02, 'max_depth': 8, 'n_estimators': 1000, 'subsample': 0.5}
0.39741837857782814 {'learning_rate': 0.02, 'max_depth': 8, 'n_estimators': 1000, 'subsample': 0.2}
0.43375255879637037 {'learning_rate': 0.02, 'max_depth': 8, 'n_estimators': 1500, 'subsample': 0.9}
0.4122889744185871 {'learning_rate': 0.02, 'max_depth': 8, 'n_estimators': 1500, 'subsample': 0.5}
0.40760283095423794 {'learning_rate': 0.02, 'max_depth': 8, 'n_estimators': 1500, 'subsample': 0.2}
0.47349613105504024 {'learning_rate': 0.03, 'max_depth': 2, 'n_estimators': 1000, 'subsample': 0.9}
0.4594690737140569 {'learning_rate': 0.03, 'max_depth': 2, 'n_estimators': 1000, 'subsample': 0.5}
0.4543533894350437 {'learning_rate': 0.03, 'max_depth': 2, 'n_estimators': 1000, 'subsample': 0.2}
0.44317495889783987 {'learning_rate': 0.03, 'max_depth': 2, 'n_estimators': 1500, 'subsample': 0.9}
0.43001870458437974 {'learning_rate': 0.03, 'max_depth': 2, 'n_estimators': 1500, 'subsample': 0.5}
0.4296998651477966 {'learning_rate': 0.03, 'max_depth': 2, 'n_estimators': 1500, 'subsample': 0.2}
0.39487445439739466 {'learning_rate': 0.03, 'max_depth': 4, 'n_estimators': 1000, 'subsample': 0.9}
0.3835997293245262 {'learning_rate': 0.03, 'max_depth': 4, 'n_estimators': 1000, 'subsample': 0.5}
0.3850881912330891 {'learning_rate': 0.03, 'max_depth': 4, 'n_estimators': 1000, 'subsample': 0.2}
0.395186027786576 {'learning_rate': 0.03, 'max_depth': 4, 'n_estimators': 1500, 'subsample': 0.9}
0.388819941216073 {'learning_rate': 0.03, 'max_depth': 4, 'n_estimators': 1500, 'subsample': 0.5}
0.38981905398852745 {'learning_rate': 0.03, 'max_depth': 4, 'n_estimators': 1500, 'subsample': 0.2}
0.4037401446700408 {'learning_rate': 0.03, 'max_depth': 6, 'n_estimators': 1000, 'subsample': 0.9}
0.3987030387428145 {'learning_rate': 0.03, 'max_depth': 6, 'n_estimators': 1000, 'subsample': 0.5}
0.3890568570237579 {'learning_rate': 0.03, 'max_depth': 6, 'n_estimators': 1000, 'subsample': 0.2}
0.41305108506417343 {'learning_rate': 0.03, 'max_depth': 6, 'n_estimators': 1500, 'subsample': 0.9}
0.40366887040582444 {'learning_rate': 0.03, 'max_depth': 6, 'n_estimators': 1500, 'subsample': 0.5}
0.399786031773361 {'learning_rate': 0.03, 'max_depth': 6, 'n_estimators': 1500, 'subsample': 0.2}
0.4316671890920107 {'learning_rate': 0.03, 'max_depth': 8, 'n_estimators': 1000, 'subsample': 0.9}
0.41792501166325113 {'learning_rate': 0.03, 'max_depth': 8, 'n_estimators': 1000, 'subsample': 0.5}
0.40250766060061577 {'learning_rate': 0.03, 'max_depth': 8, 'n_estimators': 1000, 'subsample': 0.2}
0.4370268233120105 {'learning_rate': 0.03, 'max_depth': 8, 'n_estimators': 1500, 'subsample': 0.9}
0.4202238864805464 {'learning_rate': 0.03, 'max_depth': 8, 'n_estimators': 1500, 'subsample': 0.5}
0.4063778498773357 {'learning_rate': 0.03, 'max_depth': 8, 'n_estimators': 1500, 'subsample': 0.2}
0.45100112068068154 {'learning_rate': 0.04, 'max_depth': 2, 'n_estimators': 1000, 'subsample': 0.9}
0.43927772220082273 {'learning_rate': 0.04, 'max_depth': 2, 'n_estimators': 1000, 'subsample': 0.5}
0.44152917410971737 {'learning_rate': 0.04, 'max_depth': 2, 'n_estimators': 1000, 'subsample': 0.2}
0.4294218912667092 {'learning_rate': 0.04, 'max_depth': 2, 'n_estimators': 1500, 'subsample': 0.9}
0.41517980250739406 {'learning_rate': 0.04, 'max_depth': 2, 'n_estimators': 1500, 'subsample': 0.5}
0.4156676064827886 {'learning_rate': 0.04, 'max_depth': 2, 'n_estimators': 1500, 'subsample': 0.2}
0.3949748055358647 {'learning_rate': 0.04, 'max_depth': 4, 'n_estimators': 1000, 'subsample': 0.9}
0.38914522370176813 {'learning_rate': 0.04, 'max_depth': 4, 'n_estimators': 1000, 'subsample': 0.5}
0.39001818425548423 {'learning_rate': 0.04, 'max_depth': 4, 'n_estimators': 1000, 'subsample': 0.2}
0.39978233756910975 {'learning_rate': 0.04, 'max_depth': 4, 'n_estimators': 1500, 'subsample': 0.9}
0.39507044815115694 {'learning_rate': 0.04, 'max_depth': 4, 'n_estimators': 1500, 'subsample': 0.5}
0.38730094156576234 {'learning_rate': 0.04, 'max_depth': 4, 'n_estimators': 1500, 'subsample': 0.2}
0.41272352555900715 {'learning_rate': 0.04, 'max_depth': 6, 'n_estimators': 1000, 'subsample': 0.9}
0.40557251093134256 {'learning_rate': 0.04, 'max_depth': 6, 'n_estimators': 1000, 'subsample': 0.5}
0.39501935398901855 {'learning_rate': 0.04, 'max_depth': 6, 'n_estimators': 1000, 'subsample': 0.2}
0.4156625204444511 {'learning_rate': 0.04, 'max_depth': 6, 'n_estimators': 1500, 'subsample': 0.9}
0.4095703434330421 {'learning_rate': 0.04, 'max_depth': 6, 'n_estimators': 1500, 'subsample': 0.5}
0.4066175320762581 {'learning_rate': 0.04, 'max_depth': 6, 'n_estimators': 1500, 'subsample': 0.2}
0.43516100506982786 {'learning_rate': 0.04, 'max_depth': 8, 'n_estimators': 1000, 'subsample': 0.9}
0.4260868241573953 {'learning_rate': 0.04, 'max_depth': 8, 'n_estimators': 1000, 'subsample': 0.5}
0.4197472575914326 {'learning_rate': 0.04, 'max_depth': 8, 'n_estimators': 1000, 'subsample': 0.2}
0.43322665401448024 {'learning_rate': 0.04, 'max_depth': 8, 'n_estimators': 1500, 'subsample': 0.9}
0.4279644987244298 {'learning_rate': 0.04, 'max_depth': 8, 'n_estimators': 1500, 'subsample': 0.5}
0.4204275535001128 {'learning_rate': 0.04, 'max_depth': 8, 'n_estimators': 1500, 'subsample': 0.2}
best_model = gridsearch_gbrt.best_estimator_
sorted(zip(gridsearch_gbrt.best_estimator_.feature_importances_, X_train.columns), reverse=True)
[(0.7238389424392861, 'hour'),
(0.04384496892905651, 'temp'),
(0.0425674121293566, 'workingday'),
(0.03834706261079211, 'dayofweek'),
(0.035668256399721286, 'season'),
(0.03370358086159958, 'year'),
(0.0322601838824559, 'atemp'),
(0.030783224044040294, 'humidity'),
(0.016553611107436732, 'weather'),
(0.0024327575962549927, 'holiday')]
9.모델 예측과 성능 평가
X_test.shape
(6493, 10)
best_pred = best_model.predict(X_test)
best_pred
array([2.46204042, 1.78796519, 1.3283687 , ..., 4.73332347, 4.42257987,
3.82758356])
submission['count'] = np.expm1(best_pred)
ver = 1
submission.to_csv('bike_{}_submission.csv'.format(ver), index=False)
더 해볼만한것!
- registered와 casual을 각각 예측해서 합산해보는 방법도 있음
- 더 강력한 모델(boosting 모델)로 도전
댓글남기기