본문 바로가기
Study/Machine learning,NLP

머신러닝(7)-실습1

by 왕방개 2024. 3. 6.

*지도 학습 연습

1.kaggle

=>www.kaggle.com

 

2.자전거 대여 수요 예측

1)개\요

-url: https://www.kaggle.com/c/bike-sharing-demand 

 

Bike Sharing Demand | Kaggle

 

www.kaggle.com

-미션: 날짜, 계절, 근무랑 여부, 온도, 체감 온도, 풍속 등의 데이터를 이용해서 자전거 대여 수요 예측

-유형:회귀

-평가 지표: 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