반응형

 

 

안녕하세요~

 

2018년 올해도 벌써 약 3달 남지 않았네요~

 

 

컴퓨터가 2018이라는 필기체 숫자를 인식하려면 어떻게 해야할까요?

 

딥러닝을 공부한다면 꼭 해야하는 코스죠?

 

가장 많은 사랑받고 있는 MNIST 데이터 셋을 이용하여

 

CNN 신경망을 통한 이미지 인식 훈련을 해보도록 하겠습니다!

 


오늘은 python으로 구현한 CNN코드를 통해 알아보겠습니다.

 

CNN 이론은 Part1에 있습니다!

 

 

 

아래의 코드는 크게 3가지 부분으로 나눌 수 있습니다.

 

1. 합성곱 신경망(CNN) class 구현하기

 

2. 훈련 & 테스트 하기

 

3. 그래프 그리기

 

 

 

1. 합성곱 신경망(CNN) class 구현하기

 

CNN 신경망은 단순한 합성곱 신경망으로 아래와 같이 구성하였습니다.

 

CNN신경망_source:everyday-deeplearning

 

각각의 계층에 대해 궁금하시면 이론편을 먼저 보고오세요~

 

(각 계층과 활성함수는 자유롭게 구성할 수 있습니다!)

 

# coding: utf-8
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 # 수치미분 함수
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from common.trainer import Trainer

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 #conv공식
        pool_output_size = int(filter_num * (conv_output_size / 2) * (conv_output_size / 2)) #pooling공식

        # 가중치 초기화
        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 (1, 2, 3):
            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)]

 

 

 

2. 훈련 & 테스트 하기

 

위에서 언급했던 것과 같이 아래의 코드는 MNIST 데이터 셋을 이용합니다!

 

 

그렇다면 머신러닝 입문자들은 왜 MNIST 데이터 셋을 이용하여 학습할까요?

 

우선, MNIST 데이터란?

 

필기(손으로 쓰여진 이미지) 숫자들의 그레이스케일 28x28 픽셀 이미지를 보고,

 

0부터 9까지 모든 숫자들에 대해 이미지가 어떤 숫자를 나타내는지 판별하는 것입니다.

 

출처: Tensorflowkorea

 

즉, 이미 가공된 데이터 셋으로 전처리가 크게 필요하지 않기때문에

 

쉽게 훈련시킬 수 있으며 정확도가 높게 나옵니다.

 

 

위에서 만든 합성곱 신경망(CNN)을 이용하여 MNIST 데이터 셋 훈련을 하겠습니다.

 

# 데이터 읽기
(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 = 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)

# 매개변수 보존
network.save_params("params.pkl")
print("Saved Network Parameters!")

# 하이퍼파라미터
iters_num = 10000 # 반복 횟수를 적절히 설정한다
train_size = x_train.shape[0] # 60000 개
batch_size = 100  # 미니배치 크기
learning_rate = 0.1
train_loss_list = []
train_acc_list = []
test_acc_list = []

# 1에폭당 반복 수
iter_per_epoch = max(train_size / batch_size, 1)
print(iter_per_epoch) # 600

for i in range(iters_num): # 10000
    # 미니배치 획득  # 랜덤으로100개씩 뽑아서10000번을 수행하니까 백만번
    batch_mask = np.random.choice(train_size, batch_size) # 100개 씩 뽑아서10000번 백만 번
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]

    # 기울기 계산
    #grad = network.numerical_gradient(x_batch, t_batch)
    grad = network.gradient(x_batch, t_batch)

    # 매개변수 갱신
    for key in ('W1', 'b1', 'W2', 'b2'):
        network.params[key] -= learning_rate * grad[key]

    # 학습 경과 기록
    loss = network.loss(x_batch, t_batch)
    train_loss_list.append(loss) # cost 가 점점 줄어드는것을 보려고
    # 1에폭당 정확도 계산# 여기는 훈련이 아니라1에폭 되었을때 정확도만 체크

    if i % iter_per_epoch == 0: # 600 번마다 정확도 쌓는다
        print(x_train.shape) # 60000,784
        train_acc = network.accuracy(x_train, t_train)
        test_acc = network.accuracy(x_test, t_test)
        train_acc_list.append(train_acc) # 10000/600 개  16개# 정확도가 점점 올라감
        test_acc_list.append(test_acc)  # 10000/600 개16개# 정확도가 점점 올라감
        print("train acc, test acc | " + str(train_acc) + ", " + str(test_acc))
 

 

 

 

3. 그래프 그리기

 

위에서 만든 train과 test의 정확도를 비교하여

 

overfitting, underfitting이 발생하는지 확인하고

 

신경망의 효율과 정확도를 올리기위해 다양한 파라미터를 조정할 수 있습니다.

 

이 블로그의 포스팅 " 신경망의 효율과 정확도를 올리기위한 방법 "에서 확인할 수 있습니다.

 

# 그래프 그리기
markers = {'train': 'o', 'test': 's'}
x = np.arange(len(train_acc_list))
plt.plot(x, train_acc_list, label='train acc')
plt.plot(x, test_acc_list, label='test acc', linestyle='--')
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()

 

코드출처: R과 Python을 활용한 빅데이터 머신러닝 전문가 양성


 

오늘의 포스팅은 여기까지!!

 

궁금한 점은 댓글로 남겨주세요

 

 

Tensorflow2로 구현하는 방법도 포스팅했습니다!

현업에서 많이 사용하는 Python 모듈 | Tensorflow 2

 

현업에서 많이 사용하는 Python 모듈 | Tensorflow 2 구현하기

오늘은 딥러닝 모델을 생성할 때 많이 사용하는 Python 라이브러리 중 Tensorflow의 기본 동작 원리와 코드, Tensorflow 2.0에서는 어떻게 변화하였는지에 대해 살펴보도록 하겠습니다! 텐서플로 1 코드

everyday-deeplearning.tistory.com

 

반응형
반응형



안녕하세요 오랜만이에요~


오늘은 딥러닝의 꽃! CNN에 대해 알아보겠습니다!


CNN이란 Convolution Neural network로 합성곱 신경망이라고 부릅니다.


CNN은 이미지, 동영상 등을 분석하는데 사용됩니다.


(음성 인식, 자연어 처리와 같은 다른 작업에도 많이 사용하지만 여기서는 시각적인 부분에 초점을 맞췄습니다.)



[기존계층과 합성곱 계층 비교]


먼저, 기존 신경망과 다른 점에 대해 알아보겠습니다.



기존에 구현했던 완전 연결 계층, 즉 기존 신경망은 데이터의 형상이 무시됩니다.



따라서 글자의 크기가 달라지거나 글자가 회전되거나,


글자에 변형이 조금만 생기더라도


다른 글자로 인식하기 때문에


새로운 학습 데이터를 넣어주지 않으면


좋은 결과를 기대하기 어렵습니다.



즉, 본질적인 패턴을 읽지 못하기 때문에


인식을 위해


다양한 데이터가 많이 필요합니다.




하지만, 합성곱 계층은



원본 이미지를 가지고 여러개의 feature map (특징맵)을 만들어 분류하는


완전 연결 계층으로,



이미지의 특징을 추출하기때문에 이미지가 변형이되더라도


잘 인식할 수 있습니다.




<기존 계층>




<합성곱 계층>





합성곱 연산이란?


이미지 3차원(세로, 가로, 색상) data의 형상을 유지하면서 연산하는 작업


입력 데이터에 필터를 적용한 것을 합성곱 연산이라고 합니다.


[합성곱 연산]




<구현 예시>



# 위의 그림을 파이썬으로 출력하시오

import numpy as np

input_ = np.array([[1,2,3,0],[0,1,2,3],[3,0,1,2],[2,3,0,1]]) filter_ = np.array([[2,0,1],[0,1,2],[1,0,2]]) bias=3 a = input_.shape[0]-filter_.shape[0] + 1 b = input_.shape[1]-filter_.shape[1] + 1 result2 = []

for rn in range(a): for cn in range(b): result1 = input_[rn:rn+filter_.shape[0],cn:cn+filter_.shape[1]] * filter_ result2.append(np.sum(result1)+bias) result = np.array(result2).reshape(a,b)




패딩이란?


합성곱 연산을 수행하기 전에 데이터 주변을 특정값으로 채워 늘리는 것을 말합니다.



※ 패딩이 필요한 이유는 무엇일까요?


패딩을 하지 않을경우 data의 크기는 합성곱 계층을 지날 때마다 작아지게 되므로


가장자리 정보들이 사라지는 문제가 발생하기 때문에 패딩을 사용합니다.



<구현 예시>



# 위의 그림을 파이썬으로 출력하시오 import numpy as np input_ = np.array([[1,2,3,0],[0,1,2,3],[3,0,1,2],[2,3,0,1]]) input_pad = np.pad(input_, pad_width=1, mode='constant', constant_values=0) # padding method filter_ = np.array([[2,0,1],[0,1,2],[1,0,2]]) a = input_pad.shape[0]-filter_.shape[0] + 1 b = input_pad.shape[1]-filter_.shape[1] + 1 result2 = [] for rn in range(a): for cn in range(b): result1 = input_pad[rn:rn+filter_.shape[0],cn:cn+filter_.shape[1]] * filter_ result2.append(np.sum(result1)) result = np.array(result2).reshape(a,b)




스트라이드란?


input데이터에 filter를 적용하는 위치의 간격을 스트라이드라고 합니다.




[출력크기 공식]



(입력크기를 H(Height),W(Width), 필터크기를 FH, FW, 출력크기를 OH, OW, 패딩을 P, 스트라이드를 S라고 한다.)




3차원 합성곱 연산


이미지의 색은 보통 흑백이 아니라 RGB(Red, Green, Blue)컬러 이므로


RGB컬러에 대해서 합성곱을 해야합니다.





3차원 합성곱 구현 이론




[사진 한장을 RGB 필터로 합성곱하여 2차원 출력행렬(Feature map) 1을 출력한 그림]



위의 그림은 Feature map이 한 개가 나오는데


실제로는 사진 한 장에 대해서 여러개의 Feature map이 필요합니다.






Filter의 갯수를 늘리면 여러개의 Feature map을 출력할 수 있습니다.


하나의 Filter에 하나의 Feature map을 출력할 수 있습니다.





이미지를 한 장씩 학습시키는 것은 학습속도가 느립니다.


따라서 100장의 이미지를 묶음으로 나눠서 한번에 학습시킵니다.


이를 mini batch라고 합니다.


+ 합성곱 연산에 편향(bias)을 더해줍니다.




하지만 합성곱 계층을 미니 배치로 구현할 때 4차원 행렬의 연산이 됩니다.


따라서 연산의 속도가 느려지므로


행렬 연산을 빠르게 하기 위해 4차원이 아닌 2차원으로 차원을 축소해야할 필요가 있습니다.



※ 이때 im2col함수를 이용합니다.




원리: 각 4차원 블럭을 R,G,B로 각각 나누어 2차원으로 변환 후 합쳐줍니다.


※ 배치를 사용할 때 위의 방법을 여러번 하여 나온 결과인 2차원 행렬을 합쳐줍니다.



[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



※ 2차원으로 변경해야할 행렬 2가지



1. 원본이미지를 필터 사이즈에 맞게 2차원으로 변경한 행렬


-> im2col 함수 이용



2. 4차원 필터 행렬을 2차원으로 변경


-> reshape 함수를 이용 (FN, -1)



[CNN 구현하기]



"Convolution 계층에서 일어나는 일"


1. 원본이미지를 im2col을 사용하여 2차원 행렬로 변경한다.


2. filter를 reshape을 사용하여 2차원 행렬으로 변경한다.


3. 2차원 행렬로 변환한 두 행렬을 내적한다.


4. 내적한 결과인 2차원 행렬을 다시 4차원으로 변환한다.



CNN층 구조


" conv  ->  pooling  -> fully connected "



- conv: 이미지 특징(feature map)을 추출하는 층


- pooling: 추출한 feature map을 선명하게 하는 층




pooling 계층의 역활


출력 값에서 일부만 취하여 사이즈가 작은 이미지를 만든다.


마치 사진을 축소하면 해상도가 좋아지는 듯한 효과와 비슷하다.




※ 풀링 종류 3가지


1. 최대 풀링


: 컨볼루션 데이터에서 가장 큰 값을 대표값으로 선정



2. 평균 풀링


: 컨볼루션 데이터에서 평균값을 대표값으로 선정



3. 확률적 풀링


: 컨볼루션 데이터에서 임의 확률로 한 개를 선정




※ 그림출처: '밑바닥부터 시작하는 딥러닝'



생각보다 내용이 길어져서


파이썬으로 Convolution 계층 구현하는 것은


다음 포스팅에서 하겠습니다!



궁금하신 부분은 댓글 남겨주세요!!

반응형
반응형


안녕하세요~



오늘은 신경망의 정확도를 높이기위한 여러가지 방법을 소개하겠습니다.


Ⅰ. 매개변수 갱신


Ⅱ. 가중치 초기값 설정


Ⅲ. 배치 정규화


Ⅳ. Dropout / weight decay


Ⅴ. 적절한 하이퍼 파라미터 값 찾기



이제 각각의 방법에 대해 자세히 다뤄보도록 하겠습니다.



Ⅰ. 매개변수 갱신 ( 고급 경사하강법 )


underfitting을 해결하기위한 방법 중 하나입니다.



GD(GradientDescent)에는 큰 단점 2가지가 있습니다.


1) 계산량이 많아서 속도가 느리다.


2) local minima에 빠질 수 있다.



GD는 가장 기본적인 뉴럴넷 학습 방법으로


전체 데이터에 대한 비용함수의 weight 과 미분하고


각 W(가중치) 파라미터에 대한 기울기를 이용하여


W를 업데이트하는 방법입니다.



그런데 이러한 GD는 층이 많아질수록 parameter의 복잡도가 늘어남에 따라


비용함수가 더욱 복잡해져서


local minima에 빠지는 현상이 더욱 잘 발생하게 됩니다.



이러한 문제를 해결하기 위해 나온 것이


SGD(Stochastic Gradient Descent) 확률적 경사하강법입니다.



1. SGD


전체 data 중 랜덤하게 추출한 1/N의 데이터만을 사용해서 빠르게 가중치를 업데이트 하는 방법입니다.


[SGD 식]



GD의 단점을 해결한 SGD에도 문제가 있는데,

단순하고 구현도 쉽지만 문제에 따라 비효율 적일 때가 있다는 것입니다.


또한 아래와 같은 그래프의 경우 global minima 쪽으로 기울기 방향이 있는게 아니기 때문에

local minima에 빠지고 global minima 쪽으로 도달하지 못하는 단점이 있습니다.




2. Momentum


Momentum은 운동량을 뜻하는 단어로 기울어진 방향으로 물체가 가속되는 물리 법칙을 나타냅니다.


[Momentum 식]




(α: 마찰계수, v: 속도, η : 학습률, W: 가중치, ∂L / ∂W : W에 대한 손실함수 미분)



원리:


내리막: 기울기가 음수이면 속도가 증가


오르막: 기울기 양수이면 속도 감소



3. AdaGrad


learning rate 가 학습되면서 자체적으로 조정되는 경사하강법


보통 처음에는 크게 학습하다가 조금씩 작게 학습을 진행하는 방법입니다.


[AdaGrad 식]



(h: 기울기에 값을 제곱하여 더해줌, ◎: 행렬의 원소별 곱셈)



원리:


learning rate가 클 경우: 발산될 위험은 있지만 수렴의 속도가 빨라진다.


learning rate가 작을 경우 : 발산될 위험은 작지만 local minima에 빠지거나 학습이 안될 수 있다.



각각의 매개변수 원소가 갱신되는 값이 다르다는 것이 특징입니다.



4. Adam


Momentum의 장점 (가속도) + RMSProp의 장점 (각 매개변수마다 학습률 조절) == Adam


최근 딥러닝에서 가장 많이 사용하는 최적화 방법이라고 합니다! : )


* 참고: RMSProp는 가장 최근 반복에서 비롯된 그래디언트만 누적함으로

AdaGrad의 단점(local minima에 수렴함)을 보안한 옵티마이저 입니다.


[각각의 optimizer를 파이썬으로 구현하는 코드]


# gradient descent 종류 class SGD: def __init__(self, lr=0.01): self.lr = lr def update(self, params, grads): for key in params.keys(): params[key] -= self.lr * grads[key] class Momentum: def __init__(self, lr=0.01, momentum=0.9): self.lr = lr self.momentum = momentum self.v = None def update(self, params, grads): if self.v is None: self.v = {} for key, val in params.items(): self.v[key] = np.zeros_like(val) for key in params.keys(): self.v[key] = self.momentum * self.v[key] - self.lr * grads[key] params[key] += self.v[key] class AdaGrad: def __init__(self, lr=0.01): self.lr = lr self.h = None def update(self, parmas, grads): if self.h is None: self.h = {} for key, val in params.items(): self.h[key] = np.zeros_like(val) for key in parmas.keys(): self.h[key] += grads[key] * grads[key] params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7) class Adam: def __init__(self, lr=0.001, beta1=0.9, beta2=0.999): self.lr = lr self.beta1 = beta1 self.beta2 = beta2 self.iter = 0 self.m = None self.v = None def update(self, params, grads): if self.m is None: self.m, self.v = {}, {} for key, val in params.items(): self.m[key] = np.zeros_like(val) self.v[key] = np.zeros_like(val) self.iter += 1 lr_t = self.lr * np.sqrt(1.0 - self.beta2 ** self.iter) / (1.0 - self.beta1 ** self.iter) for key in params.keys(): self.m[key] += (1 - self.beta1) * (grads[key] - self.m[key]) self.v[key] += (1 - self.beta2) * (grads[key] ** 2 - self.v[key]) params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)



[각 optimizer 가 최소값에 수렴하는 방법]





Ⅱ. 가중치 초기값 설정


underfitting을 해결하기위한 방법 중 하나입니다.


가중치 초기값을 적절히 설정하면 각 층의 활성화 값의 분포가 적당히 퍼지는 효과가 발생합니다.


활성화 값이 적당히 퍼지게 되면 학습이 잘되고 정확도가 높아집니다.



1. 초기값을 0으로 설정


초깃값을 모두 0으로 설정하면

오차역전파법에서 모든 가중치의 값이 똑같이 갱신되기 때문에

학습이 올바르게 이뤄지지 않습니다.


2. 표준편차가 1인 정규분포를 사용해 초기값 설정


- 표준편차가 작을수록 데이터가 평균에 가깝게 분포


- 표준편차가 클 수록 데이터가 많이 흩어져 있다.


즉 데이터가 0과 1에 치우쳐 분포하게 되면 역전파의 기울기 값이 점점 작아지다 사라집니다.


층을 깊게 하는 딥러닝에서는 기울기 소실은 더 심각한 문제가 될 수 있습니다.


3. Xavier 초기값 설정


- 표준편차가 √1/n 인 정규분포로 초기화한다.


- sigmoid 와 짝꿍


각 층의 활성화 값들을 광범위하게 분포 시킬 목적으로 적절한 분포를 찾고자 했습니다.


그리고 앞 계층의 노드가 n개라면 표준편차가 √1/n 인 분포를 사용하면 된다는 결론을 이끌었습니다.



4. He 초깃값


- 표준편차가 √2/n 인 정규분포로 초기화한다.


- ReLU 와 짝꿍


ReLU는 음의 영역이 0이라서 더 넓게 분포시키기 위해


Xavier함수보다 2배의 계수가 필요하다고 해석할 수 있습니다.



[초기화 그래프]





Ⅲ. 배치 정규화


마찬가지로 underfitting을 해결하기 위한 방법 중 하나 입니다.


앞에서 가중치 초기값을 적절히 설정하면 각 층의 활성화 값의 분포가 적당히 퍼지는 효과를 보았습니다.


그런데,

배치 정규화는 바로 각 층에서의 활성화 값이 적당히 분포되도록 강제로 조정하는 것을 말합니다.



딥러닝의 경우는 hidden layer가 많아서


가중치의 조금한 변화가 가중되어서 쌓이면


hidden layer가 많아질수록 출력되는 값의 변화가 크기 때문에


학습이 어렵습니다.



배치 정규화는 값이 활성화 함수를 통과하기 전에 가중의 변화를 줄이는 것이 목표


가중의 합이 배치 정규화에 들어오게 되면 기존의 값의 스케일을 줄여버리게 됩니다.



[ 배치초기화 식]



[파이썬으로 구현하기]


class BatchNormalization: """ http://arxiv.org/abs/1502.03167 """ def __init__(self, gamma, beta, momentum=0.9, running_mean=None, running_var=None): self.gamma = gamma self.beta = beta self.momentum = momentum self.input_shape = None # 합성곱 계층은 4차원, 완전연결 계층은 2차원 # 시험할 때 사용할 평균과 분산 self.running_mean = running_mean self.running_var = running_var # backward 시에 사용할 중간 데이터 self.batch_size = None self.xc = None self.std = None self.dgamma = None self.dbeta = None def forward(self, x, train_flg=True): self.input_shape = x.shape if x.ndim != 2: N, C, H, W = x.shape x = x.reshape(N, -1) out = self.__forward(x, train_flg) return out.reshape(*self.input_shape) def __forward(self, x, train_flg): if self.running_mean is None: N, D = x.shape self.running_mean = np.zeros(D) self.running_var = np.zeros(D) if train_flg: mu = x.mean(axis=0) xc = x - mu var = np.mean(xc**2, axis=0) std = np.sqrt(var + 10e-7) xn = xc / std self.batch_size = x.shape[0] self.xc = xc self.xn = xn self.std = std self.running_mean = self.momentum * self.running_mean + (1-self.momentum) * mu self.running_var = self.momentum * self.running_var + (1-self.momentum) * var else: xc = x - self.running_mean xn = xc / ((np.sqrt(self.running_var + 10e-7))) out = self.gamma * xn + self.beta return out def backward(self, dout): if dout.ndim != 2: N, C, H, W = dout.shape dout = dout.reshape(N, -1) dx = self.__backward(dout) dx = dx.reshape(*self.input_shape)

return dx def __backward(self, dout): dbeta = dout.sum(axis=0) dgamma = np.sum(self.xn * dout, axis=0) dxn = self.gamma * dout dxc = dxn / self.std dstd = -np.sum((dxn * self.xc) / (self.std * self.std), axis=0) dvar = 0.5 * dstd / self.std dxc += (2.0 / self.batch_size) * self.xc * dvar dmu = np.sum(dxc, axis=0) dx = dxc - dmu / self.batch_size self.dgamma = dgamma self.dbeta = dbeta return dx



[구현 예]

# 계층생성 layers = OrderedDict() # 순전파 순서의 반대로 역전파가 된다. layers['Affine1'] = Affine(params['W1'], params['b1']) layers['BatchNorm1'] = BatchNormalization(gamma=1.0, beta=0.) layers['Relu1'] = Relu() layers['Affine2'] = Affine(params['W2'], params['b2']) layers['BatchNorm2'] = BatchNormalization(gamma=1.0, beta=0.) layers['Relu2'] = Relu() layers['Affine3'] = Affine(params['W3'], params['b3']) lastLayer = SoftmaxWithLoss()




Ⅳ. Dropout / weight decay


두 방법 모두 overfitting을 규제하기 위한 방법입니다.



1. Dropout(드롭아웃)


오버피팅을 억제하기 위해서 뉴런을 임의로 삭제하면서 학습시키는 방법


사공이 많으면 배가 산으로 간다는 속담이 있습니다.


따라서 몇 명의 전문가만 선별해서 반복적으로 같은 결과가 나오면 그것을 답으로 보는 방법입니다.



일반적으로 입력층에서는 20% ~ 50% 정도,


은닉층에서는 50% 정도의 노드를 생략한다고 합니다.



[파이썬 코드]

class Dropout: """ http://arxiv.org/abs/1207.0580 """ def __init__(self, dropout_ratio=0.15): self.dropout_ratio = dropout_ratio self.mask = None def forward(self, x, train_flg=True): if train_flg: self.mask = np.random.rand(*x.shape) > self.dropout_ratio return x * self.mask else: return x * (1.0 - self.dropout_ratio) def backward(self, dout): return dout * self.mask


[구현 예]

# 계층생성 layers = OrderedDict() # 순전파 순서의 반대로 역전파가 된다. layers['Affine1'] = Affine(params['W1'], params['b1']) layers['BatchNorm1'] = BatchNormalization(gamma=1.0, beta=0.) layers['Relu1'] = Relu() layers['Dropout1'] = Dropout() layers['Affine2'] = Affine(params['W2'], params['b2']) layers['BatchNorm2'] = BatchNormalization(gamma=1.0, beta=0.) layers['Relu2'] = Relu() layers['Dropout2'] = Dropout() layers['Affine3'] = Affine(params['W3'], params['b3']) lastLayer = SoftmaxWithLoss()




2. Weight Decay(가중치 감소)


학습과정에서 큰 가중치에 대해서는 그에 상응하는 큰 패널티를 부여하여 오버피팅을 억제하는 방법


가중치 감소는 모든 가중치 각각의 손실함수에 (1/2) * λW **2를 더합니다.



[구현 예]

weight_decay_lambda = 0.001


# 1. 비용함수 수정

weight_decay = 0 for idx in range(1, 4): # 3계층 하드코딩 W = self.params['W' + str(idx)] weight_decay += 0.5 * self.weight_decay_lambda * np.sum(W ** 2)


# 2. 기울기 계산하는 코드 변경

for idx in range(1, 4): grads['W' + str(idx)] = self.layers['Affine' + str(idx)].dW + \ self.weight_decay_lambda * self.layers['Affine' + str(idx)].W grads['b' + str(idx)] = self.layers['Affine' + str(idx)].db



Ⅴ. 적절한 하이퍼파라미터 값 찾기


신경망에서 말하는 하이퍼파라미터는 각 층의 뉴런 수, 배치 크기, 매개변수 생신 시의 학습률과 가중치 감소 등입니다.



1. 검증데이터


우선 하이퍼파라미터를 조정할 때는 검증데이터로 하이퍼파라미터의 적절성을 평가해야합니다.


test_data에서 하이퍼파라미터를 조정하면 값이 test_data에 오버피팅 되기 때문에

범용 성능이 떨어지는 모델이 될 수도 있기 때문입니다.



즉, 아래와 같이 데이터를 나눠서 사용할 수 있습니다.


- 훈련데이터: 매개변수 학습


- 검증데이터: 하이퍼파라미터 성능 평가


- test 데이터: 신경망의 범용 성능 평가



※ Tip! 만약 적은 양의 데이터라면 교차검증(cross validation)방법을 사용할 수 있습니다.



2. 하이퍼 파라미터 최적화


하이퍼파라미터를 최적화할 때의 핵심은


무작위로 샘플링 후 정확도를 살피면서 하이퍼파라미터의 '최적 값'이 존재하는 범위를 조금씩 줄여간다는 것입니다.


0단계: 하이퍼파라미터 값의 범위를 설정합니다.


1단계: 설정된 범위에서 하이퍼파라미터의 값을 무작위로 추출합니다.


2단계: 1단계에서 샘플링한 하이퍼 파라미터 값을 사용하여 학습하고, 검증 데이터로 정확도를 평가합니다.


3단계: 1단계와 2단계를 반복하며, 그 정확도의 결과를 보고 하이퍼파라미터의 범위를 좁힙니다.



※ Tip!


위에서 설명한 하이퍼파라미터 최적화 방법은

과학이라기보다는 직관에 의존한다는 느낌이 들지만 많이 사용되는 실용적인 방법입니다.


더 세련된 기법을 원한다면 베이즈 최적화(수학이론을 구사)를 사용할 수 있습니다 : )




오늘은 여기까지!!


유난히 긴 포스팅이었네요!


※ '밑바닥부터 시작하는 딥러닝' 을 참고하였습니다


도움이 되었다면 공감♡ 꾸욱 

반응형

+ Recent posts