본문 바로가기

AI/딥러닝

[밑바닥부터 시작하는 딥러닝] 4장 신경망 학습

728x90
반응형

4.1 데이터에서 학습하다!

학습 - 훈련 데이터로부터 가중치 매개변수의 최적값을 자동으로 획득하는 것
신경망이 학습할 수 있게 해주는 지표 → 손실 함수
 
실제 신경망의 매개변수는 무수히 많음 → 수작업으로 결정 X
 

4.1.1 데이터 주도 학습

기계학습의 중심에는 데이터 존재 
주어진 데이터를 활용해서 해결)  이미지에서 특징 추출, 특징의 패턴을 기계학습 기술로 학습

회색부분 - 사람 개입 X

두번째 접근 방식- 특징을 사람이 설계
세번째 접근 방식- 특징도 '기계'가 스스로 학습
 
종단간(처음부터 끝까지) 기계학습 - 데이터에서 목표한 결과를 사람의 개입없이 얻음
 
 

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

훈련 데이터 / 시험 데이터
훈련 데이터만 사용하여 학습하며 최적의 매개변수를 찾음
시험 데이터를 사용하여 훈련한 모델의 실력을 평가
 
WHY?
범용 능력(아직 보지 못한 데이터로도 문제를 올바르게 풀어내는 능력)을 제대로 평가하기 위해
 
오버피팅- 한 데이터셋에만 지나치게 최적화된 상태
 

4.2 손실 함수

'하나의 지표'를 기준으로 최적의 매개변수 값을 탐색
신경망에서 사용하는 지표 →  손실 함수 (신경망 성능의 '나쁨'을 나타내는 지표)
 

4.2.1 오차제곱합

오차제곱합

y_k - 신경망의 출력(신경망이 추정한 값)
t_k - 정답 레이블
k - 데이터의 차원 수
 

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]

원-핫 인코딩 방식
정답을 가리키는 원소 1 / 그 외에는 0
 

def sum_squares_error(y, t):
	return 0.5 * np.sum((y-t)**2)

첫번째 예의 손실 함수 쪽 출력이 작음 = 정답 레이블과의 오차도 작음
 

4.2.2 교차 엔트로피 오차

교차 엔트로피 오차

y_k - 신경망의 출력(신경망이 추정한 값)
t_k - 정답 레이블 (정답에 해당하는 인덱스의 원소만 1, 나머지는 0)
실질적으로 정답일 때(t_k가 1일때)의 추정의 자연로그를 계산하는 식

x가 1일 때 y는 0

x가 0에 가까워질수록 y의 값은 점점 작아짐
정답에 해당하는 출력이 커질수록 0에 다가감 / 출력이 1일 때 0 → 정답일 때의 출력이 작을 수록 오차는 커짐

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

np.log() 에 0 입력을 막기 위해 아주 작은 값 delta 더함

오차제곱합의 판단과 일치
 

4.2.3 미니배치 학습

 교차 엔트로피 오차 (훈련 데이터 모두에 대한 손실함수의 합을 구함)

t_nk - n번째 데이터의 k번째 값
y_nk - 신경망의 출력
t_nk - 정답 레이블
 
1/n - N으로 나눔으로써 '평균 손실 함수'를 구함
 
신경망 학습에서도 훈련 데이터로부터 일부만 골라 학습을 수행 - 미니배치 학습
일부 = 미니배치
 

train_size = x_train.shape[0] # 60000
batch-size = 10
batch_mask = np.random.choice(train_size, batch_size) # randomly select indecies from 0-59999
x_batch = x_train[batch_mask]
t_train = t_train[batch_mask]

'''
>>> np.random.choice(60000, 10)
array([8013, 14666, 58210, 10, 33333 ...])
'''

np.random.choice() - 지정한 범위의 수 중에서 무작위로 원하는 개수만 꺼냄
 

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

def cross_entropy_error(y, t):
  if y.ndim == 1: # if dimension of y is 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))

y- 신경망의 출력
t-  정답 레이블
 
y가 1차원 - 데이터 하나당 교차 엔트로피 오차를 구함  → reshape()로 데이터 형상 바꿔줌
 
원-핫 인코딩이 아닐 때

def cross_entropy_error(y, t):
  if y.ndim == 1: # if dimension of y is 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[np.arange(batch-size), t] + 1e - 7))

※ t가 0인 원소는 교차 엔트로피 오차도 0이므로 계산 무시해도 좋음
 

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

'정확도'라는 지표 대신 '손실 함수의 값' 사용.
 
WHY?
신경망 학습에서는 최적의 매개변수 탐색할 때 손실 함수의 값을 가능한 작게하는 매개변수 값 찾음
→ 매개 변수의 미분을 계산 → 미분 값을 단서로 매개변수의 값을 서서히 갱신하는 과정 반복
 
가중치 매개변수의 손실 함수의 미분 = '가중치 매개변수의 값을 아주 조금 변화 시켰을 때, 손실 함수가 어떻게 변하나'
 
정확도는 미분 값이 대부분의 장소에서 0이 되어 매개변수를 갱신할 수 없음
 
계단함수 - 값이 불연속적으로 변화(대부분의 장소에서 기울기 0)
시그모이드함수 - 값이 연속적으로 변하고 곡선의 기울기도 연속적으로 변함 (기울기가 0이 되지 않음)
 

4.3 수치 미분

기울기 값을 기준으로 나아갈 방향을 정함

미분 - 한순간의 변화량을 표시

def numerical_diff(f, x):
    h = 1e-50 
    return (f(x+h) - f(x)) / h

※ 개선점 
1. 반올림 오차 - 1e-50을 float32형으로 나타내면 0.0이 됨
2. f의 차분 - (x+h)와 x 사이의 기울기는 진정한 미분과 엄밀히 일치 X
 
(x+h) (x-h) 
x를 중심으로 그 전후의 차분을 계산 - 중심 차분 or 중앙 차분
 
(x+h) 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

>>> 0.1999999999990898
>>> 0.2999999999986347

진정한 미분 - 0.2, 0.3
 

4.3.3 편미분

def function_2(x):
  return x[0]**2 + x[1]**2 
  # 또는 return np.sum(x**2)

x- 넘파이 배열
편미분 - 변수가 여럿인 함수에 대한 미분 
 
 
# x0=3, x1=4일때, x0에 대한 편미분 af/ax0

def function_tmp1(x0):
  return x0*x0 + 4.0**2.0
  
  
print(numerical_diff(function_tmp1, 3.0))
>>> 6.00000000000378

# x0 = 3, x1 = 4일때, x1에 대한 편미분 af/ax1

def function_tmp2(x1):
  return 3.0**2.0 + x1*x1
  
print(numerical_diff(function_tmp2, 4.0))  
  >>> 7.999999999999119

 

4.4 기울기

def numerical_gradient(f, x):
  h = 1e-4
  grad = np.zeros_like(x) # x와 형상이 같은 배열 생성
 
  for idx in range(x.size):
    tmp_val = x[idx]
    # f(x+h) 계산
    x[idx] = tmp_val + h
    fxh1 = f(x)
 
    #f(x-h) 계산
    x[idx] = tmp_val - h
    fxh2 = f(x)
 
    grad[idx] = (fxh1 - fxh2) / (2*h)
    x[idx] = tmp_val # 값 복원
  
  return grad

동작방식 - 변수가 하나일 때의 수치 미분과 거의 같음
np.zero_like(x) - x와 형상이 같고 원소가 모두 0인 배열을 만듦
f - 함수 / x - 넘파이 배열
 

  • 방향을 가진 벡터로 그려짐
  • 기울기는 함수의 '가장 낮은 장소(최솟값'를 가르킴
  • '가장 낮은 곳'에서 멀어질수록 화살표의 크기가 커짐

 
기울기가 가리키는 쪽은 각 장소에서 함수의 출력 값을 가장 크게 줄이는 방향
 

4.4.1 경사법(경사 하강법)

매개 변수 공간이 광대하여 어디가 최솟값인지 짐작 X
→ 이런 상황에서 기울기를 이용해 함수의 최솟값을 찾으려는 것 = 경사법
 
각 지점에서 함수의 값을 낮추는 방안을 제시하는 지표 - 기울기 (보장 x)
 
경사법 - 현 위치에서 기울어진 방향으로 일정 거리만큼 이동
이동한 곳에서 기울기를 구하고, 기울어진 방향으로 나아가기를 반복 → 함수의 값을 점차 줄여나감
 
최솟값 찾음- 경사 하강법
최댓값 찾음- 경사 상승법

경사법

에타 - 갱신하는 양
 

def gradient_descent(f, init_x, lr = 0.01, step_num = 100):
  x = init_x
  for i in range(step_num):
    grad = numerial_gradient(f, x)
    x -= lr * grad
  
  return x

init_x - 초깃값
lr - learning reate를 의미하는 학습률
step_num - 경사법에 따른 반복 횟수
 

그림 4-10

값이 낮은 장소인 원점에 점차 가까워짐
 

  • 학습률이 너무 크면 큰 값으로 발산
  • 학습률이 너무 작으면 거의 갱신되지 않은 채 끝남

※하이퍼파라미터 - 학습률 같은 매개변수, 사람이 직접 설정해야하는 매개 변수
                                    여러 후보 값 중에서 시험을 통해 잘 학습하는 값을 찾는 과정을 거침
 

4.4.2 신경망에서의 기울기

신경망에서의 기울기 = 가중치 매개 변수에 대한 손실 함수의 기울기

가중치가 W, 손실 함수가 L
1행 1번째 원소 - W11을 조금 변경했을 때 손실함수 L 이 얼마나 변하느냐를 나타냄
형상 W와 같음 2x3
 
 
simpleNet 클래스

class simpleNet:
  def __init__(self):
    self.W = np.random.randn(2, 3) # init with normal distribution
    
  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

형상 2x3인 가중치 매개변수 하나를 인스터스 변수로 가짐
메서드 2개
 
predict(x) - 예측 수행
loss(x,t) - 손실 함수의 값을 구함 
x- 입력 데이터
t - 정답 레이블
 

f= lambda w: net.loss(x,t)
dW= numerical_gradient(f, net.W)

신경망의 기울기 구함 → 경사법에 따라 가중치 매개변수를 갱신
 

4.5 학습 알고리즘 구현하기


전제
신경망에 적응 가능한 가중치, 편향이 있음
<경사 하강법>
1단계 - 미니배치
훈련 데이터 일부를 무작위로 가져옴. 선별 데이터 = 미니배치, 미니배치의 손실 함수 값을 줄이는 겂이 목표
 
2단계 - 기울기 산출
손실함수 값을 줄이기 위해 각 가중치 매개변수의 기울기를 구함. 손실 함수의 값을 가장 작게 하는 방향을 제시
 
3단계 - 매개변수 갱신
가중치 매개변수를 기울기 방향으로 아주 조금 갱신
 
4단계 - 반복
1~3 단계 반복


데이터를 미니 배치로 무작위 선정 → 확률적 경사 하강법 (SGD)
 

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

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

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(input_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
  
  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
  
  def numerical_gradient(self, x, t):
    loss_W = lambda W: self.loss(x, t)
    
    grads = {}
    grads['W1'] = numerical_gradient(loss_W, self.parmas['W1'])
    grads['b1'] = numerical_gradient(loss_W, self.parmas['b1'])
    grads['W2'] = numerical_gradient(loss_W, self.parmas['W2'])
    grads['b2'] = numerical_gradient(loss_W, self.parmas['b2'])
    
    return grads

<변수>
params - 신경망의 매개변수를 보관하는 딕셔너리 변수(인스턴스 변수)
                params['W1] - 1번째 층의 가중치, params['b1'] - 1번째 층의 편향
 
grads - 기울기 보관하는 딕셔너리 변수 (numerical_gradient() 메서드의 반환값)
             grads['W1] - 1번째 층의 가중치의 기울기, grads['b1'] - 1번째 층의 편향의 기울기


<메서드>
__init__(self, input_size, hidden_size, output_size) - 초기화를 수행
                                                                                         (입력층이 뉴런수, 은닉층의 뉴런수, 출력층의 뉴런수)
predict(self,x) - 예측(추론)을 수행
                                x - 이미지 데이터
loss(self,x,t) - 손실 함수의 값 구함
                             x - 이미지 데이터 / t - 정답 레이블
accuracy(self,x,t) - 정확도 구함
numerical_gradient(self,x,t) - 가중치 매개변수의 기울기를 구함
gradient(self,x,t) - 가중치 매개변수의 기울기를 구함 
 
 

4.5.2 미니배치 학습 구현하기

훈련 데이터 중 일부를 무작위로 꺼냄

import numpy as np
from mnist import load_mnist

(x_train, t_train), (x_test, t_test) = \
  load_mnist(normalize=True, one_hot_label = True)
  
train_loss_list = []

iters_num = 10000
train_size = x_train.sahpe[0]
batch-size = 100
learning_rate = 0.1

network = TwoLayerNetwork(input_size = 784, hidden_size = 50, output_size = 10)

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)
  
  #매개변수 갱신
  for key in ('W1', 'b1', 'W2', 'b2'):
    network.parmas[key] -= learning_rate * grad[key]
  
  #학습 경과 기록
  loss = network.loss(x_batch, t_batch)
  train_loss_list.append(loss)

 
미니 배치 크기 100

왼쪽: 10,000회 반복까지의 추이
 
 

4.5.3 시험 데이터로 평가하기

 
에폭 - 훈련데이터 10,000개를 100개의 미니 배치로 학습할 경우 확률적 경사 하강법 100회 반복
           100회 = 1 에폭
 

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

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

훈련 데이터에 대한 정확도 = 실선
시험 데이터에 대한 정확도 = 점선
 
에폭이 진행될 수록 훈련 데이터와 시험 데이터를 사용한 정확도가 좋아짐 (차이 X)

728x90
반응형