수학/딥러닝 이론

02-2. 신경망 (2) - 출력층 설계와 MNIST 구현

AI 꿈나무 2020. 9. 9. 23:46
반응형

(밑바닥부터 시작하는 딥러닝, 사이토고치)를 바탕으로 작성하였습니다.


 

신경망 (2) - 출력층 설계와 MNIST 구현

 이전 포스팅에서는 3층 신경망 구현을 해보았습니다.

 이번 포스팅에서는 출력층 설계와 MNIST를 구현해보겠습니다.

 


 

 

5. 출력층 설계하기

 신경망은 분류와 회귀 모두에 이용할 수 있습니다. 다만 둘 중 어떤 문제냐에 따라 출력층에서 사용하는 활성화 함수가 달라집니다. 일반적으로 회귀에는 항등 함수를, 분류에는 소프트맥스 함수를 사용합니다.

 

 기계학습 문제는 분류(classification)와 회귀(regression)로 나뉩니다. 분류는 데이터가 어느 클래스에 속하느냐 문제입니다. 사진 속 인물의 성별을 분류하는 문제가 여기에 속합니다. 한편 회귀는 입력 데이터에서 (연속적인) 수치를 예측하는 문제입니다. 사진 속 인물의 몸무게(57.4kg?)를 예측하는 문제가 회귀입니다.

 

5.1 항등 함수와 소프트맥스 함수 구현하기

 항등 함수(identity function)는 입력을 그대로 출력합니다. 입력과 출력이 항상 같다는 뜻의 항등입니다.

 따라서 출력층에서 항등 함수를 사용하면 입력 신호가 그대로 출력 신호가 됩니다.

 

항등 함수

 

 한편 분류에서 사용하는 소프트맥수 함수(softmax function)의 식은 다음과 같습니다.

 

소프트맥스 함수 식

 

 n은 출력층의 뉴런 수, $y_{k}$는 그 중 $k$번째 출력임을 뜻합니다.

 소프트맥스 함수의 분자는 입력 신호 $a_{k}$의 지수 함수, 분모는 모든 입력 신호의 지수 함수의 합으로 구성됩니다.

 분모에서 보듯 출력층의 각 뉴련이 모든 입력 신호에서 영향을 받습니다.

 

소프트맥수 함수

 

 소프트맥스 함수를 구현해 봅시다.

a = np.array( [0.3, 2.9, 4.0] )

exp_a = np.exp(a) # 지수 함수
print(exp_a)
>>> [ 1.34985881 18.17414537 54.59815003 ]

sum_exp_a = np.sum(exp_a) # 지수 함수의 합
print(sum_exp_a)
>>> 74.1221542102

y = exp_a / sum_exp_a # 소프트 맥스 함수
print(y)
>>> [ 0.01821127 0.24519181 0.73659691 ]



함수로 정의해 봅시다.

def softmax(a):
    exp_a = np.exe(a)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    
    return y

 

 5.2 소프트맥스 함수 구현 시 주의점

 컴퓨터로 계산할 때는 오버플로 문제가 있습니다. 소프트맥스 함수는 지수 함수를 사용하는데, 지수 함수는 쉽게 아주 큰 값을 내뱉습니다. $e^{10}$은 20,000이 넘고, $e^{100}$은 0이 40개 넘는 큰 값이 됩니다. 그리고 이런 큰 값끼리 나눗셈을 하면 결과 수치가 '불안정'해집니다.

 

 오버플로(overflow)란?
 컴퓨터는 수를 4바이트나 8바이트와 같이 크기가 유한한 데이터로 다룹니다. 다시 말해 표현할 수 있는 수의 범위가 한정되어 너무 큰 값은 표현할 수 없다는 문제가 발생합니다. 이것을 오버플로라 하며, 컴퓨터로 수치를 계산할 때 주의할 점입니다.

 이 문제를 해결하도록 소프트맥스 함수 구현을 개선해봅시다. 다음은 개선한 수식입니다.

 

 

 이 식이 말하는 것은 소프트맥스의 지수 함수를 계산할 때 어떤 정수를 더해도 ( 혹은 빼도 ) 결과가 바뀌지 않는 다는 것입니다. 오버플로를 막을 목적으로 C를 입력 신호 중 최댓값을 이용하여 빼주는 것이 일반적입니다.

 

 

 구체적인 예시를 보여드리겠습니다.

a = np.array( [1010, 1000, 990] )
print( np.exp(a) / np.sum(np.exp(a)) ) # 소프트맥스 함수의 계산
>>> array( [ nan, nan, nan] ) # 제대로 계산되지 않습니다.



오버플로 문제를 개선해보겠습니다.

c = np.max(a) # c는 입력의 최댓값을 이용합니다.
print( a - c )
>>> array( [0, -10, -20] )

print( np.exp(a - c) / np.sum( np.exp(a - c) )
>>> array( [ 9.9995460e-01, 4.53978686e-05, 2.06106005e-09 ] ) # 제대로 계산이 되는것을 확인할 수 있습니다.

 

 이를 바탕으로 소프트맥스 함수를 다시 구현하면 다음과 같습니다.

def softmax(a):
    c = np.max(a)
    exp_a = np.exp( a - c ) # 오버플로 대책
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a
    
    return y

 

 

 5.3 소프트맥스 함수의 특징

 softmax() 함수를 사용하면 신경망의 출력은 다음과 같이 계산할 수 있습니다.

a = np.array( [0.3, 2.9, 4.0] )
y = softmax(a)

print(y)
>>> [ 0.01821128 0.24519181 0.73659691]

print( np.sum(y) ) # 소프트맥스 함수 출력의 총합은 1 입니다.
>>> 1.0

 

 

소프트맥스 함수의 특징

1. 출력 총합이 1이 됩니다.
 이 성질 덕분에 소프트맥스 함수의 출력을 '확률'로 해석할 수 있습니다. 예를 들어 y[0]의 확률은 1.8%, y[2]의 확률은 73.7%로 해석할 수 있습니다. 그리고 이 확률들로부터 '2번째 원소의 확률이 가장 높으니, 답은 2번째 클래스다'라고 할 수 있습니다.

2. 소프트맥스 함수를 적용해도 각 원소의 대소 관계는 변하지 않습니다.
 이는 지수 함수 $y$ = exp($x)가 단조 증가 함수이기 때문입니다. 예를 들어 a에서 가장 큰 원소는 2번째 원소이고, y에서 가장 큰 원소도 2번째 원소입니다.

3. 현업에서 출력층의 소프트맥스 함수는 생략하는 것이 일반적입니다.
 지수 함수 계산에 드는 자원 낭비를 줄이고자 하는 것이 이유입니다. 신경망을 이용한 분류에서는 일반적으로 가장 큰 출력을 내는 뉴런에 해당하는 클래스로만 인식합니다. 그리고 소프트맥스 함수를 적용해도 출력이 가장 큰 뉴런의 위치는 달라지지 않습니다. 결과적으로 신경망으로 분류할 때는 출력층의 소프트맥스 함수를 생략해도 됩니다.

 

 

 기계학습의 문제 풀이는 학습추론(inference)의 두 단계를 거쳐 이뤄집니다. 학습 단계에서 모델을 학습하고(직업 훈련을 받고), 추론 단계에서 앞서 학습한 모델로 미지의 데이터에 대해서 추론(분류)을 수행합니다(현장에 나가 진짜 일을 합니다). 방금 설명한 대로, 추론 단계에서는 출력층의 소프트맥스 함수를 생략하는 것이 일반적입니다. 한편, 신경망을 학습시킬 때는 출력층에서 소프트맥스 함수를 사용합니다.

 

 5.4 출력층의 뉴런 수 정하기

 출력층의 뉴런 수는 풀려는 문제에 맞게 적절히 정해야 합니다. 분류에서는 분류하고 싶은 클래스 수로 설정하는 것이 일반적입니다. 예를 들어 입력 이미지를 숫자 0부터 9 중 하나로 분류하는 문제라면 출력층의 뉴런을 10개로 설정합니다.

 

출력층의 뉴런은 각 숫자에 대응한다.

 출력층 뉴런은 위에서부터 차례로 숫자 0, 1, ~ , 9에 대응하며, 뉴런의 짙은 농도가 해당 뉴런의 출력 값의 크기를 의미합니다. 이 그림에서는 색이 가장 짙은 $y_{2}$ 뉴런이 가장 큰 값을 출력하는 것입니다. 따라서 이 신경망이 선택한 클래스는 $y_{2}$, 즉 입력 이미지를 숫자 '2'로 판단했음을 의미합니다.


 

 

6. 손글씨 숫자 인식

 신경망의 구조를 배웠으니 손글씨 숫자 분류를 해봅시다. 학습 과정은 생략하고, 추론 과정만 구현할 것입니다. 이 추론 과정을 신경망의 순전파(foward propagation)라고도 합니다.

 

 기계학습과 마찬가지로 신경망도 두 단계를 거쳐 문제를 해결합니다. 먼저 훈련 데이터(학습 데이터)를 사용해 가중치 매개변수를 학습하고, 추론 단계에서는 앞서 학습한 매개변수를 사용하여 입력 데이터를 분류합니다.

 

 6.1 MNIST 데이터셋

 이번 예에서 사용하는 데이터셋은 MNIST라는 손글씨 숫자 이미지 집합입니다. MNIST 데이터 셋은 0부터 9까지의 숫자 이미지로 구성됩니다. 훈련 이미지 60,000장, 시험 이미지 10,000장로 구성되어 있으며, 밑바닥부터 시작하는 딥러닝에서 제공해주는 파일을 이용했습니다.

 여기에서 다운로드 받을 수 있습니다.

 

MNIST 이미지 데이터셋의 예

 MNIST의 이미지 데이터는 28 X 28 크기의 회색조 이미지이며, 각 픽셀은 0에서 255까지의 값을 취합니다. 각 이미지에는 또한 '7', '2', '1'과 같이 그 이미지가 실제 의미하는 숫자가 레이블로 붙어 있습니다.

 

 책에서는 MNIST 데이터셋을 내려받아 이미지를 넘파이 배열로 변환해주는 파이썬 스크립트를 제공해주고 있습니다.

이미지를 numpy 배열로 변환되면 배열 연산이 가능하다는 것을 의미합니다. 확인해보도록 하겠습니다.

 

 

 MNIST 이미지 데이터를 넘파이 배열로 변환해주는 스크립트 (load_mnist함수)

# coding: utf-8
try:
    import urllib.request
except ImportError:
    raise ImportError('You should use Python 3.x')
import os.path
import gzip
import pickle
import os
import numpy as np


url_base = 'http://yann.lecun.com/exdb/mnist/'
key_file = {
    'train_img':'train-images-idx3-ubyte.gz',
    'train_label':'train-labels-idx1-ubyte.gz',
    'test_img':'t10k-images-idx3-ubyte.gz',
    'test_label':'t10k-labels-idx1-ubyte.gz'
}

dataset_dir = os.path.dirname(os.path.abspath(__file__))
save_file = dataset_dir + "/mnist.pkl"

train_num = 60000
test_num = 10000
img_dim = (1, 28, 28)
img_size = 784


def _download(file_name):
    file_path = dataset_dir + "/" + file_name
    
    if os.path.exists(file_path):
        return

    print("Downloading " + file_name + " ... ")
    urllib.request.urlretrieve(url_base + file_name, file_path)
    print("Done")
    
def download_mnist():
    for v in key_file.values():
       _download(v)
        
def _load_label(file_name):
    file_path = dataset_dir + "/" + file_name
    
    print("Converting " + file_name + " to NumPy Array ...")
    with gzip.open(file_path, 'rb') as f:
            labels = np.frombuffer(f.read(), np.uint8, offset=8)
    print("Done")
    
    return labels

def _load_img(file_name):
    file_path = dataset_dir + "/" + file_name
    
    print("Converting " + file_name + " to NumPy Array ...")    
    with gzip.open(file_path, 'rb') as f:
            data = np.frombuffer(f.read(), np.uint8, offset=16)
    data = data.reshape(-1, img_size)
    print("Done")
    
    return data
    
def _convert_numpy():
    dataset = {}
    dataset['train_img'] =  _load_img(key_file['train_img'])
    dataset['train_label'] = _load_label(key_file['train_label'])    
    dataset['test_img'] = _load_img(key_file['test_img'])
    dataset['test_label'] = _load_label(key_file['test_label'])
    
    return dataset

def init_mnist():
    download_mnist()
    dataset = _convert_numpy()
    print("Creating pickle file ...")
    with open(save_file, 'wb') as f:
        pickle.dump(dataset, f, -1)
    print("Done!")

def _change_one_hot_label(X):
    T = np.zeros((X.size, 10))
    for idx, row in enumerate(T):
        row[X[idx]] = 1
        
    return T
    

def load_mnist(normalize=True, flatten=True, one_hot_label=False):
    """MNIST 데이터셋 읽기
    
    Parameters
    ----------
    normalize : 이미지의 픽셀 값을 0.0~1.0 사이의 값으로 정규화할지 정한다.
    one_hot_label : 
        one_hot_label이 True면、레이블을 원-핫(one-hot) 배열로 돌려준다.
        one-hot 배열은 예를 들어 [0,0,1,0,0,0,0,0,0,0]처럼 한 원소만 1인 배열이다.
    flatten : 입력 이미지를 1차원 배열로 만들지를 정한다. 
    
    Returns
    -------
    (훈련 이미지, 훈련 레이블), (시험 이미지, 시험 레이블)
    """
    if not os.path.exists(save_file):
        init_mnist()
        
    with open(save_file, 'rb') as f:
        dataset = pickle.load(f)
    
    if normalize:
        for key in ('train_img', 'test_img'):
            dataset[key] = dataset[key].astype(np.float32)
            dataset[key] /= 255.0
            
    if one_hot_label:
        dataset['train_label'] = _change_one_hot_label(dataset['train_label'])
        dataset['test_label'] = _change_one_hot_label(dataset['test_label'])    
    
    if not flatten:
         for key in ('train_img', 'test_img'):
            dataset[key] = dataset[key].reshape(-1, 1, 28, 28)

    return (dataset['train_img'], dataset['train_label']), (dataset['test_img'], dataset['test_label']) 


if __name__ == '__main__':
    init_mnist()

 

 

load_mnist 함수

 MNIST 데이터를 '(훈련 이미지, 훈련 레이블), (시험 이미지, 시험 레이블)' 형식으로 반환합니다.
 또한 이미지 파일을 넘파이 배열로 변환하여 배열 연산을 가능하게 해줍니다.
 인수로는 normalize, flatten, one_hot_label 세 가지를 설정할 수 있습니다.

1. normalize 인수
 입력 이미지의 픽셀 값을 0.0~1.0 사이의 값으로 정규화할지를 정합니다. False로 설정하면 입력 이미지의 픽셀은 원래 값 그대로 0~255 사이의 값을 유지합니다.

2. flatten 인수
 입력 이미지를 평탄하게, 즉 1차원 배열로 만들지를 정합니다. False로 설정하면 입력 이미지를 1 X 28 X 28의 3차원 배열로, True로 설명하면 28 X 28 = 784개의 원소로 이뤄진 1차원 배열로 저장합니다.

3. one_hot_label
 레이블을 원-핫 인코딩 형태로 저장할지를 정합니다.
 원-핫 인코딩이란, 예를 들어 [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]처럼 정답을 뜻하는 원소만 1이고 나머지는 모두 0인 배열입니다. False로 설정하면 '7'이나 '2'와 같이 숫자 형태의 레이블을 저장합니다.

 

pickle 기능
 이는 프로그램 실행 중에 특정 객체를 파일로 저장하는 기능입니다. 저장해준 pickle 파일을 로드하면 실행 당시의 객체를 즉시 복원할 수 있습니다. MNIST 데이터셋을 읽는 load_mnis() 함수에서도 (2번째 이후의 읽기 시) pickle을 이용합니다.
pickle 덕분에 MNIST 데이터를 순식간에 준비할 수 있습니다.

 

 load_mnist 함수를 이용하여 MNIST 데이터를 아주 쉽게 가져올 수 있습니다.

 구글 colab를 이용하여 데이터를 가져오도록 하겠습니다.

 

 

 MNIST 데이터 갖고오기

from google.colab import drive # 코랩과 구글드라이드를 연동해줍니다.
drive.mount('/gdrive', force_remount=True)


# MNIST 데이터가 저장되어 있는 구글 드라이브 디렉토리로 이동합니다.
cd /gdrive/My Drive/Deep_Learning_from_scratch/ch03


import sys, os
sys.path.append(os.pardir) # 부모 디렉터리의 파일을 가져올 수 있도록 해줍니다.
from dataset.mnist import load_mnist

# 처음 한 번은 몇 분 정도 걸립니다.
(x_train, t_train), (x_test, t_test) = load_mnist(flatten = True, normalize=False)

# 각 데이터의 형상 출력
print(x_train.shape) # (60000, 784) 784는 28 X 28 크기의 이미지 파일을 1D 배열로 변환시킨 것을 의미합니다.
print(t_train.shape) # (60000,)
print(x_test.shape) # (10000, 784)
print(t_test.shape) # (10000,)

type(x_train) # numpy.ndaaray 넘파이 배열을 의미합니다.

 mnist.py 파일은 dataset 디렉토리에 있고, 이 파일을 이용하는 스크립트는 ch03 디렉터리에서 수행한다고 가정합니다. 즉, mnist.py 파일을 찾으려면 부모 디렉터리부터 시작해야 해서 sys.path.append(os.pardir) 문장을 추가했습니다.

 여기에서 자세히 설명하였습니다.

 

 

 MNIST 이미지를 화면으로 불러보기

import sys, os
sys.path.append(os.pardir)

import numpy as np

from dataset.mnist import load_mnist

improt cv2
from google.colab.patches import cv2_imshow # 코랩에서 이미지를 호출하기 위한 모듈입니다.


(x_train, t_train), (x_test, t_test) = load_mnist(flatten = True, normalize = False)

img = x_train[0]
label = t_train[0]
print(label) # 5

print(img.shape)          # (784,)
img = img.reshape(28, 28) # 원래 이미지의 모양으로 변경
print(img.shape)          # (28, 28)

cv2_imshow(img)           # 이미지 출력

코랩에서 출력한 MNIST 이미지

 

 

 6.2 신경망의 추론 처리

 MNIST 데이터셋을 가지고 추론을 수행하는 신경망을 구현해봅시다. 이 신경망은 입력층 뉴런을 784개, 출력층 뉴런을 10개로 구성합니다.

 

 입력층 뉴런이 784개인 이유: 이미지 크기가 28 X 28 = 784이기 때문입니다.

 출력층 뉴런이 10개인 이유: 이 문제가 0에서 9까지의 숫자를 구분하는 문제이기 때문입니다.

 

 은닉층은 두개로 구성하며, 첫 번째 은닉층에 50개의 뉴런을, 두 번째 은닉층에는 100개의 뉴런을 배치할 것입니다. ( 50과 100은 임의로 정한 값 입니다. )

 

 신경망 구현하기

def get_data():
    (x_train, t_train), (x_test, t_test) = load_mnist(normalize = True, flatten = True, one_hot_label = False)
    
    return x_test, t_test # x_test와 t_test만 반환한 이유는 x_train과 t_train은 학습을 위해 필요한 데이터 이기 때문입니다.
                          # 이미 학습은 되어있다고 가정했으므로 x_test와 t_test 데이터만을 불러와 추론을 해봅시다.

def init_network(): # pickle 파일인 sample_weight.pkl에 저장된 '학습된 가중치 매개변수'를 읽습니다.
                    # 이 파일에는 가중치와 편향 매개변수가 딕셔너리 변수로 저장되어 있습니다.
    with open('sample_weight.pkl', 'rb') as f:
        network = pickle.load(f)
        
    return network
    

def predict(network, x):
    W1, W2, W3 = network['W1'], network['W2'], network['W3']
    b1, b2, b3 = network['b1'], network['b2'], network['b3']

    a1 = np.dot(x, W1) + 1
    z1 = sigmoid(a1)
    a2 = np.dot(z1, W2) + b2
    z2 = sigmoid(a2)
    a3 = np.dot(z2, W3) + b3
    y = softmax(a3)

    return y

 

 

 신경망에 의한 추론을 수행해보고, 정확도(분류가 얼마나 올바른가)도 평가해봅시다.

x, t = get_data()                # MNIST 데이터셋을 얻습니다. ( 입력 데이터 생성(시험 데이터) )
network = init_network()         # network를 생성합니다 ( 순전파 생성, 학습완료가정 )

accuracy_cnt = 0

for i in range(len(x)):           # x에 저장된 이미지 데이터를 1장씩 꺼냅니다.
    y = predict( network, x[i] )  # 꺼낸 데이터를 predict() 함수로 분류 합니다.
                                  # predict() 함수는 각 레이블의 확률을 넘파이 배열로 반환합니다.
    p = np.argmax(y)              # 확률이 가장 높은 원소의 인덱스를 얻습니다. ( 예측 결과 )
    
    if p == t[i]:                 # 신경망이 예측한 답변과 정답 레이블을 비교합니다.
        accuracy_cnt += 1         # 맞췄으면 accuracy_cnt를 +1 해줍니다.
        
print( 'Accuracy:' + str(float(accuracy_cnt) / len(x)) )
>>> Acuracy: 0.9352               # 정확도는 93.52%

 

 

정규화(normalization) : 데이터를 특정 범위로 변환하는 처리를 의미합니다. 위의 예에서는 0~255 범위인 각 픽셀의 값을
                               0.0~1.0 범위로 변환합니다. ( 단순히 픽셀의 값을 255로 나눕니다. )
전처리(pre-processing) : 신경망의 입력 데이터에 특정 변환을 가하는 것을 의미합니다.

 현업에서도 신경망에 전처리를 활발히 사용합니다. 전처리를 통해 식별 능력을 개선하고 학습 속도를 높이는 등의 사례가 많이 제시되고 있습니다. 앞의 예에서는 각 픽셀의 값을 255로 나누는 단순한 정규화를 수행했지만, 현업에서는 데이터 전체의 분포를 고려해 전처리 하는 경우가 많습니다.
 예를 들어 데이터 전체 평균과 표준편차를 이용하여 데이터들이 0을 중심으로 분포하도록 이동하거나 데이터의 확산 범위를 제한하는 정규화를 수행합니다. 그 외에도 전체 데이터를 균일하게 분포시키는 데이터 백색화(whitening)등 도 있습니다.

 

 6.3 배치 처리

 데이터를 배치로 처리함으로써 효율적이고 빠르게 처리할 수 있습니다.

 배치 처리 구현에 앞서 구현한 신경망 각 층의 가중치 형상을 출력해봅시다.

 

x, _ = get_data()
network = init_network()
W1, W2, W3 = network['W1'], network['W2'], network['W3']

x.shape
>>> (10000, 784)

x[0].shape
>>> (784)

W1.shape
>>> (784, 50)

W2.shape
>>> (50, 100)

W3.shape
>>> (100, 10)

 

신경망 각 층의 배열 형상의 추이

 이 결과에서 다차원 배열의 대응하는 차원의 원소 수가 일치함을 확인할 수 있습니다. ( 편향은 생략했습니다. )

 입력 데이터 형상은 784, 출력 데이터의 형상은 10 입니다. 

 이는 이미지 데이터를 1장만 입력했을 떄의 처리 흐름입니다.

 

 

 

 그렇다면 이미지 여러 장을 한꺼번에 입력하는 경우를 생각해봅시다.

 이미지 100개를 묶어 predict() 함수에 한 번에 넘기는 것입니다. x의 형상을 100 X 784로 바꿔서 100장 분량의 데이터를 하나의 입력 데이터로 표현하면 될 것입니다.

 

 

배치 처리를 위한 배열들의 형상 추이

 

 입력 데이터의 형상은 100 X 784, 출력 데이터 형상은 100 X 10이 됩니다.

 이는 100장 분량 입력 데이터의 결과가 한 번에 출력 됨을 나타냅니다.

 x[0]와 y[0]에는 0번째 이미지와 그 추론 결과가, x[1]과 y[1]에는 1번째의 이미지와 그 결과가 저장되는 식입니다.

 

 이처럼 하나로 묶은 입력 데이터를 배치(batch)라 합니다.

 

배치 처리는 컴퓨터로 계산할 때 큰 이점을 줍니다. 이미지 1장당 처리 시간을 대폭 줄여주기 때문입니다.
이유는
         1. 수치 계산 라이브러리 대부분이 큰 배열을 효율적으로 처리할 수 있도록 고도로 최적화되어 있기 때문
            입니다.
         2. 커다란 신경망에서는 데이터 전송이 병목으로 작용하는 경우가 자주 있는데, 배치 처리를 함으로써
            버스에 주는 부하를 줄입니다. ( 느린 I/O를 통해 데이터를 읽는 횟수가 줄어, 빠른 CPU나 GPU로 순수
            계산을 수행하는 비율이 높아집니다.)

 즉, 배치 처리를 수행함으로써 큰 배열로 이뤄진 계산을 하게 되는데, 컴퓨터에서는 큰 배열을 한꺼번에 계산하는 것이 분할된 작은 배열을 여러번 계산하는 것보다 빠릅니다.

 

 배치 처리를 구현해봅시다.

x, t = get_data()
network = init_network()

batch_size = 100 # 배치 크기
accuracy_cnt = 0

for i in range(0, len(x), batch_size):
    x_batch = x[i : i + batch_size]         # x[0:100], x[100:200] 와 같이 100장씩 묶어 꺼냅니다.
    y_batch = predict(network, x_batch)
    p = np.argmx(y_batch, axis = 1 )        # axis = 1 의미는 100 X 10의 배열 중 1번째 차원을 구성하는 원소에서 최댓값을 찾습니다.
    accuracy_cut += np.sum(p == t[i : i+batch_size] ) # == 연산자를 이용해 bool 배열을 만들고, True가 몇 개인지 셉니다.

print('Accuracy:' + str(float(accuracy_cnt) / len(x)))

 

 데이터를 배치로 처리함으로써 효율적이로 빠르게 처리할 수 있습니다.

 이상으로 배치 처리 구현에 대한 설명을 마칩니다.


7. 정리

 신경망 포스팅에서는 3층 신경망의 순전파 설계와 MNIST를 구현해 보았습니다.

 퍼셉트론과 신경망의 차이는 활성화 함수에 큰 차이가 있었습니다. 신경망에서는 매끄럽게 변화하는 시그모이드 함수를, 퍼셉트론에서는 계단 함수를 활성화 함수로 사용했습니다. 이 차이가 신경망 학습에서 중요합니다. 이에 대해서는 다음 포스팅에서 설명하겠습니다.

 

 

02 - 신경망 에서 배운 내용

1. 신경망에서는 활성화 함수로 시그모이드 함수와 ReLU 함수 같은 매끄럽게 변화하는 함수를 이용합니다.
2. 넘파이의 다차원 배열을 잘 사용하면 신경망을 효율적으로 구현할 수 있습니다.
3. 기계학습 문제는 크게 회귀와 분류로 나눌 수 있습니다.
4. 출력층의 활성화 함수로는 회귀에서는 주로 항등 함수를, 분류에서는 주로 소프트맥스 함수를 이용합니다.
5. 분류에서는 출력층의 뉴런 수를 분류하려는 클래스 수와 같에 설정합니다.
6. 입력 데이터를 묶은 것을 배치라 하며, 추론 처리를 이 배치 단위로 진행하면 결과를 훨씬 빠르게 얻을 수
   있습니다.
반응형