AI

CNN(Convolutional Neural Network) - 합성곱 신경망

Ohs_ 2021. 1. 24. 16:41

CNN(Convolutional Neural Network)

CNN은 이미지 처리에 효과적인 딥러닝 신경망 클래스이다. 스스로 글자의 특징을 추출하고 학습하는 것은 머신러닝의 오랜 과제였다. 딥러닝은 스스로 데이터에서 특징을 추출할 수 있기 때문에 머신러닝보다 한 단계 진보한 기술로 평가된다.

CNN은 이미지를 마스크로 강조하여 예측, 분류한다.

4*4 이미지와 2*2 마스크

이미지에 마스크 값을 곱하여 이미지의 특정 부분을 강조한다. 즉, 행렬의 곱을 연산하여 0은 0으로 유지하고 0이 아닌 수는 증폭시키는 형태이다. 즉 위의 그림의 이미지와 마스크 연산을 하면 아래 그림과 같은 형태의 이미지 행렬이 나온다.

마스크 연산 후 결과

위와 같은 과정으로 이미지의 특정 부분들을 강조하는데, 마스크를 하나만 사용할 경우 원래 이미지와 크게 다른 상황이 생길 수 있다. 그래서 마스크의 개수를 여러개로 설정하여 사용하는데 대신 여러개 사용할 경우 추가로 생성되는 데이터의 양이 늘어나서 시간이 오래걸릴 수 있다.

이처럼 마스크 연산을 통해 새롭게 생겨난 층을 컨볼루션(합성곱)이라고 한다. 컨볼루션을 만들면 입력 데이터로 부터 더욱 정교한 특징을 추출할 수 있다. 마스크를 여러 개 사용하면 여러 개의 컨볼루션이 생성된다. 케라스에서는 Conv2D() 함수를 제공하여 해당 함수로 합성곱을 추가할 수 있다.

 

MNIST 데이터 셋

MNIST 데이터 셋은 미국 국립표준기술원(NIST)이 고등학생과 인구조사국 등이 쓴 손글씨로 만든 데이터이다. 70,000개의 이미지로 구성되어 있으며 0부터 9까지 이름표를 붙인 데이터 셋이다. CNN 딥러닝 모델의 정확도를 시험하기 위해 도전하는 유명한 데이터 중 하나이다. 이미지는 28 * 28의 픽셀로 구성되어 있다. MNIST 데이터 셋은 케라스(Keras)를 이용하면 간단히 불러올 수 있다. 총 70,000개의 데이터 중 60,000개는 학습용이고 10,000개는 테스트용으로 미리 구분되어있다.

 

Keras, Numpy, Tensorflow, matplotlib 설치

pip를 사용하여 TensorFlow 설치==> 텐서플로는 참고하여 설치

 

pip를 사용하여 TensorFlow 설치

TensorFlow 2 패키지 사용 가능 tensorflow - CPU와 GPU 지원이 포함된 안정적인 최신 출시(Ubuntu 및 Windows) tf-nightly - 미리보기 빌드(불안정). Ubuntu 및 Windows에는 GPU 지원이 포함되어 있습니다. 이전 버전의

www.tensorflow.org

pip install keras
pip install numpy
pip install matplotlib

 

실습 코드

from keras.datasets import mnist

import matplotlib.pyplot as plt

import numpy
import tensorflow as tf



seed = 0
numpy.random.seed(seed)
tf.random.set_seed(3)


(X_train, Y_class_train), (X_test, Y_class_test) = mnist.load_data()

print("train image : %d " % (X_train.shape[0]))
print("test image : %d " % (X_test.shape[0]))

plt.imshow(X_train[0], cmap='Greys')
plt.show()

 

Keras.datasets 에서 mnist 데이터 셋을 불러오고 데이터를 확인하기 위한 라이브러리를 불러온다. seed 값 설정은 모델 훈련 결과가 매번 달라지는 것을 막아주기 위해 사용한다. seed 값을 설정 안 했을 때 모델 학습 때마다 같은 조건에서도 정확도, 손실률이 변화한 경험이 있을 수 있다. 이러한 변화를 막고 모델 튜닝, 데이터 전처리, 후처리 등의 과정 후 모델의 변화를 확인하기 위해 랜덤하게 변하는 것이 아닌 값을 고정하여 변화를 확인한다.

mnist 데이터를 로드한 후 matplotlib로 데이터를 확인한다.

60,000개의 학습셋과 10,000개의 테스트셋
학습셋의 0번째에 들어있는 이미지

 

위의 이미지를 보면 5라고 쓴 것으로 볼 수 있다. 컴퓨터는 해당 이미지를 행렬로 인식한다. 이미지는 28 * 28로 784개의 픽셀로 구성되어 있다. 흑백으로 구성되어 있기 때문에 밝기 정도에 따라 0~255 사이의 값을 가진다. 0은 흰색이되고 255는 검정색이 된다. 해당 이미지를 컴퓨터가 바라보는 행렬로 출력해본다. 아래 코드를 이어서 작성한다.

import sys

for x in X_train[0]:
    for y in x:
        sys.stdout.write('%d\t' % y)
    sys.stdout.write('\n')
    

행렬로 출력한 이미지

이미지의 밝기에 따라 0~255로 표현되는 것을 볼 수 있다. 위의 데이터는 2차원 행렬로 표현되는 이미지와 1차원 행렬로 표현되는 이름표로 구성되어 있다. 딥러닝에 해당 데이터를 적용하기 위해 2차원 행렬을 1차원 행렬로 변환해야 하는데, 이 때, reshape() 함수를 사용할 수 있다. reshape(총 샘플 수, 1차원 속성의 수)이렇게 사용하면 되며 총 샘플 수는 X_train.shape[0]에 저장되어 있고, 1차원 속성의 수는 28*28=784이다.

케라스는 0과 1 사이의 값에서 최적의 성능을 나타낸다. 따라서 현재 0부터 255로 값으로 이루어진 데이터를 0과 1사이의 값으로 변환해야 한다. 간단하게 각 값을 255로 나누면 된다. 이처럼 데이터의 폭이 클 때, 일정한 규칙에 따라 분산의 정도를 바꾸는 과정을 데이터 정규화라고 한다. 아래 코드는 확인을 위해 작성했다.

X_train = X_train.reshape(X_train.shape[0], 28, 28, 1).astype('float64')/255

for x in X_train[0]:
    for y in x:
        sys.stdout.write('%0.1f\t' % y)
    sys.stdout.write('\n')

0과 1사이로 바뀐 것을 확인할 수 있다.

학습셋과 테스트셋을 정규화 해준다.

X_train = X_train.reshape(X_train.shape[0], 784).astype('float64')/255
X_test = X_test.reshape(X_test.shape[0], 784).astype('float64')/255

 

이제 각각의 데이터의 이름표를 벡터화 해준다. train 데이터 셋은 라벨링 데이터이다. 즉, 각각의 데이터에 정답인 이름이 붙어있는 형태인데 이 이름표를 컴퓨터가 알 수 있게 벡터화하여 표현해 줄 필요가 있다. 예를 들어 앞의 이미지인 5의 경우 벡터화하면 [0,0,0,0,0,1,0,0,0,0]로 표현할 수 있다. 다중 분류 문제의 경우 원 핫 코딩을 통해 0과 1로만 이루어진 벡터로 값을 수정해야 하기 때문에 다음 코드를 사용하여 0~9 까지의 숫자 이름표를 벡터화 해준다.

from keras.utils import np_utils

Y_train = np_utils.to_categorical (Y_class_train, 10)
Y_test = np_utils.to_categorical (Y_class_test, 10)

10개 데이터 확인 결과 라벨이 잘 붙은 것을 확인할 수 있다.

 

여기까지 학습을 위한 데이터 가공이 끝났다. 정규화, 전처리 등의 과정이 진행된 것으로 이제 해당 데이터를 학습시키고 테스트할 모델을 생성한다. 기본 프레임은 다음과 같이 작성한다.

from keras.models import Sequential
from keras.layers import Dense

model = Sequential()
model.add(Dense(512, input_dim=784, activation='relu'))
model.add(Dense(10, activation='softmax'))
model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])

 

Sequential()는 케라스에서 제공하는 레어를 선형으로 구성할 수 있는 모델이다. 속성은 총 784개이며, 0부터 9까지의 총 10개의 클래스가 있다. 따라서 모델의 입력층은 784개로 설정하고 출력할 결과는 10개 중 하나 이므로 마지막 출력층은 10으로 설정한다. 입력층에서는 512개의 뉴런과 relu 활성화 함수를 사용한다. 시그모이드는 0보다 작은 값이 들어오면 0을 반환하고 0보다 큰 값이 들어오면 1을 반환한다. relu의 경우 0보다 작은 값이 들어올 경우 0을 반환하고 0보다 큰 값이 들어올 때 그 값을 온전히 사용한다. 때문에 이미지의 값이 밝기에 따라 값이 나눠지기 때문에 relu를 사용한다. 출력층에서는 다중분류 문제에 효과적은 softmax 함수를 사용한다. softmax는 분류 해야되는 클래스가 x개라고 할 때 x개의 차원 벡터를 입력 받고 각 클래스에 대한 확률을 나타낸다.

모델 학습 과정에서 모델의 성과를 저장하고, 모델의 최적화 단계에서 학습을 자동으로 중단하게 설정할 수 있다. 다음 코드를 통해 가능하다.

from keras.callbacks import ModelCheckpoint, EarlyStopping

import os

MODEL_DIR = './model/'
if not os.path.exists(MODEL_DIR):
    os.mkdir(MODEL_DIR)

modelpath = "./model/{epoch:02d}-{val_loss:.4f}.hdf5"
checkpointer = ModelCheckpoint(filepath=modelpath, monitor='val_loss', verbose=1, save_best_only=True)
early_stopping_callback = EarlyStopping(monitor='val_loss', patience=10)

 

샘플 200개를 모두 30번 실행하게 설정한다 epoch=30으로 설정하고 모델의 정확도를 확인한다. fit은 모델의 학습 횟수와 학습 결과 갱신 등을 수행한다. Epochs는 전체 데이터를 몇 번 반복할 것인지를 설정하는 것이고 Batch_size는 몇 개의 샘플로 가중치를 갱신할 것인지를 지정한다.

history = model.fit(X_train, Y_train, validation_data=(X_test, Y_test), \
    epochs=30, batch_size=200, verbose=0, \
        callbacks=[early_stopping_callback, checkpointer])

print("\n Test Accuracy: %.4f" % (model.evaluate(X_test, Y_test)[1]))

학습 결과 정확도 98.3% 출력

 

실행 결과를 그래프로 표현하고 학습셋의 오차와 테스트 셋의 오차를 그래프로 확인한다.

24번째에서 정지된 학습 결과

여기까지 작성한 모델은 은닉층이 하나밖에 없는 단순한 모델이다. 은닉층을 개선하므로써 성능 향상을 기대할 수 있다. 하지만 아직 CNN을 사용하지 않았다. CNN은 케라스의 Conv2D() 함수로 사용할 수 있다. CNN을 적용하기 위한 코드는 아래와 같다.

 

from keras.layers import Dropout, Flatten, Conv2D, MaxPooling2D

...[..]...

model.add(Conv2D(32, kernel_size=(3, 3), input_shape=(28, 28, 1), activation='relu'))	#추가
model.add(Conv2D(64, (3, 3), activation='relu'))	#추가

model.add(Dense(512, input_dim=784, activation='relu'))
model.add(Dense(10, activation='softmax'))

...[..]...

기존에 생성한 층을 제거하고 CNN을 적용한 층을 추가한다. 층은 총 2개를 적용했다. Conv2D의 첫 번째 인자는 마스크를 몇 개 적용할 것인지를 결정한다. 32로 설정했으니 32개의 마스크가 적용되며 각 이미지에 대해 32개의 마스크가 적용된 합성곱이 32개가 나올 것이다. kernel_size는 마스크의 크기를 결정한다. 위 코드에서는 3*3 행렬의 마스크를 사용한다. 마스크의 크기는 작으면 작을 수록 강조되는 부분이 짙어진다. 하지만 그 만큼 연산의 양이 늘어나게 되므로 이미지의 픽셀이 클경우 마스크의 크기도 어느 정도 크게 설정하는게 학습 속도가 빠르다. input_shape는 (행, 열, 색상)을 각각 인자로 받으며 전처리 과정을 통해 입력값은 현재 1차원 행렬로 정렬되어 있는데, 합성곱을 적용하기 위해서는 이를 다시 원본으로 구성해야 한다. 그래서 원래 이미지는 28*28 픽셀이고 흑백이기 때문에 (28, 28, 1)을 해준다. 컬러 이미지의 경우 마지막 인자를 3으로 설정해주면 된다.

위의 과정으로 이미지의 특징을 강화했다. 하지만 첫 번째 층에서 32개의 마스크를 사용하고 두 번째 층에서 64개의 마스크를 사용하게 되면서 데이터의 개수가 더 늘어났다. 이에 성능 향상을 위하여 다시 축소할 필요가 있다. 이 과정을 pooling 또는 sub sampling 이라고 한다. pooling 중 max pooling은 정해진 구역 안에서 최대값을 뽑아내는 형식이다. 본 실습에서는 max pooling을 이용하여 pooling을 진행한다. 코드는 다음과 같다.

model.add(MaxPooling2D(pool_size=2))

pool_size를 2로 설정하면 합성곱을 2*2의 행렬로 변환한다. 아래 그림은 예시이다.

Pooing을 거친 결과

 

다음은 드롭 아웃(Drop out)을 설정한다. 드롭 아웃은 과적합을 피하는 기법 중 하나이다. 은닉층에 배치된 노드의 일부를 꺼버려서 학습을 덜 시키는 방식으로 과적합을 피한다. 드롭 아웃은 모델 학습 후 과적합이 발생했다면 추가적으로 설정하면 되는 부분이다. 본 실습에서는 25%로 드롭아웃을 설정한다.

model.add(Dropout(0.25))

 

다음은 플래튼(Flatten)을 적용한다. 본 실습에서 데이터 전처리 과정을 통해 이미지를 1차원 행렬로 변환했었다. 하지만 합성곱을 적용하면서 1차원 데이터를 다시 2차원으로 변환되었고 활성화 함수가 있는 층에서 해당 데이터를 처리하기 위해서는 다시 2차원 데이터를 1차원으로 변경해야 한다. 이과정을 플래튼을 이용하여 처리할 수 있다.

model.add(Flatten())

 

여기까지 작성 된 코드는 다음과 같다.

from keras.datasets import mnist
from keras.utils import np_utils
from keras.models import Sequential
from keras.layers import Dense, Dropout, Flatten, Conv2D, MaxPooling2D
from keras.callbacks import ModelCheckpoint, EarlyStopping

import matplotlib.pyplot as plt

import numpy
import tensorflow as tf

import os
#import sys



seed = 0
numpy.random.seed(seed)
tf.random.set_seed(3)



(X_train, Y_class_train), (X_test, Y_class_test) = mnist.load_data()

print("train image : %d " % (X_train.shape[0]))
print("test image : %d " % (X_test.shape[0]))



#plt.imshow(X_train[0], cmap='Greys')
# plt.show()



X_train = X_train.reshape(X_train.shape[0], 28, 28, 1).astype('float64')/255
X_test = X_test.reshape(X_test.shape[0], 28, 28, 1).astype('float64')/255



# for x in X_train[0]:
#     for y in x:
#         sys.stdout.write('%0.1f\t' % y)
#     sys.stdout.write('\n')



Y_train = np_utils.to_categorical (Y_class_train, 10)
Y_test = np_utils.to_categorical (Y_class_test, 10)



model = Sequential()

model.add(Conv2D(32, kernel_size=(3, 3), input_shape=(28, 28, 1), activation='relu'))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=2))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(512, input_dim=784, activation='relu'))
model.add(Dense(10, activation='softmax'))

model.compile(loss='categorical_crossentropy', optimizer='adam', metrics=['accuracy'])



MODEL_DIR = './model/'
if not os.path.exists(MODEL_DIR):
    os.mkdir(MODEL_DIR)

modelpath = "./model/{epoch:02d}-{val_loss:.4f}.hdf5"
checkpointer = ModelCheckpoint(filepath=modelpath, monitor='val_loss', verbose=1, save_best_only=True)
early_stopping_callback = EarlyStopping(monitor='val_loss', patience=10)

history = model.fit(X_train, Y_train, validation_data=(X_test, Y_test), \
    epochs=30, batch_size=200, verbose=0, \
        callbacks=[early_stopping_callback, checkpointer])

print("\n Test Accuracy: %.4f" % (model.evaluate(X_test, Y_test)[1]))



# y_vloss = history.history['val_loss']
# y_loss = history.history['loss']

# x_len = numpy.arange(len(y_loss))
# plt.plot(x_len, y_vloss, marker='.', c='red', label='Testset_loss')
# plt.plot(x_len, y_loss, marker='.', c='blue', label='Trainset_loss')

# plt.legend(loc='upper right')

# plt.grid()
# plt.xlabel('epoch')
# plt.ylabel('loss')
# plt.show()


reshape로 이미지 정규화 하는 부분을 CNN 적용 전 코드로 진행 시 CNN 학습 과정 중 오류가 발생한다. 따라서 위 코드를 참고하여 수정해준다. 위의 모델의 학습 결과는 다음과 같다.

약 98.9%의 정확도

적지만 CNN 적용 전 모델보다 정확도가 상승한 것을 볼 수 있다. 모델의 정확도는 학습 데이터의 전처리, 후처리, 정규화가 큰 영향을 미치며 이후에 모델이 사용하는 알고리즘, 은닉층의 수, 뉴런의 수 등 모델 튜닝과 학습의 횟수가 영향을 미친다. 위의 모델을 응용하여 여러 상황에 사용할 수 있다.

 

CNN 활용

CNN은 이미지 인식 분야에서 강력한 성능을 보인다. 응용하여 악성코드 예측에도 사용될 수 있다. CNN은 이미지의 특징을 한 번 더 강조하는 방식으로 마스크를 이용하여 행렬의 곱을 연산한다. 그래서 0은 0으로 유지하고 0이 아닌 수는 증폭 시키는 방식으로 작동한다. 이를 이용하여 악성코드를 예측할 수 있는데 악성코드의 경우 많은 수가 기존의 악성코드를 조금 바꾸는 형태로 사용한다. 이 점을 이용하여 기존의 발견된 악성코드들을 이미지화하여 학습시키고 학습한 악성코드들에서 조금 변형된 새로운 악성코드들을 찾아낼 수 있다.

 

반응형