본문 바로가기

Deep Learning(강의 및 책)/밑바닥부터 시작하는 딥러닝

7. 합성곱 신경망(CNN)

728x90

밑바닥부터 시작하는 딥러닝을 바탕으로 제작하였습니다.

 

7.1. 전체 구조

합성곱 신경망(Convolution neural network)은 이미지 인식과 음성 인식 등 다양한 곳에서 사용되는데, 특히 이미지 인식 분야에서 딥러닝을 활용한 기법은 거의 다 CNN을 기초로 합니다.

CNN을 보기 전까지 나온 신경망들은 인접하는 계층의 모든 뉴런과 결합되어 있습니다. 이를 완전연결(fully-connected)이라고 하며 완전히 연결된 계층을 Affine 계층이라는 이름으로 구현했습니다. 예를 들어 층이 5개인 완전연결 신경망을 그림으로 그리면

이와 같이 나타낼 수 있습니다. 완전연결 신경망은 Affine 계층 뒤에 활성화 함수를 갖는 ReLU 계층(혹은 sigmoid 계층)이 이어집니다. 이 그림에서는 Affine-ReLU 조합이 4개가 쌓였고, 마지막 5번째 층은 Affine 계층에 이어 softmax 계층에서 최종 결과를 얻습니다.

이제 CNN 구저에 대해서 그려보면

이와 같이 나타낼 수 있습니다. CNN 구조는 합성곱 계층(Conv)과 풀링 계층(pooling)이 추가됩니다. CNN의 계층은 'Conv-ReLU-(Pooling)'흐름으로 연결됩니다(풀링 계층은 생략하기도 합니다). 지금까지의 'Affine-ReLU' 연결이 'Conv-ReLU-(Pooling)'으로 바뀌었다고 생각할 수 있습니다. 또 다른 점은 출력에 가까운 층에서는 지금까지의 'Affine-ReLU' 구성을 사용할 수 있다는 것입니다. 또, 마지막 출력 계층에서는 'Affine-Softmax' 조합을 그대로 사용합니다. 이상은 일반적인 CNN에서 흔히 볼 수 있는 구성입니다.

 

7.2. 합성곱 계층

CNN에서는 패딩(padding), 스트라이드(stride) 등 CNN 고유의 용어가 등장합니다. 또, 각 계층 사이에는 3차원 데이터같이 입체적인 데이터가 흐른다는 점에서 완전연결 신경망과 다릅니다.

 ■ 완전연결 계층의 문제점

완전연결 계층은 '데이터의 형상이 무시'된다는 문제가 있습니다. 입력 데이터가 이미지인 경우를 예를 들면, 이미지는 통상 세로, 가로, 채널(색상)로 구성된 3차원 데이터입니다. 그러나 완전연결 계층에 입력할 때는 3차원 데이터를 평평한 1차원 데이터로 평탄화해줘야 합니다. 지금까지 MNIST 데이터셋을 이용할 때 1줄로 세운 784개의 데이터를 첫 Affine 계층에 입력했습니다.

이미지는 3차원 형상이며, 이 형상에는 공간적 정보가 있습니다. 예를 들어 인접한 픽셀은 값이 비슷하거나, RGB의 각 채널은 서로 밀접하게 관련되어 있습니다. 거리가 먼 픽셀끼리는 별 연관이 없는 등, 3차원 속에서 의미를 갖는 본질적인 패턴이 숨어 있을 것입니다. 하지만 완전연결 계층은 형상을 무시하고 모든 입력 데이터를 동등한 뉴런으로 취급하여 형상에 담긴 정보를 살릴 수 없습니다.

완전연결 계층은 이러한 문제가 있지만, 합성곱 계층은 형상을 유지합니다. 이미지도 3차원 데이터로 입력받으며, 마찬가지로 다음 계층에도 3차원 데이터로 전달합니다. 그래서 CNN에서는 이미지처럼 형상을 가진 데이터를 제대로 이해할 수 있습니다.

 

■ 합성곱 연산

합성곱 계층에서의 합성곱 연산을 처리합니다. 합성곱 연산은 이미지 처리에서 말하는 필터 연산에 해당합니다. 예를 보면

그림과 같이 합성곱 연산은 입력 데이터에 필터를 적용합니다. 이 예에서 입력 데이터는 세로, 가로 방향의 형상을 가졌고, 필터 역시 세로, 가로 방향의 차원을 갖습니다. 데이터와 필터의 형상을 (높이, 너비)로 표기하며, 이 예에서는 입력은 (4, 4), 필터는 (3, 3), 출력은 (2, 2)가 됩니다.

합성곱 연산은 위 그림처럼 필터의 윈도우를 일정 간격으로 이동해가며 입력 데이터에 적용합니다. 여기에서 말하는 윈도우는 회색 3x3 부분을 가리킵니다. 입력과 필터에서 대응하는 원소끼리 곱한 후 그 총합을 구합니다(이 계산을 단일 곱셈-누산(fused multiply-add, FMA이라 합니다). 그리고 그 결과를 출력의 해당 장소에 저장하고 모든 장소에서 수행하면 합성곱 연산의 출력이 완성됩니다.

완전연결 신경망에는 가중치 매개변수와 편향이 존재하는데. CNN에서는 필터의 매개변수가 '가중치'에 해당합니다. CNN에도 편향이 존재합니다.

그림과 같이 편향은 필터를 적용한 후의 데이터에 더해집니다. 그리고 편향은 항상 하나(1x1)만 존재합니다. 그 하나의 값을 필터를 적용한 모든 원소에 더합니다.

 

 ■ 패딩(padding)

합성곱 연산을 수행하기 전에 입력 데이터 주변을 특정 값으로 채우기도 하는데 이를 패딩이라 합니다.

예를 들어 (4, 4) 크기의 입력 데이터에 폭이 1인 패딩을 적용한 모습입니다. 폭 1짜리 패딩이라 하면 입력 데이터 사방 1픽셀을 의미합니다. 처음 크기가 (4, 4)인 입력 데이터에 패딩이 추가되어 (6, 6)이 됩니다. 이 입력에 (3, 3) 크기의 필터를 걸면 (4, 4) 크기의 출력 데이터가 생성됩니다.

 

 ■ 스트라이드(stride)

필터를 적용하는 위치의 간격을 스트라이드라고 합니다. 지금까지 본 예는 모두 스트라이드가 1이었지만, 예를 들어 스트라이드를 2로 하면 필터를 적용하는 윈도우가 두 칸씩 이동합니다.

예시를 보면 크기가 (7, 7)인 입력 데이터에 스트라이드를 2로 설정한 필터를 적용합니다. 이처럼 스트라이드는 필터를 적용하는 간격을 지정합니다. 스트라이드를 2로 설정하니 출력은 (3, 3)이 됩니다. 이처럼 스트라이드를 키우면 출력의 크기는 작아집니다. 한편, 패딩을 크게 하면 출력의 크기는 커집니다. 이러한 관계를 수식화할 수 있는데 수식을 살펴보겠습니다.

위에 표와 수식을 이용해 출력 결과의 크기를 알 수 있습니다. 딥러닝 프레임워크 중에는 값이 딱 나눠떨어지지 않을 때도 있는데 이 경우 가장 가까운 정수로 반올림하는 등, 특별히 에러를 내지 않고 진행하도록 구현하는 경우도 있습니다.

 

 ■ 3차원 데이터의 합성곱 연산

이번에는 채널까지 고려한 3차원 데이터를 다루는 합성곱 연산을 살펴보겠습니다.

예를 들어 연산을 보면 이와 같이 이루어집니다. 채널쪽으로 특징 맵이 여러 개 있다면 입력 데이터와 필터의 합성곱 연산을 수행하고, 그 결과를 더해서 하나의 출력을 얻습니다. 3차원의 합성곱 연산에서 주의할 점은 입력 데이터의 채널 수 와 필터의 채널 수가 같아야 합니다.

 

 ■ 블록으로 생성하기

3차원의 합성곱 연산의 데이터와 필터를 직육면체 블록(3차원 직육면체)이라고 생각하면 쉽습니다.

3차원 데이터를 다차원 배열로 나타낼 때는 (채널, 높이, 너비) 순으로 작성을 하고 입력 데이터의 형상은 (C, H, W)가 되고 필터의 형상은 (C, FH, FW)가 됩니다. 이 예시에서 출력 데이터는 한 장의 특징 맵(채널이 1개인 특징 맵)입니다.

만약 합성곱 연산의 출력으로 다수의 채널을 내보내기를 원한다면 필터를 여러 개 사용하면 됩니다. 예를 들어

이와 같이 필터를 FN개를 적용하면 출력 맵도 FN개가 생성됩니다. 그리고 그 FN개의 맵을 모으면 형상이 (FN, OH, OW)가 됩니다. 이 완성된 블록을 다음 계층으로 넘기겠다는 것이 CNN의 처리 흐름입니다.

 

 ■ 배치 처리

신경망 처리에서는 입력 데이터를 한 덩어리로 묶어 배치로 처리했습니다. 완전연결 신경망을 구현하면서는 이 방식을 지원하여 처리 효율을 높이고, 미니배치 방식의 학습도 지원하도록 했습니다. 합성곱 연산도 마찬가지로 배치 처리를 지원할 수 있습니다.

각 계층을 흐르는 데이터의 차원을 하나 늘려 4차원 데이터로 저장합니다. 구체적으로는 데이터를 (데이터 수, 채널 수, 높이, 너비) 순으로 저장합니다. 데이터가 N개일 때

이와 같은 흐름을 보여줍니다. 각 데이터의 선두에 배치용 차원을 추가했습니다. 이처럼 데이터는 4차원 형상을 가진 채 각 계층을 타고 흐릅니다. 여기서 주의할 점은 신경망에 4차원 데이터가 하나 흐를 때마다 데이터 N개에 대한 합성곱 연산이 이뤄진다는 것입니다. 즉 N회 분의 처리를 한 번에 수행하는 것입니다.

 

7.3. 풀링 계층

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

이와 같이 2x2 영역을 원소 하나로 집약하여 공간 크기를 줄입니다. 위 그림은 2x2 최대 풀링(max pooling)을 스트라이드 2로 처리하는 순서를 보여줍니다. 최대 풀링은 최댓값을 구하는 연산으로, '2x2'는 대상 영역의 크기를 뜻합니다. 대상 영역에서 가장 큰 원소를 하나씩 꺼내는 연산을 합니다. 스트라이드는 2로 설정이 되어서 2칸씩 이동하면서 연산을 하는 것을 볼 수 있습니다.

 

 ■ 풀링 계층의 특징

 - 학습해야 할 매개변수가 없다

풀링 계층은 합성곱 계층과 달리 학습해야 할 매개변수가 존재하지 않습니다. 풀링은 대상 영역에서 최댓값이나 평균을 취하는 명확한 처리이므로 특별히 학습해야 할 것이 없습니다.

 - 채널 수가 변하지 않는다

풀링 연산은 입력 데이터의 채널 수 그대로 출력 데이터로 내보냅니다. 채널마다 독립적으로 계산하기 때문에 채널 수가 줄지 않고 데이터의 크기가 줄어들 뿐입니다.

 - 입력의 변화에 영향을 적게 받는다

입력 데이터가 조금 변해도 풀링의 결과는 잘 변하지 않습니다. 

예를 들어 위 그림과 같이 예시가 존재할 때 입력 데이터의 차이(데이터가 오른쪽으로 1칸씩 이동)를 풀링이 흡수해 사라지게 하는 모습을 보여줍니다.

 

7.4. 합성곱/풀링 계층 구현하기

 ■ 4차원 배열

CNN 계층에 흐르는 데이터는 4차원입니다. 예를 들어 데이터의 형상이 (10, 1, 28, 28)이라면, 이는 높이 28, 너비 28, 채널 1개인 데이터가 10개라는 이야깁니다. 이를 파이썬으로 구현하면

이와 같이 나타낼 수 있습니다.

 

 

여기에서 (10개 중) 첫 번째 데이터에 접근하려면 단순히 x[0]이라고 씁니다. 마찬가지로 두 번째 데이터는 x[1] 위치에 있습니다.

이와 같이 각 인덱스에 존재하는 데이터에 형상을 확인할 수 있습니다.

 

 

 

 ■ im2col로 데이터 전개하기

합성곱 연산을 곧이곧대로 구현하려면 for문을 겹겹이 써야 합니다. 이는 매우 귀찮고, 성능이 떨어집니다. for문 대신 사용할 것이 im2col이라는 편의 함수입니다.

im2col은 입력 데이터를 필터링하기 좋게 전개하는 함수입니다.

위 그림과 같이 3차원 입력 데이터에 im2col을 적용하면 2차원 행렬로 바뀝니다. 이 전개를 필터를 적용하는 모든 영역에서 수행하는 게 im2col입니다. 하지만 실제 상황에서 im2col을 이용해 데이터를 펼칠 때 펼치는 영역이 겹치는 경우가 대부분입니다. 필터 적용 영역이 겹치게 되면 im2col로 전개한 후의 원소 수가 원래 블록의 원소 수보다 많아집니다. 그래서 im2col을 사용해 구현하면 메모리가 더 많이 소비되는 단점이 있습니다. 그래도 컴퓨터는 큰 행렬을 묶어서 계산하는 데 탁월하며 빠르게 계산할  수 있습니다.

 

 ■ 합성곱 계층 구현하기

일단 im2col 함수를 먼저 구현하면

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(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1)
    return col

이와 같이 작성할 수 있습니다. im2col 함수의 인터페이스는

im2col(input_data, filter_h, filter_w, stride = 1, pad =0) 이와 같이 있고 각각 살펴보면

- input_data = (데이터 수, 채널 수, 높이, 너비)의 4차원 배열로 이뤄진 입력 데이터

- filter_h = 필터의 높이

- filter_w = 필터의 너비

- stride = 스트라이드

- pad = 패딩

이와 같습니다. 이 im2col은 필터 크기, 스트라이드, 패딩을 고려하여 입력 데이터를 2차원 배열로 전개합니다. 이 함수를 직접 실행해보면

import numpy as np
import sys, os
sys.path.append(os.pardir)
from common.util import im2col

x1 = np.random.rand(1, 3, 7, 7) # (데이터 수, 채널 수, 높이, 너비)
col1 = im2col(x1, 5, 5, stride = 1, pad = 0)
print(col1.shape)

x2 = np.random.rand(10, 3, 7, 7) # 데이터 10개
col2 = im2col(x2, 5, 5, stride = 1, pad = 0)
print(col2.shape)

실행결과

이와 같이 형상을 확인할 수 있습니다. 두 가지 예를 보여주고 있는데 첫 번째는 배치 크기가 1(데이터 1개), 채널은 3개, 높이, 너비는 7x7의 데이터이고, 두 번째는 배치 크기만 10이고 나머지는 첫 번째와 같습니다. im2col 함수를 적용한 두 경우 모두 2번째 차원의 원소는 75개입니다. 이 값은 필터의 원소 수와 같습니다.(채널 3, 5x5 데이터). 또한 배치 크기가 1일 때는 im2col의 결과의 크기가 (9, 75)이고, 10일 때는 그 10배인 (90, 75) 크기의 데이터가 저장됩니다.

이제는 im2col함수를 이용하여 합성곱 계층을 구현해보겠습니다. 합성곱 계층을 Convolution이라는 클래스로 구현하겠습니다.

class Convolution:
    def __init__(self, W, b, stride = 1, pad = 0):
        self.W =W
        self.b = b
        self.stride = stride
        self.pad = pad
        
    def forward(self, x):
        FN, C, FH, FW = self.W.shape
        N, C, H, W = x.shape
        out_h = int(1 + (H + 2 * self.pad - FH) / self.stride)
        out_w = int(1 + (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(0, 3, 1, 2)
        
        return out

이와 같이 구현할 수 있습니다. 합성곱 계층은 필터, 편향, 스트라이드, 패딩을 인수로 받아 초기화합니다. 필터는 4차원 형상을 가집니다. forward에서는 입력 데이터를 im2col로 전개하고 필터도 reshape를 통해 2차원 배열로 전개합니다. 그리고 이렇게 전개된 두 행렬의 곱을 구합니다. 마지막에서는 출력 데이터를 적절한 형상으로 바꿔줍니다.  이때 넘파이의 transpose 함수를 사용하는데, 이는 다차원 배열의 축 순서를 바꿔주는 함수입니다. 인덱스를 지정하여 축의 순서를 변경합니다.

 

 ■ 풀링 계층 구현하기

풀링 계층 구현도 합성곱 계층과 마찬가지로 im2col을 사용해 입력 데이터를 전개합니다. 단, 풀링의 경우엔 채널 쪽이 독립적이라는 점이 합성곱 계층 때와는 다릅니다.

입력 데이터를 전개한 후 전개한 행렬에서 행별 최댓값을 구하고 적절한 형상으로 바꿔주는 과정을 거치면 됩니다. 이러한 풀링 계층을 구현해보겠습니다.

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)
        out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)

        return out
        

이와 같이 구현할 수 있습니다. 입력 데이터를 전개하고 행별 최댓값을 구한 후 적절한 모양으로 성형하는 과정을 잘 나타냅니다.

 

7.5. CNN 구현하기

MNIST 데이터셋을 사용하는 CNN을 구현해보겠습니다. 계층들을 조합하여 손글씨 숫자를 인식하는 CNN을 구현할 것이고 네트워크는 'Convolution - ReLU - Pooling - Affine - ReLU - Affine - Softmax' 순으로 흐릅니다. SimpleConvNet이라는 이름의 클래스로 작성하겠습니다.

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=(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):
        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()

일단 초기화 메서드를 살펴보겠습니다. 합성곱 계층의 하이퍼파라미터는 딕셔너리 형태로 주어집니다(conv_param). 필요한 하이퍼파라미터의 값이 ['filter_num':30, 'filter_size':5, 'pad':0, 'stride':1]처럼 저장된다는 뜻입니다.

초기화 인수로 주어진 합성곱 계츠으이 하이퍼파라미터를 딕셔너리에서 꺼냅니다. 그리고 합성곱 계층의 출력 크기를 계산합니다. 그리고 가중치 매개변수를 초기화합니다. 그다음, 순서가 있는 딕셔너리인 layers에 계층들을 차례로 추가하고 마지막 SoftmaxWithLoss 계층만큼은 last_layer라는 별도 변수에 저장해둡니다.

이제 predict 메서드와 손실 함수의 값을 구하는 loss 메서드를 구현하겠습니다.

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)

인수 x는 입력 데이터, t는 정답 레이블입니다. 추론을 수행하는 predict 메서드는 초기화 때 layers에 추가한 계층을 맨 앞에서부터 차례로 forward 메서드를 호출하며 그 결과를 다음 계층에 전달합니다. 손실함수를 구하는 loss 메서드는 predict 메서드의 결과를 인수로 마지막 층의 forward 메서드를 호출합니다.

이어서 오차역전파법으로 기울기를 구하는 코드를 구현하겠습니다.

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

매개변수의 기울기는 오차역전파법으로 구합니다. 이 과정은 순전파와 역전파를 반복합니다. 마지막으로 grads라는 딕셔너리 변수에 각 가중치 매개변수의 기울기를 저장합니다.

이제 SimpleConvNet으로 MNIST 데이터셋을 학습해보겠습니다.

# coding: utf-8
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)

# 시간이 오래 걸릴 경우 데이터를 줄인다.
#x_train, t_train = x_train[:5000], t_train[:5000]
#x_test, t_test = x_test[:1000], t_test[:1000]

max_epochs = 3

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'}
x = 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(0, 1.0)
plt.legend(loc='lower right')
plt.show()

이러한 학습 코드가 존재하고 실행 결과를 보면

test 결과가 98% 이상인 것을 볼 수 있고 그래프를 그리면

 

 

 

 

 

 

 

이와 같은 정확도를 볼 수 있습니다.

'Deep Learning(강의 및 책) > 밑바닥부터 시작하는 딥러닝' 카테고리의 다른 글

8. 딥러닝  (0) 2022.02.18
6. 학습 관련 기술들  (0) 2022.02.15
5. 오차역전파법  (0) 2022.02.14
4. 신경망 학습  (0) 2022.02.07
3. 신경망  (0) 2022.02.07