EXCELSIOR

합성곱신경망(CNN, Convolutional Neural Network) 본문

DeepLearning/알고리즘

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

Excelsior-JH 2017. 4. 17. 17:50

CNN에 대해 더 자세한 내용은 아래의 링크를 참고하시면 됩니다.

자세한 설명 : 06. 합성곱 신경망 - Convolutional Neural Networks

텐서플로 실습 위주 : [러닝 텐서플로]Chap04 - 합성곱 신경망 CNN



1. Convolutional Neural Network(CNN)의 역사

CNN은 1989년 LeCun이 발표한 논문 “Backpropagation applied to handwritten zip code recognition”에서 처음 소개되었다.

그당시에는 필기체 인식에 있어서 의미있는 결과가 나왔지만 이를 범용화 하는데에는 미흡한 단계였다.

LeCun은 추후에 LeNet이라는 Network를 1998년에 제안하게 된다. 이것이 최초의 CNN이라고 할 수 있다. 

2003년 Behnke의 논문 “Hierarchical Neural Networks for Image Interpretation” 과 Simard의 논문 “Best Practices for Convolutional Neural Networks Applied to Visual Document Analysis”을 통해 단순화되면서 CNN 분야에 많은 연구가 진행되게 된다.



2. CNN의 구조

CNN의 구조는 완전연결계층(Fully-Connected Layer)과는 다른점이 있다. Fully-Connected Layer에서는 Affine계층으로 구현했지만, CNN에서는 아래의 [그림1]과 같이 합성곱 계층(Convolutinal Layer) 과 풀링 계층(Pooling Layer)가 추가된다.

[그림 1] FC 와 CNN의 구조적 차이

[출처: 밑바닥 부터 시작하는 딥러닝]


3. 기존 Multi-Layer Neural Network(MLNN)의 문제점

기존 Multi-Layer Neural Network 문제점은 변수의 개수, 네트워크 크기, 학습시간 세가지 문제가 발생할 수 있다

예를 들어 MLNN을 이용해서 아래의 그림과 같이 16 * 16 크기의 필기체를 인식하는 네트워크를 만든다고 하면, hidden layer가 한 층이고 100개의 뉴런을 가지고 있는 경우, 이 네트워크에 필요한 가중치(weight)와 편향(bias)는 총 28326개가 필요하게 된다.

만약, hidden layer를 한층 더 사용할 경우 파라미터 개수는 매우 많아지게 된다. 또한 아래의 [그림2]와 같이 글자를 전체적으로 2픽셀씩 이동하게 되면 새로운 학습데이터로 처리해줘야 하는 문제점이 있을 뿐만아니라, 글자의 크기, 회전, 변형이 있게 되면, 이에 대한 새로운 학습데이터를 넣어줘야 하는 문제가있다. 

글자의 형상은 고려하지 않고, raw data를 직접 처리하기 때문에 많은 양의 학습데이터가 필요하고, 따라서 학습시간이 길어지게 된다.

[그림 2] MLNN의 문제점

[출처: http://laonple.blog.me/220587920012]


4. 합성곱 계층(Convolutional Layer, Conv Layer)

이러한 MLNN의 문제를 해결하고자 만들어진 것이 합성곱 계층이다. 위의 [그림 2]의 필기체나 MNIST 데이터 같은 이미지 데이터는 일반적으로 채널, 세로, 가로 이렇게 3차원으로 구성된 데이터이다. Affine 계층에서는 이 3차원 데이터를 1차원 데이터(784=28*28)로 바꿔 입력했지만 합성곱에서는 3차원 데이터(1, 28, 28)를 입력하고 3차원의 데이터로 출력하므로 형상을 유지할 수 있다. CNN에서는 이러한 입출력 데이터를 특징맵(Feautre Map)이라고한다.

[그림 3] Conv Layer에서의 입출력 데이터 형상



4-1. 합성곱 계층 - 연산

그럼, 합성곱 계층에서 연산이 어떻게 이루어지는지 알아보도록 하자. 데이터와 필터의 모양을 (높이, 너비)로 나타내고, 윈도우(Window)라고 부른다. 여기서 입력데이터는 (4, 4), 필터는 (3, 3)이고, 필터가 바로 Conv Layer의 가중치에 해당한다. 

합성곱 연산은 필터의 윈도우를 일정한 간격으로 이동해가며 계산한다. [그림 4]를 통해 계산 방법을 알아보면. 합성곱 연산은 입력데이터와 필터간에 서로 대응하는 원소끼리 곱한 후 총합을 구하게 되며, 이것을 Fused Multiply-Add(FMA)라고한다. 

마지막으로 편향은 필터를 적용한 후에 더해주게 된다.


[그림 4] 합성곱 연산 예제


 


4-2. 합성곱 계층 - 패딩(Padding)

패딩(Padding)은 합성곱 연산을 수행하기 전, 입력데이터 주변을 특정값으로 채워 늘리는 것을 말한다. 패딩(Padding)은 주로 출력데이터의 공간적(Spatial)크기를 조절하기 위해 사용한다. 패딩을 할때, 채울 값은 hyperparameter로 어떤 값을 채울지 결정할 수 있다. 주로 zero-padding을 사용한다.

패딩을 사용하는 이유는 패딩을 사용하지 않을 경우, 데이터의 Spatial 크기는 Conv Layer를 지날 때 마다 작아지게 되므로, 가장자리의 정보들이 사라지는 문제가 발생하기 때문에 패딩을 사용한다.


4-3. 합성곱 계층 - 스트라이드(Stride)

스트라이드는 입력데이터에 필터를 적용할 때 이동할 간격을 조절하는 것, 즉 필터가 이동할 간격을 말한다. 스트라이드 또한 출력 데이터의 크기를 조절하기 위해 사용한다. 스트라이드(Stride)는 보통 1과 같이 작은 값이 더 잘 작동하며, Stride가 1일 경우 입력 데이터의 spatial 크기는 pooling 계층에서만 조절하게 할 수 있다.

아래의 [그림 5]는 1폭 짜리 zero-padding과 Stride값을 1로 적용한 뒤 합성곱 연산을 수행하는 예제이다.

[그림 5] zero-padding, stride 적용한 합성곱 연산


4-4. 합성곱 계층 - 출력크기 계산

패딩과 스트라이드를 적용하고, 입력데이터와 필터의 크기가 주어졌을 때 출력 데이터의 크기를 구하는 식은 아래 [식 1]과 같다

[식 1] 출력 크기 계산식


아래의 [그림 6]은 패딩 1 스트라이드 1 일때의 출력데이터 크기를  구한 예제다.  

출력크기가 정수가 아닌 경우에는 에러가 발생할 수 있는데, 보통 딥러닝 프레임워크에서는 반올림을 통해 에러없이 작동한다.

[그림 6] 출력 크기 계산 예제




5. 풀링 계층(Pooling Layer)

이때까지 합성곱 계층에 대해 알아보았다. 이제는 CNN의 또 다른 계층인 풀링계층(Pooling Layer)에 대해 알아보도록 하자.

풀링계층은 합성곱 계층의 패딩과 스트라이드처럼 데이터의 공간적 크기를 축소하는데 사용한다

주로 합성곱 계층(Conv Layer)에서 출력데이터의 크기를 입력데이터의 크기 그대로 유지하고, 풀링계층(Pool 에서만 크기를 조절한다풀링에는 Max-Pooling과 Average pooling이 있는데 Max-Pooling은 해당영역에서 최대값을 찾는 방법이고, Average-Pooling은 해당영역의 평균값을 계산하는 방법이다. 이미지 인식 분야에서는 주로 Max-Pooling을 사용한다. 또한 풀링계층에서는 풀링의 윈도우 크기와 스트라이드 값은 같은 값으로 설정한다. 아래의 [그림 7]에서는 풀링의 윈도우 사이즈는 (2, 2)이며 스트라이드는 2로 설정하였다. [그림 7]은 풀링 종류 중 맥스풀링을 사용하여 처리한 예제다.

[그림 7] Max-pooling 예제



6. 텐서플로우를 이용한 CNN 구현

아래의 소스코드는 여기 서 참고했으며, CNN을 시각화 해주기 위한 부분을 추가하였다. 

# -*- coding: utf-8 -*-
'''
TensorFlow를 이용한 CNN 구현
'''

import tensorflow as tf
from visualize_filter import filter_show

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets("./mnist/data/", one_hot=True)


#########
# 신경망 모델 구성
######
# 기존 모델에서는 입력 값을 28x28 하나의 차원으로 구성하였으나,
# CNN 모델을 사용하기 위해 2차원 평면과 특성치의 형태를 갖는 구조로 만듭니다.
X = tf.placeholder(tf.float32, [None, 28, 28, 1])
Y = tf.placeholder(tf.float32, [None, 10])

# 가중치 초기화
params = {}

# 각각의 변수와 레이어는 다음과 같은 형태로 구성됩니다.
# W1 [3 3 1 32] -> [3 3]: 커널 크기, 1: 입력값 X 의 특성수, 32: 필터 갯수
# L1 Conv shape=(?, 28, 28, 32)
#    Pool     ->(?, 14, 14, 32)
params['W1'] = tf.Variable(tf.random_normal([5, 5, 1, 32], stddev=0.01))
# tf.nn.conv2d 를 이용해 한칸씩 움직이는 컨볼루션 레이어를 쉽게 만들 수 있습니다.
# padding='SAME' 은 커널 슬라이딩시 최외곽에서 한칸 밖으로 더 움직이는 옵션
L1 = tf.nn.relu(tf.nn.conv2d(X, params['W1'], strides=[1, 1, 1, 1], padding='SAME'))
# Pooling 역시 tf.nn.max_pool 을 이용하여 쉽게 구성할 수 있습니다.
L1 = tf.nn.max_pool(L1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
L1 = tf.nn.dropout(L1, 0.8)

# L2 Conv shape=(?, 14, 14, 64)
#    Pool     ->(?, 7, 7, 64)
#    Reshape  ->(?, 256)
# W2 의 [3, 3, 32, 64] 에서 32 는 L1 에서 출력된 W1 의 마지막 차원, 필터의 크기 입니다.
params['W2'] = tf.Variable(tf.random_normal([5, 5, 32, 64], stddev=0.01))
L2 = tf.nn.relu(tf.nn.conv2d(L1, params['W2'], strides=[1, 1, 1, 1], padding='SAME'))
L2 = tf.nn.max_pool(L2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')
# Full Connect 를 위해 차원을 줄입니다.
# 직전의 Pool 사이즈인 (?, 7, 7, 64) 를 참고합니다.
L2 = tf.reshape(L2, [-1, 7 * 7 * 64])
L2 = tf.nn.dropout(L2, 0.8)

# FC 레이어: 입력값 7x7x64 -> 출력값 256
W3 = tf.Variable(tf.random_normal([7 * 7 * 64, 256], stddev=0.01))
L3 = tf.nn.relu(tf.matmul(L2, W3))
L3 = tf.nn.dropout(L3, 0.5)

# 최종 출력값 L3 에서의 출력 256개를 입력값으로 받아서 0~9 레이블인 10개의 출력값을 만듭니다.
W4 = tf.Variable(tf.random_normal([256, 10], stddev=0.01))
model = tf.matmul(L3, W4)

cost = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(model, Y))
optimizer = tf.train.AdamOptimizer(0.001).minimize(cost)
# 최적화 함수를 RMSPropOptimizer 로 바꿔서 결과를 확인해봅시다.
# optimizer = tf.train.RMSPropOptimizer(0.001, 0.9).minimize(cost)


#########
# 신경망 모델 학습
######
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)

batch_size = 100
total_batch = int(mnist.train.num_examples/batch_size)

# 결과 저장


for epoch in range(15):
    total_cost = 0

    for i in range(total_batch):
        batch_xs, batch_ys = mnist.train.next_batch(batch_size)
        # 이미지 데이터를 CNN 모델을 위한 자료형태인 [28 28 1] 의 형태로 재구성합니다.
        batch_xs = batch_xs.reshape(-1, 28, 28, 1)
        _, cost_val = sess.run([optimizer, cost], feed_dict={X: batch_xs, Y: batch_ys})
        total_cost += cost_val

    print('Epoch:', '%04d' % (epoch + 1), \
        'Avg. cost =', '{:.3f}'.format(total_cost / total_batch))
    print(params['W2'].get_shape().as_list())

    #print(sess.run(params['W1']))

    #filter = sess.run(params['W1'])
    #filter_show(filter.transpose(3,2,0,1))

   # CNN 필터를 시각화
    filter = sess.run(params['W2'])
    filter_show(filter.transpose(3,2,0,1)) 

print('최적화 완료!')


#########
# 결과 확인
######
check_prediction = tf.equal(tf.argmax(model, 1), tf.argmax(Y, 1))
accuracy = tf.reduce_mean(tf.cast(check_prediction, tf.float32))
print('정확도:', sess.run(accuracy,
                       feed_dict={X: mnist.test.images.reshape(-1, 28, 28, 1),
                                  Y: mnist.test.labels}))
# coding: utf-8
import numpy as np
import matplotlib.pyplot as plt

'''
CNN 필터 가중치 시각화 함수
'''

def filter_show(filters, nx=8, margin=3, scale=10):
    """
    c.f. https://gist.github.com/aidiary/07d530d5e08011832b12#file-draw_weight-py
    """
    FN, C, FH, FW = filters.shape
    ny = int(np.ceil(FN / nx))

    fig = plt.figure()
    fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0.01, wspace=0.01)

    for i in range(FN):
        ax = fig.add_subplot(ny, nx, i+1, xticks=[], yticks=[])
        ax.imshow(filters[i, 0], cmap=plt.cm.gray_r, interpolation='nearest')
    plt.show()


'DeepLearning > 알고리즘' 카테고리의 다른 글

RNN - LSTM(Long Short Term Memory networks)  (1) 2017.05.30
Comments