시작은 미약하였으나 , 그 끝은 창대하리라

[CNN] Conv1D 커널(필터) 작동 방식 설명 (시계열 데이터 비교) 본문

인공지능/딥러닝 사이드 Project

[CNN] Conv1D 커널(필터) 작동 방식 설명 (시계열 데이터 비교)

애플파ol 2022. 9. 2. 11:28

 

(들아가기전에->

예시코드를 이용해 시계열 데이터를 비교하며, 커널(필터)이 어떻게 작동되는지 써놓은 글 입니다.) 

 

(많은 예시와 코드들이 있지만 시계열 데이터 와 아닌 것에는 엄연히 코드의 차이가 있다(데이터 가공하는 부분에 있어서 다름 ))

 

 

<시계열 데이터X> 

1. 모듈 불러옴.

import numpy as np
import pandas as pd
import tensorflow as tf

from keras.models import Sequential
from keras.layers import Dense,Conv1D,Flatten
from tensorflow import keras
from sklearn.datasets import load_iris

2.seed 설정(하이퍼 파라미터 바꿀시 다른 값들 안바뀌도록)

##seed 설정.

seed=42
tf.random.set_seed(seed)
np.random.seed(seed)

3. 데이터 추출(보스턴 집 값 예측).

boston_housing = tf.keras.datasets.boston_housing

(train_data, train_labels), (test_data, test_labels) = boston_housing.load_data()

print("Training set: {}".format(train_data.shape))  # 404 examples, 13 features
print("Testing set:  {}".format(test_data.shape))   # 102 examples, 13 features

데이터의 모습

4.데이터 정규화

mean = train_data.mean(axis=0)
std = train_data.std(axis=0)
train_data = (train_data - mean) / std
test_data = (test_data - mean) / std

 

5.데이터 가공. (conv1d의 input은 3차원이어야함으로.)

 -> 가공형식=(batch_size, time_steps, input_dimension) , time_steps=feature개수 , input_dimension=1

                     (input_dimension=1 인 이유= 각각의 행을 묶어주기 위해서.

 

sample_size = train_data.shape[0]  # number of samples in train set
time_steps  = train_data.shape[1]  # number of features in train set
input_dimension = 1                # each feature is represented by 1 number


#Train data 3차원 가공.
train_data_reshaped = train_data.reshape(sample_size,time_steps,input_dimension)
train_data_reshaped.shape   # (404,13,1) =(batch_size, time_steps, input_dimension)



# Test data 3차원 가공.
test_data_reshaped = test_data.reshape(test_data.shape[0],test_data.shape[1],input_dimension)
test_data_reshaped.shape   #(102,13,1)

6. 모델 설계.

n_features = train_data_reshaped.shape[1] #13
n_input_dimension  = train_data_reshaped.shape[2] #1 

model = Sequential()

model.add(keras.layers.Conv1D(filters=64, kernel_size=7, 
                              input_shape=(n_features,n_input_dimension),
                              activation='relu'))
model.add(keras.layers.Dropout(0.5))
model.add(keras.layers.Conv1D(filters=32, kernel_size=3, activation='relu' ))

model.add(keras.layers.Conv1D(filters=16, kernel_size=2, activation='relu'))

model.add(keras.layers.MaxPooling1D(pool_size=2, ))
model.add(keras.layers.Flatten())
model.add(keras.layers.Dense(32, activation='relu'))
model.add(keras.layers.Dense(1))
model.summary()

커널(필터가) 작동방법:

1. 한개의 데이터에 13개의 feature가 존재

2. 필터(커널)사이즈에 맞게 돌아감

3. strides=1 임으로 한칸씩 가면서 특징맵(output한칸씩)을 추출함.

 

주의) 1. 커널은 feature의 개수보다 클 수가 없다.(여기서는 최대 13까지 가능함.)

https://colab.research.google.com/github/kmkarakaya/ML_tutorials/blob/master/Conv1d_Predict_house_prices.ipynb#scrollTo=UuyXqm-GZtJ-

   

                                    

                   

 

<시계열 데이터 일때 > 

(상당히 어려웠다.. 진짜 conv1d 와 동시에 시계열 데이터를 찾아내야하는게 어려웠다.....)

 

 

(이 글이 상당히 도움이 되었다. 감사해요 글써 주신 분...)

https://blog.goodaudience.com/introduction-to-1d-convolutional-neural-networks-in-keras-for-time-sequences-3a7ff801a2cf

(윗 그림에서 말하는 x,y,z는 feature라고 생각하면 된다.)

https://www.macnica.co.jp/business/ai_iot/columns/135112/

 (윗 그림에서 오해 하기 쉬운부분이 있다 -> 윗그림은 시간을 4일치로 묶고 커널 사이즈를 4로 설정한것이다.) 

 

1. 모듈 import

import pandas as pd                                         
import numpy as np   
import tensorflow as tf

from keras.layers import Conv1D, AveragePooling1D,Dense, Dropout , MaxPooling1D ,LSTM,InputLayer,Flatten,Reshape
from keras.models import Sequential
from keras.callbacks import EarlyStopping, ModelCheckpoint
from sklearn.model_selection import train_test_split

2. seed설정 (안하면 실행할 때 마다 결과값 바뀜..설정 안하면 하이퍼파라미터 못찾습니다..)

#seed설정.
seed=42
tf.random.set_seed(seed)
np.random.seed(seed)

3. 엑셀 데이터 불러오기 (랩실과제라서 이것저것 수정하기 귀찮아서 생략..)

 

4. 데이터셋을 만드는 함수.

# 데이터 셋을 만드는 함수
def make_dataset(data, label, window_size):
    feature_list = []
    label_list = []
    for i in range(len(data) - window_size):                          
        feature_list.append(np.array(data.iloc[i:i+window_size]))      # np.array=values= pd dataframe을 np형식으로 바꿔줌.
        label_list.append(np.array(label.iloc[i+window_size]))
    return np.array(feature_list), np.array(label_list)

https://put-idea.tistory.com/44 를 클릭하면 데이터셋(window_size)를 만드는 방법을 자세하게 설명해 두었습니다.

DODI_C_20_5의 데이터 모습

# DODI_C_20_5 = Train DATA 

#### ' DODI_C_20_5 ' = train DATA

DODI_C_20_5=np.array(DODI_C_20_5)   #슬라이싱을 하기위해 np.array 형식으로 바꿈.

DODI_C_20_5_feature=pd.DataFrame(DODI_C_20_5[:,1])    # feature 열 추출
DODI_C_20_5_label=pd.DataFrame(DODI_C_20_5[:,0])      # label 열 추출


DODI_C_20_5_feature, DODI_C_20_5_label=make_dataset(DODI_C_20_5_feature,DODI_C_20_5_label,5)  #window_size=5 설정

x_train, x_valid, y_train, y_valid = train_test_split(DODI_C_20_5_feature,DODI_C_20_5_label, test_size=0.2 , random_state=seed)  
x_train.shape, x_valid.shape,   # ->(2981, 5, 1), (746, 5, 1)

# DODI_C_20_6 = Test DATA 

#### ' DODI_C_20_6 ' = TEST DATA


DODI_C_20_6=np.array(DODI_C_20_6)  #슬라이싱을 하기위해 np.array 형식으로 바꿈.

DODI_C_20_6_feature = pd.DataFrame(DODI_C_20_6[:,1])
DODI_C_20_6_label = pd.DataFrame(DODI_C_20_6[:,0])

DODI_C_20_6_feature.shape 
test_feature, test_label = make_dataset(DODI_C_20_6_feature, DODI_C_20_6_label, 5)


test_feature.shape, test_label.shape  # (3727, 5, 1), (3727, 1)

 

그렇다면 여기서 만들어진 window_size는 LSTM에서와 어떻게 다르게 작동하는것일까라는 궁금함이 생길것이다.

 

# window_size함수를 적용하면 아래 사진과 같이 짝이 지어지는 모습이다.

 

window_size 함수 적용시 짝 지어지는 모습.

# 지정한 window_size에 맞게 리스트화 된 모습(아래사진).

 

 

X_train의 모습

 

 

그렇다면 여기서 만들어진 window_size는 LSTM에서와 어떻게 다르게 작동하는것일까라는 궁금함이 생길것이다.

 

아래와 같이 커널이 window_size에서 돌아가게 된다. 즉 5일치의 값들을 보며 커널이 돌아가며 특징들을 추출하는것이다!!(생겨난 빈네모들= 특징추출된것임, 특징맵 총 2개 =주황색열,초록색열 )

5. 모델 설계.

model = Sequential()
                
model.add(Conv1D(64,3,strides=1,
               input_shape=(DODI_C_20_5_feature.shape[1], DODI_C_20_5_feature.shape[2]),   #input shape=(time_step=windowsize,featrue개수)
               activation='relu', 
               )
          )
model.add(AveragePooling1D(pool_size=2,strides=1))  #strides 설정 안하면 pool_size랑 동일한 strides 로 잡힘.
model.add(Dropout(0.5)) 
model.add(Dense(128, activation='relu')) 
 
model.add(Flatten())              # dense layer에 맞춰야 함으로 flatten사용
model.add(Dense(64, activation='relu'))  
model.add(Dropout(0.5))                 #  drop out 50% 함
model.add(Dense(1))  
model.summary()

layer설명. conv1D : 커널(필터)개수 =64개 , 커널 사이즈=3  , 커널이 몇칸씩 이동=strides )AveragePooling1D : 위에서 출력된 주황색3칸,초록색 3칸 에서 pool_size=2가 내려(stride만큼)가면서 평균값을 뽑아냄

                                  (※ stride 설정안하면 pool_size로 자동 설정됨 ->  케라스 공식 링크: https://keras.io/api/layers/pooling_layers/)

Dropout  :과접합 방지를 위해 노드를 임의로 꺼줌

6. 모델 컴파일.

model.compile(loss='mse',optimizer='adam',metrics="accuracy")
early_stop = EarlyStopping(monitor='val_loss', patience=100)

model.fit(x_train,y_train,validation_data=(x_valid, y_valid), callbacks=[early_stop],epochs=1000,batch_size=100)

 

7. 그래프 

import matplotlib.pyplot as plt

pred=model.predict(test_feature)

plt.figure(figsize=(12, 9))
plt.plot(test_label, label = 'actual')
plt.plot(pred, label = 'prediction')
plt.legend()
plt.show()

 

(시계열 데이터에서 사용할 CNN과 LSTM을 공부해봣는데.... 어려운데 재밋다... )

(음 근데 CNN이랑 LSTM을 썻을때 그닥 그래프의 큰차이가 없네....CNN+LSTM으로 구현을 해봐야겟다...)

 

 

햇갈릴 수 있는 부분 정리: kernal_size는 n일치로 묶은거보다 같거나 작아야 한다.(n일치들의 관계를 보며 특징을

                                          찾아내기 때문)

 

<결론>

시계열 데이터 일 때) input_shape = (batch_size, time_steps, input_dimension) ,

                                                               = (batch_size, feature개수, 1)   

 

시계열 데이터 아닐 때)  input_shape = (batch_size, time_steps, feature개수) ,

                                                                    = (height, time_steps, feature개수)   

Comments