4 minute read

이미지 데이터는 공간적인 특성을 가지고 있고 (높이, 너비, 깊이) 라는 3차원 텐서로 표현되는데 여기서 깊이란 이미지가 흑백인지 컬러인지를 구분할 수 있게 한다. 예를 들어 (28,28,1)의 이미지는 가로 세로 벡터의 크기가 28인 이미지이고 깊이(채널)은 1이기 때문에 흑백 이미지다. (28,28,3)은 채널이 3개(R,G,B)이므로 컬러이미지라고 할 수 있다.

MLP(다층 퍼셉트론 신경망)를 통해서도 이미지 데이터를 분류할 수 있지만, 패턴이 복잡한 컬러 이미지 같은 경우 공간적인 특성을 고려하면서 이미지를 분류하는 것은 어려운 일이다. 합성공 신경망(Convolutional Neural Network, CNN)은 이미지의 일부분을 훑으면서 연산을 하기 때문에 층이 깊어지더라도 특징을 잘 잡아내어 학습하고, 공간적 특성을 최대한 보존할 수 있다. 합성곱이 NLP에도 쓰일 수 있지만, 이번 포스팅에서는 가장 대표적으로 쓰이는 이미지 처리에서의 CNN에 대해 정리해보자.

CNN의 구조

image

https://towardsdatascience.com/a-comprehensive-guide-to-convolutional-neural-networks-the-eli5-way-3bd2b1164a53

CNN은 이미 숫자로 표현되어 있는 input vector가 들어오면 이 이미지 벡터의 특징을 추출하는 부분과 추출된 특징을 기반으로 분류를 수행하는 부분으로 나누어져있다. 특히, 특징을 추출하는 부분은 합성곱(Convolution layer)층과 풀링층(Pooling layer)으로 나눌 수 있는데 먼저 이 두 가지 층에 대해 알아보자.

합성곱(Convolution)

합성곱 층에서는 커널이라고 불리는 합성곱 필터(n x m 행렬)가 입력된 이미지 데이터를 슬라이딩하면서 부분부분의 특징을 추출한다. 슬라이딩은 왼쪽 위부터 오른쪽 아래까지 이동한다.

image

image

image

이미지출처: https://wikidocs.net/64066

위의 이미지 처럼 커널과 맵핑되는 부분을 합성곱해서 결과를 더한 후 특성 맵(feature map)이라는 출력 행렬을 만들어낸다. MLP에서도 경사하강법을 위한 미분값을 사용할 수 있도록 비선형성을 추가한 것처럼 합성곱 연산을 통해 얻은 특성 맵도 ReLU나 ReLU를 변형한 활성화 함수들을 주로 사용하여 비선형성을 추가한다. 합성곱층은 이렇게 특성맵을 추출하고, 활성화 함수를 지나는 연산이 이루어지는 층이다.

패딩(Padding) 과 스트라이드(Stride)

합성곱 연산을 수행할 때 패딩과 스트라이드라는 것을 조정할 수 있다.

image

이미지출처: https://yceffort.kr/2019/01/29/pytorch-3-convolutional-neural-network

먼저 패딩은 합성곱 연산 후 출력되는 특성맵의 크기가 기존 input의 크기 보다 작아지기 때문에 발생할 수 있는 이미지 데이터 정보의 손실을 방지하기 위해 input 데이터의 테두리를 일정한 수(주로 zero padding)로 둘러싸는 것을 말한다. 패딩을 통해 특성맵의 크기를 조절하여 기존 이미지 행렬의 차원을 보존하고, 원래 한번만 연산되는 가장 자리 값을 여러번 연산 시킬 수 있게 함으로써 모든 값을 최대한 활용한다.



image

image

스트라이드는 보폭을 의미하는 단어로 합성곱 필터가 이미지 데이터를 얼만큼의 보폭을 가지고 훑은 것인지를 정한다. 만약 stride가 1이면 위의 그림처럼 한칸씩 이동하고, 2라면 2칸씩 이동하게 된다. 가로 세로를 따로 stides를 적용하고 싶으면 (2,3) 이런식으로 하면 된다.

이렇게 패딩과 스트라이드를 통해 특성맵의 크기가 달라지고, 이를 수학적인 공식으로 나타내면 다음과 같다.

image

풀링(Pooling)

image

이미지 출처: https://cheris8.github.io/artificial%20intelligence/CV-CNN/

풀링은 추출된 특성맵의 크기를 줄이기 위해 사용된다. 행렬의 차원이 줄어들면 연산량도 줄어들기 때문에 정보를 압축해서 표현하는 것이다. 최대 풀링(Max pooling)은 정해진 범위 내에서 가장 큰 값을 추출하고, 평균 풀링(Average pooling)은 정해진 범위 내의 원소의 평균값을 추출한다. 풀링층은 단순히 큰 값이나 평균값을 추출하는 역할만 하기 때문에 학습해야 할 가중치가 없고, 채널 수가 변하지 않는다.

또한 풀링에서는 스트라이드를 따로 지정하지 않으면 풀링의 크기만큼으로 자동으로 지정되어있다.

완전 연결 신경망(Fully Connected Layer)

CNN에서 특징 추출 부분이 완료되면(합성곱, 풀링) 분류를 하기 위해 완전 연결 신경망을 구축해야 한다. 완전 연결 신경망은 MLP로 구성되어 있다(입력, 은닉, 출력). 입력은 특징 추출 부분에서 추출한 특징 벡터가 될 것이고, CNN에서 학습되는 가중치는 필터의 값들이다(커널의 원소). 이미지 데이터는 Convolution과 Pooling층을 거치면서 작아지고, 이렇게 층이 깊어질수록 이미지의 더 세부적인 특징을 학습하게 된다. 따라서 역전파 과정에서는 특징을 가장 잘 추출하도록 가중치인 필터값들을 업데이트시키며 손실을 줄일 것이다.

CNN 실습(feats. Cifar 데이터 예제)

from tensorflow.keras.datasets import cifar10
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense, Conv2D, MaxPooling2D, Flatten
from tensorflow.keras.layers import Dense, Flatten
from tensorflow.keras.models import Sequential
from tensorflow.keras.datasets import cifar10

from sklearn.model_selection import train_test_split

import numpy as np
import tensorflow as tf
np.random.seed(42)
tf.random.set_seed(42)
(X_train, y_train), (X_test, y_test) = cifar10.load_data()
# 이미지 데이터를 정규화한다.
# 0 ~ 1 사이로
X_train = X_train.astype('float32') / 255.
X_test = X_test.astype('float32') / 255.

여기에서는 3개의 convolution 층과 각 층 사이에 pooling층을 넣어서 특징을 추출한다.

model = Sequential()
# 32 : 커널(필터)의 갯수, (3,3)은 커널의 크기, same: 패딩후 결과가 input과 똑같이 유지되도록
# 첫번째 합성곱 층 가중치 갯수 = 크기*갯수*채널 = (3*3) * 32 * 3 = 864
# 첫번째 합성곱 층 편향 갯수 = 커널 갯수 = 32
# 첫번째 합성곱 층 파라미터 개수 = 가중치 + 편향 = 896
model.add(Conv2D(32, (3,3), padding='same', activation='relu'))
model.add(MaxPooling2D(2,2)) #풀링의 범위 (2,2) 행렬에서 최대 원소만 추출
model.add(Conv2D(64, (3,3), padding='same', activation='relu')) # relu를 지나며 비선형성 추가
model.add(MaxPooling2D(2,2))
model.add(Conv2D(64, (3,3), padding='same', activation='relu'))
model.add(Flatten())
model.add(Dense(128, activation='relu')) #분류기 시작, 은닉층
model.add(Dense(10, activation='softmax')) #출력층
model.compile(optimizer='adam',
              loss='sparse_categorical_crossentropy',
              metrics=['accuracy'])
model.fit(X_train, y_train,
          batch_size=128,
          validation_split=0.2,
          epochs=10)
model.evaluate(X_test, y_test, verbose=2)

전이 학습(Transfer Learning)

전이 학습은 대량의 데이터를 이미 학습한 사전 학습 모델(Pre-trained Model)의 가중치를 그대로 가져온 뒤 분류기와 같은 완전 연결 신경만 부분만 추가로 붙여서 사용하는 것을 말한다. 이미지 분류를 예시로 들면, CNN에서 특징 추출 부분의 Layer들은 이미 학습되어 있는 것으로 가져오고 분류기만 따로 설계해서 원하는 결과를 출력하는 것이다.

사전 학습 모델은 이미 대량의 데이터로 학습되어있기 때문에 어떠한 데이터에서도 괜찮은 성능을 낼 수 있고, 일반적으로 사전 학습 된 가중치는 더 이상 학습시킬 필요 없이 고정(freeze) 시켜서 학습 시간도 단축된다는 장점을 가지고 있다. 이미지 분류를 위한 사전 학습 모델로는 VGG, Inception, ResNet 등이 있다.

VGG를 이용한 이미지 분류(feats. Cifar 예제)

from tensorflow.keras.applications.vgg16 import VGG16
from tensorflow.keras.layers import Dense, GlobalAveragePooling2D
from tensorflow.keras.models import Sequential
from tensorflow.keras.datasets import cifar10

from sklearn.model_selection import train_test_split

import numpy as np
import tensorflow as tf
np.random.seed(42)
tf.random.set_seed(42)

(X_train, y_train), (X_test, y_test) = cifar10.load_data()

#정규화
X_train = X_train.astype('float32') / 255.
X_test = X_test.astype('float32') / 255.
# include_top:  VGG의 분류기도 가져올것인가.
# 보통 분류기가 아니라 특징추출 부분인 convolution이나 pooling layer만 가져옴
pretrained_model = VGG16(weights='imagenet', include_top=False)
#특징 추출 부분은 이미 가져왔으니까 완전 연결 신경망 부분만 추가
model = Sequential()
model.add(pretrained_model) # 사전 학습된 특징 추출부분 모델을 가장 앞에 둬야한다.
model.add(GlobalAveragePooling2D()) # 차원을 2차원으로 변경
model.add(Dense(128,activation='relu'))
model.add(Dense(10,activation='softmax'))

그 후는 학습과 검증…

이미지 증강(Image Augmentation)

이미지 증강이란 기존 이미지를 회전, 반전, 밝기 변화 등의 변환을 해서 데이터를 늘리는 방법이다. 즉, 사람처럼 일반화가 잘 되는 모델을 만들기 위해 train data의 이미지를 일부러 변환하는 것이다. 사람이 이해하는 것처럼 기존 이미지에 노이즈를 조금 추가해서 기존 이미지와 아주 조금만 다른 이미지라면 컴퓨터가 같은 이미지라는 것을 인식할 수 있게 한다. 이미지 증강을 사용하면 기존 데이터를 조금 변형해서 더 많은 데이터를 사용할 수 있기 때문에 일반화가 잘된 강건(Robust)한 모델을 만들 수 있다.

Categories:

Updated: