*지도 학습 연습
1.kaggle
=>www.kaggle.com
2.자전거 대여 수요 예측
1)개\요
-url: https://www.kaggle.com/c/bike-sharing-demand
-미션: 날짜, 계절, 근무랑 여부, 온도, 체감 온도, 풍속 등의 데이터를 이용해서 자전거 대여 수요 예측
-유형:회귀
-평가 지표: RMSLE(평균 제곱 대수 오류)로 평가
-데이터를 다운로드 받아서 적절한 곳에 복사
2) 데이터 다운
-kaggle 가서 다운
3)데이터 확인
=>데이터 불러오기
import pandas as pd
train = pd.read_csv('./bike/train.csv')
test = pd.read_csv('./bike/test.csv')
submission = pd.read_csv('./bike/sampleSubmission.csv')
print(train.head())
=>데이터 확인
train.info()
#결측치 없고 datetime 다 숫자형
test.info()
#train 데이터에는 존재하지만 test 데이터에 존재하지 않는 컬럼 발견
#casual,registered,count
#count는 타겟이라 없는 것
#훈련할 때 casual 과 registered 는 제외
=>feature 에 대한 설명
-datetime:날짜와 시간
-season: 1- 봄,2-여름.3-가을,4-겨울
-holiday: 0- 휴일이 아닌 날 1- 주말을 제외한 국경일등의 휴일
-workingday: 0- 주말 및 국경일 1- 근무하는 날
-weather: 1 -맑음, 거의 없음 구름, 약간 흐림, 약간 흐림
2- 안개 + 흐림, 안개 + 부서진 구름, 안개 + 약간의 구름, 안개
3- 가벼운 눈, 가벼운 비 + 뇌우 + 흩어진 구름, 가벼운 비 + 흩어진 구름
4- 폭우 + 얼음 팔레트 + 뇌우 + 안개, 눈 + 안개
=======여기까지 범주형 =========
- temp:온도
-atemp:체감온도
-humidity:습도
-windspeed:풍속
-casual:예약하지 않은 사용자가 대여하는 횟수
-registered:예약한 사용자가 대여한 횟수
-count:대여 횟수
=>날짜가 존재할 경우는 Datetime으로 변환해서 분할을 해보는 것이 좋습니다.
#datetime을 datetime타입으로 변겨하고 분할해서 열로 추가
#astype을 활용할 수 있음
#함수에 데이터를 전부 대입해서 변환
train['datetime']=train.datetime.apply(pd.to_datetime)
train['year']=train['datetime'].apply(lambda x:x.year)
train['month']=train['datetime'].apply(lambda x:x.month)
train['year']=train['datetime'].apply(lambda x:x.year)
train['hour']=train['datetime'].apply(lambda x:x.hour)
train['minute']=train['datetime'].apply(lambda x:x.minute)
train['second']=train['datetime'].apply(lambda x:x.second)
train['weekday']=train['datetime'].apply(lambda x:x.weekday())
#데이터 탐색을 할 때는 범주형의 경우는 원래 이름으로 변경해 두는 것이 좋습니다.
train['season'] = train['season'].map({
1:'봄', 2:'여름', 3:'가을', 4:'겨울'
})
train['weather'] = train['weather'].map({
1:'맑음', 2:'안개', 3:'가벼운 눈이나 비', 4:'심한 눈이나 비'
})
train.head()
=>타겟 확인
#타겟확인
#타겟이 범주형인 경우는 확인을 해서 특정 범주의 데이터가 아주 많거나 적으면
#층화 추출이나 오버샘플링 이나 언더 샘플링을 고려
#타겟이 연속형인 데이터인 경우 정규분포에 가까운지 확인을 해서
#한쪽에 몰려있는 경우 로그 변환 등을 이용해서 분포를 변경
sns.displot(train['count'])
#데이터의 분포가 왼쪽으로 치우쳐 있음
왼쪽으로 치우쳐져 있어서 log 변환을 시도
#타겟을 로그 변환해서 시각화
import numpy as np
sns.distplot(np.log(train['count']))
위에 상태 보다 좀 더 가운데에 몰리는 것을 볼 수 있음
=>년 월 일 시 분 초 에 따른 타겟의 변화확인
import matplotlib as mpl
figure ,axes = plt.subplots(nrows=3,ncols=2)
sns.barplot(x='year', y='count', data=train, ax=axes[0, 0])
sns.barplot(x='month', y='count', data=train, ax=axes[0, 1])
sns.barplot(x='day', y='count', data=train, ax=axes[1, 0])
sns.barplot(x='hour', y='count', data=train, ax=axes[1, 1])
sns.barplot(x='minute', y='count', data=train, ax=axes[2, 0])
sns.barplot(x='second', y='count', data=train, ax=axes[2, 1])
=>feature을 지워도 될때는 분산이 아주 작을 떄. 분산이 아주 작으면 유의미한 변화를 가져올 수 없기 때문에 제거해도 됩니다. 일, 분, 초는 제거해도 될 것 같습니다.
=>계절, 날씨, 공휴일 여부, 근무일 별 대여 수량을 시각화
figure, axes = plt.subplots(nrows=2, ncols=2) # 2행 2열
sns.boxplot(x='season', y='count', data=train, ax=axes[0, 0])
sns.boxplot(x='weather', y='count', data=train, ax=axes[0, 1])
sns.boxplot(x='holiday', y='count', data=train, ax=axes[1, 0])
sns.boxplot(x='workingday', y='count', data=train, ax=axes[1, 1])
#x축 라벨 겹침 해결
axes[0, 1].tick_params('x', labelrotation=10) # 10도 회전
boxplot 을 이용하면 이상치의 분포 여부를 확인하는 것이 가능
=>막대 그래프를 통한 그래프 확인
figure, axes = plt.subplots(nrows=5) # 5행 1열
figure.set_size_inches(12, 18)
sns.pointplot(x='hour', y='count', data=train, hue='workingday', ax=axes[0])
sns.pointplot(x='hour', y='count', data=train, hue='holiday', ax=axes[1])
sns.pointplot(x='hour', y='count', data=train, hue='weekday', ax=axes[2])
sns.pointplot(x='hour', y='count', data=train, hue='season', ax=axes[3])
sns.pointplot(x='hour', y='count', data=train, hue='weather', ax=axes[4]);
=>연속형 데이터는 feature와 상관 관계를 파악할 필요가 있음: 상관관계가 거의 없다면 제거해도됨
상관관계 파악은 상관계수를 확인해도 되고 산포도(regplot - reg는 선형회귀 직선을 같이 출력)를 이용하기도 하면 상관계수를 구한 후 heatmap 같은 도구를 이용해서 시각화 하기도 합니다.
#연속형인 온도,체감온도,풍속,습도별 대여 수량 확인
figure,axes = plt.subplots(nrows=2,ncols=2)
sns.regplot(x='temp', y='count', data=train, ax=axes[0, 0],
scatter_kws={'alpha': 0.2}, line_kws={'color': 'blue'})
sns.regplot(x='atemp', y='count', data=train, ax=axes[0, 1],
scatter_kws={'alpha': 0.2}, line_kws={'color': 'blue'})
sns.regplot(x='windspeed', y='count', data=train, ax=axes[1, 0],
scatter_kws={'alpha': 0.2}, line_kws={'color': 'blue'})
sns.regplot(x='humidity', y='count', data=train, ax=axes[1, 1],
scatter_kws={'alpha': 0.2}, line_kws={'color': 'blue'});
4)규제 없는 선형회귀 적용
#데이터 탐색할 때 변경된 내용이 있을 수 있기 때문에 탐색이 끝나면
#데이터를 가져와 작업을 수행
import pandas as pd
train = pd.read_csv('./bike/train.csv')
test = pd.read_csv('./bike/test.csv')
#이 데이터는 답안을 만들 때 참고하기 위한 데이터이므로 실제로 가져오지 않아도 됨
submission = pd.read_csv('./bike/sampleSubmission.csv')
print(train.head())
=>EDA 하면서 데이터가 혹시 변경되있을수도 있으므로 다시 한번 불러옴
일반적인 경우에는 train데이터와 test 데이터 구조가 같음.
경진대회용 데이터는 train.csv 와 test.csv 를 분리해서 제공하는데 2개의 데이터 구조가 다른 경우가 있으며
test.csv 에는 타겟이 없음
경진대회는 train 데이터를 가지고 모델을 만들어서 test 데이터의 타겟을 예측해서 제출하고 이를 기반으로 채점
#train데이터와 test 데이터를 합치기 = 동일한 구조를 만들기 위해
all_data_temp = pd.concat([train,test])
all_data_temp
위에서도 확인했지만 test 데이터에는 casual,registered,count가 존재하지 않음
여러개의 데이터를 행방향으로 결합할 때 주의할 점
- 별도의 인덱스를 설정하지 않으면 인덱스는 0부터 시작하는 일련 번호
-인덱스를 설정하지 않은 상태로 만든 DataFrame을 행 방향으로 결합하면 인덱스가 중첩될 수 있습니다
-인덱스가 6492 번이 마지막으로 나오는데 17378 개의 row 가 존재.=> 인덱스가 잘못됨을 암
# 인덱스가 중복되서 나오기 떄문에 기존의 인덱스 무시
all_data_temp = pd.concat([train,test],ignore_index=True)
all_data_temp
=>새로운 feature (년월일,년,시,요일)을 생
from datetime import datetime
#파생 피처 생성 - datetime 을 이용해서 새로운 열 (년월일, 년,시 , 요일)을 생성
#datetime 필드에서 앞의 날짜 부분만 잘라서 date 필드 생성
all_data['date']=all_data['datetime'].apply(lambda x:x.split()[0])
#datetime 필드에서 앞의 년도 부분만 잘라서 year 필드 생성
all_data['year']=all_data['datetime'].apply(lambda x:x.split()[0].split('-')[0])
#datetime 필드에서 앞의 시간 부분만 잘라서 hour 필드 생성
all_data['hour']=all_data['datetime'].apply(lambda x:x.split()[1].split(':')[0])
#datetime 필드에서 요일을 가지고 weekday 필드 생성
#날자 컬럼을 datetime으로 변환하고 거기서 weekday 메서드 호출해서 요일을 리턴
from datetime import datetime
all_data['datetime'] = all_data['date'].apply(
lambda x:datetime.strptime(x,'%Y-%m-%d').weekday())
#split 이나 substring을 잘 사용해야함 - 코테에서도 사용
all_data.head()
=>불필요한 feature 제거
# 불필요한피처 제거
#casual, registered ,datetime, date, windspeed
#삭제 작업을 할 때는 내가 무엇을 지웠는지를 변수에 저장해두는 것이 좋습니다.
#되도록이면 리터럴을 직접 사용하는 것보다 변수를 사용하는 것이 유지보수에 도움됩니다.
drop_features=['casual', 'registered' ,'datetime', 'date', 'windspeed']
all_data = all_data.drop(drop_features,axis=1)
위에 처럼 변수를 선언하고 하는게 더 깔끔한 코딩. all_data = all_data.drop(['casual', 'registered' ,'datetime', 'date', 'windspeed'],axis=1) 이랑은 같은 의미이지만 유지보수등을 위해서 좋음
=>훈련데이터,테스트데이터 분리
#훈련데이터,테스트 데이터 분리
X_train = all_data[~pd.isnull(all_data['count'])]
X_test = all_data[pd.isnull(all_data['count'])]
#feature 와 target 분리
X_train = X_train.drop(['count'],axis=1)
X_test = X_test.drop(['count'],axis=1)
y= train['count']
X_train.head()
=>평가 지표 함수
#평가 지표 함수 - 이 경진 대회의 평가 지표는 RMSLE
#타겟이 치우쳐 있어서 로그 변환을 수행하는 것이 좋은 모델을 만들 수 있습니다.
import numpy as np
#y_true 는 실제 값이고 y_pred 는 예측한 값이고 convertExp는 로그 변환 여부
def rmsle(y_true,y_pred,convertExp=True):
#로그 변환을 한 경우 원래 값으로 복원
if convertExp:
y_true = np.exp(y_true)
y_pred = np.exp(y_pred)
#로그 변환 할 때 1을 더해주지 않으면 0인경우 에러 발생
#로그 변환 할 때 1을 더해서 이를 방지 -log1p
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
=>모델 생성 및 훈련
# 모델 생성 및 훈련
from sklearn.linear_model import LinearRegression
linear_reg_model = LinearRegression()
#타겟의 로그 변환
log_y =np.log(y)
#훈련
linear_reg_model.fit(X_train,log_y)
#예측
preds = linear_reg_model.predict(X_train)
print("일반 선형 회귀의 RMSLE:",rmsle(log_y,preds,True))
=>답안 생성 후 제출
#답안 생성
linearreg_preds = linear_reg_model.predict(X_test)
#원래 데이터 복원
submission['count']=np.exp(linearreg_preds)
submission.to_csv('submission.csv',index=False)
5)규제가 있는 모델
=>Ridge, Lasso, Elastic Net
from sklearn.linear_model import Ridge,Lasso,ElasticNet
from sklearn.model_selection import GridSearchCV
from sklearn import metrics
#기본 모델
ridge_model = Ridge()
lasso_model = Lasso()
EN_model =ElasticNet()
#파라미터 생성
ridge_params ={
'max_iter':[3000],
'alpha':[0.1,0.3,1,2,3,4,10,100,300,500,800,1000]
}
lasso_params ={
'max_iter':[3000],
'alpha':[0.1,0.3,1,2,3,4,10,100,300,500,800,1000]
}
EN_params ={
'max_iter':[3000],
'alpha':[0.1,0.3,1,2,3,4,10,100,300,500,800,1000],
'l1_ratio':[0.2,0.4,0.6,0.7,0.9]
}
#사용자 정의 함수를 평가 지표로 사용
#우리가 만든 rmsle 함수를 사용하고 적은 점수가 좋다고 설정
rmsle_scorer = metrics.make_scorer(rmsle,greater_is_better=False)
#ridge
gridsearch_ridge_model=GridSearchCV(estimator=ridge_model,
param_grid = ridge_params,
scoring=rmsle_scorer,
cv=5)
log_y=np.log(y)
gridsearch_ridge_model.fit(X_train,log_y)
#lasso
gridsearch_lasso_model=GridSearchCV(estimator=lasso_model,
param_grid = lasso_params,
scoring=rmsle_scorer,
cv=5)
log_y=np.log(y)
gridsearch_lasso_model.fit(X_train,log_y)
#ElasticNet
gridsearch_EN_model=GridSearchCV(estimator=EN_model,
param_grid = EN_params,
scoring=rmsle_scorer,
cv=5)
log_y=np.log(y)
gridsearch_EN_model.fit(X_train,log_y)
#최적의 모델로 예측
#이 데이터는 비선형으로 예측됨
preds=gridsearch_ridge_model.best_estimator_.predict(X_train)
print('릿지 적용한 RMSLE:',rmsle(log_y,preds,True))
preds=gridsearch_lasso_model.best_estimator_.predict(X_train)
print('라쏘 적용한 RMSLE:',rmsle(log_y,preds,True))
preds=gridsearch_EN_model.best_estimator_.predict(X_train)
print('ElasticNet 적용한 RMSLE:',rmsle(log_y,preds,True))
6)부스팅 모델(RandomForest, AdaBoosting,GradientBoosting,HistGradientBoosting,XGBM,LightGBM,CatBoost적용)
#부스팅 모델 생성
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor, HistGradientBoostingRegressor
from xgboost import XGBRegressor
from lightgbm import LGBMRegressor
from catboost import CatBoostRegressor
rf_reg = RandomForestRegressor()
gbm_reg = GradientBoostingRegressor(n_estimators=500)
hgbm_reg = HistGradientBoostingRegressor(max_iter=500)
xgb_reg =XGBRegressor(n_estimators=500)
lgbm_reg = LGBMRegressor(n_estimators=500)
catgbm_reg = CatBoostRegressor(iterations=500)
=>각 모델 별 RMSLE 값 구하기
#randomforest 적용
gridsearch_random_forest_model =GridSearchCV(estimator=rf_reg,
param_grid=params,
scoring=rmsle_scorer,
cv=5)
gridsearch_random_forest_model.fit(X_train,log_y)
preds = gridsearch_random_forest_model.best_estimators_.predict(X_train)
print("RF의 RMSLE값:",rmsle(log_y,preds,True))
#XGB
gridsearch_xgb_model = GridSearchCV(estimator=xgb_reg,
param_grid=params,
scoring=rmsle_scorer,
cv=5)
gridsearch_xgb_model.fit(X_train,log_y)
preds = gridsearch_xgb_model.best_estimators_.predict(X_train)
print("XGB의 RMSLE값:",rmsle(log_y,preds,True))
#그래디언트 부스팅
gridsearch_gbm_model = GridSearchCV(estimator=gbm_reg,
param_grid=params,
scoring=rmsle_scorer,
cv=5)
gridsearch_gbm_model.fit(X_train,log_y)
preds = gridsearch_gbm_model.best_estimators_.predict(X_train)
print("gbm의 RMSLE값:",rmsle(log_y,preds,True))
#LGBM
gridsearch_lgbm_model = GridSearchCV(estimator=lgbm_reg,
param_grid=params,
scoring=rmsle_scorer,
cv=5)
gridsearch_lgbm_model.fit(X_train,log_y)
preds = gridsearch_lgbm_model.best_estimators_.predict(X_train)
print("LGBM의 RMSLE값:",rmsle(log_y,preds,True))
#CatBoost
gridsearch_catgbm_model = GridSearchCV(estimator=catgbm_reg,
param_grid=catgbm_params,
eval_metric=rmsle_scorer,
cv=5)
gridsearch_catgbm_model.fit(X_train.values,log_y.values)
preds = gridsearch_catgbm_model.best_estimator_.predict(X_train.values)
print("catboost의 RMSLE값:",rmsle(log_y,preds,True))
#HGBM - 255 개의 구간으로 피처를 나누어 학습
gridsearch_hgbm_model = GridSearchCV(estimator=hgbm_reg,
param_grid=hgbm_params,
scoring=rmsle_scorer,
cv=5)
#XGB는 DataFrame을 데이터로 사용하는 것이 안됩니다.
#numpy의 ndarray만 가능
gridsearch_hgbm_model.fit(X_train.values,log_y.values)
preds = gridsearch_hgbm_model.best_estimator_.predict(X_train.values)
print("HGBM의 RMSLE값:",rmsle(log_y,preds,True))
=>n_estimators 의 값만 하이퍼 파라미터 튜닝을 했는데 학습률이나 max_depth 등의 매개변수도 파라미터 튜닝을 하게 되면 더 좋은 성능을 기대할 수 있습니다.
필요하다면 feature engineering 도 수행해보는 것이 좋습니다.
'Study > Machine learning,NLP' 카테고리의 다른 글
머신 러닝(9)-차원 축소 (0) | 2024.03.07 |
---|---|
머신러닝(8)-실습(2) (0) | 2024.03.07 |
머신러닝(6)-Ensemble (0) | 2024.03.05 |
머신러닝(5) - Regression(2) (0) | 2024.03.05 |
머신러닝(4)-Regression(1) (0) | 2024.03.04 |