딥러닝기초_0413

allzeroyou·2022년 4월 13일
0

딥러닝기초

목록 보기
10/22

중간고사

4월 27일 수요일 대면

신경망 학습에 대한 내용을 다룹니다.
학습목표는 손실함수의 값을 적게 만드는 경사법에 대해 이해한다.

데이터 학습

학습이란,
훈련 데이터를 이용해 매개변수가중치의 최적값을 자동으로 구하는 것을 의미.

신경망이 학습할 때, 손실 함수를 지표로 하는데
이때, 손실 함수의 결과값을 가장 작게 하는 가중치를 구하는 것이 학습의 목표이다.

기존에는 데이터가 주어지면 사람이 모든 규칙을 만들거나 특징을 설계 => 신경망은 데이터로부터 스스로 학습해 규칙을 찾아냄.

이러한 이유로 딥러닝을 종단간 기계학습(end-to-end learning)이라고도 함.
처음부터 끝까지 데이터 입력에서 결과 출력까지 사람의 개입없이 얻음을 뜻함.

훈련 데이터와 시험 데이터

머신 러닝은 데이터를 훈련데이터, 시험데이터로 나눠 학습과 실험을 수행.
훈련 데이터만을 이용해 학습을 진행하며 최적의 매개변수가중치와 편향 찾기

시험 데이터를 사용해 훈련된 모델의 성능을 평가

이때 시험 데이터를 사용하는 이유는 범용 능력을 평가함을 위함. 다뤘던 문제인 훈련 데이터에만 성능이 높고 새로운 데이터에 대해서 문제가 있으면 안되고 말 그대로 '범용적인', universal하게 성능을 보이길 원함.

한 데이터셋에만 지나치게 최적화된 상태를 오버피팅(overfitting)이라고 한다.

손실 함수

손실함수는 측정한 데이터를 토대로 산출한 모델의 예측값(y)과 실제값, 정답(t)의 차이를 표현하는 지표로, 모델이 얼마나 데이터를 잘 표현하지 못하는가 를 나타낸다.

손실함수는 정답과 예측을 입력으로 받아 실숫값 지표를 만드는데, 이 지표의 숫자가 높을 수록 모델의 성능이 좋지 않은것.

따라서 손실함수의 함숫값이 최소가 되도록 가중치편향을 찾는 것이 딥러닝 학습의 목표.

➡️ 예측값실제값에 대한 오차를 줄이기 위함.

평균제곱오차(Mean Square Error, MSE)

  • y(예측값)과 t(실제값, 정답) 사이의 평균제곱오차를 정의.
  • 공식이 매우 간단
  • 차가 커질수록, 제곱 연산으로 인해 값이 더욱 뚜렷해짐
  • 제곱으로 인해 오차가 양수이든 음수이든 누적 값 증가시킴


y는 신경망 출력층의 결과인 소프트맥스 함수의 출력값
t는 one-hot encoding으로 표현된 정답값

one-hot-encoding
레이블 중 오직 하나만 HOT하고, 나머지는 COLD한 데이터.
고유 값에 해당하는(hot) 컬럼에만 1을 표시, 나머지 컬럼(cold)에는 0을 표시

손글씨 숫자 인식에서 손실함수(평균 제곱 오차) 구하기

import numpy as np

# 손실함수 정의
def sum_square_error(y, t):
  return 0.5 * np.sum((y-t)**2)

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]


print(sum_square_error(np.array(y), np.array(t))) 
# 0.09750000000000003
# 손실 함수 값 낮음 => 성능 좋음(정답, 잘 추정된 값)


y = [0.1, 0.05, 0.1, 0.0, 0.7, 0.3, 0.0, 0.0, 0.0, 0.0]

print(sum_square_error(np.array(y), np.array(t)))
# 0.7012499999999999
# 손실 함수 값 높음 => 성능 안 좋음(오답, 잘못 추정된 값)

교차엔트로피오차

엔트로피는 물리학상 어지러운 정도를 뜻하되, 신경망에서는 정보의 양을 뜻하며 신호인식에 쓰인다.

이때, 정보는 신호에 존재하는 정보의 양이며 누구나 알만한 정보가 아닌 새롭고 특이해 사람들을 놀라게 만드는 정도가 클수록 많다.
ex. 나 내일 학교가 => 누구나 예측가능만 정보임. 정보의 양 적음
나 내일 우주선 타 => 예측하기 어려운 새로운 정보임. 정보의 양 많음.

  • 정보엔트로피: 하나의 확률분포가 갖는 불확실성(놀람의 정도)혹은 정보량을 정량적으로 계산할 수 있도록 하는 개념이다.

  • 교차엔트로피: 두 가지 확률분포가 얼마나 비슷한지, 수리적으로 나타내는 개념.

실제 분포 q에 대해 알지못하는 상태에서 모델링을 통해 구한 분포인 p를 통해 q를 예측함.

이때 q와 p가 모두 들어가기 때문에 '교차'엔트로피라고 함

  • q는 딥러닝 모델의 추정 확률 분포
  • p는 딥러닝 모델이 추구해야할 미지의 확률 분포

교차엔트로피에서는 실제값과 예측값이 맞는 경우에 0으로 수렴, 값이 틀릴 경우 발산
따라서, 두 확률분포가 서로 얼마나 다른지 나타내는 정량적 지표 역할수행.

목적은 단 하나, 예측값과 실제값의 차이를 줄이기 위해 사용됨.


교차 엔트로피오차 수식에서 log는 밑이 e인 자연로그.
t는 one-hot encoding으로 표현된 정답 레이블이므로, 결국 실제로 정답일 때(t=1) softmax 예측값에 대한 자연로그를 계산하는 식.

ex. 정답이 2이고, 신경망의 softmax 출력 값이 0.6 => 교차엔트로피오차는 -log0.6=0.51이 된다.(왜?)

같은 조건에서 신경망의 출력값이 0.1 => -log0.1=2.30

결론적으로, 교차 엔트로피오차는 정답일때 신경망 출력 값이 전체 값이 된다.


y=logx

  • x=1일때 y=0
  • x가 0에 가까워질때 y는 작아짐.

즉, 예측값이 커질수록 오차는 0에 가까워지고, 예측 값이 작아질수록 오차는 커진다.

from cmath import e
import numpy as np
import math

def cross_entropy_error(y, t):
  delta = 1e-7
  return -np.sum(t * np.log(y + delta))
  # 예측값 y에 아주 작은 값인 delta를 더함

# 잘 추정된 값
y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
# 잘못 추정된 값
y2 = [0.1, 0.05, 0.3, 0.0, 0.25, 0.6, 0.0, 0.1, 0.0, 0.0]
# 정답 레이블
t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]

print(cross_entropy_error(np.array(y), np.array(t))) # 0.510825457099338
print(cross_entropy_error(np.array(y2), np.array(t))) # 1.2039724709926583

평균제곱오차와 마찬가지로 교차엔트로피오차에서도 더 작은 값이 출력되는 y가 정답에 가까운 추정값이다.

미니배치학습

손실 함수는 훈련 데이터가 100개 있다면 그 100개의 손실 함수의 값들의 합으로 이뤄진다.

즉, 손실함수는 모든 훈련 데이터를 대상으로 구해야 한다.

그러나 데이터의 개수가 많으면 모든 데이터를 학습하는데 시간이 오래 걸린다.
따라서 전체 데이터 중 일부를 랜덤으로 선별해 근사치 값을 구한다.
선별된 일부 데이터미니배치(mini-batch)라고 한다

# 미니 배치 학습을 위한 준비
import sys, os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist

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

print(x_train.shape) # (60000, 784)
print(t_train.shape) # (60000, 10)

# 훈련데이터에서 무작위로 10장 뽑기
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]

print(np.random.choice(60000, 10)) # [50675 57384 51203 13246 33019  1454 49923 32680 42774   545]

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

1) one-hot-encoding

# one-hot-encoding
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

ndim?

numpy.ndarray객체의 메소드 중 하나로, ndarray.ndim은 배열이 몇 차원인지 반환한다.

shape?

numpy.ndarray객체의 메소드 중 하나로, ndarray.shape은 배열 각 차원의 크기를 튜플형태로 표현한다.
ex. n행과 m열의 행렬의 경우 (n,m) 형태의 튜플로 나타남.

ndim vs dim

ndim은 array dimension(배열의 차원)을 의미
dim은 차원을 의미(선형대수에서 말하는 행렬의 차원과 같은 개념)
예를 들어

import numpy as np
x = np.arange(24).reshape(2,3,4)

print(x)
'''
[[[ 0  1  2  3]
  [ 4  5  6  7]
  [ 8  9 10 11]]

 [[12 13 14 15]
  [16 17 18 19]
  [20 21 22 23]]]
 '''

reshape 함수의 인자로 (2,3,4)가 들어갔음
우선 3,4의 의미는 행렬의 3행, 4열이고 차원이 3차원이다. 마찬가지로 배열의 차원도 3이다.
이때 3으로 숫자는 같지만 차원과 배열의 차원은 다른 개념임을 염두하자.

그렇다면, shape의 첫번째 인자는?
추측하건데 x가 크게 두 덩어리의 배열로 이뤄져 있기 때문에 2가 등장한 것 같다.
여기서 '두 덩어리'라는 것은 차원이나 배열의 차원을 의미하지는 않는다.
첫번째 인자를 차원이나 배열의 차원과는 구분해 frame을 나타내는 인자라고 이름짓겠다.

ndim의 의미란?

import numpy as np
x = np.arange(24).reshape(2,3,4)

print(x[0])
print(x[0][0])
print(x[0][0][0])

각각 인덱싱을 한번, 두번, 세번 해보았는데 각각의 결과가 어떻게 다를지 짐작이 가시나요?

인덱스 한번의 결과

print(x[0])
'''
[[ 0  1  2  3]
 [ 4  5  6  7]
 [ 8  9 10 11]]
'''

maxtrix형태의 array 등장.
여기서 x[0].ndim으로 확인해보면 array dimension의 크기는 2이다.

인덱스 두번의 결과

print(x[0][0])
'''
[ 0  1  2  3]
'''

한번의 인덱싱으로 2차원 배열이 등장했으니, 두번의 인덱싱을 하면 1차원 배열이 등장할 것. 즉, vector이다.

인덱스 세번의 결과

print(x[0][0][0])
'''
0
'''

세번의 인덱싱을 거치면 0차원 배열이 나옴.
알고 있는 친숙한 것으로 바꿔 말하면 scalar이다.

다시 복기해보자.
x[0]으로 두 덩어리로 나눠진 배열들 중 첫번째 배열(matrix)에 접근함.
그 다음 x[0][0]을 통해 첫번째 배열의 첫번째 행에 접근함(vector), 마지막으로 x[0][0][0]을 통해 첫번째 행의 첫번째 원소(scalar)에 접근함.

즉,

  • frame:2(두개의 덩어리)
  • dim: 3(3 x 4 array)
  • ndim: 3(3개의 차원이므로 3번의 인덱싱 가능)

2) 숫자 레이블

# number label
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

왜 정확도 대신 손실 함수?

적절한 매개변수를 찾기위한 지표로 정확도를 사용하지 않고 손실함수를 설정하는 이유는,

신경망 학습에서의 미분 역할에 있다.

신경망 학습에서 최적의 매개변수를 탐색할 때 매개변수에 대해 미분(기울기)을 계산하고 미분 값을 갱신하는 과정을 반복
➡️ 손실 함수의 값을 작게 하는 매개변수를 find!

이때 미분 값이 양수이면 매개변수를 음의 방향으로 갱신,

미분 값이 음수이면 매개변수를 양의 방향으로 갱신.

반면, 정확도를 지표로 삼으면 대부분의 미분값이 0이라 갱신불가.
ex. 100장의 데이터 중 30장을 제대로 인식한다고 가정하자.
이때 정확도는 30%
매개변수의 값을 미세하게 조정 ➡️ 정확도가 개선되지 않고 일정할 것.

왜?
30, 31, 32%와 같이 불연속적으로 개선되고 30.01444%처럼 조정되지 않기 때문이다.

즉, 손실함수는 매개변수의 값이 미세하게 변해도 그에 연속적인 값으로 반응하나 정확도는 거의 반응을 보이지 않는다.

이러한 성질은 계단 함수활성화함수로 사용하지 않는 이유와 일맥상통.

수치 미분

미분이란 한 점에서의 기울기를 의미한다.
기울기는 두 점 사이에서 발생하는 경사인데 미분을 한 점에서의 기울기라고 하는 이유는 두 점 사이의 거리를 매우 좁혀 한 점으로 보일때 그 점에서 기울기를 구하기 때문이다.

즉, 처음에는 두 점 사이의 기울기에서 시작해 최종적으로는 거의 한 점에서의 기울기가 된다.

차분을 통해 미분하는 것을 수치 미분이라고 하는데, 수치 미분은 아래와 같다.

차분
임의 두 점에서의 함수 값들의 차이
f(xi+Δx) - f(xi) 또는 fk+1 - fk

f(x)를 x에 대해 미분한다는 것은,
x의 변화함수 f(x)를 얼마나 변화시키는지 구하겠다는 것이며, 시간 h를 무한히 0으로 근접시켜 한 순간의 변화량을 나타낸다.

위의 식대로 미분 계산을 구현해보면 다음과 같이 할 수 있는데, 이렇게 구현하면 2가지의 문제점 야기한다.

문제점1

h에 최대한 작은값인 10e-50을 대입하였는데, 10e-50은 0.00...1 형태에서 소수점 아래 0이 49개 있다는 의미이다.
이 값은 결국 반올림 오차 문제를 야기하고, 작은 값(예를 들면 소수점 8자리 이하)는 생략되어 최종 계산 결과에 오차가 생긴다.

파이썬에서 10e-50을 float형으로 변환하면, 0의 값을 반환한다.

print(np.float32(10e-50)) # 0.0

따라서 반올림 오차 문제가 없는 값을 사용하기 위해 일반적으로 작은 값인 h = 10^(-4)을 이용한다.

문제점2

함수 f의 차분 문제이다.
위 구현에서는 x+h와 x사이의 함수 f의 차분을 계산하고 있다.
하지만 아래 그림과 같이 진정한 접선은 x에서의 함수기울기(접선)이지만, 위의 구현은 x+h와 x 사이의 기울기이다.

그림을 보면 진정한 접선과는 일치하지 않는다.
이것은 h를 무한히 0으로 근접시키는 것이 불가능해서 발생한 한계이다.
이 오차를 극복하기 위해 함수 f의 (x+h)와 (x-h) 사이 차분을 계산하는 방법이 있다.
이 방법은 x를 중심으로 전, 후를 계산하므로 중심 차분 혹은 중앙 차분이라고 불린다.

따라서, 문제점1-반올림 오차 문제와 문제점2-차분문제(진정한 접선과으 불일치)의 두가지 문제점을 개선해 미분을 구현해보면 다음과 같다.

편미분

변수가 2개인 함수

파이썬 구현

def function_2(x): # 매개변수 x는 numpy 배열
  return x[0]**2 + x[1]**2

위의 함수와 같이 변수가 여러개인 함수에 대해 미분 하는 것을 편미분이라고 한다.
편미분은 변수들 중 어떤 변수에 대해 미분할 것인지 정해줘야 하는데, 지정한 변수를 제외한 나머지 변수는 상수 취급해 계산하면 된다.

예를 들어, x0가 3, x1가 4일때 x0에 대한 편미분을 구하면 다음과 같다.
이때 numerical_diff는 위에서 문제점 2가지를 개선한 수치 미분을 구현한 함수이다.

def function_tmp1(x0): 
  return x0 * x0 + 4.0 ** 2.0

print(numerical_diff(function_tmp1, 3.0))

기울기

함수 f를 x0, x1에 대해 동시에 편미분을 계산하면?
이처럼 모든 변수의 편미분을 벡터로 정리한 것을 기울기라고 한다.

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.zeros_like(x)는 x와 형상이 같고 그 원소가 모두 0인 배열을 만드는 함수이다.
위에서 구현한 기울기 함수를 통해 아래 손실함수에 대한 기울기를 실제로 구해보자.

def function_2(x): # 매개변수 x는 넘파이배열
  return x[0] ** 2 + x[1] ** 2

# (3, 4), (0, 2), (3, 0) 세 점에서의 기울기 구하기
print(numerical_gradient(function_2, np.array([3.0, 4.0]))) # [6. 0.]
print(numerical_gradient(function_2, np.array([0.0, 2.0]))) # [0. 0.]
print(numerical_gradient(function_2, np.array([3.0, 0.0]))) # [6. 0.]

기울기는 각 지점에서 함수의 값이 낮아지는 방향을 가리킨다.
=> 기울기가 가리키는 쪽은 각 장소에서 함수의 출력값을 가장 크게 줄이는 방향임을 기억하자.

경사하강법

신경망은 학습을 통해 최적의 매개변수(가중치, 편향)을 찾는다.
이때 최적의 매개변수손실 함수가 최솟값이 될 때의 매개변수 값을 의미한다.

매개변수의 기울기를 이용해 함숫값이 작아지는 방향으로 이동
=> 극값에 도달할 때 까지 반복함.

함수의 값을 낮추는 방향을 나타내는 지표가 기울기이지만, 최솟값을 위한 방향을 실제로 제시하는 지를 보장 불가.

함수의 극솟값, 최솟값에서는 기울기가 0이다. 따라서 기울기가 0인곳을 찾는 경사법은 최솟값이 아닌 극솟값일수도 있고, 만약 손실함수가 복잡하고 오류가 있다면 더 이상 학습이 진행되지 않을 수 있다.

그렇지만, 기울기가 가리키는 방향이 함수의 값을 줄일 수 있는 단서이므로 기울기를 이용해야 한다.

경사법이란, 현 위치에서 기울어진 방향으로 일정 거리만큼 이동한 후 이동한 위치에서 다시 기울기를 구하고 그 기울어진 방향으로 나아가는 것을 반복해 함수 값을 줄이는 것을 말한다.



기호 에타(η)는 갱신하는 양을 나타내고 이를 학습률(learning rate)라고 한다.
즉, 매개변수 값을 얼마나 갱신할지(함수 값이 낮아지는 곳으로 얼만큼 이동할 것인지? = 보폭)를 정하는 것!

이때, 학습률은 미리 값을 정해두어야 하는데 너무 크거나 작으면 손실 함수의 값이 커지므로 적절하게 설정 필요.

머신러닝 최적화에 흔히 쓰이는 방법

  • 최댓값 찾기: 경사 상승법
  • 최솟값 찾기: 경사 하강법
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)
    x -= lr * grad
  return x

경사 하강법 구현

f는 최적화하려는 손실 함수, init_x는 초깃값, lr는 learning rate, step_num은 경사법에 따른 반복횟수를 의미한다!

기울기에 learning rate을 곱한 값으로 갱신되는 처리를 step_num번 반복.

아래 함수의 최솟값을 구하는 문제는 다음과 같이 구현

손실 함수

from numerical_diff import numerical_gradient

# 수치미분
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

# 경사하강법
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)
    x -= lr * grad
  return x

# 편미분
def function_2(x): # 매개변수 x는 넘파이배열
  return x[0] ** 2 + x[1] ** 2

init_x = np.array([-3.0, 3.0])
print(gradient_descent(function_2, init_x=init_x, lr=0.1, step_num=100))

경사법으로 최솟값을 구하는 문제 구현


학습 후 결과

learning rate같은 매개변수를 하이퍼파라미터(hyper parameter, 초매개변수)라 한다. training data와 학습 알고리즘에 의해 자동으로 구해지는 가중치와 편향과 달리, 사람이 직접 설정하는 매개변수를 뜻한다.
하이퍼파라미터 역시 test 를 통해 가장 잘 학습하는 값을 찾는 과정을 거쳐야 한다.

신경망에서의 기울기

신경망 학습에서는 가중치 매개변수에 대한 손실함수의 기울기를 구해야 한다.
예를 들어, 형상이 2x3, 가중치가 w, 손실함수가 l인 신경망의 경우
경사는 다음과 같다.


가중치 행렬

각 가중치에 대한 편미분 행렬(경사)

각 가중치에 대한 편미분 행렬에서 첫번째 원소는 w11을 조금 변경했을 때의 손실함수 l이 얼마나 변하는지(변화량)를 나타낸다.

import sys, os
sys.path.append(os.pardir)
import numpy as np
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
  
net = simpleNet()
print(net.W)

x = np.array([0.6, 0.9])
p = net.predict(x)
print(p)
print(np.argmax(p)) # 최댓값의 인덱스

t = np.array([0, 0, 1]) # 정답레이블
print(net.loss(x, t))

신경망 구현

functions.py에서 정의한 softmax, cross_entropy_error 메서드와 gradient.py에서 정의한 numerical_gradient 메서드를 이용한다.
이제 신경망을 만들었으니, 기울기를 구해보자.

def f(W):
  return net.loss(x, t)

dW = numerical_gradient(f, net.W)
print(dW)

손실 함수를 가중치 매개변수 W에 대해 미분


미분 결과

결과를 보면 손실 함수를 w11에 대해 미분한 결과는 대략 0.12이다.
이건 w11를 h만큼 증가시키면 손실 함수의 값은 0.12*h 만큼 증가함을 의미.

마찬가지로 w13대해 미분한 결과는 대략 -0.24이니 w13을 h만큼 증가시키면 손실 함수의 값은 0.24*h 만큼 감소함을 의미.

따라서 손실함수의 값을 줄여야 하므로,
w11은 음의 방향으로 갱신되고
w13은 양의 방향으로 갱신해야 한다!

절댓값을 보면, w11보다 w13이 한 번에 갱신되는 양이 크다는 것도 알 수 있다.

요약

  • 신경망 학습은 손실 함수를 지표로, 손실 함수의 값이 작아지는 방향으로 가중치 매개변수 갱신
  • 가중치 매개변수를 갱신할 때는 가중치 매개변수의 기울기를 이용하고, 기울어진 방향으로 가중치의 값을 갱신하는 작업 반복

신경망 학습 알고리즘 구현

신경망에는 적응 가능한 가중치와 편향이 있고, 이 가중치와 편향을 training data에 적응하도록 조정하는 과정을 '학습'이라고 한다.
신경망의 학습은 4단계로 이뤄짐.

1단계 - 미니배치

training data 중 일부를 random하게 가져옴.
선별된 데이터를 미니배치라 하며, 미니배치의 손실 함수 값을 줄이는 것이 목표임!

2단계 - 기울기 산출

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

3단계 - 매개변수 갱신

가중치 매개변수를 기울기 방향으로 갱신

4단계 - 반복

1~3단계 반복

신경망의 학습은 경사 하강법으로 매개변수를 갱신하는 과정이다!
하지만 매개변수를 한 번 갱신하기위해 모든 데이터를 다 입력해야 하는데, 이는 엄청난 시간이 걸린다.
때문에 전체 데이터 셋 중 일부를 랜덤하게 선별해 학습하는 것을 반복하는 방법을 고안했는데, 이를 확률적 경사 하강법이라 한다.
확률적으로 무작위로 골라낸 데이터에 대해 경사 하강법을 수행한다는 의미.

<2-layer 신경망 클래스 만들기>

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

  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.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

# params 확인
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

# 입력데이터에 무작위 데이터 넣고 기울기 값 계산
x = np.random.rand(100, 784)
Y = net.predict(x)
t= np.random.rand(100,10)

grads = net.numerical_gradient(x,t)
grads['W1'].shape
grads['b1'].shape
grads['W2'].shape
grads['b2'].shape
profile
모든 건 zero 부터, 차근차근 헛둘헛둘

0개의 댓글