Ch4 신경망 학습

Study/밑바닥부터 시작하는 딥러닝 2021. 1. 19. 17:27

 

 

 

 

4.1 데이터에서의 학습

 
  • 학습 : 훈련 데이터로부터 가중치 매개변수의 최적값을 자동으로 획득하는 것
  • 신경망의 특징은 데이터를 보고 학습할 수 있다는 점, 가중치 매개변수의 값을 데이터를 보고 자동으로 결정한다는 뜻
 

4.1.1 데이터 주도 학습

  • 사람이 생각하는 알고리즘 -> 결과
  • 사람이 생각한 특징 (SIFT, HOG 등) -> 기계학습 (SVM, KNN 등) -> 결과
  • 신경망(딥러닝) -> 결과
 

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

  • 훈련 데이터만 사용하여 최적의 매개변수를 찾음
  • 시험 데이터를 통해 훈련한 모델의 실력 평가
  • 범용적으로 사용할 수 있는 모델을 찾기 위해
  • 오버피팅을 피해야 함
 

4.2 손실 함수

  • 손실 함수 : 신경망 학습에서 최적의 매개변수를 탐색하는 지표
 

4.2.1 오차제곱합

  • E = 1/2 * summation(y_k - t_k)^2
In [1]:
def sum_squares_error(y,t):
    return 0.5 * np.sum((y-t)**2)
In [2]:
import numpy as np

t=[0,0,1,0,0,0,0,0,0,0] # 정답 == 2
y=[0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0] # 2일 확률이 가장 높다고 추정
y2=[0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0] # 7일 확률이 가장 높다고 추정

print(sum_squares_error(np.array(y), np.array(t)))
print(sum_squares_error(np.array(y2), np.array(t)))
 
0.09750000000000003
0.5975
 

4.2.2 교차 엔트로피 오차

  • E = -summation(t_k*log(y_k))
  • 교차 엔트로피 오차는 정답일 때의 출력이 전체 값을 정하게 됨
  • 정답에 해당하는 출력이 커질수록 0에 다가가다가, 그 출력이 1일 때 0이 됨
  • 반대로 정답일 때의 출력이 작아질수록 오차는 커짐
In [3]:
def cross_entropy_error(y,t):
    delta=1e-7 # np.log 함수에 0을 입력하면 -inf가 되어 계산 진행 불가하기 때문에 작은 값을 더해 0이 되지 않도록 조정
    return -np.sum(t*np.log(y+delta))
In [4]:
print(cross_entropy_error(np.array(y), np.array(t))) # 첫 번째 추정이 정답일 가능성이 높다고 판단
print(cross_entropy_error(np.array(y2), np.array(t)))
 
0.510825457099338
2.302584092994546
 

4.2.3 미니배치 학습

  • 많은 데이터를 대상으로 일일이 손실 함수를 계산하는 것은 불가능
  • 데이터 일부를 추려 전체의 근사치로 이용할 수 있음 -> 미니배치
In [5]:
import sys, os
sys.path.append(os.pardir)
from dataset.mnist import load_mnist

(x_train, t_train), (x_test,t_test) = load_mnist(normalize=True, one_hot_label=True)
In [6]:
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] #이를 대상으로 미니배치 학습
 

4.2.4 배치용 교차 엔트로피 오차 구현하기

In [7]:
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+1e-7))/batch_size
In [8]:
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(np.log(y[np.arange(batch_size),t]+1e-7)) / batch_size
 

4.2.5 손실함수를 설정하는 이유

  • 최적의 매개변수를 탐색할 때 손힐 함수의 값을 가능한 한 작게 하는 매개변수 값을 찾음
  • 매개변수의 미분을 계산하고, 그 미분 값을 단서로 매개변수의 값을 서서히 갱신하는 과정을 반복
  • 따라서, 신경망을 학습할 때 정확도를 지표로 삼아서는 안됨
  • 정확도를 지표로 하면 매개변수의 미분이 대부분의 장소에서 0이 되기 때문
 

4.3 수치 미분

  • 경사법에서는 기울기 값을 기준으로 나아갈 방향을 정함
 

4.3.1 미분

  • 특정 순간의 변화량
In [9]:
# 나쁜 구현의 예
def numerical_diff(f,x):
    h=10e-50
    return (f(x+h)-f(x))/h
 
  • 1e-50은 반올림오차 문제를 일으킴
  • 위 식은 x+h와 x의 기울기에 해당함. h를 무한이 0으로 좁히는 것은 불가능함.
In [10]:
# 중심 차분
def numerical_diff(f,x):
    h=1e-4
    return (f(x+h)-f(x-h))/(2*h)
 

4.3.2 수치 미분의 예

  • 다음과 같은 2차 함수가 있다
  • y=0.01 x^2 + 0.1x
In [11]:
def function_1(x):
    return 0.01*x**2 + 0.1*x
In [12]:
import matplotlib.pylab as plt

x=np.arange(0.0, 20.0, 0.1)
y=function_1(x)
plt.xlabel('x')
plt.ylabel('f(x)')
plt.plot(x,y)
plt.show()

print(numerical_diff(function_1,5))
print(numerical_diff(function_1,10))
 
<Figure size 640x480 with 1 Axes>
 
0.1999999999990898
0.2999999999986347
 

4.3.3 편미분

  • 편미분 : 변수가 여럿인 함수에 대한 미분
  • f(x_0, x_1) = (x_0)^2 + (x_1)^2 이 있다 하자
In [13]:
def function_2(x):
    return x[0]**2 + x[1]**2
In [14]:
# x_0 = 3, x_1 = 4일때 x_0에 대한 편미분
def function_tmp1(x0):
    return x0*x0 + 4.0**2.0

numerical_diff(function_tmp1, 3.0)
Out[14]:
6.00000000000378
In [15]:
# x_0 = 3, x_1 = 4일때 x_1에 대한 편미분
def function_tmp2(x1):
    return 3.0**2.0 + x1*x1

numerical_diff(function_tmp2, 4.0)
Out[15]:
7.999999999999119
 
  • 편미분은 특정 장소의 기울기를 구함
  • 여러 변수 중 목표 변수 하나에 초점을 맞추고 다른 변수는 값을 고정
 

4.4 기울기

  • 기울기 : 모든 변수의 편미분을 벡터로 정리한 것
In [16]:
def numerical_gradient(f,x):
    h=1e-4 #0.0001
    grad = np.zeros_like(x)
    
    for idx in range(x.size):
        tmp_val = x[idx]
        
        x[idx] = tmp_val+h
        fxh1 = f(x)
        
        x[idx] = tmp_val-h
        fxh2 = f(x)
        
        grad[idx] = (fxh1 - fxh2) / (2*h)
        x[idx] = tmp_val
    return grad
In [17]:
numerical_gradient(function_2, np.array([3.0, 4.0]))
Out[17]:
array([6., 8.])
 
  • 기울기는 각 지점에서 낮아지는 방향을 가리킴
  • 각 장소에서 함수의 출력 값을 가장 크게 줄이는 방향
 

4.1.1 경사법

  • 신경망에서 최적의 매개변수를 학습 시에 찾아야 함
  • 최적이란 손실 함수가 최솟값이 될 때의 매개변수
  • 함수의 값을 낮추는 방안을 제시하는 지표가 기울기
 
  • 경사법은 현 위치에서 기울어진 방향으로 일정 거리만큼 이동
  • 그런 다음 이동한 곳에서 마찬가지로 기울기를 구하고, 또 그 기울어진 방향으로 나아가기를 반복
  • 이렇게 해서 함수의 값을 점차 줄이는 것
  • 학습률 : 갱신하는 양 (너무 커도, 작아도 안됨)
In [18]:
def gradient_descent(f, init_x, lr=0.01, step_num=100):
    x = init_x #초깃값
    
    for i in range(step_num):
        grad = numerical_gradient(f,x) #f : 최적화하려는 함수
        x -= lr * grad
    return x
In [19]:
# x_0 ^2 + x_1 ^ 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 = init_x, lr=0.1, step_num=100) # 거의 0에 근접
Out[19]:
array([-6.11110793e-10,  8.14814391e-10])
In [20]:
# 학습률이 너무 큰 경우 lr = 10
gradient_descent(function_2, init_x = init_x, lr=10, step_num=100) # 발산
Out[20]:
array([ 2.34235971e+12, -3.96091057e+12])
In [21]:
# 학습률이 너무 큰 경우 lr = 10
gradient_descent(function_2, init_x = init_x, lr=1e-10, step_num=100) # 거거의 갱신되지 않은채 끝
Out[21]:
array([ 2.34235971e+12, -3.96091057e+12])
 
  • 하이퍼파라미터 : 사람이 직접 설정해야하는 매개변수
 

4.4.2 신경망에서의 기울기

  • 가중치 매개변수에 대한 손실 함수의 기울기
In [22]:
import sys, os
sys.path.append(os.pardir)
from common.functions import softmax, cross_entropy_error
from common.gradient import numerical_gradient

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
In [23]:
net = simpleNet()
net.W #가중치 매개변수
Out[23]:
array([[-0.61459739, -0.22910884, -0.1635636 ],
       [-0.09844498,  0.48387276,  0.64585465]])
In [24]:
x = np.array([0.6, 0.9])
p = net.predict(x)
print(p)
print(np.argmax(p)) #최댓값의 인덱스

t= np.array([0,0,1]) # 정답 레이블
net.loss(x,t)
 
[-0.45735891  0.29802018  0.48313103]
2
Out[24]:
0.7981595344689355
In [25]:
def f(W):
    return net.loss(x,t)

dW = numerical_gradient(f, net.W)
dW
Out[25]:
array([[ 0.10545456,  0.22445142, -0.32990598],
       [ 0.15818185,  0.33667713, -0.49485897]])
 
  • dw11 은 약 0.2이다. 이는 w11을 h만큼 늘리면 손실함수의 값은 0.2h만큼 증가한다는 의미이다.
  • 따라서 손실 함수를 줄인다는 관점에서 w11은 음의 방향으로 갱신해야 함을 알 수 있다.
  • dw23 은 약 -0.5이니 양의 방향으로 갱신해야 함을 알 수 있다.
 

4.5 학습 알고리즘 구현하기

  • 전제 : 신경망에는 적응 가능한 가중치와 편향이 있고, 이 가중치와 편향을 훈련 데이터에 적응하도록 조정하는 과정을 '학습' 이라고 함
  • 1단계 - 미니배치 : 훈련 데이터 중 일부를 무작위로 가져옴. 이렇게 선별한 데이터를 미니배치라 하며, 그 미니배치의 손실 함수 값을 줄이는 것이 목표
  • 2단계 - 기울기 산출 : 미니배치의 손실 함수 값을 줄이기 위해 각 가중치 매개변수의 기울기를 구함. 기울기는 손실 함수의 값을 가장 작게 하는 방향을 제시
  • 3단계 - 매개변수 갱신 : 가중치 매개변수를 기울기 방향으로 아주 조금 갱신함
  • 4단계 - 반복 : 1~3단계 반복
 
  • 데이터를 미니배치로 무작위로 선정하기 때문에 확률적 경사 하강법이라고 불림
 

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

In [26]:
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

    def gradient(self, x, t):
        W1, W2 = self.params['W1'], self.params['W2']
        b1, b2 = self.params['b1'], self.params['b2']
        grads = {}

        batch_num = x.shape[0]

        # forward
        a1 = np.dot(x, W1) + b1
        z1 = sigmoid(a1)
        a2 = np.dot(z1, W2) + b2
        y = softmax(a2)

        # backward
        dy = (y - t) / batch_num  # ????
        grads['W2'] = np.dot(z1.T, dy)
        grads['b2'] = np.sum(dy, axis=0)

        da1 = np.dot(dy, W2.T)
        dz1 = sigmoid_grad(a1) * da1
        grads['W1'] = np.dot(x.T, dz1)
        grads['b1'] = np.sum(dz1, axis=0)

        return grads
 
  • params : 신경망의 매개변수를 보관하는 딕셔너리 변수
  • grads : 기울기 보관하는 딕셔너리 변수
In [27]:
net = TwoLayerNet(input_size = 784, hidden_size=100, output_size = 10)
net.params['W1'].shape, net.params['b1'].shape, net.params['W2'].shape, net.params['b2'].shape,
Out[27]:
((784, 100), (100,), (100, 10), (10,))
 

4.5.2 미니배치 학습 구현하기

In [ ]:
import numpy as np
import sys
import os
sys.path.append(os.pardir)
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet



"""
60000개의 훈련 데이터에서 임의로 100개의 데이터(이미지&정답 레이블)을 추려냄.
100개의 미니배치를 대상으로 확률적 경사 하강법을 수행해 매개변수를 갱신한다.
경사법에 의한 갱신 횟수를 1000번으로 설정하고 갱신할 때마다 손실 함수를 계산한다.
"""
(x_train, t_train), (x_test, t_test) = \
    load_mnist(normalize=True, one_hot_label=False)

# 하이퍼 파라메터
iters_num = 10  # 반복횟수
train_size = x_train.shape[0]
batch_size = 100  # 미니배치 크기
learning_rate = 0.1
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)

train_loss_list = []
train_acc_list = []
test_acc_list = []

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

for i in range(iters_num):
    print(i)
    # 미니배치 획득
    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)

    # 1에폭 당 정확도 계산
    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))

# print(train_loss_list)


# 4.5.3 시험 데이터로 평가하기
"""
위의 계산에서 손실 함수의 값이 점점 감소하게 되는데, 이때의 손실 함수는
훈련 데이터의 미니배치에 대한 손실 함수를 말한다.
훈련 데이터 외의 데이터를 올바르게 인식하는지(오버피팅이 일어나지 않았는지) 확인 필요.
1 에폭별로 훈련 데이터와 시험 데이터에 대한 정확도를 기록하도록 수정.
에폭epoch : 학습에서 훈련 데이터를 모두 소진했을 때의 횟수.
10000개를 100개의 미니배치로 학슬할 경우 100회가 1에폭이 된다.

훈련 데이터와 시험 데이터의 정확도 추이가 비슷하다면 오버피팅이 일어나지 않은 것이다.
오버피팅이 발생했다면, 어느 순간부터 시험 데이터에 대한 정확도가 떨어지기 시작한다.
오버피팅이 발생하기 전에 학습을 중단해 오버피팅을 예방하는 기법을 조기 종료early stopping라고 한다.
"""
 

4.5.3 시험 데이터로 평가히기

  • 1에폭 : 학습에서 훈련 데이터를 모두 소진했을 때의 횟수, 훈련 데이터 10000개를 100개의 미니배치로 학습할 경우, 확률적 경사하강법을 100회 반복하면 모든 훈련 데이터를 소진하게 됨. 이 경우 100회가 1에폭
  • 에폭을 반복할 경우 정확도는 거의 수렴하게 됨
 

4.6 정리

  • 기계학습에서 사용하는 데이터셋은 훈련 데이터와 시험 데이터로 나눠 사용한다.
  • 훈련 데이터로 학습한 모델의 범용 능력을 시험 데이터로 평가한다.
  • 신경망 학습은 손실 함수를 지표로, 손실함수의 값이 작아지는 방향으로 가중치 매개변수를 갱신한다.
  • 가중치 매개변수를 갱신할 때는 가중치 매개변수의 기울기를 이용하고, 기울어진 방향으로 가중치의 값을 갱신하는 작업을 반복한다.
  • 아주 작은 값을 주었을 때의 차분으로 미분하는 것을 수치 미분이라고 한다.
  • 수치 미분을 이용해 가중치 매개변수의 기울기를 구할 수 있다.
  • 수치 미분을 이용한 계산에는 시간이 걸리지만, 그 구현은 간단한다.

'Study > 밑바닥부터 시작하는 딥러닝' 카테고리의 다른 글

Ch6 학습 관련 기술들  (0) 2021.01.25
Ch5 오차역전파법  (0) 2021.01.25
Ch3 신경망  (0) 2021.01.06
Ch2 퍼셉트론  (0) 2021.01.06
TOP