[DeepLeaning from Scratch] 신경망 학습

정해원·2022년 5월 29일
0
post-thumbnail

신경망 학습에서 학습이란 훈련 데이터로부터 가중치 매개변수의 최적값을 자동으로 획득하는 것
손실 함수는 신경망이 학습할 수 있도록 해주는 지표
이 손실 함수의 결과값을 가장 작게 만드는 가중치 매개변수를 찾는 것이 학습의 목표

데이터에서 학습한다는 것은 가중치 매개변수의 값을 데이터를 보고 자동으로 결정한다는 뜻

이전 예제에서는 매개변수가 3개였으나, 실제 신경망에서는 매개변수가 수천에서 수만개이기 때문에 매개변수를 수작업으로 정한다는 것은 불가능

Machine Learning의 중심에는 데이터가 존재
Machine Learning에서는 사람의 개입을 최소화하고 수집한 데이터로부터 패턴을 찾으려 시도
이미지에서 특징(feature)을 추출하고 그 특징의 패턴을 Machine Learning 기술로 학습하는 방법
특징은 입력 데이터(이미지)에서 본질적인 데이터르 ㄹ정확하게 추출할 수 있도록 설계된 변환기
이미지의 특징은 보통 벡터로 기술
이런 특징을 사용하여 이미지 데이터를 벡터로 변환하고, 변환된 벡터를 가지고 지도 학습 방식의 대표 분류 기법인 SVM, KNN 등으로 학습

모아진 데이터로부터 규칙을 찾아내는 역할을 Machine이 담당하지만, 이미지를 벡터로 변환할 때 사용하는 특징은 여전히 '사람'이 설계하는 것임에 주의

신경망은 이미지에 포함된 중요한 특징까지도 기계가 스스로 학습

훈련 데이터와 시험 데이터

훈련 데이터만 사용하여 학습하면서 최적의 매개변수 확인
그 다음 시험 데이터를 사용하여 앞서 훈련한 모델의 실력을 평가
범용 능력을 평가하기 위해 훈련/시험 데이터를 구분
범용 능력은 아직 보지 못한 데이터(훈련 데이터에 포함되지 않은 데이터)로도 문제를 올바르게 풀어내는 능력

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

손실 함수

손실 함수(loss function) : 신경망이 최적의 매개변수 값을 탐색하기 위해 '하나의 지표'를 기준으로 삼는 것

일반적으로는 오차제곱합교차 엔트로피 오차를 사용

오차제곱합
수식

E=12k(yktk)2E = {1 \over 2}{\sum_k{(y_k-t_k)}^2}

yky_k는 신경망의 추력(신경망이 추정한 값)
tkt_k는 정답 레이블
kk는 데이터의 차원 수

예제

y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
# y는 소프트맥스 함수의 출력 : 확률로 해석
이미지가 '0'일 확률이 0.1, '1'일 확률은 0.05, '2'일 확률은 0.6이라는 의미

t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
t는 정답 레이블로, 정답을 가리키는 원소만 1로 표현하고 나머지는 0으로 표기
위 예에서는 숫자 '2'에 해당하는 원소의 값이 1이므로 정답이 '2'임을 알 수 있음

오차제곱합은 각 원소의 출력(추정 값)과 정답 레이블(참 값)의 차(yky_k - tkt_k)를 제곱한 후, 그 총합을 구함

def sum_suqares_error(y, t):
  return 0.5 * np.sum((y-t)**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]
sum_suqares_error(np.array(y), np.array(t))
0.09750000000000003

# 신경망의 출력 '7'
y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
sum_suqares_error(np.array(y), np.array(t))
0.5975

교차 엔트로피 오차
또 다른 손실 함수로서 교차 엔트로피 오차도 자주 이용
수식

E=ktklogykE = -{\sum_k{t_klogy_k}}

loglog는 밑이 ee인 자연로그
yky_k는 신경망의 출력,
tkt_k는 정답 레이블 : tkt_k는 정답에 해당하는 인덱스의 원소만 1이고 나머지는 0(One-hot Encoding)
실질적으로 정답일 때의 추정(tkt_k가 1일 때의 yky_k)의 자연로그를 계산하는 식

예를 들어, 정답 레이블은 '2'가 정답이라고 하고, 이때의 신경망 출력이 0.6이라면 교차 엔트로피 오차는 log0.6=0.51-log0.6 = 0.51
또한, 같은 조건에서 신경망 출력이 0.1이라면 log0.1=2.30-log0.1 = 2.30
교차 엔트로피 오차는 정답일 때의 출력이 전체 값을 정하게 됨

교차 엔트로피 구현

def cross_entropy_error(y, t):
  delta = 1e-7 # np.log를 계산할 때 np.log 함수에 0을 입력하는 마이너스 무한대인 -inf가 반환되어 더 이상 계산을 진행할 수 없기 때문에 추가
  return -np.sum(t * np.log(y + delta))

import numpy as np
t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
# 정답일 때의 출력이 0.6
y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
cross_entropy_error(np.array(y), np.array(t))
0.510825457099338 # 오차가 더 작은 첫 번째 추정이 정답일 가능성이 높다고 판단

# 정답일 때의 출력이 0.1
y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
cross_entropy_error(np.array(y), np.array(t))
2.302584092994546

미니배치 학습

훈련 데이터에 대한 손실 함수의 값을 구하고, 그 값을 최대한 줄여주는 매개변수 확인 필요
모든 훈련 데이터를 대상으로 손실 함수 값을 구해야 함
훈련 데이터 모두에 대한 손실 함수의 합을 구하는 방법

E=1NnktnklogynkE = -{1\over N}{\sum_n}{\sum_k{t_{nk}logy_{nk}}}

손실 함수를 단순히 N개의 데이터로 확장
마지막에 N으로 나누어 정규화(N으로 나눔으로써 '평균 손실 함수'를 구하는 것)

훈련 데이터가 너무 많은 경우 시간이 오래 걸리기 때문에 현실적이지 않아, 데이터 일부를 추려 전체의 '근사치'로 이용 --> 일부를 미니배치라 함
예를 들어, 60,000장의 훈련 데이터 중에서 100장을 무작위로 뽑아 그 100장만을 사용하여 학습
이러한 학습 방법을 미니배치 학습이라고 함

훈련 데이터에서 지정한 수의 데이터를 무작위로 골라내는 코드

from google.colab import drive
drive.mount('/content/drive')

%cd /content/drive/MyDrive/temp/dataset
/content/drive/MyDrive/temp/dataset

#!python3 mnist.py 
from mnist import load_mnist
(x_train, t_train), (x_test, t_test) = \
  load_mnist(normalize=False, one_hot_label=True)

print(x_train.shape)
(60000, 784)

print(t_train.shape)
(60000, 10)

훈련 데이터는 60,000개
입력 데이터는 784열(원래는 28 x 28) 이미지 데이터
정답 레이블은 10줄짜리 데이터

훈련 데이터에서 무작위로 10장만 빼내려면? np.random.choice() 함수 사용
np.random.choice(60000,10)은 0이상 60,000미만의 수 중에서 무작위로 10개를 골라냄

train_size = x_train.shape[0]
print(train_size)
60000

batch_size = 10
batch_mask = np.random.choice(train_size, batch_size) # 지정한 범위의 수 중에서 무작위로 원하는 개수만 꺼냄
print(batch_mask)
[ 1594 44487 45813 19047 51835  8335 25719 58235 17757 12448]

x_batch = x_train[batch_mask] # batch_mask를 미니배치로 뽑아낼 데이터의 인덱스로 사용
t_batch = t_train[batch_mask] # batch_mask를 미니배치로 뽑아낼 데이터의 인덱스로 사용

이제 무작위로 선택한 인덱스(위 예제에서는 batch_size=10이 인덱스 개수)를 사용해서 미니배치를 계산

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

데이터가 하나인 경우와 데이터가 배치로 묶여 입력될 경우 모두를 처리할 수 있도록 구현
아래 코드는 정답 레이블이 One-Hot Encoding으로 되어 있을 때
(정답만 1이고, 나머지는 모두 0인 형태)

def cross_entropy_error(y, t):
  if y.ndim == 1: # y가 1차원 이라면
  # reshape 함수로 (784,)인 경우 (1, 784)
    t = t.reshpae(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

정답 레이블이 One-Hot Encoding이 아니라 '2'나 '7'등의 숫자 레이블로 주어진 경우 다음과 같이 구현

def cross_entropy_error(y, t):
  if y.ndim == 1: # y가 1차원 이라면
  # reshape 함수로 (784,)인 경우 (1, 784)
    t = t.reshpae(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

t * np.log(y)를 np.log(y[np.arange(batch_size), t])로 구현
y[np.arange(batch_size), t]까지 배열 생성

batch_size가 5이면 np.arange(batch_size)는 [0, 1, 2, 3, 4]인 numpy 배열 생성
t는 [2, 7, 0, 9, 4]와 같이 저장되어 있으므로

y[np.arange(batch_size), t]는
결국 [y[0,2], y[1,7], y[2,0], y[3,9], y[4,4]]인 numpy 배열 생성

손실 함수 설정 이유

손실 함수를 사용하는 목적은 높은 '정확도'를 끌어내는 매개변수를 찾는 것
왜 '정확도'라는 지표를 놔두고 '손실 함수의 값'이라는 우회적인 방법을 택할까?

신경망 학습에서는 최적의 매개변수(가중치와 편향)를 탐색할 때 손실 함수의 값을 가능한 작게 하는 매개변수 값을 찾으려고 함
이 때 매개변수의 미분(기울기)을 계산하고, 그 미분 값을 단서로 매개변수의 값을 서서히 갱신하는 과정을 반복

손실 함수의 비분이란 '가중치 매개변수의 값을 아주 조금 변화시켰을 때, 손실 함수가 어떻게 변하는가?'라는 의미

만약, 미분 값이 음수면 그 가중치 매개변수를 양의 방향으로 변화시켜 손실 함수의 값을 줄일 수 있음

반대로, 미분 값이 양수면 가중치 매개변수를 음의 방향으로 변화시켜 손실 함수의 값을 줄일 수 있음 하지만 미분 값이 0이면 가중치 매개변수를 어느 쪽으로 움직여도 손실 함수의 값이 줄어드지 않기 때문에 가중치 매개변수의 갱신이 멈춤

'정확도'를 지표로 삼으면 매개변수의 미분이 대부분의 장소에서 0이 된다?
정확도는 매개변수의 미소한 변화에는 거의 반응을 보이지 않고, 반응이 있더라도 그 값이 불연속적으로 갑자기 변화
이는 '계단 함수'를 활성화 함수로 사용하지 않는 이유와 동일

수치 미분

미분은 '특정 순간'의 변화량을 의미

df(x)dx=limh0f(x+h)f(x)h{df(x) \over dx} = \lim_{h \to 0}{f(x+h)-f(x) \over h}

좌변은 f(x)f(x)xx에 대한 미분(xx에 대한 f(x)f(x)의 변화량)을 나타내는 기호
이 때, 시간을 뜻하는 hh를 한없이 0에 가깝게 한다는 의미로

limh0\lim_{h\to0}

사용

'진정한 미분'은 xx위치의 함수의 기울기(이를 접선이라 함)에 해당

위 그림에서와 같이 수치 미분에는 오차가 포함
이 오차를 줄이기 위해 (x+h)(x+h)(xh)(x-h)일 때의 함수 f의 차분을 계산하는 방법 사용
이 차분은 xx를 중심으로 그 전후의 차분을 계산한다는 의미에서 중심 차분 혹은 중앙 차분이라 함

def numerical_diff(f, x):
  h = 1e-4 # 0.0001
  return (f(x+h) - f(x-h)) / (2*h)

수치 미분의 예

y=0.01x2+0.1xy = 0.01x^2 + 0.1x

파이썬으로 구현

def function_1(x):
  return 0.01*x**2 + 0.1*x

import numpy as np
import matplotlib.pylab as plt

x = np.arange(0.0, 20.0, 0.1) # 0에서 20까지 0.1 간격의 배열 x
y = function_1(x)
plt.xlabel("x")
plt.ylabel("f(x)")
plt.plot(x, y)
plt.show()

x=5x = 5일 때와 10일 때 함수의 미분 계산

numerical_diff(function_1, 5)
0.1999999999990898
  
numerical_diff(function_1, 10)
0.2999999999986347

이렇게 계산한 미분 값이 xx에 대한 f(x)f(x)의 변화량

f(x)=0.01x2+0.1xf(x) = 0.01x^2 + 0.1x의 해석적 해는 df(x)dx=0.02x+0.1{df(x)\over dx} = 0.02x + 0.1

'진정한 미분'은 차례로 0.2와 0.3
앞의 수치 미분 결과를 비교하면 그 오차가 매우 작음

편미분

인수들의 제곱 합을 계싼하는 단순한 식이지만, 앞의 예와 달리 변수가 2개라는 점

f(x0,x1)=x02+x12f(x_0, x_1) = x_0^2 + x_1^2

파이썬으로 구현

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

이 함수를 그래프로 그려보면 다음과 같이 3차원으로 표현

def function_2(x, y):
  return x**2 + y**2
  
import numpy as np
import matplotlib.pylab as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm

x = np.arange(-3.0, 3.0, 0.01)
y = np.arange(-3.0, 3.0, 0.01)
plt.scatter(x, y)
plt.grid()
X, Y = np.meshgrid(x, y) 
Z = function_2(X, Y)
fig = plt.figure() # plt.figure()는 새로운 figure를 생성해준다.
ax = fig.gca(projection='3d') 
# gca()로 현재의 Axes를, gcf()로 현재의 Figure 객체를 구할 수 있다.
# axes는 figure 내에서 축을 가지는 하나의 좌표평면과 같은 개념
surf = ax.plot_surface(X, Y, Z)
# Matplotlib의 축 생성 함수 중 하나에projection = '3d' 인수를 전달하여 3 차원 축을 만들 수 있습니다. 
# 3 차원 축이 초기화되면plot_surface()메소드를 사용하여 표면 플롯을 생성 할 수 있습니다.
ax.view_init(elev=30, azim=50)
# view_init(elev=None , azim=None)
# elev : elevation을 약자로 쓴것으로 z plane의 각도를 의미. 입력한 각도대로 위 아래로 변화함.
# azim : azimuth angle 로 x,y plane의 각도를 의미. 
plt.show()

이와 같이 변수가 여럿인 함수에 대한 미분이 편미분
x0x_0x1x_1 중 어느 변수에 대한 미분이냐를 구별해야 함

수식으로는 afax0af\over ax_0afax1af\over ax_1으로 표현

편미분은 변수가 하나인 미분과 마찬가지로 특정 장소의 기울기를 계산
여러 변수 중 목표 변수 하나에 초점을 맞추고 다른 변수는 값을 고정

기울기

x0x_0x1x_1의 편미분을 동시에 계산하고 싶다면?
예를 들어, x0=3x_0=3x1=4x_1=4 일 때 (x0x_0, x1x_1) 양쪽의 편미분을 묶어서 (afax0af\over ax_0, afax1af\over ax_1)을 계산하다고 가정

(afax0af\over ax_0, afax1af\over ax_1)처럼 모든 변수의 편미분을 벡터로 정리한 것을 기울기(gradient)라 함

기울기 구현

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

def numerical_gradient(f, x):
  h = 1e-4 # 0.0001
  grad = np.zeros_like(x) # x와 형상이 같은 배열을 생성

  for idx in range(x.size):
    tmp_val = x[idx]
    x[idx] = tmp_val + h # x+h
    fxh1 = f(x) # f(x+h)

    x[idx] = tmp_val - h # x-h
    fxh2 = f(x)

    grad[idx] = (fxh1 - fxh2) / (2*h)
    x[idx] = tmp_val

  return grad

numerical_gradient(function_2, np.array([3.0, 4.0]))
array([6., 8.]) # 점 (3,4)의 기울기는 (6,8)

numerical_gradient(function_2, np.array([0.0, 2.0]))
array([0., 4.]) # 점 (0,2)의 기울기는 (0,4)

numerical_gradient(function_2, np.array([3.0, 0.0]))
array([6., 0.]) # 점 (3,0)의 기울기는 (6,0)

기울기의 결과에 마이너스를 붙인 벡터를 그려보기

구현 코드

import numpy as np
import matplotlib.pylab as plt
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

def numerical_gradient(f, X):
    if X.ndim == 1:
        return _numerical_gradient_no_batch(f, X)
    else:
        grad = np.zeros_like(X)
        
        for idx, x in enumerate(X):
            grad[idx] = _numerical_gradient_no_batch(f, x)
        
        return grad

def function_2(x):
    if x.ndim == 1:
        return np.sum(x**2)
    else:
        return np.sum(x**2, axis=1)

if __name__ == '__main__':
    x0 = np.arange(-2, 2.5, 0.25)
    x1 = np.arange(-2, 2.5, 0.25)
    X, Y = np.meshgrid(x0, x1)
    
    X = X.flatten()
    Y = Y.flatten()
    
    grad = numerical_gradient(function_2, np.array([X, Y]).T).T # .T는 배열 전치(Transpose)

[[-4.  -3.5 -3.  -2.5 -2.  -1.5 -1.  -0.5  0.   0.5  1.   1.5  2.   2.5
   3.   3.5  4.   4.5 -4.  -3.5 -3.  -2.5 -2.  -1.5 -1.  -0.5  0.   0.5
   1.   1.5  2.   2.5  3.   3.5  4.   4.5 -4.  -3.5 -3.  -2.5 -2.  -1.5
  -1.  -0.5  0.   0.5  1.   1.5  2.   2.5  3.   3.5  4.   4.5 -4.  -3.5
  -3.  -2.5 -2.  -1.5 -1.  -0.5  0.   0.5  1.   1.5  2.   2.5  3.   3.5
   4.   4.5 -4.  -3.5 -3.  -2.5 -2.  -1.5 -1.  -0.5  0.   0.5  1.   1.5
   2.   2.5  3.   3.5  4.   4.5 -4.  -3.5 -3.  -2.5 -2.  -1.5 -1.  -0.5
   0.   0.5  1.   1.5  2.   2.5  3.   3.5  4.   4.5 -4.  -3.5 -3.  -2.5
  -2.  -1.5 -1.  -0.5  0.   0.5  1.   1.5  2.   2.5  3.   3.5  4.   4.5
  -4.  -3.5 -3.  -2.5 -2.  -1.5 -1.  -0.5  0.   0.5  1.   1.5  2.   2.5
   3.   3.5  4.   4.5 -4.  -3.5 -3.  -2.5 -2.  -1.5 -1.  -0.5  0.   0.5
   1.   1.5  2.   2.5  3.   3.5  4.   4.5 -4.  -3.5 -3.  -2.5 -2.  -1.5
  -1.  -0.5  0.   0.5  1.   1.5  2.   2.5  3.   3.5  4.   4.5 -4.  -3.5
  -3.  -2.5 -2.  -1.5 -1.  -0.5  0.   0.5  1.   1.5  2.   2.5  3.   3.5
   4.   4.5 -4.  -3.5 -3.  -2.5 -2.  -1.5 -1.  -0.5  0.   0.5  1.   1.5
   2.   2.5  3.   3.5  4.   4.5 -4.  -3.5 -3.  -2.5 -2.  -1.5 -1.  -0.5
   0.   0.5  1.   1.5  2.   2.5  3.   3.5  4.   4.5 -4.  -3.5 -3.  -2.5
  -2.  -1.5 -1.  -0.5  0.   0.5  1.   1.5  2.   2.5  3.   3.5  4.   4.5
  -4.  -3.5 -3.  -2.5 -2.  -1.5 -1.  -0.5  0.   0.5  1.   1.5  2.   2.5
   3.   3.5  4.   4.5 -4.  -3.5 -3.  -2.5 -2.  -1.5 -1.  -0.5  0.   0.5
   1.   1.5  2.   2.5  3.   3.5  4.   4.5 -4.  -3.5 -3.  -2.5 -2.  -1.5
  -1.  -0.5  0.   0.5  1.   1.5  2.   2.5  3.   3.5  4.   4.5 -4.  -3.5
  -3.  -2.5 -2.  -1.5 -1.  -0.5  0.   0.5  1.   1.5  2.   2.5  3.   3.5
   4.   4.5]
 [-4.  -4.  -4.  -4.  -4.  -4.  -4.  -4.  -4.  -4.  -4.  -4.  -4.  -4.
  -4.  -4.  -4.  -4.  -3.5 -3.5 -3.5 -3.5 -3.5 -3.5 -3.5 -3.5 -3.5 -3.5
  -3.5 -3.5 -3.5 -3.5 -3.5 -3.5 -3.5 -3.5 -3.  -3.  -3.  -3.  -3.  -3.
  -3.  -3.  -3.  -3.  -3.  -3.  -3.  -3.  -3.  -3.  -3.  -3.  -2.5 -2.5
  -2.5 -2.5 -2.5 -2.5 -2.5 -2.5 -2.5 -2.5 -2.5 -2.5 -2.5 -2.5 -2.5 -2.5
  -2.5 -2.5 -2.  -2.  -2.  -2.  -2.  -2.  -2.  -2.  -2.  -2.  -2.  -2.
  -2.  -2.  -2.  -2.  -2.  -2.  -1.5 -1.5 -1.5 -1.5 -1.5 -1.5 -1.5 -1.5
  -1.5 -1.5 -1.5 -1.5 -1.5 -1.5 -1.5 -1.5 -1.5 -1.5 -1.  -1.  -1.  -1.
  -1.  -1.  -1.  -1.  -1.  -1.  -1.  -1.  -1.  -1.  -1.  -1.  -1.  -1.
  -0.5 -0.5 -0.5 -0.5 -0.5 -0.5 -0.5 -0.5 -0.5 -0.5 -0.5 -0.5 -0.5 -0.5
  -0.5 -0.5 -0.5 -0.5  0.   0.   0.   0.   0.   0.   0.   0.   0.   0.
   0.   0.   0.   0.   0.   0.   0.   0.   0.5  0.5  0.5  0.5  0.5  0.5
   0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5  0.5  1.   1.
   1.   1.   1.   1.   1.   1.   1.   1.   1.   1.   1.   1.   1.   1.
   1.   1.   1.5  1.5  1.5  1.5  1.5  1.5  1.5  1.5  1.5  1.5  1.5  1.5
   1.5  1.5  1.5  1.5  1.5  1.5  2.   2.   2.   2.   2.   2.   2.   2.
   2.   2.   2.   2.   2.   2.   2.   2.   2.   2.   2.5  2.5  2.5  2.5
   2.5  2.5  2.5  2.5  2.5  2.5  2.5  2.5  2.5  2.5  2.5  2.5  2.5  2.5
   3.   3.   3.   3.   3.   3.   3.   3.   3.   3.   3.   3.   3.   3.
   3.   3.   3.   3.   3.5  3.5  3.5  3.5  3.5  3.5  3.5  3.5  3.5  3.5
   3.5  3.5  3.5  3.5  3.5  3.5  3.5  3.5  4.   4.   4.   4.   4.   4.
   4.   4.   4.   4.   4.   4.   4.   4.   4.   4.   4.   4.   4.5  4.5
   4.5  4.5  4.5  4.5  4.5  4.5  4.5  4.5  4.5  4.5  4.5  4.5  4.5  4.5
   4.5  4.5]]

    plt.figure() # Figure인스턴스를 생성하는데 Figure인스턴스의 역할은 이미지 전체의 영역을 확보하는 것
    plt.quiver(X, Y, -grad[0], -grad[1],  angles="xy",color="#666666")
    # quiver는 2차원 평면 상에서 좌표마다 scaled된 화살표를 그릴 때 사용
    plt.xlim([-2, 2])
    plt.ylim([-2, 2])
    plt.xlabel('x0')
    plt.ylabel('x1')
    plt.grid()
    # https://stackoverflow.com/questions/23141452/difference-between-plt-draw-and-plt-show-in-matplotlib
    plt.draw() # display the current figure that you are working on
    plt.show() # re-draw the figure. This allows you to work in interactive mode and, should you have changed your data or formatting, allow the graph itself to change.

경사법(경사 하강법)

최적의 매개변수(가중치와 편향)를 학습 시에 찾아야 함
최적이란 손실 함수가 최솟값이 될 때의 매개변수 값
각 지점에서 함수의 값을 낮추는 방안을 제시하는 지표가 기울기
경사법은 현 위치에서 기울어진 방향으로 일정 거리만큼 이동
이동한 곳에서도 마찬가지로 기울기를 구하고, 또 그 기울어진 방향으로 나아가기를 반복
이렇게 해서 함수의 값을 점차 줄이는 것이 경사법

경사법 수식

x0=x0ηfx0x_0 = x_0 - \eta{\partial f\over \partial x_0}
x1=x1ηfx1x_1 = x_1 - \eta{\partial f\over \partial x_1}

η\eta 기호는 갱신하는 양 : 신경망 학습에서는 학습률
매개변수 값을 얼마나 갱신하느냐를 정하는 것이 학습률

경사 하강법 구현

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은 경사법에 따른 반복 횟수
함수의 기울기는 numerical_gradient(f, x)로 계산
그 기울기에 학습률을 곱한 값으로 갱신 처리를 step_num만큼 반복

경사법 예상 문제

f(x0,x1)=x02+x12f(x_0, x_1) = x_0^2 + x_1^2

최종 결과 : [-0.03458765 0.04611686]

import numpy as np
import matplotlib.pylab as plt
#from gradient_2d import numerical_gradient

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

def numerical_gradient(f, X):
    if X.ndim == 1:
        return _numerical_gradient_no_batch(f, X)
    else:
        grad = np.zeros_like(X)
        
        for idx, x in enumerate(X):
            grad[idx] = _numerical_gradient_no_batch(f, x)
        
        return grad

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)

def function_2(x):
    return x[0]**2 + x[1]**2

init_x = np.array([-3.0, 4.0])    

lr = 0.1
step_num = 20
x, x_history = gradient_descent(function_2, init_x, lr=lr, step_num=step_num)

plt.plot( [-5, 5], [0,0], '--b')
plt.plot( [0,0], [-5, 5], '--b')
plt.plot(x_history[:,0], x_history[:,1], 'o')

plt.xlim(-3.5, 3.5)
plt.ylim(-4.5, 4.5)
plt.xlabel("X0")
plt.ylabel("X1")
plt.show()

학습률이 너무 크거나 작으면 좋은 결과를 얻을 수 없음
학습률이 너무 크면 큰 값으로 발산 : [-2.58983747e+13 -1.29524862e+12]
학습률이 너무 작으면 거의 갱신되지 않고 종료 : [-2.99999999 3.99999998]

학습률 같은 매개변수는 하이퍼파라미터
가중치와 편향 같은 신경망의 매개변수와는 달리, 하이퍼파라미터는 사람이 직접 설정해야 하는 매개변수

신경망에서의 기울기

신경망에서 기울기는 가중치 매개변수에 대한 손실 함수의 기울기
형상이 2x3, 가중치가 W, 손실 함수가 L인 신경망인 경우
경사는 LW\partial L\over \partial W

W=(w11w21w31w12w22w23)W = \begin{pmatrix} w_{11} & w_{21} & w_{31}\\ w_{12} & w_{22} & w_{23} \end{pmatrix}
LW=(LW11LW12LW13LW21LW22LW23){\partial L\over \partial W} = \begin{pmatrix} \partial L\over \partial W_{11} & \partial L\over \partial W_{12} & \partial L\over \partial W_{13}\\ \partial L\over \partial W_{21} & \partial L\over \partial W_{22} & \partial L\over \partial W_{23} \end{pmatrix}

LW11\partial L\over \partial W_{11}w11w_{11}을 조금 변경했을 때 손실 함수 L이 얼마나 변화하느냐를 의미

간단한 신경망을 통한 실제로 기울기를 구하는 코드 구현

학습 알고리즘 구현하기

profile
VMware에서 Senior Technical Support Engineer로 일하고 있습니다.

0개의 댓글