오늘은 깃허브에 대해 공부했습니다. 그 전부터 몇번 시도는 했었으나 branch는 뭐고 commit은 뭔지 도통 감이 잡히지 않아서 나중에 하기로 미룬지가 거의 반년이 넘어 갔습니다. 공부 할수록 깃허브의 중요성을 조금씩 알게 되었고, 프로젝트를 만들면서 버전관리에 중요성을 깨달았는지라 오늘 시간을 내어봤습니다.
영상 총 길이가 1시간 좀 넘었는데, 좋은 설명 덕분에 이해하기 수월했습니다. 현재는 배운내용을 토대로 commit과 push를 연습 중에 있습니다.
이 과정에서 몇 시간동안 제 발목을 잡은 에러가 있었습니다. 소스트리에서 push를 누르니까 비밀번호를 누르라고 해서 깃허브 비번을 입력했더니 에러가 발생했습니다. 다시 푸시를 눌러도, 재설치를 해봐도 에러가 발생했습니다. 알고 보니 해당 비밀번호는 깃허브 홈페이지에서 할당받은 토큰을 넣는 것으로 변경되었다고 합니다.
구글링하면서 이것저것 시도하다가 ssh로 시도하게 되었습니다. 제가 참고했던 ssh 적용방법 블로그는 아래에 걸어 두겠습니다.
그런데, 위 과정으로만 했더니 또 에러가 발생했습니다. 에러 메세지를 읽어 보니까 저는 좀 다른 방식으로 해야했음을 알게 되었어요.
블로그에서는 위 이미지처럼 ssh 클라이언트를 OpenSSH로 변경하도록 안내하고 있습니다. ssh키는 내가 ssh키를 저장해 놓은 위치입니다. 아무튼 이 과정대로 따라가니까 에러가 발생했습니다.
저 같은 경우는 나머지 모두 같지만 딱 하나 다르게 설정하니까 push가 되었습니다. 해당 블로그가 틀린 정보를 소개해준게 아닙니다 다른 블로그들 보면 같은 방법으로 안내를 하고 있는데, 저는 좀 다른 케이스 인것 같더라구요. 정확한 원인은 잘 모르겠지만 나도 이제 깃허브를 사용할수 있다는 생각에 좋았답니다
이참에 현재 진행중인 따릉이 프로젝트도 버전관리 하고 싶어서 코랩으로 깃허브 연동까지 진행했습니다. 코랩의 깃허브 사본저장만 누르면 끝이라서 상당히 간편했습니다.
태양열 에너지 예측은 코드가 뒤죽박죽에다가 EDA가 제외가 된 터라(EDA는 파일이 별도로 존재함) 제외하고 업로드 해봤습니다.
시간 별 count 평균값을 활용하여 hour_mean 변수를 생성했습니다. 해당 변수를 수행한 결과, hour 변수와 연관성이 컸습니다. feature 중요도에서 hour값을 빼면 hour_mean 값이 크게 증가 한걸 확인했습니다. 다만 hour_mean 중요도가 hour 중요도보다 약 9% 더 크게 나왔습니다. (이미지가 아까 있었는데 다른 실험들 하느라 없어졌네요..;)
model name is KNeighborsRegressor,Grid best score:-3458.5501989157733, Grid best_params_:{'n_neighbors': 4}
model name is RandomForestRegressor,Grid best score:-1715.3496565899343, Grid best_params_:{'max_depth': 4, 'min_samples_leaf': 3, 'min_samples_split': 2, 'n_estimators': 100}
model name is DecisionTreeRegressor,Grid best score:-2033.204198431899, Grid best_params_:{'max_depth': 4, 'min_samples_leaf': 3, 'min_samples_split': 2}
model name is AdaBoostRegressor,Grid best score:-2149.0152774244843, Grid best_params_:{'n_estimators': 40}
model name is XGBRegressor,Grid best score:-1477.5776373356193, Grid best_params_:{'gamma': 0.12889993895241564, 'max_depth': 4, 'n_estimators': 100}
model name is LGBMRegressor,Grid best score:-1524.0959447354717, Grid best_params_:{'gamma': 0.273931886786571, 'max_depth': 6, 'n_estimators': 100}
model name is Ridge,Grid best score:-1751.7217163613059, Grid best_params_:{'alpha': 0.1, 'fit_intercept': True, 'normalize': False}
model name is Lasso,Grid best score:-1754.1340908572297, Grid best_params_:{'alpha': 0.1, 'fit_intercept': True, 'normalize': False}
def get_stacking_base_datasets(model, x_train, y_train, test, n_folds):
# 지정된 n_folds 값으로 kFold 생성
kf = KFold(n_splits=n_folds, shuffle=False , random_state=2021)
#추후에 메타 모델이 사용할 학습 데잍 반환을 위한 넘파이 배열 초기화
train_fold_pred = np.zeros((x_train.shape[0],1)) #(1459,1)
test_fold_pred = np.zeros((test.shape[0],n_folds)) #(715,5)
print(model.__class__.__name__,"model 시작")
for folder_counter,(train_index,valid_index) in enumerate(kf.split(x_train)):
# print('train_index:',train_index,'valid_index:',valid_index)
# print('valid 갯수:',len(valid_index))
# print('\t 폴드 세트:', folder_counter,"시작")
#입력된 학습 데이터에서 기반 모델이 학습/예측할 폴드데이터 세트 추출
x_tr = x_train[train_index]
y_tr = y_train[train_index]
x_te = x_train[valid_index]
#폴드 세트 내부에서 다시 만들어진 학습데이터로 기반 모델의 학습 수행
model.fit(x_tr,y_tr)
#폴드 세트 내부에서 다시 만들어진 검증 데이터로 기반 모델 예측 후 데이터 저장
train_fold_pred[valid_index,:]=model.predict(x_te).reshape(-1,1)
#입력된 원본 테스트 데이터를 폴드 세트내 학습된 기반 모델에서 예측 후 데이터 저장
test_fold_pred[:,folder_counter]=model.predict(test)
#폴드 세트 내에서 원본테스트 데이터르 예측한데이터를 평균하여 테스트 데이터로 생성
test_pred_mean = np.mean(test_fold_pred, axis =1).reshape(-1,1)
#train_fold_pred는 최종 메타 모델이 사용하는 학습 데이터, test_pred_mean은 테스트 데이터
return train_fold_pred, test_pred_mean
lgb_reg.fit(stack_final_x_train,y_train) #원본 학습 label과 fit
stack_final=lgb_reg.predict(stack_final_x_test)
# evaluate(Y_test, stack_final)
스태킹관련 코드 내용들은 "머신러닝 완벽가이드"를 참고했습니다. 각 개별 모델의 최적 param을 찾은 뒤, 각 개별에서 실시한 train, valid 값을 np.concatenate 하여 최종 모델에서 다시 한번 학습과 예측을 실시합니다. 자세한 내용은 책을 통해 공부해 보시길 강추 드립니다. 해당 스태킹 결과는 내일이나 주중에 제출해 볼 계획입니다(제출 횟수 초과함)
결측치가 많은 ozone, pm10, pm2.5를 예측하여 값을 채우려 시도하고 있습니다. 기존에는 각 시간, 변수 별 평균값으로 결측치를 채웠습니다. 하지만 이러한 방법은 정확성을 떨어뜨릴수 있습니다 게다가 그러한 결측치가 워낙 많다 보니까 새로운 방법을 찾아야 했습니다. ozone, pm10, pm2.5를 제외한 feature을 활용했습니다.(id, count 제외)
먼저, 사용될 X feature들의 결측치 먼저 처리해야 합니다. 결측치 수가 많지 않으므로 이는 기존에 만들어 사용했던 시간,변수별 평균값 함수를 사용하여 채웠습니다. 아래 step 1을 통해 상세하게 설명드립니다.
Step 1. nan_mean_fill 함수를 사용하여 X feature 채우기 (단, precipitation은 분류 이므로 이 또한 제외)
def nan_mean_fill(dataset):
#결측치가 있는 컬럼
nan_columns = [col for col in dataset.iloc[:,2:-1].columns.to_list() if col!='hour_bef_precipitation' and col!='hour_bef_ozone' and col!='hour_bef_pm10' and col!='hour_bef_pm2.5']
#for 문을 통해 결측치를 각 시간대별 평균값으로 대체
for col in nan_columns:
hour_mean_value = dataset.groupby('hour').mean()[col]
hour_mean_value.fillna(hour_mean_value.mean(),inplace=True) #회귀를 통해서 값 채우기 시도
for nan_hour in dataset[dataset[col].isna()]['hour'].unique():
#nan index 구하기
index= dataset[dataset[col].isna()].loc[dataset['hour']==nan_hour,col].index
#채우기
for idx in index:
dataset.loc[idx, col] = hour_mean_value[nan_hour]
print(dataset.isna().sum())
return dataset
train = nan_mean_fill(train)
test= nan_mean_fill(test)
각 target별 연관성이 있다고 판단한 변수들만 뽑아서 예측해 보았습니다. ozone변수를 보면 여러개의 X feature들과 연관성을 가지고 있습니다. 반면 pm10, pm 2.5는 영향력있는 변수가 적었기 때문에 예측성능이 좋지 못했습니다. 따라서
ozone은 예측값을 사용하고 그외 pm10, pm 2.5은 예측값을 넣되 사용하지 하지않기로 결정했습니다. 왜냐하면 이 둘의 결측값의 수만 해도 100개 이상이며 최종 target인 count와 연관성이 적기 때문입니다. (아래 이미지를 참고해주세요)
* 다음 시간 예고
다음 시간부터는 count의 출퇴근 수요를 설명해줄 변수를 생성하려고 합니다.
아래는 X feature들의 분포입니다.
colum=['hour_bef_temperature','hour_bef_windspeed','hour_bef_humidity','hour_bef_ozone','hour_bef_pm10','hour_bef_pm2.5']
for col in colum:
train.groupby(by='hour').mean()[col].plot(label=col)
plt.legend()
plt.show()
*Count 분포
train.groupby(by='hour').mean()['count'].plot()
출퇴근 시간에 수요가 급등하는 모습을 보이고 있습니다. 이러한 특징적인 현상을 설명해줄 변수가 필요합니다. X feature들의 분포를 보면 몇몇 변수에서 퇴근시간 증가하는 모습을 보입니다. 그렇지만 증가하는 분포? 폭이 넓기 때문에 기대이상으로 퇴근 시간대의 수요를 설명해 주기란 어려울 것으로 생각했습니다. 특히 출근 시간대의 수요증가는 hour뿐 이기 때문에 새로운 변수의 필요성을 느끼게 됩니다. 아직은 이렇게 해서 만들어야 겠다! 라는 아이디어는 떠오르지 않아서 시간이 좀 걸리지 않을까 생각해 봅니다
결측값이 있었던 걸 잊고 있었습니다. 해당 결측값들은 train할때 정확한 정보를 주고자 삭제를 시켰었습니다. 하지만 원본 데이터에는 그대로 존재하기 때문에 이 결측값을 먼저 처리해야 합니다. 결측값이 각 변수 별로 얼마 되지 않기 때문에 기존에 만들어 놓은 함수로 평균값 채우기 할 생각입니다. 오늘이 여기까지 진도 나갔습니다. 더 개선된 내용으로 업로드 하겠습니다.
저번 포스팅까지는 X 변수가 하나라는 가정하에 진행해 왔습니다. 이제 부터는 X가 둘 이상인 multi을 다룹니다.
위 그림처럼, X들이 여러개 나뉘어 졌다. X1: 공부시간, X2: 등하교 시간으로 예를 들수 있겠습니다. 그래서 각각의 변수 데이터를 가지고 해당되는 target 값인 y를 예측하게 됩니다.
X2가 추가가 되었음을 볼 수 있다. 각 theta, X에 대해서 벡터 형식으로 정리가 가능하다. X 벡터의 1인 dummy 변수를 넣어 줘야 dot product 했을때 계산이 가능합니다.
Loss function에 th가 3개로 확장 되었으므로 더이상 평면이 아닌 공간 상에서 표현이 될 것입니다.
전에 배웠던 내용과 다른 점이 있다면 th2가 추가 되었다는 점 뿐이다.
각 th별로 미분을 하면 빨간색과 같은 수식이 나온다. 다만 달라진 점은 X인데 해당 X에 따라서 데이터 어떤 영향을 끼치는지 계속 자각해야 한다.
gradient을 구한뒤 GDM의 수식을 위 처럼 정리 할수 있겠다. th2은 X2에 영향을 받고, th1은 X1에 영향을 받는다는 점이 핵심이다.
Cost도 같은 과정을 거치게 되면 최종적으로 위 그림처럼 정리할 수 있다.
이번엔 X가 m개일 경우를 살펴본다. 예를 들어 100*100 이미지가 있다면 10000개의 X가 존재하고 이를 벡터형식으로 바꾼 모습이 위와 같을 것이다.
각각의 th,X의 벡터을 dot product을 시키면 맨 아래 수식처럼 간단한 형식으로 표현이 가능하다. Loss도 마찬가지로 같은 원리를 통해 간단하게 표한가능하다.
weight M개, bias 1개를 포함한 M+1개를 각각 gradients 구하면 위처럼 정리 할수 있다. m+1 차원의 Gradients가 만들어 진다. weight에는 P라는 첨자가 붙고 bias에는 붙지 않는다는 것에 대한 의미를 까먹지 않도록 상기 시켜야 한다.
Affine function 부분에서 forward, backward propagation이 발생하 넘겨주고, 받는 역할을 하게 된다. 누구에게 넘겨주느냐? Activation Funtion에게 넘겨주게 된다. 이런 전체적인 과정이 딥러닝이 일어나는 과정이다.
왼쪽 그림을 보면 둘로 나눠져 있다. 즉,affine funtion 뒤에 Activation이 붙든, Cross entropy가 붙든, soft max가 붙든, Loss 구하든지, cost를 구하든지 일절 상관이 없다. Affine은 단지 Z값을 전달하고, 자기가 내줬던 값에 대한 편미분 값을 받아서 업데이트 하는 것 뿐이다.
지금까지 배웠던 것을 그림으로 표현하면 위와 같다. Z값을 받고 Loss 혹은 Cost를 구한뒤 편미분을 통해 업데이트가 일어난다. 뉴런 1개에 이와 같은 연산이 발생한다.
multi linear에서는 Loss function이 어떻게 변하는지 알아보자.
Loss funtion
-편의상 두개의 x값을 가지고 진행한다.
각각의 datset에 따른 prediction과 Loss는 위 처럼 구할수 있을 것이다. 이것을 다음에 적용해 보도록 한다.
해당 식과 data가 있을때, 변화를 면밀하게 살펴보기 위해서 세개의 값중 하나를 고정시켜 보도록 한다. 가장 왼쪽은 bias가 학습이 다 됐다는 가정 하에 th의 변화를, 두번째는 th1이 학습완료가 됐다는 가정하에 th2와 th0간에 변화를 나타내고 마지막도 같은 원리로 진행한다.
가장 왼쪽 사진에서 적용된 data는 (1,1)이다. 파란색 식을 보면 (1,1)일때 비율이 같기 때문에(학습량이 같다) a=-x방향의 Loss funtion을 볼수 있다.
두번째 사진에서 적용된 data는 x1의 1뿐이다. 그러면 th2와 th0 간의 비율도 역시 같기 때문에 대각선 방향이며 세번째 그림도 같은 원리로 대각선 방향이다.
첫번째 사진에서 적용된 data는 (2,1)이다. th2가 더 높은 비율을 가지므로 th1보다 빨리 학습 될것이다. 그림에서 보듯이 기울기가 y축 방향으로 붙는걸 확인할수있다. th2방향으로 먼저 projection이 된다.
두번째 그림에서 적용된 값은 x2의 2이다. 파란색 식을 보면 2*a*x2 VS 2*a 이므로 2배차이가 발생한다. 그래서 첫번째 그림처럼 y축에 붙게 된다.
세번째 그림은 th1과 th0은 서로 같은 비율이기 때문에 y = -x 방향의 대각선을 갖게된다.
파란색과 빨간색은 서로 같은 비율이기 때문에 겹쳐있다.
왼쪽그림에서 x2=3, x1=2 일때, 이 둘의 비율 차이가 1.5배이다. 1보다는 크고 2보다는 작은 모습으로 만들어진 것이다.
가운데 그림에서는 비율이 3배 차이가 나기 때문에 기울기가 더욱 기울어진 모습을 보인다.
오른쪽 그림은 비율의 차이가 2배이므로 1보다는 크고 3보다는 작은 모습으로 만들어진다.
왼쪽그림에서 x1=10, x2= 3 일때, x1이 더 크기 때문에 먼저 학습이 될 것이다. 전에 배웠던 X의 값이 1보다 작을때의 Loss function 형태와 같아진다.
두번째 그림에서는 x2와 bias의 비율 차이가 3배이기 때문에 이와 같이 만들어진다.
세번째 그림에서는 10배 만큼 비율의 차이가 있으므로 y축에 바짝 붙는 형태가 만들어진다.
표준정규분포를 따르는 데이터를 입력으로 넣었을 때는 th들이 균등하게 학습이 되는 특징을 가지고 있다. 위 모습이 가장 "이상적인 학습의 모습"이다.
x2=5로 늘리 다. 또한 Loss의 양을 보면 초반에 초반에만 급격하게 치솟아 올랐다. 이는 급격한 th2의 학습이 원인이다. x값이 크면 Loss의 양도 비례하게 된다. Loss가 크다는 건 학습해야 할 양이 많다는 건데 X값이 너무 커지면 lr로 더욱 작게 설정하여 발산하지 못하도록 해야한다.
x2가 1보다 작은 수 라면, th2만 혼자서 학습속도가 느려진 걸 확인 할수 있다. 하단의 그림을 보면 th1, th0이 먼저 학습할려고 위쪽으로 끌어 당기는 모습이 보인다. 그리고 th1,th0은 같은 학습속도 이므로 좌하단처럼 대각선 모양을 가진다.
극단적으로 0.1를 std로 설정하면 거의 0 근처 값들만 나오게 될 것이다. 그래서 하단 그림을 보면 거~의 학습이 되지 않는다. interation을 많이 주게 되면 결국은 목표값에 도달하겠지만 그만큼 시간과 자원이 소모되는 걸 감안 했을때는 비효율적이다. 또한 중간에 끊기게 되면 th1,th0은 학습이 된 상태될 것이고, th2은 학습이 미완성인 상태로 끝나게 되는 문제점도 있다.
이번엔 평균이 바뀔때 변하는 모습을 보도록 한다.
X2의 평균값 자체가 커지면서 th2의 지배적인 학습이 진행됨을 볼수있다. 가운데를 보면 th2가 자기쪽으로 당기기 때문에 th0에 대한 학습이 지그재그로 천천히 학습이 되는 문제점이 생긴다.
상하단 그림을 비교해 보자. mean값이 커질수록 먼저 학습이 되기 때문에 하단처럼 th2,1이 먼저 치고 올라 가는걸 볼수 있다. 반면 th0은 더욱 느려진다. 왜냐면 Loss가 th2,1에게 많이 뺐겼기 때문이다. 하단의 loss를 보면 평행을 이룰만큼 거의 존재하지 않는다.
하단을 보면 공통적으로 보이는 것은 th2,1 쪽으로 먼저 빠르게 학습이 된 후에야 th0가 지그재그로 학습 되고 있다.
x2 평균이 -5 음수이다. 이때 특이한 모양이 보이는데 바로 윗 그림을 보면 th0값이 뒤로 오히러 퇴보? 뒤로 당겨지는 모습을 보인다. 이유는 간단하다. -5의 Loss function 방향으로 projection이 되다 보니까 이런 현상이 발생한 것이다. 초기값이 만일 다른 곳에서 출발했다면 또 다른 모습을 보일 것이다.
-> 처음에는 전체 데이터를 가지고 주말,평일을 나눠볼려고 시도했습니다. 그래서 군집분석을 해봤는데, 시도할수록
2개로 군집 분석해도 이 의미가 과연 주말과 평일을 나누는 기준인가? 를 모르겠다는 겁니다 ㅎ;; 시도해 보고 나니 깨달았네요, 그대신 얻은 게 있다면 중요도가 낮은 변수들을 가지고 군집 분석을 통해 파생변수를 시도 했다는 것입니다. 제가 알수 없는 날씨 데이터간의 의미를 알아보는 시간이었습니다. 제 생각이지만 예를들어, 해당 군집 변수를 통해서 비가 내렸는지 , 눈이 내렸는지, 매우 더웠는지 등 알수 있지 않을까 생각해 봅니다(뇌피셜ㅎㅎ;;) 아래는 제가 책 보면서 실습해본 내용들입니다. 여러 실험을 해봤는데 .. 그닥 유의미한 변수를 뽑아 내지 못해서 사용하진 않을 것같습니다 ㅠ
*KMeans
#Train
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_samples,silhouette_score
recent_value = float('-inf')
for i in range(2,20):
kmean = KMeans(n_clusters=i,random_state=2021)
kmean.fit(X_features_log)
#실루엣 평가
score_samples = silhouette_samples(X_features_log, kmean.labels_)
#실루엣 평균값
# np.mean(train['silhouette_coeff'].values)
print('n_clusters:',i,"평균값",np.mean(score_samples))
if recent_value < np.mean(score_samples):
recent_value = np.mean(score_samples)
train['cluster'] = kmean.labels_
rfecv를 간략하게 말하자면, 변수 하나씩 모두 투입하여 가장 최적의 변수를 선택하는 기법입니다.
grid를 통해 가장 성능이 좋게 나왔던 모델로 rfecv를 진행했습니다. 그 결과 위 컬럼들을 알수 있었고 해당 컬럼만을 가지고 다시 돌려본 결과 좀 더 나빠졌습니다. 물론 데이콘에 직접 제출하기 전까지는 단정짓긴 힘들지만 하루에 제출 3번이 한계라서 막 제출 할수 없어서 피드백이 좀 아쉽습니다. 하여튼 성능개선을 보이지 않아 이 방법도 일단 보류하게 되었습니다.
Last 3) 원 핫인코딩 및 target log화
# 데이터 분리
x_train = train[[column for column in train.columns if column != 'count']]
y_train = train['count']
# target log
y_target_log = np.log1p(y_train)
#원핫 인코딩
x_train = pd.get_dummies(x_train, columns=['hour'])
test = pd.get_dummies(test, columns=['hour'])
X_train, X_test, y_train, y_test = train_test_split(x_train, y_target_log, test_size=0.3, random_state=2021)
target 변수가 전에 봤을때 살짝 비대칭 이었지만 정규분포이라는 통계량을 받았었습니다. 하지만 해보지 않으면 모르는법. 할수 있는 것들을 모두 해보자는 생각에 일단 target에 log화 시켜서 정규분포에 가깝게 만들었습니다. 그리고 기존에 피처 중요도에서 hour가 가장 크게 나왔습니다. hour가 0~23까지 있는데, 숫자형 값으로 입력하게 되면 해당 값에 크게 영향을 받게 되어서 피처 중요도가 높게 찍히게 됩니다. 카테고리 형은 원-핫 인코딩을 적용하여 변환해 줘야 합니다. 사실 이 부분을 간과하고 있었습니다..;
그래서 현재 target log 했을때의 성능 vs 원핫 + log 했을때의 성능
이 둘을 비교 하고 싶은데, 앞서 제출 기회 3번을 이미 쓴지라 제출 자료만 미리 만들어 놨습니다
그런데, 확실히 원핫인코딩 한 후에 성능이 꽤 좋아진 걸 확인 할수 있었습니다. 제출해 봐야 알겠지만 자세한 내용은 아래를 참고하겠습니다.
이번 포스팅에서는 주로 weight 변화에 따라서 bias에 어떠한 영향을 미치는지를 중점적으로 살펴보도록 한다.
weight, bias가 3,3인 것으로 부터 mu, sigma의 변화에 따른 영향을 살펴본다.
*표준편차가 1이하 일 때
(0, std)를 따를 때, std가 0에 가까울수록 뽑히는 x의 값이 0에 가까운 값이 뽑히게 될 것이다. 예를 들어 x의 값이 0.1, 0.5처럼 1보다 작은 값들이 뽑히게 된다는 것이다. 저번 포스팅에서 배웠던 것처럼 x의 값이 1보다 작게 되면 weight가 학습되는 양 자체가 적어진다. 그리고 std =1 일때는 th1,th0이 균등하게 대각선 방향으로 학습이 된다. 위 왼쪽 그림의 보락색이 이에 해당 된다.
th1의 loss grdient 양은 -2*x*lr(y-pred)이 된다. 이때 입력되는 x 값이 소수점이라면 gradient의 양 자체가 감소하게 된다. 그래서 th1의 학습속도가 느려지게 된다. 즉, step by step으로 학습이 매번 이뤄질때마다 학습 되는 양이 적다는 얘기다.
왼쪽 그림의 보락색은 초반에 띄엄 띄엄 학습되는 걸 볼수 있다. 반면 파란색은 매 학습 step마다 촘촘히 학습되고 있다. 이는 곧 "학습되는 양" 자체가 적어서 업데이트도 적게 이뤄진다는 걸 알수 있다.
오른쪽 th0의 학습 그래프를 보면, 학습 되는 속도가 std 변화에 따라서 크게 영향을 끼치지 않는다. std가 작아지든 커지든 th0의 학습에는 영향을 주지 않는 것인가? th0의 gradient를 보면 -2*lr*(y-pred) , x가 빠져 있다. 즉 x에 영향을 받지 않는 다는 것이다. 왼쪽 그림은 th1이 혼자 느려질때의 th1,th0의 그래프이고, th0만 따로 떼어 보면 오른쪽 처럼 균일한 학습속도를 보이게 되는 것이다.
*표준편차가 1이상 일때
반대로 std가 1보다 큰 값이 들어오게 된다면, th1의 gradient 값이 th0보다 많아 지기 때문에 왼쪽 그래프 처럼 th1에 지배적인 학습이 먼저 이뤄진다. gradient가 많아지게 되면 발생하는 대표적인 문제점이 하나 있다. 바로 발산이다. 그리고 발산하기 직전에는 지그재그로 왔다갔다 하게 된다. 왼쪽 그림에서 std가 가장 높은 점들을 보면 지그재그로 왔다갔다 학습이 이뤄지고 있다. 지그잭로 학습 되기 때문에 당연히 전반적인 학습 속도가 느리게 된다.
오른쪽 그림의 th0은 마찬가지로 gradient에서 x가 영향을 끼치지 않으므로 gradient의 양 변동이 크지 않다. 오직 th1의 gradient가 커졌다 작아졌다 할뿐이다.
가장 이성적인 학습속도는 어떤 것인가?? 바로 th1,th0이 "동시에" 학습이 완료되는 것이다. 마치 std=1일때 처럼 말이다.
정리를 하자면(std의 변화에 따른 결론이다)
1. std의 값이 1이하 일 때
-> th1의 gradient 양이 "작아서" 학습 속도가 느려진다.
2. std의 값이 1 이상 일 때
-> th1의 gradient 양이 커져서 "지그재그로 학습되어" 속도가 느려진다.
*mean 변화량에 따른 학습 영향
mean값이 커질수록 loss function은 수직으로 세워지게 된다.(앞서 학습 내용과 겹치므로 자세한 내용 생략) 따라서 mean이 높을수록 더욱 지그재그로 학습이 많이 이뤄진다. mean 0일 때와 5일 때를 비교해보자. th0의 학습 속도에 차이가 난다.
왜일까? mean =5이기 때문에 지그재그로 더 많이 학습이 되기 때문이다. 쉽게 말하자면 빨리 갈 수 있는 길을 내버려두고 돌고 돌아서 가기 때문에 목표지점에 도달하는 시간이 더 길어지는 것이다.
그런데, 오른쪽 그림을 보면 아까와는 다른 그래프 모양을 가진다. 왜일까? "지그재그"때문이다. 지그재그 할 때는 증감을 반복하기 때문이다. th0 입장에서 왼쪽 하늘색을 보면 증감의 반복을 볼수있다. 이런 현상이 왼쪽 그래프 반영된 것이다.
이러한 현상때문에 bias은 mean에 대해서 아주 큰 영향을 받게 된다. 따라서 input값들을 평균 0으로 맞춰 주는게 중요하다. 0으로 맞추면 std가 크든 작든 좌우 대칭으로 dataset이 뽑히기 때문에 위와 같은 악영향을 줄일수 있다.
이해를 돕기 위해서 위 학습 내용을 벡터로 표현해본다. 단, 그래프 표현을 위해서 벡터의 크기는 1로 통일한다 (gradient의 학습 양을 벡터로 그대로 표현하면 그래프로 보기가 힘들기 때문이다)
*벡터로 표현한 학습
오른쪽 벡터의 중심점이 본래 가지고 있던 점이다. 즉, 출발점이다. 벡터가 가리키는 것이 학습의 방향성이다. 왼쪽의 학습 방향을 벡터로 표현한 것이 오른쪽이다.
*std를 높힌 경우
std를 높힌다는 것은 x의 절대값이 커진다는 것이고, losss function contour plot이 y축으로 기울어 진다는 것이다.
오른쪽 그림를 보면 양쪽 좌우에 벡터가 뭉쳐 있다. 반면 상대적으로 가운데 벡터는 적다. 왜그럴까? std가 커진다는건 x의 절대값이 커진다는 것이다. 예를들어 -3,-5,3,4 이러한 x가 반영된다. 1보다 큰 x값들은 y축으로 기울진 즉, 수직에 가까운 loss function을 갖게 된다. 이러한 loss function에 projection되는 방향으로 학습이 되기 때문에 벡터 좌우에 몰리게 되고, 가운데 분포는 적게 된다. 좌우로 쏠린 벡터들이 서로 상쇄가 되면서 학습된다. 즉, 평균이 0이고, std만 달라져도 서로 대칭되는 값들이 뽑히기 때문에 서로 상쇄가 되어서 학습에 큰 영향을 주지 않게 된다. 진짜 문제는 평균이 0이 아닐때 발생하게 되는것이다.
참고로 가운데 벡터는 x축과 평행에 가까운 loss function에 projection되는 방향이다. 즉, x의 값이 1보다 작은 값들이 나올 때 이러한 벡터 방향이 나온다.
*mean를 높인 경우
mean을 키우게 되면, 전반적인 x의 값들을 키운 것과 같다. mean을 키운 순간 대각선 방향으로의 th1,th0 학습을 하지 못하게 된다. 어느 한쪽으로 쏠리게 되는데, 이 경우는 양수에 치우친 x값들이 생기게 된다. 따라서 왼쪽 하단에 분포가 많이 생기게 된다.
또한 (1,1)이므로 0에 가까운 loss function도 많이 만들어질 것이다. 그래서 오른쪽 상단 벡터에도 분포가 많이 생기게 된다. 즉, 왼쪽 오른쪽 분포가 많으므로 지그재그 형태를 띠게 된다.
mean더 키우게 된다는 것은 y축과 더욱 평행한 loss function이 만들어진다는 것이며, 사실상 음수 loss function이 만들어지지 않는다고도 볼 수 있다.
지금까지는 데이터 하나를 가지고 loss function의 변화를 봤다면, 지금부터는 여러 개의 데이터 샘플을 사용한 cost의 변화를 보도록 하자.
가장 이상적인 모습을 보인 건 전체적인 데이터 특성을 제일 잘 나타낸 배치 사이즈 32이다. 배치 사이즈가 커지면 아웃라이어의 영향을 줄일 수 있다. 그래서 배치 2를 보면 다른 것에 비해 불규칙적으로 움직인 걸 볼 수 있다.
*std 변화에 따른 영향
std =5 일 때의 그래프이다. 배치가 2인 파란색이 확실히 변동이 있는 반면, 배치가 32인 빨간색은 안정적으로 학습됨을 볼 수 있다.
*mean 변화에 따른 영향
mean 자체가 커지면서 지그재그 파동이 많아짐을 볼 수 있다. cost라도 mean이 커지면 지그재그 현상이 없어지지 않는다.
* std 변화에 따라서 벡터의 모양을 비교해 보록 하자
배치가 2일 땐 데이터 샘플 하나를 이용했을 때와 별 차이점이 없다. 그래서 좌우로 지그재그로 학습이 되기 때문에 벡터에도 좌우에 많은 분포를 가진다.
배치가 커질수록 벡터의 얖옆이 줄어들면서 가운데에 분포를 하게 된다. 다시 말해서 좌우로 가는 벡터가 적어짐에 따라 지그재그 현상이 감소하게 된다.
* mean변화에 따라서 벡터의 모양을 비교해 보록 하자
mean의 변화에 따른 벡터의 모양을 보면 비슷함을 알 수 있다. 특히 좌우 벡터의 양이 std 변화에 따른 벡터처럼 줄어들지가 않는다. 왜일까?
mean 자체를 증가시켰기 때문에 std=1이든 2이든 어찌 됐든 x가 큰 값이 input으로 들어가게 된다. mean이 커지면 더 이상 표준 정규분포가 아니게 된다. 위 그래프의 학습 cost function 모양이 대략 \일 것이다.
아무리 32개를 뽑고 cost를 계산해봐도 \과 비슷한 cost function을 만들게 된다. 또한 x의 값이 커지면 발산의 우려가 있다. 그 전초기 증상이 바로 지그재그이다. 이를 막기 위해서 lr의 조정이 필요한 것이다.