정규화 방법론(Regularized Method, Penalized Method, Contrained Least Squares)
-선형회귀 계수(weight)에 대한 제약 조건을 추가함으로써 모형잉 과최적화를 맏는 방법
정규화 회귀분석이 왜 필요하는걸까?? 이 개념이 왜 등장 한 걸까?
아래 처럼 세가지 모델이 있다고 가정하자
이 중에서 어떤 모델이 가장 좋은 걸까? 라는 생각에서 시작이 해봅니다. 위 모델들을 아래 그래프로 설명하자면
1번 모델은 왼쪽 실선 박스에 해당하고 3번 모델은 오른쪽 회색 실선 박스에 해당됩니다. 1번은 train,test 둘다 에러가 높고 3번모델은 train에 아주아주 잘 적합이 되어서 train error는 작게 나옵니다. 그치만 정작 test error는 점점 높아집니다. train의 bias가 0에 가깝게 나왔지만 variance가 엄청 높게 나와서 test 성능이 나쁘게 나오게 된겁니다.
쉽게 생각하자면 시키는 일만!! 잘하는 사람입니다. 회사생활할때는 말하지 않아도, 시키지 않아도 해야하는 일들이 있는데 오른쪽 회색 박스에 있는 사람들은 훈련된 일만, 시키는 일만! 잘하고 다른건 일절 못하는 사람이라고 봐도 될것같습니다. 이럴거면 차라리 업무는 조금은 못하더라도 알아서 할줄 아는 사람이 훨씬 유능하다는 소리를 듣기 마련이죠.
이와 마찬가지로 train으로 훈련할때 어느정도 일반화에 가깝게 훈련을 진행해야 합니다. 따라서 train 성능이 좀 나빠지더라도 test 성능이 개선되는 편이 올바른 방향입니다. 3번 모델이 2번 모델로 만들어야 올바른 방향인데 이때 사용되는 것이 정규화 회귀분석입니다.
1) Ridge Regression
빨간색 밑줄이 추가된 부분입니다. 이 부분을 최소화 하는 값을 찾는다는 의미입니다.
아래 이미지에 나온 식이 있는데 이 전체 값이 가장 적은 숫자가 나오기 위해서는 어떻게 해야 할까요?
5000이라는 큰 값이 있어서 베타가 1만 나와도 큰 값이 더해집니다. 이 전체 값을 가장 적게 만들기 위해서는 베타가 0에 가까운 값, 예를들어 소수점 여야 합니다. 이런 식으로 베타 값에 제한을 줄수 있습니다. 그래서 책에서는
람다 값이 커지면 정규화 정도가 커지고 가중치 베타는 커질수 없다. 작아진다 라는 얘기가 나오게 된 것입니다.
만일 5차식의 복잡한 모델이 있다고 가정할때, 람다값을 10000을 주게 되면 베타 값들은 거의 0으로 떨어지고 남은건 상수인 베타제로 만 남게 될것입니다. 즉 5차식이 상수가이를 언더 핏팅이라고 합니다.
반대로 람다값이 제로에 가까운 값을 주게되면 제약이 미미하기 때문에 베타값이 다 살아나서 5차식이 그대로 사용됩니다. 여기서 정규화 정도라는 것은 "제약을 주었다",'혼란스러운 모델을 정리해 주었다' 정도의 의미가 이해하기 좋습니다
큰 숫자가 나오지 못하도록 제한을 한다는 의미를 다시 한번 생각해 볼 필요가 있습니다.
어떤 회귀분석 결과의 계수값들이 말도 안되게 큰 단위로만 구성이 되어 있다고 할때 릿지를 통해서 베타(계수값)를 줄인다면 이는 스케일링 효과도 내포함을 알수 있습니다. 상식적으로 불가능한 큰 계수를 조금 더 현실성 있는 값으로 변환 해줄수 있게 되는 겁니다.
2) Lasso Regression
이번에는 절대값이 제약을 가하게 되면 0에 수렴하게됩니다. 아래 그래프를 보면서 왜 0인지 설명하겠습니다.
절대값은 제곱 형태와 달리 마름모 형태로 그래프가 생성이 됩닌다. 여기서 mse인 타원형과 만나는 지점을 보면
w1=0입니다. 다시말해서 Y값에 영향을 주지 못하는 것들은 0으로 만들어 버리는 것이 lasso입니다.
람다를 점점 더 크게 줄 수록 제거 되는 베타 값들이 늘어 나게 됩니다.
3) Elastic Net
Lasso 식에서 Ridge의 제약 형식이 추가가 된 형태입니다. 큰 개념은 상관관계가 높은 변수의 계수는 같은 값으로 생각하겠다는 것입니다. 즉 중요하면 똑같이 중요하다 라고 하고 중요하지 않다면 똑같이 중요치 않도록 판별하겠다는 의미입니다. 저도 이 부분에 대해서는 좀더 공부가 필요해 보입니다. 완벽히 이해를 하지 못했습니다 ㅠ
앙상블 모델
오늘 앙상블 모델 강의에서 무릎을 탁치는 순간이 있었습니다. 먼저 배깅의 무릎 탁 순간을 말씀드리겠습니다
1. 배깅
배깅은 샘플을 여러번 뽑아서 각 모델을 학습시켜 결과를 집계(Aggregration)하는 방법입니다.여기서
무릎 탁 순간은 이 배깅이 중심극한정리의 원리와 매우 흡사하다는 점이 였습니다.
중심극한정리는 각 샘플에서 나온 통계량 값을 다시 평균을 취하면 모집단을 대표한다는 이론입니다. 이론만 봐도 이 둘이 매우 흡사 하다는 점입니다.
배깅 중 랜덤포레스트는 배깅의 마지막 과정인 취합 부분에서 평균을 내는것이 아니라 가장 예측 값이 잘 나온 것을 대표로 선정합니다.
2. 부스팅
부스팅은 여러 모델들(weak learner)을 만들고, 이들을 합쳐서 하나의 stronger learner로 만듭니다.
과소적합된 부분에 가중치를 높혀서 적합한 모델로 만드는 과저인데, 과소적합된 부분을 판별하는 기준은 잔차입니다. 잔차가 크면 이를 줄이기 위해서 모델이 변하게 됩니다. 여기가 무릎 탁 순간입니다. 부스팅 원리는 잔차진단과 비슷합니다. 즉 잔차에 남아 있는 패턴들을 소거해 나간다는 큰 개념이 일치 합니다.
*마무리 하면서
이번 포스팅에서는 실습 코드 내용보다는 이론에서 많은 걸 얻었던 내용이라서 글 위주로 적었습니다
적은 내용들 보면 간단한 것같은데 저 내용들을 깨닫기 까지의 시간이 생각보다 오래 걸렸습니다. 저처럼 같은 부분에서 막혔거나 하시는 분들에게 많은 도움이 되었으면 합니다
1.VIF(Variance Inflation Factor): 종속성이 높은 X들을 선택하는 방법
각 변수에 대해서 회귀분석을 실시합니다. 아래처럼 X1이라는 변수는 다른 변수의 선형결합으로 표현이 될수 있을지를 계산한게됩니다. 만일 R^2값이 크다면 X1는 다른 변수에 의해 표현이 되는 것이므로 제거 대상에 해당됩니다.
X1= aX2+bX3
이런 방법을 통해 모든 변수를 회귀분석을 통해 종속성이 높은 X들을 찾아냅니다.
위 VIF 공식에서 R^2이 클수록 VIF값도 커집니다.
#효과적인 X들을 VIF 방법을 통해 추출하기
from statsmodels.stats.outliers_influence import variance_inflation_factor
vif = pd.DataFrame()
#i번째 x들을 다른 x들로 회귀분석
#높을수록 선형결합으로 표현 될수 있으며 이는 종속성이 있는 변수이다.
vif['VIF_Factor']= [[variance_inflation_factor(X_train_feRS.values, i) for i in range(X_train_feRS.shape[1])]
vif['Feature'] = X_train_feRS.columns
vif.sort_values(by='VIF_Factor', ascending=True)
실습으로 사용방법 확인
# extract effective features using variance inflation factor
vif = pd.DataFrame()
vif['VIF_Factor'] = [variance_inflation_factor(X_train_feRS.values, i)
for i in range(X_train_feRS.shape[1])]
vif['Feature'] = X_train_feRS.columns
vif.sort_values(by='VIF_Factor', ascending=True)['Feature'][:10].values
array(['count_diff', 'holiday', 'count_seasonal', 'Day', 'windspeed',
'Quater_ver2', 'Hour', 'weather', 'Quater_Dummy_2', 'DayofWeek'],
dtype=object)
몇개의 변수를 사용했을때 성능이 가장 좋을지는 모르기 때문에 모든 경우의 수를 실행해 봐야 한다
아래 결과는 순서 VIF가 낮은 순으로 정렬 후 12개 변수를 사용했을때 결과입니다.
정화도는 0.864로 기존보다 정확도가 내려갔습니다
windspeed를 제외한 모두가 종속변수에 영향을 주고 있으며 계수값도 이전에 비하면 현실성 있는 수치를 보입니다.
Kurtosis가 6.022로 정규분포로 더욱 근사한 모양을 띠고 있으며 왜도 값도 줄어 들었음을 확인할수 있습니다
X1~X10까지의 10개 변수를 PCA 실행하면 X1^ ~X10^ 변수에 완전히 새로운 값이 채워지게 됩니다.
이 값은 기존의 변수들 값과 어떠한 연관성이 없습니다. 따라서 이 새 값은 서로 독립적으로 생기게끔 생성되었으므로 모델에 적용하면 되지만 단점은 완전히 새 값이 나오므로 어떤 의미를 가지고 있는지에 대한 재해석이 필요합니.
강의 실습에서는 VIF만을 실습했기때문에 따로 소스는 없습니다.
*정상성프로세스 이해하기
시계열이 정상성이다 라는 뜻은 시간의 흐름에 따라 "통계적 특성(Statistical Properties)"이 변하지 않는것이다.
통계적 특성(Statistical Properties):주로 평균(Mean)과 분산(Variance)/공분산(Covariance)를 얘기하지만 이를 포함한 모든 분포적 특성을 총칭함
시간 흐름에 따라 평균,분산,자기상관(autocorr), 공분산 등 통계적 특성이 변하지 않는다라는건 아래 녹색 그래프와 같은 의미이다. 평균을 낼때는 Frequency 기준으로 계산하면 같은 값이 나온다. 이외에 빨간색 그래프는 왼쪽부터 평균이 변하는 그래프, 중간 그래프는 분산이 일정치 않은 그래프, 맨 오른쪽 그래프는 평균과 분산은 일정한데 시점별로 짤랐을때는 자기상관이 있는 경우이다.
시계열 데이터에서 정상성이 왜 필요한 것일까?녹색 그래프 처럼 값이 일정하면 좋은 점은 무엇일까?
예를들어서 설명하면 한 회사의 매출액을 예측하고자 합니다. 매출액은 -값도 나올수 있으므로 그 범위는 +무한대, -무한대 입니다. 매출액이 유리천장이 있는 게 아니기 때문에 한계란 없습니다. 그러면 예측을 하는 입장에서는 이 무한대 범위에서 예측을 하면 틀릴확률이 높습니다. 그러면 Y값인 매출액을 점유율로 우회해서 표현하게되면 0~1사이의 확률값으로 표현이 되고 범위가 정해졌으므로 틀릴확률이 크게 줄어들게 됩니다. 점유율을 예측 후 이를 매출액으로 다시 변환하면 되는것이죠
이전에 올렸던 포스팅은 test 데이터를 알고 있을 경우에 FE를 진행하여 모델을 돌렸습니다. test 할 데이터에 대해서 FE를 했으므로 R^2 값이 1.0이 나왔습니다. 하지만 현실에서는 test할 데이터를 모릅니다. 미래가 어떻게 변할지는 그 누구도 모르기 때문입니다. 따라서 현실에 있을법한 test 데이터를 만들고자 합니다. 1-step 방법을 사용해서 미래 데이터 패턴을 예측하는 방법도 있지만 현 실습에서는 간단하게 진행했습니다
2011,2012년도 두 연도가 있는 자전거 수요 데이터에서, 현 실습에서는 2011년 데이터 패턴을 그대로 2012년도 데이터패턴에 적용했습니다. 여기에 모델을 적용한 후 스케일링 전처리 작업을 수행했습니다. 좀더 세부적인 교육 내용은 아래에 간단하게 적었습니다
현실성을 반영한 데이터패턴
# Test 기간의 FE데이터를 알 수 있을까? 2가지 방법소개
# 1) 과거 패턴이 반복될 것이다
# 2) 하나씩 예측하며 업데이트
'''
현재 데이터 그래프와 미래 데아터 그래프는 다르다. 이를 각각 어떻게 FE를 진행하는가?
이상적으로 가장 적합한 방법은 test 부분 영역은 아무도 모르는 영역이다-> 추정하는게 현실적으로 맞다
어떻게 추정하는가? train값으로 바로 t번재 테스트 값을 추정하고, 추정된 값을 포함한 값으로 t+1번째 값 추정 하는식
위 방법은 복잡하므로 좀더 간단한 방법으로 실습을 진행한다.
train에서 보인 패턴이 test에도 반복될거라는 가장 하에 진행
이렇게 되면 급작스러운 패턴 변화 때문에 다소 이질감이 생길수 있다.
2011 일년치를 가져와서 덜 이질감 있도록 2012년 적용
'''
len(raw_fe.loc['2012-01-01':'2012-12-31', 'count_trend'])
8784
len(raw_fe.loc['2011-01-01':'2011-12-31', 'count_trend'])
8760
# 2011년도 count_trend를 2012년 count_trend에 덮어 씌울려고 시도하면 에러가 발생
길이가 24개 차이가 나기 때문이다. 현재 시간 단위는 H이므로
24시간=> 윤달인 2월29일 데이터가 2011년도에는 없었던 것
#2월29일을 제외한 나머지 날짜를 대입
raw_fe.loc['2012-01-01':'2012-02-29', 'count_trend']=raw_fe.loc['2011-01-01':'2011-02-28', 'count_trend']
raw_fe.loc['2012-03-01':'2012-12-31', 'count_trend']=raw_fe.loc['2011-03-01':'2011-12-31', 'count_trend']
#2011-02-29데이터 만들기
step= (raw_fe['2011-03-01 00:00:00','count_trend']-raw_fe.loc['2011-02-28 23:00:00','count_trend'])/25
step_value=np.arange(raw_fe.loc['2011-02-28 23:00:00','count_trend']+step, raw_fe.loc['2011-03-01 00:00:00','count_trend'], step)
step_value= step_value[:24]
raw_fe.loc['2012-02-29','count_trend']=step_value
raw_fe.loc['2012-02-28 22:00:00':'2012-03-01 02:00:00', 'count_trend']
# calculation of lag data from Y
X_test_fe['count_lag1'] = Y_test_fe.shift(1).values # Y_test_fe.shift(1)값으로 새로 채우기 (현재는 train lag값이 들어가 있음)
X_test_fe['count_lag1'].fillna(method='bfill', inplace=True) #nan 값 채워주기
X_test_fe['count_lag2'] = Y_test_fe.shift(2).values
X_test_fe['count_lag2'].fillna(method='bfill', inplace=True)
X_test_fe['count_lag2']
DateTime
2012-07-01 00:00:00 149.00
2012-07-01 01:00:00 149.00
2012-07-01 02:00:00 149.00
2012-07-01 03:00:00 93.00
2012-07-01 04:00:00 90.00
...
2012-12-31 19:00:00 164.00
2012-12-31 20:00:00 122.00
2012-12-31 21:00:00 119.00
2012-12-31 22:00:00 89.00
2012-12-31 23:00:00 90.00
Freq: H, Name: count_lag2, Length: 4416, dtype: float64
현실성을 반영한 코드 요약
### Functionalize
### duplicate previous year values to next one
def feature_engineering_year_duplicated(raw, target):
raw_fe = raw.copy()
for col in target:
raw_fe.loc['2012-01-01':'2012-02-28', col] = raw.loc['2011-01-01':'2011-02-28', col].values
raw_fe.loc['2012-03-01':'2012-12-31', col] = raw.loc['2011-03-01':'2011-12-31', col].values
step = (raw.loc['2011-03-01 00:00:00', col] - raw.loc['2011-02-28 23:00:00', col])/25
step_value = np.arange(raw.loc['2011-02-28 23:00:00', col]+step, raw.loc['2011-03-01 00:00:00', col], step)
step_value = step_value[:24]
raw_fe.loc['2012-02-29', col] = step_value
return raw_fe
# target = ['count_trend', 'count_seasonal', 'count_Day', 'count_Week', 'count_diff']
# raw_fe = feature_engineering_year_duplicated(raw_fe, target)
### modify lagged values of X_test
def feature_engineering_lag_modified(Y_test, X_test, target):
X_test_lm = X_test.copy()
for col in target:
X_test_lm[col] = Y_test.shift(1).values
X_test_lm[col].fillna(method='bfill', inplace=True)
X_test_lm[col] = Y_test.shift(2).values
X_test_lm[col].fillna(method='bfill', inplace=True)
return X_test_lm
# target = ['count_lag1', 'count_lag2']
# X_test_fe = feature_engineering_lag_modified(Y_test_fe, X_test_fe, target)
#현실성 반영한 모델 실행하기
# Data Loading
# location = 'https://raw.githubusercontent.com/cheonbi/DataScience/master/Data/Bike_Sharing_Demand_Full.csv'
location = './Data/BikeSharingDemand/Bike_Sharing_Demand_Full.csv'
raw_all = pd.read_csv(location)
# Feature Engineering
raw_fe = feature_engineering(raw_all)
### Reality ###
target = ['count_trend', 'count_seasonal', 'count_Day', 'count_Week', 'count_diff']
raw_feR = feature_engineering_year_duplicated(raw_fe, target)
###############
# Data Split
# Confirm of input and output
Y_colname = ['count']
X_remove = ['datetime', 'DateTime', 'temp_group', 'casual', 'registered']
X_colname = [x for x in raw_fe.columns if x not in Y_colname+X_remove]
X_train_feR, X_test_feR, Y_train_feR, Y_test_feR = datasplit_ts(raw_feR, Y_colname, X_colname, '2012-07-01')
### Reality_lag ###
target = ['count_lag1', 'count_lag2']
X_test_feR = feature_engineering_lag_modified(Y_test_feR, X_test_feR, target)
###############
# Applying Base Model
fit_reg1_feR = sm.OLS(Y_train_feR, X_train_feR).fit()
display(fit_reg1_feR.summary())
pred_tr_reg1_feR = fit_reg1_feR.predict(X_train_feR).values
pred_te_reg1_feR = fit_reg1_feR.predict(X_test_feR).values
# Evaluation
Score_reg1_feR, Resid_tr_reg1_feR, Resid_te_reg1_feR = evaluation_trte(Y_train_feR, pred_tr_reg1_feR,
Y_test_feR, pred_te_reg1_feR, graph_on=True)
display(Score_reg1_feR)
# Error Analysis
error_analysis(Resid_tr_reg1_feR, ['Error'], X_train_feR, graph_on=True)
'''
R값이 0.9이며 유의한 변수가 25개로써 기존에 2개보다 많아졌음을 확인
Durbin-Watson도 2근방으로 다가 가면서 자기상관이 상당히 줄어들었으며
Kurtosis도 정규분포에 가까워짐
조건수는 현실 반영 전 모델과 비교 시 34,000 -> 32,000 감소를 보임
이는 독립변수가 그 전보다는 생겼다는 의미이다.
t검정을 봐도 유의한 변수들이 많이 생겼음을 확인할수있다. 조건수 수치를 보고 설명변수의 상태를
파악할수 있다.
'''
Scaling
본 내용 설명에 앞서 condition number에 대해서 설명을 하자면 예측 정확성 향상을 위해서는 두가지 방향이 있는데
1) Train 정확도 증가+ Test 정확도 증가
2) Train <<< Test⟺조건수(Condition Number) 감소
2번에서 train 의 정확성 보다는 test의 정확성을 더 중시한다는 의미입니다. test 정확성을 높이기 위해서는 조건수의 감소가 일어나야 합니다. x들의 특징에서 조건수를 추출하는데 이 값이 낮을수록 2번처럼 이상적수치가 나올수 있습니다.
조건수는 𝑒𝑖𝑔𝑒𝑛𝑣𝑎𝑙𝑢𝑒의 최대값,최소값의 비율을 나타냅니다. X라는 설명변수들이 서로 독립적이면 𝑒𝑖𝑔𝑒𝑛𝑣𝑎𝑙𝑢e
값들은 서로 비슷한 수치를 보입니다. 반대로 종속변수가 많을수록 두 𝑒𝑖𝑔𝑒𝑛𝑣𝑎𝑙𝑢𝑒 값은 차이가 커집니다.
ex) 𝜆1=1, 𝜆2=100
따라서 조건수를 줄이기 위해서는 설명변수안에 서로 독립적이여야 Test 예측 성능을 올릴수 있습니다.
*조건수를 감소하는 방법 3가지
1) 변수들의 단위차이로 숫자의 스케일들이 크게 다른 경우, 스케일링(Scaling)으로 해결 가능 2) 독립변수들 간에 상관관계가 높은 "다중공선성" 존재할 경우, Variance Inflation Factor(VIF)나 Principal Component Analysis(PCA)를 통한 변수선별로 해결 가능 3) 독립변수들 간 의존성이 높은 변수들에 패널티를 부여하는 정규화(Resularization)로 해결 가능
Scaling
숫자가 큰 변수가 그렇지 않는 변수들보다 크게 관여될수가 있으므로 값 조정은 필수다.
총 4가지 방법이 있습니다
1) Standard Scaler
스케일링 후의 분포의 모양은 각기 다르다(정규분포애 근사한 형태를 띈다)
2) Min-Max Scaler(𝑋𝑖𝑡−𝑚𝑖𝑛(𝑋𝑖)/𝑚𝑎𝑥(𝑋𝑖)−𝑚𝑖𝑛(𝑋𝑖))
본래 분포모양을 그대로 가져가면서 범위는 동일하게 적용한다 표편이 매우 작을때 효과적, 왜일까? 표편이 작다는 건 데이터들이 모여 있다는 뜻 -> 이상치가 없다 이상치가 있다면 표편이 커지게 된다. 극단치 때문에 비율이 왜곡된다 이러한 문제점을 해결하고자 Robust Scaler 사용
3) Robust Scaler(𝑋𝑖𝑡−𝑄1(𝑋𝑖)/𝑄3(𝑋𝑖)−𝑄1(𝑋𝑖))
25%~75% 데이터를 가지고 Min-Max Scaler 공식과 동일하게 적용
4) Normalizer
각 변수(Feature)를 전체𝑛n개 모든 변수들의 크기들로 나누어서 변환(by Cartesian Coordinates) 각 변수들의 값은 원점으로부터 반지름 1만큼 떨어진 범위 내로 변환한다.
회귀모델을 적용 후 R-squared,F-분포, t검정 등의 결과가 일관성이 없을때 믿어야 할건 데이터 라는 것이다.
일관성이 없다고 효과없는 데이터를 무작정 삭제 하는게 아니라, 시각화를 통해서 종속변수에 영향을 주는 데이터인지 아닌지를 판단해야한다.
모델적용
fit_reg1 = sm.OLS(Y_train, X_train).fit()
display(fit_reg1.summmary())
pred_tr_reg1 = fit_reg1.predict(X_train).values
pred_te_reg1 = fit_reg1.predict(X_test).values
'''
*R-squared
평균치 ybar 보다 적합이 좋은지 나쁜지를 나타낸다
1.00은 모드예측값이 실제값과 모두 동일함을 의미한다
*F-statistic(F검정)
유의확률이 0.00으로 유의수준보다 작으므로, 이 모델링은 효과가 있다.
모든 계수가 효과가 있다
*t 검정
count_diff, count_lag1를 제외하곤 모두 영향력이 없다
F검정에는 모든 계수가 효과 있다고 했는데? 이상함을 감지 해야한다
*잔차 보기
정규분포의 Kurtosis(첨도)는 3이다.
현 Kurtosis는 13078.751, 일반 첨도보다 훨씬 꼬리가 두터운 형태
*정규분포의 Skew(왜도)는 0이다.
현 Skew는 -114.255, negative 형태 (오른쪽 쏠림), 상당히 벗어난 값
*회귀분석 지표가 많은 이유는 모든 결과가 일관성 있을때 까지 시도하라는 의미
*지속적인 튜닝이 필요
*회귀분석은 값만 출력하고, 쓸지 말지는 사람이 선택한다. 따라서 합리적인 근거를 토대로
사용여부를 결정한다. 근거의 대표적인 도구가 시각화이다.
믿어야 할건 알고리즘이 아니라 데이터이다.
'''
위 결과에 대한 해석은 실습의 주석처리로 되어있습니다
일관성 없는 데이터에 대해서 시각화를 통해 정확한 정보를 얻어낸다
이상적인 그림은 모델 적용하기 전 시각화를 통해 변수에 대한 정보를 파악하는게 맞다
시각화
# histogram plot
raw_fe.hist(bins=20, grid=True, figsize=(16,12)) #x를 몇개 그룹으로 나눌것이가: bins
plt.show()
'''
count와 비슷한 분포가 상관관계가 높고, 유의하다고 나올 가능성이 높다
y분포와 비슷하게 만드는 것이 예측에 도움이 될것
'''
# box plot_계절별 각 수요 관계 파악
raw_fe.boxplot(column='count', by='season', grid=True, figsize=(12,5))
plt.ylim(0,1000)
raw_fe.boxplot(column='registered', by='season', grid=True, figsize=(12,5))
plt.ylim(0,1000)
raw_fe.boxplot(column='casual', by='season', grid=True, figsize=(12,5))
plt.ylim(0,1000)
'''
만일 계절의 y분포 네개가 똑같다면 어떨까?
계절에 의한 count 변화는 없다는 의미이며 계절변수가 쓸모 없다는 것
계절별로 패턴이 많이 달라야 예측에 도움이 된다.
'''
# box plot_휴일,평일과 수요(count) 관계 파악
raw_fe.boxplot(column='count', by='holiday', grid=True, figsize=(12,5))
plt.ylim(0,1000)
raw_fe.boxplot(column='count', by='workingday', grid=True, figsize=(12,5))
plt.ylim(0,1000)
'''
휴일과 평일 비교하는 그래프에서, 평일에 수요가 훨씬 많음을 알수있다
분포가 다르고 해석되는 것에 차이가 있으므로 y분포에 도움이 될수 있다.
'''
raw_fe[raw_fe.holiday==0].boxplot(columns = 'count', by ='Hour', grid=True, figsize=(12,5))
plt.show()
raw_fe[raw_fe.holiday ==1].boxplot(columns='count', by ='Hour', grid=True, figsize=(12,5))
plt.show()
'''
두 분포간의 차이가 있으며 개별 분포를 봐도 count값 분포가 다르므로 y에 효과가 있음을 추론가능
'''
# scatter plot for some group
#temp를 추가해서 삼차원 그래프
raw_fe[raw_fe.workingday == 0].plot.scatter(y='count', x='Hour', c='temp', grid=True, figsize=(12,5), colormap='viridis')
plt.show()
raw_fe[raw_fe.workingday == 1].plot.scatter(y='count', x='Hour', c='temp', grid=True, figsize=(12,5), colormap='viridis')
plt.show()
'''
temp도 두 그래프의 분포가 다르므로 영향을 줄거라는 걸 추론 할수있다.
'''
# box plot example_날씨에 따른 수요 변화
raw_fe.boxplot(column='count', by='weather', grid=True, figsize=(12,5))
plt.ylim(0,1000)
raw_fe.boxplot(column='registered', by='weather', grid=True, figsize=(12,5))
plt.ylim(0,1000)
raw_fe.boxplot(column='casual', by='weather', grid=True, figsize=(12,5))
plt.ylim(0,1000)
'''
날씨에 따른 count 변화가 보인다.
'''
# box plot example_년도에 따른 수요 변화
raw_fe.boxplot(column='count', by='Year', grid=True, figsize=(12,5))
plt.show()
'''
년도에 따른 수요가 다르다는 걸 알수 있다. y에 영향을 준다
'''
# box plot example_월에 따른 수요 변화
raw_fe.boxplot(column='count', by='Month', grid=True, figsize=(12,5))
plt.show()
#독립변수간의 상관관계를 파악하기 위한 시각화
#상관관계를 추정하는게 알고리즘의 목표
pd.plotting.scatter_matrix(raw_fe, figsize=(18,18), diagonal='kde')
plt.show()
색깔이 짙을수록 관계성이 강하다는 의미
# selecting of columns from correlation tables
#y값과 x들 간의 상관관계 보기
'''
아래 값이 회귀분석 결과에 나온 계수값과 비슷한지 비교해봐야 한다
유사하지 않기 때문에 t검정이 유의하지 않다고 한것이다.
count_lag2 회귀분석결과를 비교해도 다름을 알수있다.
'''
raw_fe.corr().iloc[:,8:11]
# correlation table example
raw_fe.corr().iloc[0:8,8:11].style.background_gradient().set_precision(2).set_properties(**{'font-size': '15pt'})
검증지표 방향
R-squared(R^2):추정된 (선형)모형이 주어진 데이터에 잘 적합된 정도, 계량경제학에서는 모델에 의해 설명되는 데이터 분산의 정도(퍼센트)
R-squared는 기준이 ybar이다. 이는 다른 mse,mae와 차이점이다. -> 평균적으로 추정된 값보다 얼만큼 잘 적합이 되었는지 확인 최소값은 마이너스 무한대이다.
Mean Absolute Error(MAE): 각 데이터의 단위를 보존하는 성능지표 단위보존 ? mse와 비교하면 알수있다.
Mean Squared Error(MSE):가장 많이 사용되며 큰 오차에 패널티(Penalty)를 높게 부여하는 성능지표
mse는 실제값-예측값의 제곱이므로, 0보다 작은값은 더 작게, 1보다 큰 값은 더 크게 만들고 애초에 y값 자체의 스케일이 클수도, 작을수도 있다.
mae는 절대값으로 이뤄져 있기 때문에 작은 값이 더 작게, 큰 값은 더 크게 계산되지 않는다 즉, mae는 y스케일을 그대로 보존할수 있다.
Summary
Squared 가 들어간 평가지표는, 이상치 하나만 있어도 오차가 제곱이 된다. 훨씬큰 오차가 반영됨 따라서 이상치가 있다면 yes로 표기된 평가지표를 쓰기를 추천
6.더미변수(Dummy variables): 이진수(0 또는 1)의 형태로 변수를 생성하는 것으로 휴일,이벤트,캠페인 등을 생성 가능
1. 더미변수를 하지 않았을때 의미
Y=ax가 있을때 Y는 매출, X는 계절(봄,여름,가을,겨울 )
a 계수가 3이라고 한다면 계절이 바뀔때마다 3씩 매출에 영향을 준다 로 해석가능하다
즉, 계절이 변할때 마다 매출이 어떻게 변하는지를 알고자 할때 더미 변수를 하지 않고 사용하는게 적합하다
2. 더미변수를 사용하고자 할때 의미
각 계절별로 '따로따로' 매출에 얼마큼 영향을 주는지 알고자 할때 쓰인다
7.지연값(Lagged values): 변수의 지연된 값을 독립변수로 반영하는 것으로,ARIMA/VAR/NNAR 등이 활용
ex) 광고비를 투입한 시점부터 매출에 영향을 준 걸까? 라는 의문점에서부터 시작된다 즉 영향을 주고 받는 시간 차이가 발생한다.
★위 이론 내용을 토대로 실습 진행
Dataset: BikeSharingDemand
Import
# Ignore the warnings
import warnings
# warnings.filterwarnings('always') #항상 경고문이 뜨게 한다
warnings.filterwarnings('ignore') # 경고 뜨지 않게 설정
# System related and data input controls
import os
# Data manipulation and visualization
import pandas as pd
pd.options.display.float_format = '{:,.2f}'.format #options클래스 안에 display 안에 float_format
pd.options.display.max_rows = 10 #전체데이터에서 row data 앞에 5개, 뒤에 5개 만 보이게 함
pd.options.display.max_columns = 20
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
# Modeling algorithms
# General
import statsmodels.api as sm
from scipy import stats
# Model selection
from sklearn.model_selection import train_test_split
# Evaluation metrics
# for regression
from sklearn.metrics import mean_squared_log_error, mean_squared_error, r2_score, mean_absolute_error
#데이터 불러오기
location = './Data/BikeSharingDemand/Bike_Sharing_Demand_Full.csv' #맨 앞에 . 점은 동일한 위치 라는 의미
raw_all = pd.read_csv(location)
raw_all
Feature Engineering: 데이터에서 시계열패턴 추출하기
Feature engineering 하기 전/후 비교하기 위해서 non_feature_engineering을 추가해여 생성합니다
def non_feature_engineering(raw):
raw_nfe= raw.copy()
#객체 타입인 datatime 변수를 datetime 타입으로 변경
if 'datetime' in raw_nfe.columns:
raw_nfe['datetime'] = pd.to_datetime(raw_nfe['datetime'])
raw_nfe['DateTime]= pd.to_datetime(raw_nfe['datetime']) #인덱스로 적용
if raw_nfe.index.dtype=='int64':
raw_nfe.set_index('DateTime', inplace=True)
#bring back
#if raw_nfe.dtype !='int64':
# raw_nfe.reset_index(drop=False, inplace=True)
#시간 단위로 시간 축 설정후 결측치는 forward fill
raw_nfe = raw_nfe.asfreq('H', method ='ffill')
return raw_nfe
def feature_engineering(raw):
raw_fe=raw.copy()
if 'datetime' in raw_fe.columns:
raw_fe['datetime'] = pd.to_datetime(raw_fe['datetime'])
raw_fe['DateTime]= pd.to_datetime(raw_fe['datetime']) #인덱스로 적용
if raw_fe.index.dtype=='int64':
raw_fe.set_index('DateTime', inplace=True)
#시간 단위로 시간 축 설정후 결측치는 forward fill
raw_fe = raw_fe.asfreq('H', method ='ffill')
#시계열 분해_추세
result= sm.tsa.seasal_decompose(raw_fe['count'], model ='additive')
Y_trend = pd.DataFrame(result.trend)
Y_trend.fillna(method='ffill', inplace=True)
Y_trend.fillna(method='bfill', inplace=True)
Y_trend.columns = ['count_trend']
#시계열 분해_계절성
Y_seasonal = pd.DataFrame(result.seasonal)
Y_seasonal.fillna(method='ffill', inplace=True)
Y_seasonal.fillna(method='bfill', inplace=True)
Y_seasonal.columns = ['count_seasonal']
#원본과 합치기
pd.concat([raw_fe, Y_trend,Y_seasonal], axis=1).isnull().sum()
if 'count_trend' not in raw_fe.columns:
raw_fe = pd.concat([raw_fe, Y_trend, Y_seasonal], axis=1)
#이동평균으로 일별, 주별 count 생성
Y_count_Day = raw_fe[['count']].rolling(24).mean()
Y_count_Day.fillna(method='ffill', inplace=True)
Y_count_Day.fillna(method='bfill', inplace=True)
Y_count_Day.columns = ['count_Day']
Y_count_Week = raw_fe[['count']].rolling(24*7).mean()
Y_count_Week.fillna(method='ffill', inplace=True)
Y_count_Week.fillna(method='bfill', inplace=True)
Y_count_Week.columns = ['count_Week']
#원본과 합치기
if 'count_Day' not in raw_fe.columns:
raw_fe = pd.concat([raw_fe, Y_count_Day], axis=1)
if 'count_Week' not in raw_fe.columns:
raw_fe = pd.concat([raw_fe, Y_count_Week], axis=1)
Y_diff = raw_fe[['count']].diff()
Y_diff.fillna(method='ffill', inplace=True)
Y_diff.fillna(method='bfill', inplace=True)
Y_diff.columns = ['count_diff']
if 'count_diff' not in raw_fe.columns:
raw_fe=pd.concat([raw_fe,Y_diff],axis=1)
#날짜 변수 생성
raw_fe['temp_group'] = pd.cut(raw_fe['temp'], 10)
raw_fe['Year'] = raw_fe.datetime.dt.year
raw_fe['Quater'] = raw_fe.datetime.dt.quater
raw_fe['Quater_ver2'] = raw_fe.datetime.dt.quater+(raw_fe.Year- raw_fe.min())*4
raw_fe['Month'] =raw_fe.datetime.dt.Month
raw_fe['Day'] = raw_fe.datetime.dt.day
raw_fe['Hour'] = raw_fe.datetime.dt.hour
raw_fe['DayofWeek'] = raw_fe.datetime.dt.dayofweek
#지연값 변수 생성
raw_fe['count_lag1'] = raw_fe['count'].shift(1)
raw_fe['count_lag2'] = raw_fe['count'].shift(2)
raw_fe['count_lag1'].fillna(method='bfill', inplace=True)
raw_fe['count_lag2'].fillna(method='bfill', inplace=True)
if 'Quater' in raw_fe.columns:
if 'Quater_Dummy' not in raw_fe.columns:
raw_fe = pd.concat([raw_fe, pd.get_dummies(raw_fe['Quater], prefix='Quater_Dummy', drop_first=True)], axis=1)
del raw_fe['Quater']
return raw_fe
데이터 분리
시계열 데이터는 분리할때 시간축이 반드시 있어야 한다.
기존에 알고 있는 train_test_split, k-fold 등 분리 방법은 비시계열 데이터 분리 방법으로써 시간차원 없다
시계열데이터에 위 방법을 사용하면 시간개념이 사라지므로 1스텝 교차검사,2스텝 교차검사 분리 방법을 사용해야한다.
하얀 점들이 예측해야할 test 미래 시기이다. 시간 단위로 index가 구성되어 있으므로
하얀점들 각각이 2021-03-15 09:00, 2021-03-15 10:00, 2021-03-15 11:00 ~~~ 로 구성되어 있을것이다.
시계열데이터는 각 시기를 예측해야한다. 비시계열은 아래처럼 test를 한 곳에 뭉쳐서 성능값이 하나 나온다
만일 90%라고 나와도, 가까운 미래 정확도가 98%나오고 먼 미래 일수록 그 정확도가 떨어진다. 이 수치를 종합해서 90%라고 해도 시계열 데이터 입장에서는 신뢰할수 없는 성능 수치이다.
그래서 각 시기별로 각각 정확도를 구하고, 정확도를 구한 시기는 train으로 들어간다.
★위에 이론 내용을 토대로 실습 진행
실습은 시계열데이터지만 전통적인 데이터분리 방법을 적용했다(강의 구성이 그렇게 되어있다...)
### Data split of cross sectional
def datasplit_cs(raw, Y_colname, X_colname, test_size, random_seed=123):
X_train, X_test, Y_train, Y_test = train_test_split(raw[X_colname], raw[Y_colname], test_size=test_size, random_state=random_seed)
print('X_train:', X_train.shape, 'Y_train:', Y_train.shape)
print('X_test:', X_test.shape, 'Y_test:', Y_test.shape)
return X_train, X_test, Y_train, Y_test
### Data split of time series => 특정 날짜를 기준으로 나눈다
def datasplit_ts(raw, Y_colname, X_colname, criteria):
raw_train = raw.loc[raw.index < criteria,:]
raw_test = raw.loc[raw.index >= criteria,:]
Y_train = raw_train[Y_colname]
X_train = raw_train[X_colname]
Y_test = raw_test[Y_colname]
X_test = raw_test[X_colname]
print('Train_size:', raw_train.shape, 'Test_size:', raw_test.shape)
print('X_train:', X_train.shape, 'Y_train:', Y_train.shape)
print('X_test:', X_test.shape, 'Y_test:', Y_test.shape)
return X_train, X_test, Y_train, Y_test
Y_colname =['count']
#중복되는 count & 문자형 변수
X_remove = ['datetime', 'DateTime', 'temp_group', 'casual', 'registered']
X_colname= [col for col in raw_fe.columns if col not in Y_colname+X_remove]
X_train, X_test, Y_train, Y_test = datasplit_ts(raw_fe, Y_colname, X_colname, '2012-07-01')