본문 바로가기

ML/프로젝트CNN

[keras] CNN분류 모델 만들기 4 - 기본 구조 잡기

데이터의 마지막 전처리 밑 기본 구조는 아래의 사이트를 활용했다

www.kaggle.com/alexanderlazarev/simple-keras-1d-cnn-features-split/notebook

 

Simple Keras 1D CNN + features split

Explore and run machine learning code with Kaggle Notebooks | Using data from Leaf Classification

www.kaggle.com

정확히는 잘 모르지만 분꽃 ? 붓꽃? 의 종류를 분류하는 모델이다.

 

예제 코드 분석 

위의 사이트에서 가져온 코드는 다음과 같다.

하나하나 분석하면서 알아보도록 한다.

import numpy as np
import pandas as pd
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import StratifiedShuffleSplit
from keras.models import Sequential
from keras.layers import Dense, Activation, Flatten, Convolution1D, Dropout
from keras.optimizers import SGD
from keras.utils import np_utils

train = pd.read_csv('../input/train.csv')
test = pd.read_csv('../input/test.csv')

def encode(train, test):
    label_encoder = LabelEncoder().fit(train.species)
    labels = label_encoder.transform(train.species)
    classes = list(label_encoder.classes_)

    train = train.drop(['species', 'id'], axis=1)
    test = test.drop('id', axis=1)

    return train, labels, test, classes

train, labels, test, classes = encode(train, test)

# standardize train features
scaler = StandardScaler().fit(train.values)
scaled_train = scaler.transform(train.values)

# split train data into train and validation
sss = StratifiedShuffleSplit(test_size=0.1, random_state=23)
for train_index, valid_index in sss.split(scaled_train, labels):
    X_train, X_valid = scaled_train[train_index], scaled_train[valid_index]
    y_train, y_valid = labels[train_index], labels[valid_index]
    

nb_features = 64 # number of features per features type (shape, texture, margin)   
nb_class = len(classes)

# reshape train data
X_train_r = np.zeros((len(X_train), nb_features, 3))
X_train_r[:, :, 0] = X_train[:, :nb_features]
X_train_r[:, :, 1] = X_train[:, nb_features:128]
X_train_r[:, :, 2] = X_train[:, 128:]

# reshape validation data
X_valid_r = np.zeros((len(X_valid), nb_features, 3))
X_valid_r[:, :, 0] = X_valid[:, :nb_features]
X_valid_r[:, :, 1] = X_valid[:, nb_features:128]
X_valid_r[:, :, 2] = X_valid[:, 128:]

# Keras model with one Convolution1D layer
# unfortunately more number of covnolutional layers, filters and filters lenght 
# don't give better accuracy
model = Sequential()
model.add(Convolution1D(nb_filter=512, filter_length=1, input_shape=(nb_features, 3)))
model.add(Activation('relu'))
model.add(Flatten())
model.add(Dropout(0.4))
model.add(Dense(2048, activation='relu'))
model.add(Dense(1024, activation='relu'))
model.add(Dense(nb_class))
model.add(Activation('softmax'))


y_train = np_utils.to_categorical(y_train, nb_class)
y_valid = np_utils.to_categorical(y_valid, nb_class)

sgd = SGD(lr=0.01, nesterov=True, decay=1e-6, momentum=0.9)
model.compile(loss='categorical_crossentropy',optimizer=sgd,metrics=['accuracy'])

nb_epoch = 15
model.fit(X_train_r, y_train, nb_epoch=nb_epoch, validation_data=(X_valid_r, y_valid), batch_size=16)

 

전처리

전처리를 정말 많이하죠 하하 

날것의 데이터를 가져와서 그렇고 만약 잘 정돈된 데이터를 가지고 실험하신다면 여기서부터 하셔도 될것입니다.

저는 사실 전처리단계에 많이 헤매고 시간도 모델보다는 전처리에 많이 들였다고 생각하는데 

실제로 아주 중요한 과정입니다. 

실제에 프로젝트를 진행할 때는 데이터를 구하기는 매우 어렵고 잘 정돈된 데이터를 구하기란 거의 불가능하죠.. (졸업프로젝트가 걱정이네요)

 

필요한 라이브러리 & 파일 불러오기
#라이브러리 임포트
import numpy as np
import pandas as pd
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import StandardScaler
from sklearn.model_selection import StratifiedShuffleSplit
from keras.models import Sequential
from keras.layers import Dense, Activation, Flatten, Convolution1D, Dropout
from keras.optimizers import SGD
from keras.utils import np_utils


#파일불러오기 
train = pd.read_csv('../input/train.csv')
test = pd.read_csv('../input/test.csv')

미리 나누어둔 train set / test set을 따로 불러온다

 

 

전처리 1 : 문자의 수치화
#전처리1

# 문자의 수치화 + label부분 drop(데이터만 남기기)
def encode(train, test):
    label_encoder = LabelEncoder().fit(train.species) 
    labels = label_encoder.transform(train.species)
    classes = list(label_encoder.classes_)

    train = train.drop(['species', 'id'], axis=1)
    test = test.drop('id', axis=1)

    return train, labels, test, classes


#encode함수 적용 리턴값 차례대로 저장 
train, labels, test, classes = encode(train, test)

 

먼저 LabelEncoder()에 대해 알아보자

LabelEncoder는 sklearn.preprocessing 안에 있는 모듈으로, 문자의 수치화를 지원한다. 

딥러닝을 진행할 때 문자로 분류된 내용을 학습에 사용하기란 매우 어렵습니다. 

 

위의 코드에서 사용하는 데이터를 가지고 있는 것이 아니라 정확히 파악할 수는 없지만 예를 들어보면

아래와 같은 데이터를 가지고 있다고 합시다.

이 데이터를 동물에 대한 데이터라고 하고,

species에는 cat , dog, rabbit이 존재한다고하면, 딥러닝에서는 이를 수치화하는 것이 학습에 훨씬 도움이 된다. 

cat : 0 , dog : 1, rabbit : 2 이런식으로 숫자로 변경해주는 기능을 해준다. 

 

사용법은 다음과 같다. 

#라벨 인코더 생성하기
encoder = LabelEncoder()


#데이터 피팅하기
encoder.fit(피팅을 원하는 데이터)

#수치화하기
저장할 새로운 변수명 = encoder.transform(피팅한 데이터)

여기에 추가적으로 문자 데이터를 남겨주어야 하므로 classes_라는 것을 사용하는데 라벨의 클래스를 남겨준다

예를 들어, 

이렇게 있을때 classes_를 이용하게되면 dog/cat/rabbit으로 중복을 제거하고 클래스로 분류해줍니다. 

클래스를 저장할변수 = list(label_encoder.classes_)

 

 

.drop

그리고 나서 학습에 넣을 데이터 input (x)값 자체에는 label이 포함되어서는 안되므로 label부분을 drop하여 제거해줍니다.

test set은 정답을 제대로 체크하는지 확인해야 하기 때문에 id부분(label , y에 해당하는 부분)을 drop하지 않습니다.

 

 

전처리2 : 스케일링 
# standardize train features
scaler = StandardScaler().fit(train.values)
scaled_train = scaler.transform(train.values)

 

스케일링 : 모든 자료에 선형 변환을 적용하여 전체 자료의 분포를 평균0 , 분산은 1이 되도록 만드는 과정이다.

→데이터의 값이 너무 크거나 작으면 모델 학습 과정에서 0이나 무한대로 수렴/발산 할 확률이 높기 때문

why? 자료의 overflow , underflow를 방지하고, 최적화 과정에서 안정성과 수렴속도를 향상시킨다.

 

마찬가지로 데이터를 fit한 후 transform을 이용해 실제로 적용한다.

 

스케일링방법에는 여러가지가 있는데 여기서는 StandardScalar를 사용했다

더보기
구분 설명
StandardScalar 각 feature의 평균을 0, 분산을 1로 변경. 모든 feature이 같은 스케일을 가짐
RobustScalar 모든 feature이 같은 스케일을 가짐. 평균,분산이 아닌 median, quartile을 사용
MinMaxScalar 모든 feature이 0,1사이에 위치하게 만듬
Normalizer 각 column의 통계치가 아닌 row를 이용해서 정규화, 유클리드 거리가 1이 되도록 데이터를 조정 

* Normalizer를 제외한 나머지는 column의 통계치를 이용

 

어떤 상황에 무슨 스케일러를 사용해야하나?

mkjjo.github.io/python/2019/01/10/scaler.html

 

[Python] 어떤 스케일러를 쓸 것인가?

* 본 포스트는 개인연구/학습 기록 용도로 작성되고 있습니다. By MK on January 10, 2019 데이터를 모델링하기 전에는 반드시 스케일링 과정을 거쳐야 한다. 스케일링을 통해 다차원의 값들을 비교 분�

mkjjo.github.io

 

 

 

Validation set 나누기
# split train data into train and validation
sss = StratifiedShuffleSplit(test_size=0.1, random_state=23)
for train_index, valid_index in sss.split(scaled_train, labels):
    X_train, X_valid = scaled_train[train_index], scaled_train[valid_index]
    y_train, y_valid = labels[train_index], labels[valid_index]

 

우리는 전 게시글에서 train/validation/test를 모두 미리 나누어주었지만... 그것은 우리 데이터 특성상 고루 섞이기 어려웠기 때문이고,

보통은 train/test만 나누어준 후 이렇게 StratifiedShuffleSplit()를 이용하여 

train set을 진짜 학습에 이용할 train set과 검증에 이용할 validation set으로 나누게 된다. 

 

StratifiedShuffleSplit의 파라미터

  • test_size : validation set의 비율을 의미합니다. 0.1이면, train으로 넣은 데이터에서 10%를 validation set으로 사용하겠다는 의미
  • random_state : random sampling(무작위 추출법)을 위한 난수 초기값이라는데... 정확히 어떻게 쓰이는지는 모르겠네요
for train_index, valid_index in sss.split(scaled_train, labels):
    X_train, X_valid = train의 X값을 저장한 변수명[train_index], train의 X값을 저장한 변수명[valid_index]
    y_train, y_valid = train의 Y값(label)을 저장한 변수명[train_index], train의 Y값(label)을 저장한 변수명[valid_index]

이런식으로 사용해주시면됩니다. 

 

 

Reshape

 

nb_features = 64 # 열의 수(데이터 values 열 수)
nb_class = len(classes) # 클래스의 수 

# reshape train data
X_train_r = np.zeros((len(X_train), nb_features, 3))
X_train_r[:, :, 0] = X_train[:, :nb_features] #0~63 (64개씩)
X_train_r[:, :, 1] = X_train[:, nb_features:128] #64~127
X_train_r[:, :, 2] = X_train[:, 128:] #128~끝 

# reshape validation data
X_valid_r = np.zeros((len(X_valid), nb_features, 3))
X_valid_r[:, :, 0] = X_valid[:, :nb_features]
X_valid_r[:, :, 1] = X_valid[:, nb_features:128]
X_valid_r[:, :, 2] = X_valid[:, 128:]

 

 

전 게시글에서 일단 x,y,z를 한 행으로 합치고 reshape을 진행한다고 하는 것을 보셨을탠데 

바로 이게 reshape단계입니다.

이런식으로 나누어서 모양을 변경하여 n개의 데이터를 만드는 것입니다

우리의 목적은 위와 같은 그림인데 코드는 조금 다릅니다.

 

일단 알아보기 전에 shape를 한번 살펴보고 넘어갑시다.

 

#초기화
X_train_r = np.zeros((len(X_train), nb_features, 3))

#데이터 넣기
X_train_r[:, :, 0] = X_train[:, :nb_features] #0~63 (64개씩)
X_train_r[:, :, 1] = X_train[:, nb_features:128] #64~127
X_train_r[:, :, 2] = X_train[:, 128:] #128~끝 

이 코드를 보시면 열 부분에 64개(nb_features)의 데이터를 넣은 모습을 볼 수 있고

초기화 부분에서도 64행, 3열을 행의 수(데이터의 갯수)만큼 만들었네요

이를 그림으로 이해하자면 다음과 같을 것입니다.

n = len(X_train) 이 될 것입니다.

음.. 왜 굳이 열로 했는지에 대해서는 저도 잘 모르겠네요.. 무언가 차이가 있을까요? 

실제로 실험해보면 참 좋겠지만.. 시간상 그것은 넘어갔습니다.

 

여기까지 전처리를 마쳤습니다. 

이렇게 되면 이제 드디어 실제 모델의 기본 구조를 확인해 볼 수 있겠네요

 

기본 구조 파악하기

케라스에서는 add함수를 통해 간단하게 CNN 모델을 제작할 수 있게 여러가지 layer함수를 제공합니다.

처음부터 자기가 설계할 수도 있지만 저희는 처음이고.. 보통 어느정도로 틀이 잡혀있는 상태로 시작한다고 하네요

# Keras model with one Convolution1D layer
# unfortunately more number of covnolutional layers, filters and filters lenght 
# don't give better accuracy
model = Sequential()
model.add(Convolution1D(nb_filter=512, filter_length=1, input_shape=(nb_features, 3)))
model.add(Activation('relu'))
model.add(Flatten())
model.add(Dropout(0.4))
model.add(Dense(2048, activation='relu'))
model.add(Dense(1024, activation='relu'))
model.add(Dense(nb_class))
model.add(Activation('softmax'))


y_train = np_utils.to_categorical(y_train, nb_class)
y_valid = np_utils.to_categorical(y_valid, nb_class)

sgd = SGD(lr=0.01, nesterov=True, decay=1e-6, momentum=0.9)
model.compile(loss='categorical_crossentropy',optimizer=sgd,metrics=['accuracy'])

nb_epoch = 15
model.fit(X_train_r, y_train, nb_epoch=nb_epoch, validation_data=(X_valid_r, y_valid), batch_size=16)

 

전체적인 모델 구조를 보면 이러하다

 

이 각각의 층들이 뭔지 아래부분에서 설명할 것인데

conv1d를 사용했지만 이를 이해하기 위해서는 conv2d또한 이해하는 것이 좋으므로 

conv2d와 conv1d에 대해 먼저 설명한다 

 

 

Conv1d vs Conv2d

* 열심히 찾아보긴 했는데 이해가 조금 덜된다

텐서플로우 홈페이지 tf.nn.conv2d와 tf.nn.conv1d를 참고했음

 

가장 크게 다른점은 input 인데 

일단 Conv1d는 텐서가 3이상인 input이 필요하고, Conv2d는 텐서가 4이상인 input을 다룬다.

텐서 + 채널 에 대한 설명은 아래 링크를 참조

2020/07/18 - [ML] - [딥러닝의 정석]03. 텐서플로로 신경망 구현하기

 

근데 사실 더 직관적으로 볼 수 있는 것은 바로 input shape이다

 

채널과 배치사이즈를 혼동하기 쉽다

채널은 엄연하게 하나의 데이터(?)덩어리에 포함된 것이다.

데이터 하나를 이미지라고 하면 흑백이미지의 경우 채널이 1개이고, 

컬러 이미지의 경우 RGB 3겹이 필요하므로 하나의 이미지당 채널이 3개가 되는 것 이지 

빨간 이미지(R)  ,초록이미지(G), 파란이미지(B) 3개가 각각 다른 데이터 인 것은 아닌 것과 같다 

이렇게 conv 1d와 conv2d는 데이터의 형식이 다르다.

 

위의 keras예제는 서로 다른 3개의 데이터가 각각 64열을 가지며, 이를 하나의 데이터 덩어리로 생각하여 학습시킨다.

conv2d와 conv1d의 형식을 모두 사용할 수 있는데, 둘을 비교하면 다음과 같다.

채널을 1개로하고 3개를 이어붙여 h=3인, conv2d로 사용할 것인지

채널을3개로 하고 conv1d를 사용할 것인지(conv1d 는 h가 없으므로 채널을 늘려서 하나의 데이터로만듬)

 

 

그렇다면 keras코드에서의 conv2d와 conv1d가 어떤식으로 동작하는지 살펴보자. 

 

Conv2d

 

 Convolution2D(filters=필터 수, kernel_size=필터크기, padding='same/valid',stride=?, input_shape=(H,W,C))

input_shape에서 주로 batch_size부분은 생략해서 None으로 들어간다

 

그림을 설명하면 다음과 같다.

다음 형광팬(?) 을 같은색으로 칠한 것 끼리 같은 크기를 가진다.

일단 batch_size는 데이터의 갯수와 비슷한 개념이므로 input과 output이 동일하다

 

 

h, w는 padding에 따라 달라지는데, 

원래대로라면 (padding="valid") 입력한 kernel size크기의 필터를 stride의 크기만큼 이동시키며, 나온 값이 맞지만

편의를 위하여 zero padding등을 사용해 output과 input의 h,w를 동일하게 맞추어주는 것이 대부분이다. 

padding = "same"을 이용하면 제로패딩을 이용하여 h,w를 동일하게 맞추어준다. 

=> 이 부분에 대해서 자세한 내용은 아래 링크를 참조

2020/08/02 - [ML] - [딥러닝의 정석] 05. 합성곱 신경망-1

 

filter의 kernel size는 당연히 input데이터의 크기보다 커질수 없으며,

filter는 kernel_size(filter의 h,w), filter의 수, filter의 채널로 구성되는 데,

이때, filter의 channel(depth)는 input데이터의 channel과 같아야 한다. (그래야 하나의 필터에 대해 depth = 1의 값이 나오므로)

이렇게 하나의 필터를 한번 데이터에 돌리면 1개의 depth가 생성되고 이를 필터 갯수만큼 반복하므로

output data의 channel은 필터의 수와 동일해진다 

 

 

 

 Conv1d

2d와 크게 다른 점은 없고 input모양에 h가 존재하지 않는 다는 것에서 다르다.

 Convolution1D(filters=필터 수, kernel_size=필터크기, padding='same/valid',stride=?, input_shape=(W,C))

input_shape에서 주로 batch_size부분은 생략해서 None으로 들어간다

 

conv2d에서 filter와 input/ output전부의 h=1이라고 생각하면될 것 같다.

기본적으로 conv1d가 계산을 적게 수행하므로 시간이 더 적게 소요된다.

 

 

기타 나머지 layer들

 

Activation : 활성화 계층으로 활성화 함수를 사용하여 예측되는 y(y-hat)을 계산해낸다.

relu , softmax등과 같은 함수를 사용한다.

 

flatten : n개의 channel(depth)로 구성된 층(channel = 1, h = 1)을 하나로 펼치는 역할을 한다

conv2d의 경우에는 1 x (h * w * c)로

conv1d의 경우는 1 x (w * c) 의 크기로 한층으로 펴지게 된다 .

 

 

Dropout : 이에 대한 설명은

2020/07/12 - [ML] - [딥러닝의 정석]02. 전방향 신경망 학습

하단 부분에 설명이 되어있다. 

 

Dense : dense층은 그냥 여태까지 쭉 봐왔던 fully connected된 계층이다

그림상 모든 계층이 다 연결되어있지는 않지만 모두다 그리기 어려우니까 생략한 것이고 모든 층이 모두 위아래로 서로서로 연결된 계층을 말한다. 

 

 

 

 

이렇게 모든 layer층에 대해서 설명했다. 

이제 이를 기반으로 하면 코드가 어떤식으로 동작하는 지 대충 감이 왔길 바란다

# Keras model with one Convolution1D layer
# unfortunately more number of covnolutional layers, filters and filters lenght 
# don't give better accuracy
model = Sequential()
model.add(Convolution1D(nb_filter=512, filter_length=1, input_shape=(nb_features, 3)))
model.add(Activation('relu'))
model.add(Flatten())
model.add(Dropout(0.4))
model.add(Dense(2048, activation='relu'))
model.add(Dense(1024, activation='relu'))
model.add(Dense(nb_class))
model.add(Activation('softmax'))


y_train = np_utils.to_categorical(y_train, nb_class)
y_valid = np_utils.to_categorical(y_valid, nb_class)

sgd = SGD(lr=0.01, nesterov=True, decay=1e-6, momentum=0.9)
model.compile(loss='categorical_crossentropy',optimizer=sgd,metrics=['accuracy'])

nb_epoch = 15
model.fit(X_train_r, y_train, nb_epoch=nb_epoch, validation_data=(X_valid_r, y_valid), batch_size=16)

 

밑부분은 sgd방식으로 실행하는 것이므로 따로 설명하지 않기로 한다.

 

모델 평가하기

 

test set을 이용하여 모델 평가하기
# 모델 평가
print('\n-- Evalutate --')
scores = model.evaluate(X_test_r, test_label, verbose=1)

print("%s: %.2f%%" % (model.metrics_names[1], scores[1] * 100))

 

결과를 그래프로 저장하기

그리고 추가적으로 이런 모델의 결과를 그래프와 h5모델로 저장해주는 코드이다.

import matplotlib.pyplot as plt

# 모델 저장
model.save('/모델명.h5')

# 학습과정 그래프
fig, loss_ax = plt.subplots()

acc_ax = loss_ax.twinx()

loss_ax.plot(mhistory.history['loss'], 'y', label='train loss')
loss_ax.plot(mhistory.history['val_loss'], 'r', label='val loss')
loss_ax.set_ylim([-0.2, 1.2])

acc_ax.plot(mhistory.history['accuracy'], 'b', label='train acc')
acc_ax.plot(mhistory.history['val_accuracy'], 'g', label='val acc')
acc_ax.set_ylim([-0.2, 1.2])

loss_ax.set_xlabel('epoch')
loss_ax.set_ylabel('loss')
acc_ax.set_ylabel('accuracy')

loss_ax.legend(loc='upper left')
acc_ax.legend(loc='lower left')

fig = plt.gcf()
plt.show()
fig.savefig('/그래프 이미지 명.png', bbox_inches='tight')

 

 

 

이제 여기까지 틀이되는 코드를 이해했다면,

변인을 통제하면서 자기 코드에 맞게 하나하나 실험해보는 것만 남았다.

다음에는 나의 최종 코드와 어떤 순서로 변인을 통제해야 하는지를 알아보겠다

-> 사실상 이 부분부터 시작이긴 하지만 데이터마다 다르므로 큰 참고할만한 거리가 없음... + 저의 실험은 딱히 잘된 편이 아니기에..

 

 

<참고자료>

StratifiedShuffleSplit참고 

rfriend.tistory.com/520

 

[Python] 층화 무작위 추출을 통한 train set, test set 분할 (Train, Test set Split by Stratified Random Sampling in Py

지난번 포스팅에서는 무작위로 데이터셋을 추출하여 train set, test set을 분할(Train set, Test set split by Random Sampling)하는 방법을 소개하였습니다. 이번 포스팅에서는 데이터셋 내 층(stratum) 의 비율..

rfriend.tistory.com

www.tensorflow.org/api_docs/python/tf/nn/conv2d

 

tf.nn.conv2d  |  TensorFlow Core v2.3.0

Computes a 2-D convolution given input and 4-D filters tensors.

www.tensorflow.org

ramees.tistory.com/52

 

[Algorithm] 슬라이딩 윈도우(Sliding Window) 알고리즘

슬라이딩 윈도우 알고리즘 슬라이딩 윈도우 알고리즘은 윈도우 즉 일정한 범위를 가지고 있는 것을 유지하면서 이것을 이동(sliding) 하는 것이다. 예를들어 2가지 긴 문자열이 주어지고 알파벳 2�

ramees.tistory.com

tykimos.github.io/2017/01/27/CNN_Layer_Talk/

 

컨볼루션 신경망 레이어 이야기

이번 강좌에서는 컨볼루션 신경망 모델에서 주로 사용되는 컨볼루션(Convolution) 레이어, 맥스풀링(Max Pooling) 레이어, 플래튼(Flatten) 레이어에 대해서 알아보겠습니다. 각 레이어별로 레이어 구성 �

tykimos.github.io

towardsdatascience.com/understanding-input-and-output-shapes-in-convolution-network-keras-f143923d56ca

 

728x90