본문 바로가기

AI tech

밑바닥부터 시작하는 딥러닝 정리 Chapter 4 - 신경망 학습

반응형

개요

  • 신경망 학습 : 가중치 매개변수의 최적값 구하는 것
  • 목표 : 손실 함수의 결과값을 최소화하는 가중치 매개변수 찾기
  • 경사(하강)법 배울 예정

4.1 데이터에서 학습한다!

신경망 학습에 대한 설명

  1. 사람이 직접 생각한 알고리즘 → 아주 어려움
  2. 데이터를 이용한 기계 학습 → 기계가 규칙을 찾아냄 → 이미지 to 벡터로 변환 시 특징을 사람이 설계
  3. 사람이 개입하지 않는 블록 하나로 학습 → 이거다! 싶은 거지

3번의 장점 :

  • 모든 문제를 같은 맥락에서 풀 수 있음
  • ‘5’를 인식하든, 사람 얼굴이든, 개 얼굴이든 주어진 데이터를 온전히 학습하고 패턴을 발견하려 시도함
  • 주어진 데이터를 그대로 입력 데이터로 활용 → end-to-end 학습

 

훈련 데이터와 실험 데이터

  • 훈련 데이터로 훈련, 실험 데이터로 실력 평가 → 범용 능력 평가를 위함 / 오버피팅 피하기

 

4.2 손실 함수 - 신경망 성능의 ‘나쁨’ 정도

  • 신경망은 ‘하나의 지표’를 기준으로 최적 매개변수 값을 탐색함 → 이 지표가 손실 함수
  • 일반적으로 오차제곱합, 교차 엔트로피 오차를 사용

 

  • 오차제곱합(sum of squares for error, SSE)
  • 각 원소의 출력(추정 값) 과,
  • 정답 레이블(참 값)의 차를 제곱 후 총합

 

import numpy as np

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

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

y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0] # 총합 1 - 소프트맥스
sum_squares_error(np.array(y), np.array(t))
# 0.09750000000000003

y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0] # 총합 1 - 소프트맥스
sum_squares_error(np.array(y), np.array(t))
# 0.5975

# 결론 : 오차제곱합이 작아야 성능이 좋은 것이다~

 

  • 교차 엔트로피 오차(cross entropy error, CEE)
  • 정답일 때의 추정(신경망 출력)의 자연로그 값
  • 정답이 ‘2’이면, 이 때의 신경망 출력의 자연로그

 

import numpy as np

def cross_entropy_error(y, t):
	delta = 1e-7
	return -np.sum(t * np.log(y + delta))  #delta 더하는 이유: -inf 막기 위해

t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0] # '2'가 제일 높음
cross_entropy_error(np.array(y), np.array(t))
# 0.510825457099338

y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0] # '7'이 제일 높음
cross_entropy_error(np.array(y), np.array(t))
# 2.302584092994546

# 결론 : 역시 작아야 성능이 좋다~

 

  • 미니배치 학습데이터가 많아지면? → 모두 구하기 힘들다~
    • 훈련 데이터로부터 일부만 골라 학습 → 이게 미니배치
    • 각 훈련 데이터에 대해 손실 함수의 합을 구해서, 그걸 지표로 삼아 최소화 함

 

  • 왜 손실 함수를 설정하는가?
    • 정확도를 지표로 하면 편하겠지만, 이의 매개변수 미분이 대부분의 장소에서 0이 되어서 안됨
    • 무슨 말? - 어떤 값을 최소화하려면 미분 값을 보면서 0이 될 때 까지 조정하는게 국룰이라, 정확도는 지표로 쓸 수 없다는 뜻
    • 매개변수에 변화를 줄 때, 손실 함수 값의 변화(기울기 = 미분 값)를 관찰해야 함
    • 그래서 손실 함수를 지표로 설정

 

 

  • 시그모이드 함수가 각광 받는 이유 : 어느 지점에서도 접선 기울기가 0이 아니어서

 

4.3 수치 미분

  • 경사하강법 - 기울기(미분) 값을 기준으로 매개변수 조정
  • 완벽한 접선의 기울기를 구하는 것은 어렵다! → 수치 미분 이용
def numerical_diff(f, x):
	h = 1e-4 # 일반적으로 알려진 적당히 미세한 값(너무 작으면 반올림 오차 발생)
	return (f(x+h) - f(x-h)) / (2*h)

# 결론 : x에서의 접선기울기를 -> (x+h), (x-h)에서의 두 점의 기울기로 근사해서 구함

 

편미분

변수가 여럿인 함수에서 → 하나의 변수에 대해 미분

# x0 = 3.0, x1 = 4.0

def function_tmp1(x0): # x0에 대한 편미분 준비 / 나머지 고정
	return x0*x0 + 4.0**2.0 

def function_tmp2(x1): # x1에 대한 편미분 준비 / 나머지 고정
	return 3.0**2.0 + x1*x1

numerical_diff(function_tmp1, 3.0) # x0 = 3.0
# 6.00000000000378
numerical_diff(function_tmp2, 4.0) # x1 = 4.0
# 7.999999999999119
  • 의미 : 역시 특정 장소의 기울기 구하되, 여러 변수 중 목표 변수 하나에 초점을 맞추고 나머지 고정

 

4.4 기울기

  • 각 모든 변수의 편미분을 벡터로 정리한 것 → Gradient(기울기)
import numpy as np

# f(x0, x1) = x0^2 + x1^2
def function_2(x):
	return x[0]**2 + x[1]**2  

# f : 함수, x : 넘파이 배열
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]에 대한 미분
		x[idx] = tmp_val # 값 복원
	
	return grad

numerical_gradient(function_2, np.array([3.0, 4.0]))
# array([6., 8.])
numerical_gradient(function_2, np.array([0.0, 2.0]))
# array([0., 4.])
numerical_gradient(function_2, np.array([3.0, 0.0]))
# array([6., 0.])

# 결론 : 
# 점 (3, 4)에서의 기울기 : (6, 8)
# 점 (0, 2)에서의 기울기 : (0, 4)
# 점 (3, 0)에서의 기울기 : (6, 0)
  • 모든 점에서의 기울기가 가리키는 곳 : 함수의 출력 값을 가장 크게 줄이는 방향

결론 : 기울기를 잘 이용해서 손실 함수의 최솟값을 찾으려고 한다~

→ 경사(하강)법

 

 

경사(하강)법

  • 기울기를 이용해서 함수의 최솟값을 찾으려고 함
  • 주의1 : 기울기가 0인 장소를 찾을 수 있지만, 반드시 그 곳이 최솟값이라고 할 수는 없음
  • 주의2 : 기울어진 방향이 최솟값을 가리키는 것은 아니지만, 그 방향으로 가야 함수 값을 줄일 수 있음
  • 방법 : 현 위치에서 기울어진 방향으로 일정 거리 만큼 이동 

→ 반복해서 함수 값을 줄여나감

기계 학습을 최적화 하는데 흔히 쓰임

def gradient_descent(f, init_x, lr=0.01, step_num=100):
	x = init_x

	for i in range(step_num): # step_num : 경사법에 따른 반복 횟수
		grad = numerical_gradient(f, x)
		x -= lr*grad
	return x
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)
# [-6.11110793e-10  8.14814391e-10]

# 결론 : 실제 진정한 최솟값 위치인(0, 0)에 아주 가깝다~
  • 학습률 등의 hparam 조정을 잘 해야 한다~
init_x = np.array([-3.0, 4.0])
gradient_descent(function_2, init_x=init_x, lr=10.0, step_num=100) # 학습률이 크면 -> 발산
# [-2.58983747e+13 -1.29524862e+12]

init_x = np.array([-3.0, 4.0])
gradient_descent(function_2, init_x=init_x, lr=1e-10, step_num=100) # 학습률이 작으면 -> 느림
# [-2.99999994  3.99999992]

 

신경망에서의 기울기

  • 가중치 W(2*3 형상), 손실함수 L인 신경망
  • L을 최소화 해야 함 → L의 기울기를 통해 손실 함수 최소화 → 성능 향상이 목적
  • 이 경우 가중치와 경사를 표현하면 다음과 같음

$$ W = \begin{pmatrix} w_{11} & w_{12} & w_{13} \\ w_{21} & w_{22} & w_{23} \\ \end{pmatrix}, \frac{\partial L}{\partial W} = \begin{pmatrix} \frac{\partial L}{\partial W}{11} & \frac{\partial L}{\partial W}{12} & \frac{\partial L}{\partial W}{13} \\ \frac{\partial L}{\partial W}{21} & \frac{\partial L}{\partial W}{22} & \frac{\partial L}{\partial W}{23} \\ \end{pmatrix} $$

  • 신경망의 기울기를 구한 다음엔 경사하강법으로 가중치 매개변수를 갱신해나가기만 하면 됨

 

4.5 학습 알고리즘 구현하기

4단계 - 반복

1~3단계를 반복

  • 손글씨 숫자 학습 신경망 구현
  • (2층 신경망 대상, MNIST 데이터셋 사용)
  • 미니배치 학습 활용 예정
  • 이후 시험 데이터로 평가까지!
import numpy as np
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet

(x_train, t_train), (x_test, t_test) = load_mnist(one_hot_label=True)

network = TwoLayerNet(input_size=784, hidden_size=50, output_size=1)

# hparams
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 = []

# 1에폭당 반복 수
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.numerical_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))

정리

  • 신경망 학습에 대해 배움
  • 손실 함수 라는 ‘지표’ 도입 + 최소화 하기 위한 수단인 경사(하강)법에 대해 소개

반응형