728x90

요약

  • rush_hour 파생변수 추가
  • Dense Layer 추가

1. Rush_hour 파생변수 추가

#잔차분포 확인
Xtrain_predictions = best_model.predict(X_train[x_columns])
sns.displot(x=Xtrain_predictions-Y_train)
plt.show()

grid의 best model로 잔차 분포를 확인해 봤습니다. 제 생각에는 성능을 저하시키는 요인이 양 끝 부분에 차지한 loss들 때문이라고 생각했습니다. max, min 값의 인덱스를 가지고 데이터를 살펴보았습니다.

 

min(Xtrain_predictions-Y_train),max(Xtrain_predictions-Y_train)
(-162.76608924934504, 118.88818750028724) #245, 1387

 

df.loc[[245,1387]]

위 결과는 rush_hour를 미리 적용한 결과물입니다. 처음에는 출퇴근 시간인 18시 ,8시에 가장 많은 Loss가 있었습니다. 그래서 rush hour시간에 급증하는 포인트를 주기 위해서 아래와 같은 코드로 변수를 추가했습니다.

 

# 출근 시간: 7,8,9  퇴근시간:17,18,19시 기준
df['rush_hour'] = df['hour'].apply(lambda x: 1 if x== 7 or x==8 or x==9 
                                   or x==17 or x==18 or x==19 else 0 )

해당 변수를 넣은 후 돌린 결과 리더보드에서 상위 10% 안에 들수 있었습니다. 해당 변수의 lgb 피처 중요도를 보면 낮은 수치를 기록했습니다.

이는 아마 출퇴근 시간의 데이터가 적어서 그런게 아닐까 생각합니다. 하지만 미처 해결하지 못한 문제점이 있습니다.   

해당 이미지의 결과를 보면, 16시에 예측값이 target 보다 아주 적게 예측을 했고, 8시에는 37보다 크게 예측을 했습니다. 변수를 보면 이둘의 차이를 설명해줄만한 것이 없습니다. 제 생각엔 8시인 출근 피크 시간에 수요가 적다는 것은 아침 유동인구가 비교적은 주말이나 공유일일 확률이 크다고 봅니다. 줄곧 고민을 해보았지만 아직까지는 실마리를 찾지 못했습니다. Feature들을 가지고 군집화를 통해 내가 모르고 있던 구분을 해주지 않을까 시도 해봤지만 이 역시 잘 되지 않았습니다. 새로운 변수를 찾기 위해서 Dense층의 비선형을 이용하면 어떨가 싶어서 Dense를 추가했습니다.

 

 

2. Dense Layer 추가

import os 
from tensorflow.keras.layers import Dense, Flatten, Input,Dropout , BatchNormalization
from tensorflow.keras import regularizers
from tensorflow.keras.models import Model
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import ModelCheckpoint,ReduceLROnPlateau,EarlyStopping

MODEL_SAVE_FOLDER_PATH = '/content/weights/' 
if not os.path.exists(MODEL_SAVE_FOLDER_PATH): 
  os.mkdir(MODEL_SAVE_FOLDER_PATH)

model_path = MODEL_SAVE_FOLDER_PATH + 'weights.{epoch:02d}-{val_loss:.2f}.hdf5'

#ModelCheckpoint
mcp_cb = ModelCheckpoint(filepath=model_path, monitor='val_loss', 
                         save_best_only=True, save_weights_only=False, mode='min', verbose=1)

#ReduceLROnPlateau
rlr_cb = ReduceLROnPlateau(monitor='val_loss', factor=0.3, patience=15, mode='min', verbose=1)

#EarlyStopping
ely_cb = EarlyStopping(monitor='val_loss', patience=20, mode='min', verbose=1)


def create_model():
  input_tensor = Input(shape=(X_train[x_columns].shape[1],))
  x = Flatten()(input_tensor)
  x = Dense(12, activation='relu',kernel_regularizer=regularizers.l2(0.001))(x)
  # x= BatchNormalization()(x)
  x = Dense(10, activation='relu',kernel_regularizer=regularizers.l2(0.001))(x)
  # x= BatchNormalization()(x)
  output = Dense(1, activation='relu')(x)
  model = Model(inputs=input_tensor, outputs=output)
  return model

def train():
  model = create_model()
  model.compile(optimizer=Adam(0.001), loss = 'mse', metrics=['mse'])
  history= model.fit(X_train[x_columns],Y_train,
                      epochs=200,
                      batch_size=4,
                      validation_data=(X_test[x_columns], Y_test),
                    callbacks=[mcp_cb,ely_cb])
  return history
history = train()

 

#그래프 시각화
plt.figure(figsize=(10,10))
plt.plot(history.history['mse'], label='train_mse')
plt.plot(history.history['val_mse'],label='val_mse')
plt.legend()
plt.show()

 

from tensorflow.keras.models import load_model

#학습된 가중치 불러오기
weights_path = '/content/weights/weights.35-2130.14.hdf5'
def load_trained_model(weights_path):
   model = create_model()
   model.load_weights(weights_path)
   return model
model = load_trained_model(weights_path)

#Test Predict
dense_model=model.predict(test[x_columns])
submission.iloc[:,1] = dense_model
submission.set_index("id")
submission.to_csv('dense_2130.csv', index=False)

Dense를 이용해본 결과, 39점을 받으면서 기본보다 안좋은 성능을 받았습니다. 지금까지 많은 모델을 실험해 본 결과  트리계열의 모델이 가장 좋은 성능을 보이고 있습니다. 다음 시간에는 트리계열의 모델로만 구성된 ensemble 및 stacking model을 시도해볼 계획입니다.  

 

 

 

 

 

 

 

 

728x90
728x90

실습내용은 아래 링크에서 확인할 수 있습니다.

2021.10.05 - [실습 note] - CNN_Fundamental 실습 모음

 

CNN_Fundamental 실습 모음

해당 실습은 iflearn "CNN_Fundamental" 강의에서 사용되었습니다. 실습 진행 순서대로 list-up 했습니다 1. Gradient Descent_01 Gradient_Desecent 내용은 딥러닝 수학 강의에서 배웠으므로 따로 이론을 정리하..

ghdrldud329.tistory.com

 

 

CNN은 이미지를의 일 부분을 자동으로 뽑아서 featrue로 사용한다. 반면 머신러닝은 일일히 feature를 뽑기 때문에 효율이 떨어진다. 또한 가변적으로 feature들이 변하기 때문에 성능에도 큰 영향을 주었다.

딥러닝은 Universal 한 Feature를 유연성 있게 뽑아준다. 한개의 뉴런 당 하나의 이미지 part를 맡게 된다. 예를들어 고양이 사진에 뉴런 100개라면, 고양이 사진을 100개 부분으로 개별적으로 본다는 것이다. 다시 돌아와서 convolutional laters에서 feature 특징들이 모이게 되면 이를 "feature map"이라 부른다. 이 feature map을 Dense layer에 넣게 된다. 해당 Dense layer를 Fully connected layer 혹은 Classifier layer 라고 부른다. 해당 layer는 실제와 예측을 비교해가면서 가중치를 updata해 가는데, 이때 가중치의 updata는 Feature Extractor 가중치까지 영향을 준다. 학습하기 전 FE에서는 이미지의 특징을 정답없이? 방향성 없이 FE를 진행한다. 그러다가 특정 target에 대한 Loss가 발생하게 되고, 가중치들이 Updata가 되면서, FE가 특정 target에 대한 특징을 뽑게 된다. 다시말해서 target이 고양이 이고, input 이미지가 고양이가 찍혀 있는 공원이라고 했을때, updata가 되면서 인풋 이미지의 벤치, 하늘, 사람 등 고양이를 제외한 배경들의 가중치는 줄어들게 되고, 고양이에 해당된 가중치들이 업데이트 된다. 고양이이라는 이미지에 맞춰서 FE를 뽑게 된다.

Low level Feature 에서는 주로 이미지를 구성하는 "선"의 특징들로 구성되어 있다. 그 다음 단계인 Mid에서는 더 상세한 특징이 보다는 "추상적인" 특징들이 뽑힌다. layer가 깊어질수록 추상적인 이미지가 뽑힌다.

위 그림을 보듯이, layer가 깊어질수록 뭉개지면서 추상적인 이미지가 생기게 됨을 확인할수있다.

왼쪽 새 이미지의 한 부분에 대해서 Feature를 만든다. 이렇게 만든 Feature에 layer를 추가하여 추상적인 이미지를 뽑는다. 이 과정을 반복하여 만든 Featrue들을 Classifier layer에 넣어서 최종적으로 판단하게 된다. CNN = Feature Extractor + Classifier networt로 구성되어 있다. 최선의 FE를 위해서 필터 가중치를 updata 시킨다. 그리하여 최적의 Feature를 생성한다.

왼쪽처럼 필터를 적용하면 이미지가 작아진다. 그래서 오른쪽의 Zero padding을 사용하여 원본이미지를 7*7로 만들고 필터 계산하여 원본 사이즈인 5*5로 만들수 있다. 참고로 필터는 여러개의 커널로 구성되어 있다.

 

 

*Kernal size 특징

커널사이즈를 높일수록 계산될 파라미터들이 많아진다. 7*7는 Alexnet에서 사용된 이후로 잘 사용되지 않고 있다.

 

 

*Feature Map 개요

Feature map이란 필터들을 적용한 결과를 말한다. 4개의 feature map이 있다는건 4개의 필터를 적용했기 때문이다. 위 형식이 CNN의 기본 과정이다.

 

 

 

*Stride

 

 

 

*Padding

원본이미지를 유지하기 위해서 상하좌우 겉 테두리에 0값을 넣는게 패딩이다.

패딩의 목적은 layer가 길어질수록 feature map이 작아지는 문제점을 막기 위함이다. 모서리 부분에는 패딩에 의해서 중첩 계산이 발생하므로 모서리의 특징이 보다 더 강화가 된다.

 

 

 

*Pooling

Pooling은 Pool size 만큼 stride가 동일하게 적용된다. 만일 동일하게 하지 않을 시, 중첩되는 부분이 발생하기 때문에 이를 방지하고자 겹치지 않게 Pooling을 하게 된다.보통 Average Pooling은 잘 사용되지 않는다. 뽑고자 하는 특징과 그렇지 않는 특징이 혼합된 경우에 평균으로 계산하면 target 특징을 잘 표현하지 못하게 되기 때문이다. Max을 이용하여 가장 뚜렷하고 특징적인 부분을 뽑고자 Max Pooling을 주로 선호한다. 보통 2*2 Max Pooling을 이용한다.

leNet, AlexNet에는 Conv2d(S/P) -> Pooing 매커니즘 토대를 닦아 놓았다. VGG에서는 이러한 매커니즘을 충실히 반영한 모델이다. 하지만 최근 모델에는 Pooling을 자제하고 Stride를 이용하려는 경향이 강해지고 있다.

 

 

 

*채널과 커널의 이해

중요하게 봐야 할 점은 Input과 kernel 연산 -> "더하기" -> 2차원의 Output이 생성된다는 점이다.

bias 1을 위 그림처럼 일일히 더해줘야 한다.

*피처맵 크기 공식

s=2 적용시, 가장 오른쪽 부분이 계산 안될수가 있다. 그래서 s=2일때는 주의를 기울어야한다. 빨간 줄 부분이 연산되지 않는 부분이다.

이러한 부분을 해결하기 위해서 padding을 사용한다. s=2일때 padding='same'을 사용하게 된다. 이때 padding의 의미가 달라진다. s=1일때는 input과 output size를 동일하게 한다는 의미였지만, 위 그림을 보면 (3,3,1)로 출력 되었다. 즉, 빠지는 부분 없이 연산을 하겠다 라는 의미로 s=2에서는 padding이 사용된다.

padding='same'을 하게 되면 6*6 -> 8*8로 변한다. 사실 7*7만 있어도 빠지는 부분없이 연산이 가능하다. 그래서 위 이미지처럼 zero 패딩을 수동으로 입력할수 있다. (1,0) -> 위로 패딩 한줄 ,(1,0) -> 왼쪽에 패딩 한줄 이다. 빨간 박스를 보면 위,왼쪽에 빨간선이 추가가 된걸 확인할수 있다. 그렇다면 "아래 한줄, 오른쪽 한줄"은 무엇일까 (0,1),(0,1) 이다.

728x90
728x90

우리가 원하는 포인트는 가장 최저점을 나타내는 global point이다. 하지만 위 그림처럼 local 포인트에 빠지거나, 평평한 부분인 saddle 포인트를 만나면서 최저점이라고 인식하는 오류를 범할수 있다. 이를 해결하기 위해 다양한 optimizer들이 등장했다.

주요 Optimizer들이다. momentum은 GD값을 조정하고, adagrad,rmsprop은 lr을 조정한다. adam은 GD,lr 모두 조정하면서 최적점을 찾는다.

 

 

 

*Momentum

과거의 GD에 Momentum 가중치를 적용하여 새로운 GD를 계산한다. 기존의 GD는 wt+1 = wt- GD 이다. momentum은 이 GD에다가 감마라는 가중치를 적용해 준다. 헷갈리지 말아야 할점은 새롭게 구한 gradient에 감마를 곱하는게 아니다. 바로 이전에 구했던 GD에 감마를 곱한 뒤 새롭게 구한 GD에 더해준다. 식으로 표현하면

Wt+1 = 과거 GD - (새롭게 구한 gradient + 감마 * 과거 gradient)이다. GD는 gradient Descent이며 gradient와는 다른 값이다.

 

과거의 gradient을 감안하면 어떤 점이 좋은가?

현재 local 포인트에 갇혔다고 가정해 본다. 과거 gradient 두개 값을 더해주어서 local 지역을 빠져 나가게끔 힘을 보탤수가 있다.

각 입력되는 데이터의 크기에 따라 loss가 증감을 반복하기 때문에 SGD같은 경우엔 지그재그로 학습이 된다. 이럴경우 학습시간이 느려질뿐만 아니라 학습 자체가 안될수 있기 때문에 지그재그 학습 현상은 지양해야한다. momentum을 사용하면 이러한 지그재그 현상을 줄일수 있다.

 

 

*AdaGrad(Adaptive Gradient)

입력 데이터의 크기에 따라 각각의 가중치 학습속도에서 차이가 발생한다. 이때 AdaGrad를 활용하여 적게 학습된 가중치에는 lr을 크게 설정하고, 많이 학습된 가중치에는 작은 lr을 적용한다. 참고로, 각각의 가중치 학습 속도가 다를 경우 최적점에 도달하기엔 한계있다.

식을 보면 lr의 분모가 생겼음을 알수 있다. 해당 분모 값을 통해 비율을 조절하게 된다.분모에 있는 엡실론의 역할은 분모가 0이 되는걸 방지하기 위함이다. St를 보면 Gradient의 제곱을 해주는데 그 이유는 마이너스,플러스값이 나올수 있기 때문이다.

Gradient의 제곱은 언제나 양이기 때문에 계속 증가할 수밖에 없다. 그래서 lr 값이 아주 작게 설정되는 문제점을 안고 있다. 이를 해결한 것이 RMSprop이다.

 

 

 

* RMSprop

오래된 Gradient값에 가중치를 적용하여 영향력을 줄이도록 제한한다. 예를들어 위 분홍색 식에서 감마가 0.9라고 한다면 오래된 Gradient인 St-1에 0.9가 계속 곱해진다. 0.9*0.9*0.9*~~*0.9*오래된 Gradient +(1-0.9)*new Gradient.

 

 

 

* Adam(Adaptive Moment Estimation)

RMSProp와 거의 흡사하다. 차이점이 있다면 Momentum을 지수 가중 평균법으로 변경한 점이다. 또한 Adam은 베타1,베타2로 조절한다.

728x90
728x90

iflearn의 CNN 완벽가이드를 새로 시작했습니다. 초반 강의는 딥러닝 수학 강의 내용과 겹쳐서 내용은 생략했습니다. 

활성화 함수 part부터 업로드 합니다.

 

활성화 함수는 사용하는 이유는 비선형성으로 만들기 위함이다. 활성화 함수가 없다면 선형함수로써 왼쪽처럼 구분이 된다. 하지만 오른쪽 처럼 비선형이 적용되면 과적합 문제가 발생하기 쉽다. 활성화 함수 별 사용용도를 아래 그림을 통해 확인한다.

각 활성화 함수별 사용 용도가 각각 다르다.

 

 

*Sigmoid 함수 특성

X값이 양으로 100 이든 1000이든 출력은 1로 수렴이 된다. X값이 음수로 -100이든 -1000이든 출력은 0으로 수렴이 된다. 이때 sigmoid의 미분를 표현한 파란색 분포를 보겠다. X가 음수든 양수든 커질수록 어찌됐든 1과0으로 수렴하기 때문에 양쪽 사이드가 감소하는 모양을 띈다. 미분은 "내가 변했을때 넌 얼만큼 변해?" 의 양을 나타낸다. 다시말해 X가 얼마나 커지든 작아지든 나는 0과 1로만 수렴하기 때문에 변화의 한계가 존재한다. 이로인해 Vanishing Gradient 문제가 발생하게 된다.(은닉층으로 사용할 시)

시그모이드를 미분하면 0이 출력되는 현상때문에 위 그림처럼 층이 깊어질수록 Gradient값이 점점 사라지게 된다.

그래서 시그모이드는 이진분류를 수행할때 주로 사용된다. 왼쪽 선형식으로는 1과0으로 구분하기가 쉽지 않다. 이를 시그모이드를 통해 확률 0.5를 기준으로 1과0으로 분류할수 있다.

 

 

 

*Hyperbolic Tangent 함수 특성

시그모이드와는 달리 -1~1사이의 값을 취급하지만 같은 문제점을 안고 있다. X값에 따라 결국은 수렴을 하기때문에 vanishing이 발생한다.

 

 

*ReLU(Rectified Linear Unit)함수 특성

입력값이 0보다 작으면 0으로 출력하고, 0보다 크면 수렴이 하지 않고 똑같이 출력한다. 그래서 오른쪽 그림의 미분이 0으로 떨어지지 않고 1을 반환한다.

 

 

*SoftMax함수 특성

시그모이드는 0.9이면 "1"이라고 값을 "하나만" 출력해준다. 반면 소프트 맥스는 이를 여러개로 반환할수 있다. 위 그림처럼 output layer를 소프트맥스에 넣게 되면 그에 맞는 확률값으로 반환해준다. 반환된 확률 값중 가장 큰 값을 선택하여 클래스를 결정한다.

 

 

*Cross Entropy Loss

분류일 경우엔 예측값과 실제값 사이의 Cross Entropy를 구하여 Gradient Descent를 계산한다. 위 그림의 하단 식에서 i는 data의 건수를 의미한다. ex) 첫번째 이미지는 i=1로 표현할수 있겠다. 따라서 m은 데이터의 건수를 의미한다. Loss를 보면 m으로 나누기 때문에 Cross entropy의 평균을 Loss로 사용하고 있음을 알수있다.

하단을 먼저 보면, output layer에서 위 처럼 값이 들어오고, 이를 softmax을 통과하면 가장 큰 확률값의 인덱스를 통해 클래스를 결정하게 된다. 이때의 Loss를 구하는 것은 아래 식을 통해 자세히 보겠다.

C는 class의 갯수이다. 예를들어 고양이 부터 강아지까지 5종류의 동물을 나타낸다. 이때, CE의 값은 위 식처럼 계산된다. 식을 통해 알수 있는 점은 0인 클래스, 즉 정답이 아닌 클래스는 모두 더해봤자 log0=0이므로 0이다. Loss는 오직 Class=1일때 구할수 있다.

---

왼쪽 그래프에서 값을 크~게 잘못 예측하게 되면 loss 값 또한 커지는 걸 볼수있다.

 

시그모이드는 0 or 1 이진 분류를 나타내고자 할때 사용된다. 만일 0.7이라는 예측값을 얻었을때의 loss는 얼마인가를 생각해 보자. 이진 분류이기 때문에 두가지 경우로 생각해 볼수 있다. 실제 값이 1인 경우와 0인 경우 일것이다. 실제값이 1일때의 loss 값, 실제값이 0일때의 loss값을 알아내야 한다. 그래서 식이 softmax 와는 조금 다르다. 실제값이 1이라면

뒤에 있는 값이 0이 될것이다. 1-1 = 0 이니까. 반대로 실제값이 0이라면

위에 해당 부분이 0이 되고 뒷부분 식에서 loss값을 구할수 있게 된다.

728x90
728x90

*주요 요약

  • seasonal 추가
  • 오존,미세먼지,초미세먼지 변수 -> 카테고리로 변경

 

지금까지 여러 파생변수를 만들어 보고 실험해 봤는데 대부분이 쓸모 없었습니다 아마 오늘을 끝으로 변수에 대한 FE는 마무리가 될것 같습니다. 다음 시간부터는 최적의 모델링을 찾는데 시간을 쓸것 같습니다 

 

 

1. Seasonal 추가

시계열 기법중에 분해기법이 있습니다. 계절성 정보를 넣으면 예측에 유리할 것같아서 시도 해봤습니다. 제가 가지고 있는 데이터는 날짜 형식이 아니며, 순서도 뒤죽박죽이기 때문에 grouby로 hour 별 mean 값을 구한 뒤, 가짜 날짜 변수를 생성하여 seasonal을 구했습니다. 그 후 각 시간별 seasonal 정보를 매칭시켜서 원본 데이터에 적용했습니다. 

train_tem = train.groupby('hour').mean().reset_index()
train_tem['Date'] = pd.date_range(start='2017-04-01 00:00:00', end='2017-04-01 23:00:00', freq='H')
train_tem['Date'] = pd.to_datetime(train_tem['Date'])
train_tem.set_index(train_tem['Date'], inplace=True)
train_tem=train_tem.asfreq('H')
result = sm.tsa.seasonal_decompose(pd.concat([train_tem['count'],train_tem['count']], axis=0),model='additive', freq=24)
result.seasonal
Date
2017-04-01 00:00:00    -36.915619
2017-04-01 01:00:00    -61.075729
2017-04-01 02:00:00    -77.272450
2017-04-01 03:00:00    -87.305237
2017-04-01 04:00:00    -95.157696
2017-04-01 05:00:00    -95.567532
2017-04-01 06:00:00    -84.124909
2017-04-01 07:00:00    -46.321630
2017-04-01 08:00:00     28.006239
2017-04-01 09:00:00    -15.141302
2017-04-01 10:00:00    -29.879007
2017-04-01 11:00:00    -20.354417
2017-04-01 12:00:00      3.219353
2017-04-01 13:00:00     11.367714
2017-04-01 14:00:00     25.907878
2017-04-01 15:00:00     44.284927
2017-04-01 16:00:00     60.417714
2017-04-01 17:00:00     78.451047
2017-04-01 18:00:00    153.481648
2017-04-01 19:00:00     92.924271
2017-04-01 20:00:00     56.186566
2017-04-01 21:00:00     60.134381
2017-04-01 22:00:00     39.563616
2017-04-01 23:00:00     -4.829827
2017-04-01 00:00:00    -36.915619
2017-04-01 01:00:00    -61.075729
2017-04-01 02:00:00    -77.272450
2017-04-01 03:00:00    -87.305237
2017-04-01 04:00:00    -95.157696
2017-04-01 05:00:00    -95.567532
2017-04-01 06:00:00    -84.124909
2017-04-01 07:00:00    -46.321630
2017-04-01 08:00:00     28.006239
2017-04-01 09:00:00    -15.141302
2017-04-01 10:00:00    -29.879007
2017-04-01 11:00:00    -20.354417
2017-04-01 12:00:00      3.219353
2017-04-01 13:00:00     11.367714
2017-04-01 14:00:00     25.907878
2017-04-01 15:00:00     44.284927
2017-04-01 16:00:00     60.417714
2017-04-01 17:00:00     78.451047
2017-04-01 18:00:00    153.481648
2017-04-01 19:00:00     92.924271
2017-04-01 20:00:00     56.186566
2017-04-01 21:00:00     60.134381
2017-04-01 22:00:00     39.563616
2017-04-01 23:00:00     -4.829827
Name: count, dtype: float64

 

seasonal_df = pd.DataFrame(result.seasonal)
seasonal_df = seasonal_df.iloc[:24].reset_index(drop=True)
seasonal_df
      count
0	-36.915619
1	-61.075729
2	-77.272450
3	-87.305237
4	-95.157696
5	-95.567532
6	-84.124909
7	-46.321630
8	28.006239
9	-15.141302
10	-29.879007
11	-20.354417
12	3.219353
13	11.367714
14	25.907878
15	44.284927
16	60.417714
17	78.451047
18	153.481648
19	92.924271
20	56.186566
21	60.134381
22	39.563616
23	-4.829827

 

def seasonal_fill(x):
  value = float(seasonal_df[seasonal_df.index==x].values)
  return value
df['seasonal'] = train['hour'].apply(seasonal_fill) 
df.head()

 

 

 

 

2. 오존,미세먼지,초미세먼지 변수 -> 카테고리로 변경

해당 아이디어는 다른 분의 블로그를 보면서 참고했습니다. 왜 이 생각을 못했을까 했어요 

# 오존, 좋음/보통/나쁨/매우나쁨 을 기상청 기준으로 다른 class 부여
df['hour_bef_ozone'] = df['hour_bef_ozone'].apply(lambda x : 0 if x <= 0.03 else 
                                                                   1 if 0.03 < x and x <= 0.09 else 
                                                                   2 if 0.09 < x and x <= 0.151 else
                                                                   3 if 0.151 < x else x)

# 미세먼지, 좋음/보통/나쁨/매우나쁨 을 기상청 기준으로 다른 class 부여
df['hour_bef_pm10'] = df['hour_bef_pm10'].apply(lambda x : 0 if x <= 30 else 
                                                                 1 if 30 < x and x <= 80 else 
                                                                 2 if 80 < x and x <= 150 else
                                                                 3 if 150 < x else x)

# 초미세먼지, 좋음/보통/나쁨/매우나쁨 을 기상청 기준으로 다른 class 부여
df['hour_bef_pm2.5'] = df['hour_bef_pm2.5'].apply(lambda x : 0 if x <= 15 else 
                                                                   1 if 15 < x and x <= 35 else 
                                                                   2 if 35 < x and x <= 75 else
                                                                   3 if 75 < x else x)

#One-Hot Encoding
df = pd.get_dummies(df, columns=['hour_bef_ozone','hour_bef_pm10','hour_bef_pm2.5'])

해당 변수를 원핫 인코딩 전/후 모두 비교해 봤는데, 결과적으로는 미미한 영향이었습니다. 애초에 target과 연관성이 적었던 변수들이기 때문입니다. 그래도 이번 계기로 데이터를 바라보는 시각이 조금은 넓어진 느낌을 받았습니다 :)

 

728x90
728x90

오늘은 깃허브에 대해 공부했습니다. 그 전부터 몇번 시도는 했었으나 branch는 뭐고 commit은 뭔지 도통 감이 잡히지 않아서 나중에 하기로 미룬지가 거의 반년이 넘어 갔습니다. 공부 할수록 깃허브의 중요성을 조금씩 알게 되었고, 프로젝트를 만들면서 버전관리에 중요성을 깨달았는지라 오늘 시간을 내어봤습니다.

 

개념을 빠르게 배우기 위해서 유튜브 강의를 시청했습니다. (아래는 해당 링크 입니다)

https://www.youtube.com/watch?v=-27WScuoKQs&t=751s 

영상 총 길이가 1시간 좀 넘었는데, 좋은 설명 덕분에 이해하기 수월했습니다. 현재는 배운내용을 토대로 commit과 push를 연습 중에 있습니다. 

 

이 과정에서 몇 시간동안 제 발목을 잡은 에러가 있었습니다. 소스트리에서 push를 누르니까 비밀번호를 누르라고 해서 깃허브 비번을 입력했더니 에러가 발생했습니다. 다시 푸시를 눌러도, 재설치를 해봐도 에러가 발생했습니다. 알고 보니 해당 비밀번호는 깃허브 홈페이지에서 할당받은 토큰을 넣는 것으로 변경되었다고 합니다.

 

구글링하면서 이것저것 시도하다가 ssh로 시도하게 되었습니다. 제가 참고했던 ssh 적용방법 블로그는 아래에 걸어 두겠습니다.

https://happysalmon.tistory.com/3

 

(소스 트리) 깃랩을 이용한 소스트리 SSH key 등록 방법

소스 트리 다운로드와 깃 랩(Gitlab) SSH key 키 등록 방법, 프로젝트 클론 하는 방법에 대해서 알아보도록 하겠습니다. 준비 소스 트리 링크 다운로드 및 회원가입 2. 깃 랩(Gitlab) 링크 회원가입 순

happysalmon.tistory.com

 

그런데, 위 과정으로만 했더니 또 에러가 발생했습니다. 에러 메세지를 읽어 보니까 저는 좀 다른 방식으로 해야했음을 알게 되었어요.

블로그에서는 위 이미지처럼 ssh 클라이언트를 OpenSSH로 변경하도록 안내하고 있습니다. ssh키는 내가 ssh키를 저장해 놓은 위치입니다. 아무튼 이 과정대로 따라가니까 에러가 발생했습니다.

 

저 같은 경우는 나머지 모두 같지만 딱 하나 다르게 설정하니까 push가 되었습니다. 해당 블로그가 틀린 정보를 소개해준게 아닙니다 다른 블로그들 보면 같은 방법으로 안내를 하고 있는데, 저는 좀 다른 케이스 인것 같더라구요. 정확한 원인은 잘 모르겠지만 나도 이제 깃허브를 사용할수 있다는 생각에 좋았답니다 

 

이참에 현재 진행중인 따릉이 프로젝트도 버전관리 하고 싶어서 코랩으로 깃허브 연동까지 진행했습니다. 코랩의 깃허브 사본저장만 누르면 끝이라서 상당히 간편했습니다.

태양열 에너지 예측은 코드가 뒤죽박죽에다가 EDA가 제외가 된 터라(EDA는 파일이 별도로 존재함) 제외하고 업로드 해봤습니다.  

 

 

 

 

 

 

728x90
728x90

target 분포

저번 포스팅 마지막에는 count의 분포와 비슷한 파생변수를 만든다는 말 끝으로 끝냈었습니다. 예고 했듯이 제 나름대로 target 변수를 설명해줄 파생변수를 2가지 만들어 봤습니다. 그 외로 몇가지 더 실험해 보았는데 이는 아래 주요 요약에 적어 놓았습니다. 

 

*주요 요약

  • target 분포를 설명해줄 파생변수 2가지
  • 변수들의 왜곡 확인 & target 변수의 이상치 제거
  • 스태킹 모델 

 

1. target 분포를 설명해줄 파생변수 2가지

 

1.1 시간별 평균 이용 수

train_df = train.copy()
test_df = test.copy()
train_df['cue'] = 0
test_df['cue'] = 1
df = pd.concat([train_df,test_df],axis=0).reset_index(drop=True)

# 전체 데이터 중 train에 해당하는 행 추출
train_data = df.query('cue=="0"').reset_index(drop=True)

df['hour_mean']=1

#각 시간별 인덱스 추출
index00 = df.query('hour=="0"').index
index01 = df.query('hour=="1"').index
index02 = df.query('hour=="2"').index
index03 = df.query('hour=="3"').index
index04 = df.query('hour=="4"').index
index05 = df.query('hour=="5"').index
index06 = df.query('hour=="6"').index
index07 = df.query('hour=="7"').index
index08 = df.query('hour=="8"').index
index09 = df.query('hour=="9"').index
index10 = df.query('hour=="10"').index
index11 = df.query('hour=="11"').index
index12 = df.query('hour=="12"').index
index13 = df.query('hour=="13"').index
index14 = df.query('hour=="14"').index
index15 = df.query('hour=="15"').index
index16 = df.query('hour=="16"').index
index17 = df.query('hour=="17"').index
index18 = df.query('hour=="18"').index
index19 = df.query('hour=="19"').index
index20 = df.query('hour=="20"').index
index21 = df.query('hour=="21"').index
index22 = df.query('hour=="22"').index
index23 = df.query('hour=="23"').index

# 각 시간별 평균값을 "hourmean" 변수에 대입
df.iloc[index00,-1] = train_data.query('hour=="0"')['count'].mean()
df.iloc[index01,-1] = train_data.query('hour=="1"')['count'].mean()
df.iloc[index02,-1] = train_data.query('hour=="2"')['count'].mean()
df.iloc[index03,-1] = train_data.query('hour=="3"')['count'].mean()
df.iloc[index04,-1] = train_data.query('hour=="4"')['count'].mean()
df.iloc[index05,-1] = train_data.query('hour=="5"')['count'].mean()
df.iloc[index06,-1] = train_data.query('hour=="6"')['count'].mean()
df.iloc[index07,-1] = train_data.query('hour=="7"')['count'].mean()
df.iloc[index08,-1] = train_data.query('hour=="8"')['count'].mean()
df.iloc[index09,-1] = train_data.query('hour=="9"')['count'].mean()
df.iloc[index10,-1] = train_data.query('hour=="10"')['count'].mean()
df.iloc[index11,-1] = train_data.query('hour=="11"')['count'].mean()
df.iloc[index12,-1] = train_data.query('hour=="12"')['count'].mean()
df.iloc[index13,-1] = train_data.query('hour=="13"')['count'].mean()
df.iloc[index14,-1] = train_data.query('hour=="14"')['count'].mean()
df.iloc[index15,-1] = train_data.query('hour=="15"')['count'].mean()
df.iloc[index16,-1] = train_data.query('hour=="16"')['count'].mean()
df.iloc[index17,-1] = train_data.query('hour=="17"')['count'].mean()
df.iloc[index18,-1] = train_data.query('hour=="18"')['count'].mean()
df.iloc[index19,-1] = train_data.query('hour=="19"')['count'].mean()
df.iloc[index20,-1] = train_data.query('hour=="20"')['count'].mean()
df.iloc[index21,-1] = train_data.query('hour=="21"')['count'].mean()
df.iloc[index22,-1] = train_data.query('hour=="22"')['count'].mean()
df.iloc[index23,-1] = train_data.query('hour=="23"')['count'].mean()

시간 별 count 평균값을 활용하여 hour_mean 변수를 생성했습니다. 해당 변수를 수행한 결과, hour 변수와 연관성이 컸습니다. feature 중요도에서 hour값을 빼면 hour_mean 값이 크게 증가 한걸 확인했습니다. 다만 hour_mean 중요도가 hour 중요도보다 약 9% 더 크게 나왔습니다. (이미지가 아까 있었는데 다른 실험들 하느라 없어졌네요..;)

 

df.tail()
	id	hour	hour_bef_temperature	hour_bef_precipitation	hour_bef_windspeed	hour_bef_humidity	hour_bef_visibility	hour_bef_ozone	hour_bef_pm10	hour_bef_pm2.5	count	cue	hour_mean
2169	2148	1	24.6	0.0	2.4	60.0	1745.0	0.023833	46.0	30.25	NaN	1	47.606557
2170	2149	1	18.1	0.0	1.0	55.0	2000.0	0.027000	30.0	20.25	NaN	1	47.606557
2171	2165	9	23.3	0.0	2.3	66.0	1789.0	0.020000	17.0	15.00	NaN	1	93.540984
2172	2166	16	27.0	0.0	1.6	46.0	1956.0	0.032000	40.0	26.00	NaN	1	169.100000
2173	2177	8	22.3	0.0	1.0	63.0	1277.0	0.007000	30.0	24.00	NaN	1	136.688525

 

 

1.2 hour_bef_precipitation에 따른 평균 이용 수

df['precipitation_mean'] = 1
index0 = df.query('hour_bef_precipitation=="0.0"').index
index1 = df.query('hour_bef_precipitation=="1.0"').index

df.iloc[index0,-1] =train_data.query('hour_bef_precipitation=="0.0"')['count'].mean()
df.iloc[index1,-1] =train_data.query('hour_bef_precipitation=="1.0"')['count'].mean()

비의 유무에 따른 평균 이용 수 변수를 생성했습니다. 비가 내린 날과 내리지 않는 날을 명확하게 구분하려는 의도로 만들었지만 영향력이 0 였습니다. 

 

 

 

 

2. 변수들의 왜곡 확인 & target 변수의 이상치 제거

 

2.1 변수들의 왜곡 확인 (보통 1 이상일때 왜곡이 있다고 판정하여 log 변환 실시)

# 변수들의 왜곡 확인
from scipy.stats import skew
feature_df = df.drop(['id'	,'hour','count','cue','hour_bef_precipitation'], axis=1)
feature_index = feature_df.dtypes[feature_df.dtypes != 'object'].index 
feature_index = feature_df.dtypes[feature_df.dtypes != 'object'].index 
skew_features = feature_df[feature_index].apply(lambda x: skew(x))

#shew(왜곡) 저도가 1 이상인 칼럼만 추출
skew_features_top = skew_features[skew_features>1]
print(skew_features_top.sort_values(ascending=False))

 

hour_bef_pm10     2.645937
hour_bef_pm2.5    1.387923
dtype: float64

위에 두 변수에서 왜도가 있음을 나타내고 있습니다. 하지만 해당 변수들은 학습에서 사용하지 않기 때문에 pass 했습니다.

 

 

 

2.2 target 변수의 이상치 제거

plt.figure(figsize=(15,10))
sns.boxplot(x='hour',y='count', data=train)
plt.show()

 

target 변수 분포에서 가장 특징점은 출퇴근 시간에서 수요가 급증한다는 점이다. 해당 특징을 잘 살릴수록 성능에 큰 도움이 될거라 예상됩니다.

  • count의 퇴근 시간대 분포를 보면 이상치가 존재한다. 이는 휴일,주말에 측정한 것으로 추측되므로 이를 제거한다
  • 2017년 5월 휴일은 석가탄신일, 어린이날, 19대 선거 3일이다. 시간별 count 이상치 갯수와 비슷하다
  • 휴일 변수는 test의 count값이 없기 때문에 만들지 못한다.
#18시
train[train['hour']==18]['count']<100  # 19,1035,1113
# #19시
train[train['hour']==19]['count']<50  # 110, 306, 713

위 코드 실행 결과, 옆에 주석으로 아웃라이어 인덱스를 표시해 뒀습니다. 100과 50은 상자그림을 통해서 간단하게 설정한 겁니다. 해당 인덱스를 제거하도록 하겠습니다.

del_index = [19,1035,1113,110,306,713]
df.drop(del_index, axis=0, inplace =True)

 

 

3. CV 기반의 스태킹

#보류 -> 그리드 서치를 통해 결정하기
#개별 model 생성
knn=KNeighborsRegressor(n_jobs = -1)
rf = RandomForestRegressor(n_jobs = -1, random_state=2021)
dt = DecisionTreeRegressor(random_state=2021)
xgb = XGBRegressor(verbosity = 0, random_state=2021)
ada = AdaBoostRegressor(random_state=2021)
ridge=lm.Ridge()
lasso=lm.Lasso()
final = lm.Ridge()
lgb_reg=lgb.LGBMRegressor()

 

def print_best_params(model, params):
  best_model, best_score = None, float('inf')
  grid_model = GridSearchCV(model,param_grid=params,
                            scoring='neg_mean_squared_error',cv=5)
  grid_model.fit(X_train,Y_train)
  # predictions = grid_model.predict(X_test)
  # score = evaluate(Y_test, predictions)['mse'][0]
  print("model name is {0},Grid best score:{1}, Grid best_params_:{2} ".format(model.__class__.__name__,grid_model.best_score_,grid_model.best_params_))

 

#param 설정
knn_params ={"n_neighbors": range(2,7)}
rf_params={"max_depth": range(2, 5),"min_samples_split": range(2, 5),"min_samples_leaf": range(2, 5), "n_estimators": [100,200,300]}
dt_params={"max_depth": range(2, 5),"min_samples_split": range(2, 5),"min_samples_leaf": range(2, 5)}
xgb_params={"gamma": uniform(0, 0.5).rvs(3),"max_depth": range(2, 7), "n_estimators": [100,200,300]}
ada_params={"n_estimators": [40,50,60]}
lgb_params={"gamma": uniform(0, 0.5).rvs(3),"max_depth": range(2, 7), "n_estimators": [100,200,300,400]}
Ridge_params={'alpha': [0.01, 0.1, 1.0, 10, 100],'fit_intercept': [True, False],'normalize': [True, False]}
lasso_params={'alpha': [0.1, 1.0, 10],'fit_intercept': [True, False],'normalize': [True, False]}

 

print_best_params(knn,knn_params)
print_best_params(rf,rf_params)
print_best_params(dt,dt_params)
print_best_params(ada,ada_params)
print_best_params(xgb,xgb_params)
print_best_params(lgb_reg,lgb_params)
print_best_params(ridge,Ridge_params)
print_best_params(lasso,lasso_params)
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}

 

knn=KNeighborsRegressor(n_jobs = -1)
rf = RandomForestRegressor(max_depth= 4, min_samples_leaf= 2, min_samples_split= 2,n_jobs = -1, random_state=2021)
dt = DecisionTreeRegressor(max_depth= 4, min_samples_leaf= 4, min_samples_split= 2,random_state=2021)
xgb = XGBRegressor(gamma= 0.25883224322873616, max_depth= 4, n_estimators= 100, verbosity = 0, random_state=2021)
ada = AdaBoostRegressor(n_estimators= 40,random_state=2021)
ridge=lm.Ridge(alpha= 0.01, fit_intercept= True, normalize= True)
lasso=lm.Lasso(alpha= 1.0, fit_intercept= True, normalize= False)

#최종 메타 모델
lgb_reg=lgb.LGBMRegressor(gamma= 0.08826298344672961, max_depth= 4)

 

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

 

x_train_n=x_train.values
y_train_n=y_train.values
test_n=test.values
n_fold=5

knn_train,knn_test = get_stacking_base_datasets(knn,x_train_n,y_train_n,test_n,n_fold)
rf_train,rf_test = get_stacking_base_datasets(rf,x_train_n,y_train_n,test_n,n_fold)
xgb_train,xgb_test = get_stacking_base_datasets(xgb,x_train_n,y_train_n,test_n,n_fold)
# df_train,df_test = get_stacking_base_datasets(df,x_train_n,y_train_n,test_n,n_fold)
ada_train,ada_test = get_stacking_base_datasets(ada,x_train_n,y_train_n,test_n,n_fold)
KNeighborsRegressor model 시작
RandomForestRegressor model 시작
XGBRegressor model 시작
AdaBoostRegressor model 시작

 

stack_final_x_train=np.concatenate((knn_train,rf_train,xgb_train,ada_train),axis=1)
stack_final_x_test=np.concatenate((knn_test,rf_test,xgb_test,ada_test), axis=1)

 

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 하여 최종 모델에서 다시 한번 학습과 예측을 실시합니다. 자세한 내용은 책을 통해 공부해 보시길 강추 드립니다. 해당 스태킹 결과는 내일이나 주중에 제출해 볼 계획입니다(제출 횟수 초과함)

 

728x90
728x90

지난 포스팅에 이어서 th가 2개 이상인 멀티에서는 Update가 어떻게 진행이 되는지 살펴보도록 합니다. 이해를 돕기 위해서 input data가 1개라는 가정하에 진행합니다.

눈에 띄는 점은 th가 2개 이기 때문에 따로 분리가 된다는 점이다. Loss까지 구할 수 있으며 이를 편미분 하게 된다면 아래처럼 계산이 된다.

이 값들을 이용하여 backward를 간편하게 구할수 있다. 빨간색이 backward 진행 상황을 나타낸다.

빨간색 backward 값을 받은 Z2-2은 th1과 th2 두 방향으로 보내줘야 하는데, 전달되는 값을 보면 초록색 부분이 각각 1이므로 -2(y-y^)값을 그대로 전달한다.

나머지 부분도 편미분을 전개하면 아래와 같이 정리할 수 있다.

만일 X가 한개가 아니라 m개라면 어떻게 표현할수 있을까? 아래 그림을 통해 알수 있다.

X가 m개 이므로 th도 m개 이다. th와 x를 곱해줘야 하므로 th벡터를 transpose해야한다. 구체적인 과정을 그림을 통해 알아보자.

X가 m개 까지 있을때(feature의 수 m개) 위쪽에서 진행했던 과정을 반복해 나가면 된다. 파란색 화살표 부분이 prediction 값이 될수 있으며, affine 부분에 해당한다.

빨간색 화살표는 모두 -2(y-y^)의 값을 동일하게 전달한다. 왜냐하면 편미분 값이 전부 1이기 때문이다. 자세한 내용은 아래를 참고하면 이해하기 쉽다.

동일하게 전달받은 값은 각각 th1 ~thm까지 각각 미분해주면 된다.

 

728x90

+ Recent posts