밑바닥부터 시작하는 딥러닝 -4장

DSC W/S·2020년 1월 28일
1

4.1 데이터에서 학습한다!

4.1.1 데이터 주도 학습

기계학습이란 데이터에서 답을 찾고 데이터에서 패턴을 발견하고 데이터로 이야기를 만드는 것이다.

만약 5를 인식하고 싶다면 이미지에서 특징을 추출하고 그 특징의 패턴을 기계학습 기술로 학습하는 방섭이 있다. 여기서의 특징이란, 입력 데이터에서 본질적인 데이터를 정확하게 추출할 수 있도록 설계된 변환기를 가리킨다. 컴퓨터 비전 분야에서는 SIFT, SURF HOG 등의 특징을 사용하고, 이들의 특징을 사용하여 이미지 데이터를 벡터로 변환한 분류기법인 SVM, KNN으로 학습 가능하다. 

4.1.2 훈련 데이터와 시험 데이터

기계학습 실험 시 우선 훈련 데이터만 사용하여 학습하면서 최적의 매개변수를 찾는다. 그 이유는 범용 능력, 즉 아직 보지 못한 데이터로도 문제를 올바르게 풀어내지 못한 능력을 제대로 평가하기 위함이다. 

오버피팅 : 한 데이터만 지나치게 최적화된 상태

4.2 손실 함수

지금 얼마나 행복한지에 대한 답은 아주 행복하다 혹은 그리 행복한 거 같지 않다라고 막연한 답이 돌아오는 게 보통이다. 그러나 누군가가 수치로 10.23이다라고 답하면 질문자는 당황할 것이다. 이 사람은 자신의 행복을 행복 지표를 이용하여 측정한다.

이와 같이 신경망 학습에서도 하나의 지표가 있다. 그 지표를 가장 좋게 만들어주는 가중치 매개변수의 값을 탐색하는 것이 목적이다. 신경망 학습에서는 손실 함수가 바로 그 지표이다.

손실 함수 : 신경망 학습에서 사용하는 지표(평균 제곱 오차와 교차 엔트로피 오차를 사용함)

4.2.1 평균 제곱 오차

오차가 더 작은 경우 정답에 더욱 가깝다는 것을 알 수 있다.

y = \[0.1, 0.05,0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0\]

t = \[0,0,1,0,0,0,0,0,0,0\]

y 배열에서 보면 이미지가 '0'일 확률은 0.1, 이미지가 '1'인 확률은 0.05, '2'인 확률은 0.6이라고 해석이 된다.
t는 정답 레이블로 정답을 가리키는 위치의 원소는 1, 그 이외는 0으로 표기가 된다.
숫자 '2'가 해당하는 원소의 값이 1이므로 정답이 '2'임을 알 수가 있다.

밑의 코드는 평균 제곱 오차를 파이썬으로 구현한 것이다.

 def mean\_squared\_error(y, t):
     return 0.5\*np.sum((y-t)\*\*2)

정답은 2

4.2.2 교차 엔트로피 오차

여기서 log는 밑이 e인 자연로그이다. yk는 신경망의 출력, tk는 정답 레이블이다. 신경망 출력이 0.6이라면 교차 엔트로피 오차는 -log0.6으로 결과는 0.51이 된다. 즉, 교차 엔트로피 오차는 정답일 때의 출력이 전체 값을 정하게 된다.

#예제

def cross\_entropy\_error(y,t):
delta = 1e-7
    return -np.sum(t\*np.log(y+delta))

정답은 평균 제곱 오차에서와 같이 똑같이 2

###4.2.3 미니배치 학습

MNIST 데이터셋은 훈련 데이터가 60,000개 이므로 신경망 학습에서 훈련 데이터로부터 일부만 골라 학습을 수행한다. 그 일부가 바로 미니배치이며 가령 60,000장의 훈련 데이터 중에서 100장을 무작위로 뽑은 학습 방법이 미니배치 학습이다. 

import sys, os
sys.path.append(os.pardir)
import numpy as np

from dataset.mnist import load\_mnist
(x\_train, t\_train),(x\_train,t\_test) = \\
load\_mnist(normalie=True, one\_hot\_label=True)
 print(x\_train.shape)
 print(t\_train.shape)

load_mnist는 MNIST 데이터셋을 읽어오는 함수이다. 위 함수는 dataset/mnist.py 파일에 있다.
https://github.com/oreilly-japan/deep-learning-from-scratch/blob/master/dataset/mnist.py

 코드를 돌린 결과 훈련 데이터는 60,000개이고 입력 데이터는 784열인 이미지 데이터임을 알 수 있다. 정답 레이블을 10줄짜리 데이터이며 x_train, t_train의 모습은 각각(60000,784)와 (60000,10)이 된다.

만약 훈련 데이터에서 무작위로 10장만 빼내려면 np.random.choice()함수를 쓰면 된다.

train\_size = x\_train.shape\[0\]
batch\_size = 10
batch\_mask = np.random.choice(train\_size, batch\_size)
x\_batch = x\_train\[batch\_mask\]
t\_batch = t\_train\[batch\_mask\]

0~60000 미만의 수 중에서 무작위로 10개 골라내기 위해서는 
np.random.choice(60000,10) 사용한다.

4.2.4 (배치용)교차 에트로피 오차 구현하기

미니배치 같은 배치 데이터를 지원하는 교차 엔트로피 오차를 구현하기 위해서는 교차 엔트로피 오차(데이터를 하나씩 처리하는 구현)를 조금만 바꿔주면 된다. 

def cross\_entropy\_error(y, t):

    if y.ndim ==1:

        t = t.reshape(1,t.size)

        y = y. reshape(1, y.size)

    batch\_size = y.shape\[0\]

    return -np.sum(t\*np.log(y+le-7))/batch\_size

위 코드는 데이터가 하나인 경우와 데이터가 배치로 묶여 입력될 경우 모두를 처리할 수 있도록 구현 되었다.

이 코드에서 y는 신경망의 출력, t는 정답 레이블이다. y가 1차원이라면, 즉 데이터 하나당 교차 엔트로피 오차를 구하는 경우는 reshape 함수로 데이터의 형상을 바꿔준다. 그리고 배치의 크기로 나눠 정규화하고 이미지 1장당 평균의 교차 엔트로피 오차를 계산한다.

4.2.5 왜 손실 함수를 설정하는가?

손실함수를 사용하는 이유는 정확도를 끌어내는 매개변수 값을 찾는 것이 우리의 목표이기 때문이다. 

정확도라는 지표를 두고 손실 함수의 값을 사용하는 이유는 미분의 역할에서 주목하게 되며 다음 장에서 설명하게 된다.

정확도를 지표로 삼아서는 안되는 이유는 미분 값이 대부분의 장소에서 0이 되어 매개변수를 갱신할 수 없기 때문이다.

4.3 수치 미분

4.3.1 미분

함수의 이름은 수치 미분에서 따온 numerical_diff(f,x)로 한다. 

def numerical\_diff(f, x):

    h = 1e-4 # 0.0001

    return (f(x+h) - f(x-h)) / (2\*h)

4.3.2 수치 미분의 예

def function\_1(x):

    return 0.01\*x\*\*2 + 0.1\*x 

위 코드는 y=0.01x^2+0.1x를 나타낸 파이썬 코드이다.

계산된 미분 값은 x에 대한 f(x)의 변화량, 즉 함수의 기울기에 해당한다. 그리고 x가 5일때와 10일 때의 진정한 미분은 차례로 0.2와 0.3이다. 

4.3.3 편미분

편미분 : 변수가 여럿인 함수에 대한 미분

앞에와 달리 변수가 2개라는 점에 주의해야 한다.

def function\_2(x):

return x\[0\]\*\*2 + x\[1\]\*\*2

    #또는 return np.sum(x\*\*2)

인수 x는 넘파이 배열이라고 가정하며 넘파이 배열의 각 원소를 제곱하고 그 합을 구할 간단한 형태로 구현할 수 있다.

#문제 1: x0=3, x1=4일때, x0에 대한 편미분을 구하라

def function\_tmp1(x0):
return x0\*x0 + 4.0\*\*2.0
numerical\_diff(fuction\_tm1p, 3.0)

결과 : 6.0000000000000378

#문제 2: x0=3, x1=4일때, x1에 대한 편미분을 구하라

def function\_tmp1(x0):
    return 3.0\*\*2.0 + x1\*x1
numerical\_diff(fuction\_tm1p, 3.0)

결과 : 7.99999999999991199

4.4 기울기

from mpl\_toolkits.mplot3d import Axes3D
def \_numerical\_gradient\_no\_batch(f, x):
    h = 1e-4  # 0.0001
    grad = np.zeros\_like(x)
    for idx in range(x.size):
        tmp\_val = x\[idx\]
        x\[idx\] = float(tmp\_val) + h
        fxh1 = f(x)  # f(x+h)
        x\[idx\] = tmp\_val - h 
        fxh2 = f(x)  # f(x-h)
        grad\[idx\] = (fxh1 - fxh2) / (2\*h)
        x\[idx\] = tmp\_val  # 値を元に戻す
    return grad

여기서 numerical_gradient(f,x) 함수의 구현은 복잡해 보이지만, 동작 방식은 변수가 하나일 때의 수치 미분과 거의 같다. f는 함수, x는 넘파이 배열이므로 넘파이 배열 x의 각 원소에 대해서 수치 미분을 구한다. 

기울기는 각 지접에서 낮아지는 방향을 가리킨다. 즉, 기울기가 가리키는 쪽은 각 장소에서 함수의 출력 값을 가장 크게 줄이는 방향이다!

4.4.1 경사법(경사 하강법)

신경망에서 최적의 매개변수 중 최적이란 손실 함수가 최솟값이 될 때의 매개변수 값이다. 하지만 일반적인 손실함수는 복잡하며 최솟값이 되는 곳을 짐작하기 어렵다. 이런 상황에서 기울기를 잘 이용해 함수의 최솟값(또는 가능한 한 작은 값)을 찾으려는 것이 경사법이다.

주의할 점은 각 지점에서 함수의 값을 낮추는 방안을 제시하는 지표가 기울기라는 것이다. 경사법을 수식으로 나타낼 때 갱신하는 양을 학습률이라고 표현한다. 즉, 한 번의 학습으로 얼마만큼 학습해야 할지를 정하는 것이 학습률이다. 

from gradient\_2d import numerical\_gradient
def gradient\_descent(f, init\_x, lr=0.01, step\_num=100):
    x = init\_x
    x\_history = \[\]
    for i in range(step\_num):
        x\_history.append( x.copy() )
        grad = numerical\_gradient(f, x)
        x -= lr \* grad
    return x, np.array(x\_history)

문제 :  경사법으로 f(x0. x1)= x0**2+x1**2 최솟값을 구하라

def function\_2(x):
    return x\[0\]\*\*2 + x\[1\]\*\*2
init\_x = np.array(\[-3.0, 4.0\])    
gradient\_descent(function\_2, init\_x, lr=0.1, step\_num=100):

#학습률이 너무 큰 예 : lr = 10.0

init\_x = np.array(\[-3.0, 4.0\])    
gradient\_descent(function\_2, init\_x, lr=10.0, step\_num=100):

#학습률이 너무 작은 예 : lr = 1e-10

init\_x = np.array(\[-3.0, 4.0\])    
gradient\_descent(function\_2, init\_x, lr=1e-10, step\_num=100):

4.4.2 신경망에서의 기울기

신경망 학습에서의 기울기는 가중치 매개변수에 대한 손실 함수의 기울기이다. 가중치가 W, 손실 함수가 L인 신경망 경우 편미분을 한다. 그리고 손실 하수 L이 얼마나 변하는지에 대해서 알려주는 것이 w11이다.

기울기를 구하는 코드를 구현해보자.

class simpleNet:
    def \_\_init\_\_(self):
        self.W = np.random.randn(2,3)
    def predict(self, x):
        return np.dot(x, self.W)
    def loss(self, x, t):
        z = self.predict(x)
        y = softmax(z)
        loss = cross\_entropy\_error(y, t)
return loss

기울기를 구하는 다른 방법

x = np.array(\[0.6, 0.9\])
t = np.array(\[0, 0, 1\])
net = simpleNet()
f = lambda w: net.loss(x, t)
dW = numerical\_gradient(f, net.W)
print(dW)

4.5 학습 알고리즘 구현하기

전체 : 신경망에는 적응 가능한 가중치와 편향이 있고, 이 가중치와 편향을 훈련 데이터에 적응하도록 조정하는 과정을 학습이라 한다

1단계-미니배치 : 훈련 데이터 중 일부를 무작위로 가져온다. 미니배치의 손실 함수 값을 줄이는 것이 목표이다.

2단계-기울기 산출 :  미니배치의 손실 함수 값을 줄이기 위해 각 가주이 매개변수의 기울기를 구한다. 기울기는 손실 함수의 값을 가장 작게 하는 방향을 제시한다.

3단계-매개변수 갱신 : 가중치 매개변수를 기울기 방향으로 아주 조금 갱신한다.

4단계 : 1~3단계를 반복한다.

하강법으로 매개변수를 갱신하는 방법이며 이때 데이터를 미니배치로 무작위로 선정하기 때문에 확률적 경사 하강법이라 부른다.

4.5.1 2층 신경망 클래스 구현하기

클래스의 이름은 TwoLayerNet이다.

import sys, os
sys.path.append(os.pardir) 
from common.functions import \*
from common.gradient import numerical\_gradient

class TwoLayerNet:
    def \_\_init\_\_(self, input\_size, hidden\_size, output\_size, weight\_init\_std=0.01):
        self.params = {}
        self.params\['W1'\] = weight\_init\_std \* np.random.randn(input\_size, hidden\_size)
        self.params\['b1'\] = np.zeros(hidden\_size)
        self.params\['W2'\] = weight\_init\_std \* np.random.randn(hidden\_size, output\_size)
        self.params\['b2'\] = np.zeros(output\_size)
    def predict(self, x):
        W1, W2 = self.params\['W1'\], self.params\['W2'\]
        b1, b2 = self.params\['b1'\], self.params\['b2'\]
        a1 = np.dot(x, W1) + b1
        z1 = sigmoid(a1)
        a2 = np.dot(z1, W2) + b2
        y = softmax(a2)
        return y

    # x: 입력 데이터, t: 정답 레이블
    def loss(self, x, t):
        y = self.predict(x)
        return cross\_entropy\_error(y, t)
    def accuracy(self, x, t):
        y = self.predict(x)
        y = np.argmax(y, axis=1)
        t = np.argmax(t, axis=1)
        accuracy = np.sum(y == t) / float(x.shape\[0\])
        return accuracy

    # x: 입력 데이터, t: 정답 레이블
    def numerical\_gradient(self, x, t):
        loss\_W = lambda W: self.loss(x, t)
        grads = {}
        grads\['W1'\] = numerical\_gradient(loss\_W, self.params\['W1'\])
        grads\['b1'\] = numerical\_gradient(loss\_W, self.params\['b1'\])
        grads\['W2'\] = numerical\_gradient(loss\_W, self.params\['W2'\])
        grads\['b2'\] = numerical\_gradient(loss\_W, self.params\['b2'\])
return grads

4.5.2 미니배치 학습 구현하기

미니배치 학습이란 훈련 데이터 중 일부를 무작위로 꺼내고, 그 미니배치에 대해서 경사법으로 매개변수를 갱신하는 것이다. TwoLayerNet으로 학습을 수행해본다.

from dataset.mnist import load\_mnist
from two\_layer\_net import TwoLayerNet

(x\_train, t\_train), (x\_test, t\_test) = load\_mnist(normalize=True, one\_hot\_label=True)
network = TwoLayerNet(input\_size=784, hidden\_size=50, output\_size=10)
iters\_num = 10000  
train\_size = x\_train.shape\[0\]
batch\_size = 100
learning\_rate = 0.1
train\_loss\_list = \[\]
train\_acc\_list = \[\]
test\_acc\_list = \[\]
iter\_per\_epoch = max(train\_size / batch\_size, 1)

for i in range(iters\_num):
    batch\_mask = np.random.choice(train\_size, batch\_size)
    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)

위 코드를 통해 신경망의 가중치 매개변수가 학습 횟수가 늘어가면서 손실 함수의 값이 줄어드는 것을 확인할 수 있다. 즉, 신경망의 가중치 매개변수가 서서히 데이터에 적응하고 있음을 의미한다. 신경망이 학습하고 있다는 것이다. 

데이터를 반복해서 학습함으로서 최적 가중치 매개변수로 서서히 다가서고 있다. 

4.5.3 시험 데이터로 평가하기

신경망 학습의 목표는 범용적인 능력을 익히는 것이다. 오버피팅 일으키지 않는지 확인해야한다. 아래는 평가하기 위한 코드이다.

from dataset.mnist import load\_mnist
from two\_layer\_net import TwoLayerNet

(x\_train, t\_train), (x\_test, t\_test) = load\_mnist(normalize=True, one\_hot\_label=True)
network = TwoLayerNet(input\_size=784, hidden\_size=50, output\_size=10)
iters\_num = 10000 
train\_size = x\_train.shape\[0\]
batch\_size = 100
learning\_rate = 0.1
train\_loss\_list = \[\]
train\_acc\_list = \[\]
test\_acc\_list = \[\]
iter\_per\_epoch = max(train\_size / batch\_size, 1)

for i in range(iters\_num):
    batch\_mask = np.random.choice(train\_size, batch\_size)
    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)

    if i % iter\_per\_epoch == 0:
        train\_acc = network.accuracy(x\_train, t\_train)
        test\_acc = network.accuracy(x\_test, t\_test)
        train\_acc\_list.append(train\_acc)
        test\_acc\_list.append(test\_acc)
        print("train acc, test acc | " + str(train\_acc) + ", " + str(test\_acc))
profile
DSC Duksung 겨울방학 NLP 스터디

0개의 댓글