안녕, 세상!

7. CNN 본문

It공부/Deep learning

7. CNN

dev_Lumin 2020. 6. 26. 16:30

CNN(Convolutional Neural Network)는 합성곱 신경망으로 이미지 인식과 음성 인식 등 다양한 곳에서 사용됩니다.

특히 이미지 딥러닝 분야에서 대부분 CNN이 사용됩니다.

CNN은 고양이가 이미지를 인식하는데 이미지를 한 번에 인식하는 것이 아니라 이미지의 부분 부분을 바라보는 방식, 즉 입력을 나눠서 인식하는 방식에 유래하여 제안된 방식입니다.

 

 

완전 연결 계층의 문제점

이전까지 본 신경망은 인접하는 계층의 모든 뉴런과 결합되어 있는 완전 연결(Fully Connetcted)라 하며 Affine 계층으로 구현을 했습니다.

완전 연결 계층에서는 인접하는 계층의 뉴런이 모두 연결되고 출력의 수는 임의로 정할 수 있었습니다.

하지만 문제점은 데이터의 형상이 무시된다는 점입니다.

입력 데이터가 이미지인 경우 이미지는 통상 가로, 세로, 색상으로 구성된 3차원 데이터로 3차원 속에서 의미를 갖는 본질적인 패턴이 숨어 있을 겁니다.

하지만 완전연결 계층에 입력을 할 때 3차원 데이터를 1차원 데이터로 바꿔줘야 합니다.

이는 모든 입력데이터를 같은 차원의 뉴런으로 취급하여 형상에 담긴 정보를 살릴 수 없습니다.

 

하지만 합성곱은 이미지도 3차원 데이터로 입력받으며, 다음 계층에도 3차원 데이터로 전달합니다.

합성곱 계층은 형상을 유지시킵니다.

 

 

CNN 전체구조

CNN은 합성곱 계층(Convolutional Layer)과 풀링계층(Pooling Layer) 이 새롭게 등장합니다.

전체적인 구조를 그림으로 표현하면 다음과 같습니다.

합성곱 계층과 풀링 계층이 추가되었고 마지막 출력 계층에서는 Affine계층과 Softmax 계층 조합을 그대로 사용합니다.

 

 

 

 

(1) 합성곱 계층

① 합성곱 연산

입력 데이터에 필터(커널)를 적용시켜서 입력 데이터의 각 원소와 필터의 각 원소끼리의 같은 위치의 값들을 서로 곱한 후 그 총합을 구하는 것입니다. ( 단일 곱셈-누산 fused multiply-add(FMA) )

여기서 필터 부분이 가중치에 해당하는 것입니다.

편향 값은 스칼라 값처럼 생각해서 결과물의 원소들마다 값을 더해주면 됩니다.

 

 

② 패딩

합성곱 연산을 수행하기 전에 입력 데이터 주변을 0으로 채워주는 것을 패딩이라고 합니다.

일반적인 합성곱 연산을 하게 되면 출력 데이터가 입력 데이터와 다르게 크기가 작아지게 됩니다.

이러한 합성곱 연산을 계속 반복하게 되면 어느 순간 입력 데이터 크기가 1이 돼버려서 합성곱 연산을 할 수 없습니다. 

이를 방지하고자 주로 입출력 데이터의 크기를 같게 만드는데 주로 사용합니다.

 

 

③ 스트라이드

필터를 적용하는 위치의 간격을 스트라이드(Stride)라고 합니다.

합성곱 연산에서 보여준 예시 사진은 필터가 가로나 세로로 이동할 때 1칸씩 이동하므로 스트라이드가 1인 경우입니다.

스트라이드가 크면 클수록 합성곱 연산의 출력 데이터가 입력 데이터보다 더 작아집니다.

 

위의 세 가지 개념으로 출력 크기가 어떻게 계산되는지 살펴보겠습니다.

입력 크기를 (H, W), 필터 크기를 (FH, FW), 출력 크기를 (OH, OW), 패딩을 P, 스트라이드를 S라 하면 출력 크기는 다음 식과 같습니다.

출력 크기의 값은 정수로 나눠 떨어지는 값이어야 합니다.

 

 

④ 3차원 데이터의 합성곱

그림으로 나타내면 다음과 같습니다.

 

3차원의 합성곱 연산에서 주의할 점은 입력 데이터의 채널 수와 필터의 채널 수가 같아야 합니다.\

 

이러한 3차원 합성곱을 블록으로 표현하면 다음 그림과 같습니다.

입력 데이터는 3차원이지만 출력 데이터는 채널이 1개인 한 장의 2차원 데이터입니다.

CNN의 특징은 입력한 3차원의 형상을 그대로 유지해 다음 계층으로 전달하는 것입니다.

이를 성립하게 하기 위해서 필터(가중치)를 다수 사용합니다.

 

 

 

 

 

(2) 풀링 계층

풀링은 세로, 가로 방향의 공간을 줄이는 연산입니다. 

폴링 max pooling

위의 그림은 2x2 최대 풀링을 스트라이드 2로 처리하는 순서를 나타내는 그림입니다.

최대 풀링은 입력 데이터에서 풀링의 윈도우 크기만큼의 영역에서 원소들중 가장 큰 원소를 뽑아내는 방식입니다. 

풀링의 윈도우 크기와 스트라이드는 같은 값으로 설정하는 것이 보통입니다.

풀링은 최대 풀링 이외도 평균 풀링 등이 있지만 이미지 인식 분야에서는 주로 최대 풀링을 사용합니다.

 

풀링 계층의 특징은 다음과 같습니다.

1. 학습해야 할 매개변수가 없습니다.

2. 채널 수가 변하지 않습니다.

3. 입력의 변화에 영향을 적게 받습니다.

경우나 순간에 따라 다르겠지만 위의 예시에서는 데이터가 모두 오른쪽으로 한 칸 이동했음에도 불구하고 풀링 데이터는 일치하게 출력이 되었습니다.

 

풀링 계층을 하면 결국 크기가 작아지는데 그럼에도 불구하고 하는 이유는 다음과 같습니다.

http://ufldl.stanford.edu/tutorial/supervised/Pooling/

 

Unsupervised Feature Learning and Deep Learning Tutorial

Pooling: Overview After obtaining features using convolution, we would next like to use them for classification. In theory, one could use all the extracted features with a classifier such as a softmax classifier, but this can be computationally challenging

ufldl.stanford.edu

추출된 모든 데이터를 사용하게 되면 오버피팅이 발생할 수 있기 때문입니다.

이를 해결하기 위해서 풀링을 합니다.

이미지는 한 지역에서 유용하다면 다른 지역에서 유용하다는 것을 암시하는 정상성(자료 내의 독립성을 설명하기 위해 공간 분석에서 사용되는 용어) 을 가지고 있기 때문에 이미지로부터 Convolved Feature(합성곱을 거친 데이터, 변수)을 얻어냅니다. 

그러므로 큰 이미지를 묘사하려면 이미지로부터 얻은 데이터의 다양한 위치의 통계를 종합해야 합니다.

예를 들어 이미지의 특정 위치 너머에 있는 데이터들의 최댓값이나 평균값을 계산할 수 있습니다.

이러한 축약적 통계는 모든 값을 추출하는 것과 비교했을 때 차원을 낮출 수 있으며 오버피팅 발생을 줄여 개선된 결과를 도출해 낼 수 있습니다.

 

 

 

 

(3) im2col (image to column)

합성곱 연산을 곧이곧대로 구현하려면 for문을 겹겹이 사용해야 되겠지만 넘파이에 for문을 사용하면 성능이 떨어진다는 단점도 있기 때문에 for문 대신 im2col이라는 함수를 사용해서 구현합니다. 

im2col함수의 원리는 입력 데이터에서 필터를 적용하는 영역을 앞에서부터 순서대로 1줄로 펼칩니다.

위 그림은 스트라이드를 크게 잡아 필터의 적용 영역이 겹치지 않도록 했지만 실제 상황에서는 영역이 겹치는 경우가 대부분입니다. 

필터 적용 영역이 겹치게 되면 im2col로 전개한 후의 원소 수가 원래 블록의 원소 수보다 많아서 메모리를 많이 소비하는 단점이 있지만, 컴퓨터는 큰 행렬을 묶어서 계산하는 데 탁월합니다.

 

합성곱 전체 과정을 im2col 사용 시 그림으로 표현하면 다음과 같습니다.

im2col 방식으로 출력한 결과는 2차원 행렬이라 이를 4차원 배열로 재복원하는 과정인 reshape를 마지막에 실행합니다.

 

im2col의 순전파와 역전파 코드는 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
import numpy as np
def im2col(input_data, filter_h, filter_w, stride=1, pad=0):
    """다수의 이미지를 입력받아 2차원 배열로 변환한다(평탄화).
    
    Parameters
    ----------
    input_data : 4차원 배열 형태의 입력 데이터(이미지 수, 채널 수, 높이, 너비)
    filter_h : 필터의 높이
    filter_w : 필터의 너비
    stride : 스트라이드
    pad : 패딩
    
    Returns
    -------
    col : 2차원 배열
    """
    N, C, H, W = input_data.shape
    out_h = (H + 2*pad - filter_h)//stride + 1
    out_w = (W + 2*pad - filter_w)//stride + 1
 
    img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant')
    col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))
 
    for y in range(filter_h):
        y_max = y + stride*out_h
        for x in range(filter_w):
            x_max = x + stride*out_w
            col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]
 
    col = col.transpose(045123).reshape(N*out_h*out_w, -1)
    return col
 
 
def col2im(col, input_shape, filter_h, filter_w, stride=1, pad=0):
    """(im2col과 반대) 2차원 배열을 입력받아 다수의 이미지 묶음으로 변환한다.
    
    Parameters
    ----------
    col : 2차원 배열(입력 데이터)
    input_shape : 원래 이미지 데이터의 형상(예:(10, 1, 28, 28))
    filter_h : 필터의 높이
    filter_w : 필터의 너비
    stride : 스트라이드
    pad : 패딩
    
    Returns
    -------
    img : 변환된 이미지들
    """
    N, C, H, W = input_shape
    out_h = (H + 2*pad - filter_h)//stride + 1
    out_w = (W + 2*pad - filter_w)//stride + 1
    col = col.reshape(N, out_h, out_w, C, filter_h, filter_w).transpose(034512)
 
    img = np.zeros((N, C, H + 2*pad + stride - 1, W + 2*pad + stride - 1))
    for y in range(filter_h):
        y_max = y + stride*out_h
        for x in range(filter_w):
            x_max = x + stride*out_w
            img[:, :, y:y_max:stride, x:x_max:stride] += col[:, :, y, x, :, :]
 
    return img[:, :, pad:H + pad, pad:W + pad]
cs

 

 

 

입력 데이터 부분을 im2col함수를 적용시키는 예시 코드는 다음과 같습니다

x1, x2 두 예시 모두 열이 75인 형상이 나온 것은 5x5데이터에 3개의 채널을 가지고 있어서 75개 열을 가진 것이고, 행의 차이는 둘 다 필터가 적용된 영역이 9개로 나뉘지만 x1은 데이터수가 1개이고 x2는 10개라서 x2가 10배 더 크게 나왔습니다.

 

 

im2col을 이해했으니 이를 사용한 합성곱 계층인 Convolution 클래스를 코드로 구현하면 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class Convolution:
    def __init__(self, W, b, stride=1, pad=0):
        self.W = W
        self.b = b
        self.stride = stride
        self.pad = pad
        
        # 중간 데이터(backward 시 사용)
        self.x = None   
        self.col = None
        self.col_W = None
        
        # 가중치와 편향 매개변수의 기울기
        self.dW = None
        self.db = None
 
    def forward(self, x):
        FN, C, FH, FW = self.W.shape
        N, C, H, W = x.shape
        out_h = 1 + int((H + 2*self.pad - FH) / self.stride)
        out_w = 1 + int((W + 2*self.pad - FW) / self.stride)
 
        col = im2col(x, FH, FW, self.stride, self.pad)
        col_W = self.W.reshape(FN, -1).T
 
        out = np.dot(col, col_W) + self.b
        out = out.reshape(N, out_h, out_w, -1).transpose(0312)
 
        self.x = x
        self.col = col
        self.col_W = col_W
 
        return out
 
    def backward(self, dout):
        FN, C, FH, FW = self.W.shape
        dout = dout.transpose(0,2,3,1).reshape(-1, FN)
 
        self.db = np.sum(dout, axis=0)
        self.dW = np.dot(self.col.T, dout)
        self.dW = self.dW.transpose(10).reshape(FN, C, FH, FW)
 
        dcol = np.dot(dout, self.col_W.T)
        dx = col2im(dcol, self.x.shape, FH, FW, self.stride, self.pad)
 
        return dx
cs

 

reshape( , -1) :  -1은 형상의 나머지 값을 자동으로 형상을 정리해 준다는 의미

transpose() - 배열의 인덱스 숫자를 기준으로 함수 축 순서를 변경하는 함수

 

 

폴링을 im2col을 이용해서 나타낸 전체적인 과정을 그림으로 표현하면 다음과 같습니다.

위의 그림은 forward 과정이고 이를 다음과 같은 코드로 나타납니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
class Pooling:
    def __init__(self, pool_h, pool_w, stride=1, pad=0):
        self.pool_h = pool_h
        self.pool_w = pool_w
        self.stride = stride
        self.pad = pad
        
        self.x = None
        self.arg_max = None
 
    def forward(self, x):
        N, C, H, W = x.shape
        out_h = int(1 + (H - self.pool_h) / self.stride)
        out_w = int(1 + (W - self.pool_w) / self.stride)
         
        #전개
        col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
        col = col.reshape(-1, self.pool_h*self.pool_w)
 
       
        arg_max = np.argmax(col, axis=1)
        # 최댓값  
        out = np.max(col, axis=1)
        # reshape(성형)
        out = out.reshape(N, out_h, out_w, C).transpose(0312)
 
        self.x = x
        self.arg_max = arg_max
 
        return out
 
    def backward(self, dout):
        dout = dout.transpose(0231)
        
        pool_size = self.pool_h * self.pool_w
        dmax = np.zeros((dout.size, pool_size))
        dmax[np.arange(self.arg_max.size), self.arg_max.flatten()] = dout.flatten()
        dmax = dmax.reshape(dout.shape + (pool_size,)) 
        
        dcol = dmax.reshape(dmax.shape[0* dmax.shape[1* dmax.shape[2], -1)
        dx = col2im(dcol, self.x.shape, self.pool_h, self.pool_w, self.stride, self.pad)
        
        return dx
cs

 

4차원 넘파이 배열을 2차원으로 전개시킬 경우를 좀 더 이해하기 위해서 다음과 같은 예시를 참고합니다.

 

 

 

(4) CNN 구현

CNN을 구현하기 위해서 Mnist 손글씨 분류로 아래 그림과 같은 CNN을 구현하고자 합니다.

우선 이를 구현하기 위해 필요한 SimpleConvNet 클래스는 다음 코드와 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
import sys, os
sys.path.append(os.pardir)  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import pickle
import numpy as np
from collections import OrderedDict
from common.layers import *
from common.gradient import numerical_gradient
 
 
class SimpleConvNet:
    """단순한 합성곱 신경망
    
    conv - relu - pool - affine - relu - affine - softmax
    
    Parameters
    ----------
    input_size : 입력 크기(MNIST의 경우엔 784)
    hidden_size_list : 각 은닉층의 뉴런 수를 담은 리스트(e.g. [100, 100, 100])
    output_size : 출력 크기(MNIST의 경우엔 10)
    activation : 활성화 함수 - 'relu' 혹은 'sigmoid'
    weight_init_std : 가중치의 표준편차 지정(e.g. 0.01)
        'relu'나 'he'로 지정하면 'He 초깃값'으로 설정
        'sigmoid'나 'xavier'로 지정하면 'Xavier 초깃값'으로 설정
    """
    def __init__(self, input_dim=(12828), 
                 conv_param={'filter_num':30'filter_size':5'pad':0'stride':1},
                 hidden_size=100, output_size=10, weight_init_std=0.01):
        filter_num = conv_param['filter_num']
        filter_size = conv_param['filter_size']
        filter_pad = conv_param['pad']
        filter_stride = conv_param['stride']
        input_size = input_dim[1]
        conv_output_size = (input_size - filter_size + 2*filter_pad) / filter_stride + 1
        pool_output_size = int(filter_num * (conv_output_size/2* (conv_output_size/2))
 
        # 가중치 초기화
        self.params = {}
        self.params['W1'= weight_init_std * \
                            np.random.randn(filter_num, input_dim[0], filter_size, filter_size)
        self.params['b1'= np.zeros(filter_num)
        self.params['W2'= weight_init_std * \
                            np.random.randn(pool_output_size, hidden_size)
        self.params['b2'= np.zeros(hidden_size)
        self.params['W3'= weight_init_std * \
                            np.random.randn(hidden_size, output_size)
        self.params['b3'= np.zeros(output_size)
 
        # 계층 생성
        self.layers = OrderedDict()
        self.layers['Conv1'= Convolution(self.params['W1'], self.params['b1'],
                                           conv_param['stride'], conv_param['pad'])
        self.layers['Relu1'= Relu()
        self.layers['Pool1'= Pooling(pool_h=2, pool_w=2, stride=2)
        self.layers['Affine1'= Affine(self.params['W2'], self.params['b2'])
        self.layers['Relu2'= Relu()
        self.layers['Affine2'= Affine(self.params['W3'], self.params['b3'])
 
        self.last_layer = SoftmaxWithLoss()
 
    def predict(self, x):
        for layer in self.layers.values():
            x = layer.forward(x)
 
        return x
 
    def loss(self, x, t):
        """손실 함수를 구한다.
        Parameters
        ----------
        x : 입력 데이터
        t : 정답 레이블
        """
        y = self.predict(x)
        return self.last_layer.forward(y, t)
 
    def accuracy(self, x, t, batch_size=100):
        if t.ndim != 1 : t = np.argmax(t, axis=1)
        
        acc = 0.0
        
        for i in range(int(x.shape[0/ batch_size)):
            tx = x[i*batch_size:(i+1)*batch_size]
            tt = t[i*batch_size:(i+1)*batch_size]
            y = self.predict(tx)
            y = np.argmax(y, axis=1)
            acc += np.sum(y == tt) 
        
        return acc / x.shape[0]
 
    def numerical_gradient(self, x, t):
        """기울기를 구한다(수치미분).
        Parameters
        ----------
        x : 입력 데이터
        t : 정답 레이블
        Returns
        -------
        각 층의 기울기를 담은 사전(dictionary) 변수
            grads['W1']、grads['W2']、... 각 층의 가중치
            grads['b1']、grads['b2']、... 각 층의 편향
        """
        loss_w = lambda w: self.loss(x, t)
 
        grads = {}
        for idx in (123):
            grads['W' + str(idx)] = numerical_gradient(loss_w, self.params['W' + str(idx)])
            grads['b' + str(idx)] = numerical_gradient(loss_w, self.params['b' + str(idx)])
 
        return grads
 
    def gradient(self, x, t):
        """기울기를 구한다(오차역전파법).
        Parameters
        ----------
        x : 입력 데이터
        t : 정답 레이블
        Returns
        -------
        각 층의 기울기를 담은 사전(dictionary) 변수
            grads['W1']、grads['W2']、... 각 층의 가중치
            grads['b1']、grads['b2']、... 각 층의 편향
        """
        # forward
        self.loss(x, t)
 
        # backward
        dout = 1
        dout = self.last_layer.backward(dout)
 
        layers = list(self.layers.values())
        layers.reverse()
        for layer in layers:
            dout = layer.backward(dout)
 
        # 결과 저장
        grads = {}
        grads['W1'], grads['b1'= self.layers['Conv1'].dW, self.layers['Conv1'].db
        grads['W2'], grads['b2'= self.layers['Affine1'].dW, self.layers['Affine1'].db
        grads['W3'], grads['b3'= self.layers['Affine2'].dW, self.layers['Affine2'].db
 
        return grads
        
    def save_params(self, file_name="params.pkl"):
        params = {}
        for key, val in self.params.items():
            params[key] = val
        with open(file_name, 'wb') as f:
            pickle.dump(params, f)
 
    def load_params(self, file_name="params.pkl"):
        with open(file_name, 'rb') as f:
            params = pickle.load(f)
        for key, val in params.items():
            self.params[key] = val
 
        for i, key in enumerate(['Conv1''Affine1''Affine2']):
            self.layers[key].W = self.params['W' + str(i+1)]
            self.layers[key].b = self.params['b' + str(i+1)]
cs

 

 

위의 클래스를 이용해서 CNN을 구현하는 코드는 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import sys, os
sys.path.append(os.pardir)  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from simple_convnet import SimpleConvNet
from common.trainer import Trainer
 
# 데이터 읽기
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=False) #flatten이 false라서 4차원 데이터로 
 
# 시간이 오래 걸릴 경우 데이터를 줄인다.
#x_train, t_train = x_train[:5000], t_train[:5000]
#x_test, t_test = x_test[:1000], t_test[:1000]
 
max_epochs = 20
 
network = SimpleConvNet(input_dim=(1,28,28), 
                        conv_param = {'filter_num'30'filter_size'5'pad'0'stride'1},
                        hidden_size=100, output_size=10, weight_init_std=0.01)
                        
trainer = Trainer(network, x_train, t_train, x_test, t_test,
                  epochs=max_epochs, mini_batch_size=100,
                  optimizer='Adam', optimizer_param={'lr'0.001},
                  evaluate_sample_num_per_epoch=1000)
trainer.train()
 
# 매개변수 보존
network.save_params("params.pkl")
print("Saved Network Parameters!")
 
# 그래프 그리기
markers = {'train''o''test''s'}
= np.arange(max_epochs)
plt.plot(x, trainer.train_acc_list, marker='o', label='train', markevery=2)
plt.plot(x, trainer.test_acc_list, marker='s', label='test', markevery=2)
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(01.0)
plt.legend(loc='lower right')
plt.show()
cs

 이미 학습되어있는 params.pkl 객체를 불러와서 코드를 돌렸습니다.

 

 

 

(5) CNN 세부사항

1번째 층의 가중치 시각화

앞에서 mnist 데이터셋으로 한 CNN학습에서 1번째 층의 합성곱 계층의 가중치 형상이 (30,1,5,5) 였습니다.

채널이 1개라는 것은 이 필터를 1채널의 회색조 이미지로 시각화할 수 있다는 뜻입니다.

이 합성곱 계층의 1층째 필터를 이미지로 시각화해서 전 후로 나타내는 코드는 다음과 같습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import numpy as np
import matplotlib.pyplot as plt
from simple_convnet import SimpleConvNet
 
def filter_show(filters, nx=8, margin=3, scale=10):
    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.05, wspace=0.05)
 
    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()
 
 
network = SimpleConvNet()
# 무작위(랜덤) 초기화 후의 가중치
filter_show(network.params['W1'])
 
# 학습된 가중치
network.load_params("params.pkl")
filter_show(network.params['W1'])
cs

학습 전 필터
학습 후 필터

가중치의 원소는 실수이며 이미지에서는 가장 작은 값(0)은 검은색, 가장 큰 값(255)은 흰색으로 정규화하여 표시됩니다.

학습 전 필터는 무작위로 초기화되고 있어 흑백의 정도에 규칙성이 없습니다.

한편, 학습을 마친 필터는 흰색에서 검은색으로 점차 변화하는 필터와 덩어리가 진 필터 등 규칙을 띄는 필터로 바뀌었습니다.

이러한 규칙성 있는 필터는 에지(색상이 바뀐 경계선)와 블롭(국소적으로 덩어리 진 영역) 등을 바라보고 있는 것입니다.

 

 

 

딥러닝 시각화에 관한 연구에 따르면, 계층이 깊어질수록 추출되는 정보는 더 추상화된다는 것을 알 수 있습니다.

합성곱 계층을 여러 겹 쌓으면, 층이 깊어지면서 더 복잡하고 추상화된 정보가 추출된다는 것입니다.

처음 층은 단순한 에지에 반응하고, 이어서 텍스처에 반응하고, 더 복잡한 사물의 일부에 반응하도록 변화합니다.

즉, 층이 깊어지면서 뉴런이 반응하는 대상이 단순한 모양에서 고급 정보로 변화해갑니다.

 

CNN을 좀 더 시각화해서 설명하는 시뮬레이션은 아래의 url을 통해 확인할 수 있습니다.

https://cs.stanford.edu/people/karpathy/convnetjs/demo/cifar10.html

 

ConvNetJS CIFAR-10 demo

 

cs.stanford.edu

youtu.be/3JQ3hYko51Y

 

 

 

(6) 대표적인 CNN

① LeNet

LeNet은 손글씨 숫자를 인식하는 네트워크로 첫 CNN이며 합성곱 계층과 풀링 계층을 반복하고, 마지막으로 완전 연결 계층을 거치면서 결과를 출력하는 CNN입니다.

LeNet

현재 쓰이는 CNN과 다른 점은 활성화 함수를 ReLU가 아닌 Sigmoid를 사용했으며, 최대 풀링과정이 없고 중간 데이터의 크기가 작아지는 Subsampling을 하였습니다.

 

 

② AlexNet

LeNet의 큰 구조와는 다른 점이 없으며 활성화 함수로 ReLU를 이용하고 LRN(Local Response Normalization)이라는 국소적 정규화를 실시하는 계층을 이용하며 드롭아웃을 사용했습니다.

또한 2개의 GPU로 병렬연산을 수행하기 위해서 병렬적인 구조로 설계되었다는 점이 가장 큰 변화입니다.

AlexNet

 

 

 

'It공부 > Deep learning' 카테고리의 다른 글

(2)-2. 자연어와 단어의 분산 표현  (2) 2020.06.30
(2)-1 자연어 처리 하기 전에  (0) 2020.06.29
6. 학습 관련 기술들  (0) 2020.06.21
5. 오차역전파법  (0) 2020.06.19
4. 신경망 학습  (0) 2020.06.17
Comments