신경망 학습
학습이란?
- 훈련 데이터로부터 가중치 매개변수의 최적값을 자동으로 획득하는 것
신경망이 학습할 수 있도록 해주는 지표인 손실함수에 대해서 알아볼 예정.
손실함수의 값을 가급적 작게 만드는 기법으로 함수의 기울기를 활용하는 경사법 소개.
데이터 주도 학습
알고리즘을 밑바닥부터 설계하는 대신 주어진 데이터를 잘 활용해서 해결
이미지에서 특징(feature)를 추출하고 그 특징의 패턴을 기계학습 기술로 학습하는 방법 존재.
특징은 입력 데이터에서 본질적인 데이터를 정확하게 추출할 수 있도록 가르킴
이미지를 베겉로 변환하고 변환된 벡터를 가지고 지도학습의 대표 분류 방법인 SVM KNN 등으로 학습
즉, 딥러닝은 종단간 기계학습이라고도 불리며, 사람의 개입없이 출력 결과로 얻는다.
훈련 데이터와 시험 데이터
훈련 데이터와 시험 데이터로 나눠 학습과 실험을 수행하는 것이 일반적.
우선 훈련 데이터만 사용하여 학습하면서, 최적의 매개변수를 찾음.
그런 다음에 시험 데이터를 사용하여 앞서 훈련한 모델의 실력을 평가
우리가 원하는 것은 범용적으로 사용할 수 있는 능력 즉, 범용 능력을 평가하기 위해 시험 데이터와 훈련 데이터를 분리
특정 데이터 셋에만 최적화된 상태를 오버피팅이라고 함.
오버피팅을 피하는 것도 기계학습의 중요한 과제
손실 함수
신경망 학습에서 사용하는 지표는 손실함수라고 한다. 이 손실함수는 임의의 함수를 사용할 수도 잇지만, 일반적으로는 오차제곱합과 교차 엔트로피를 사용
손실 함수는 얼마나 잘 처리하지 못하느냐를 나타냄.
오차 제곱합
가장 맣이 쓰이는 손실 함수는 오차제곱합
장점: 연속적인 값들에 대한 차이를 측정하여 회귀에 유용하며, 미분 가능 함수라 경사 하강법 등 최적화 알고리즘에 유용
단점: 이상치에 민감하게 반응, 오차가 클수록 크게 패널티로 인한 안정성 영향
y = [0.1, 0.05, 0.6, 0.25] # 소프트 맥스 함수
t = [0, 0, 1, 0] # 정답 레이블(참값)
원-핫 인코딩을 통한 값
def sum_squares_error(y, t):
return 0.5 * np.sum((y-t)**2)
t = [0, 0, 1, 0]
y = [0.1, 0.05, 0.6, 0.25]
sum_squares_error(np.array(y), np.array(t))
0.0095... // 오차 제곱 합 값이 작다
t = [0, 0, 1, 0]
y = [0.05, 0.3, 0.1, 0.55]
sum_squares_error(np.array(y), np.array(t))
0.5995... // 오차 제곱 합 값이 크다
첫번째 손실함수 쪽 값이 작다. 따라서 첫번째가 더 좋다.
교차 엔트로피
tk: 정답레이블(원-핫 인코딩)
yk: 신경망의 출력
해당 수식에서 정답 레이블이 원-핫 인코딩을 사용하고 있으므로 실질적으로 정답일 때의 추청의 로그를 계산하는 식이 된다.
즉, 교차 엔트로피는 정답일 때의 출력이 전체 값을 정하게 된다.
위 그림에서 보듯 x가 1일때 y = 0이 되고, x가 0에 가까워질수록 y의 값은 점점 작아진다.
def corss_entropy_error(y, t):
delta = 1e-7
return -np.sum(t * np.log(y + delta))
아주 작은 값을 더해서, y가 0일 경우 -inf가 되어 계산이 더이상 진행되지 못하는 문제를 막음.
t = [0, 0, 1, 0]
y = [0.1, 0.05, 0.6, 0.25]
cross_squares_error(np.array(y), np.array(t))
0.518...
t = [0, 0, 1, 0]
y = [0.05, 0.3, 0.1, 0.55]
cross_squares_error(np.array(y), np.array(t))
2.3024...
위는 교차제곱을 사용한 방법으로 오차제곱합과 결과가 동일하다.
미니 배치학습
기계학습 문제는 훈련 데이터를 사용해 학습.
즉, 훈련 데이터에 대한 손실 함수의 값을 구하고, 그 값을 최대한 줄여주는 매개변수를 찾음.
이렇게 하려면 모든 훈련 데이터를 대상으로 손실 함수 값을 구해야 함.
지금까지는 데이터 하나에 대한 손실함수를 고려했고, 이제는 데이터 전체에 대한 손실함수를 생각해보고자 함.
어떤 데이터가 N개라면, tnk는 n번째 데이터의 k번째 값을 의미
단순히 데이터를 N개로 확장하였으며, 마지막에 N으로 나누어 정규화. N으로 나눔으로써 평균 손실 함수를 구함.
이렇게 평균을 구해 사용하면, 훈련 데이터 개수와 관계없이 언제든 통일된 지표를 구함.
데이터 많을 경우 일부를 추려 전체의 근사치로 이용할 수 있음.
훈련 데이터로부터 일부만 골라 학습을 수행하며, 이를 100장정도 뽑아 사용하는걸 미니배치라고 하고, 이러한 학습 방법을 미니배치 학습이라고 함.
아래 구현에서의 핵심은
원-핫 인코딩일 때 t가 0인 원소는 교차 엔트로피 오차도 0이므로, 그 계산은 무시해도 좋다는 것이 핵심.
import numpy as np
import sys
import os
sys.path.append(os.pardir)
from dataset.mnist import load_mnist
# 4.2.3 미니배치 학습
# 훈련 데이터 전체에 대한 오차함수
# E = -1/N * ∑ _n (∑ _k (tk * log(yk)))
# N : 데이터의 개수
# 훈련 데이터 전체에 대한 손실 함수를 계산하기에는 시간이 오래걸리기 때문에
# 일부를 추려 전체의 근사치로 이용할 수 있다.
(x_train, t_train), (x_test, t_test) = \
load_mnist(normalize=True, one_hot_label=False)
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) # 60000 미만의 수에서 무작위로 10개를 골라냄.
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
# 4.2.4 (배치용) 교차 엔트로피 오차 구현하기
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[np.arange(batch_size), t])) / batch_size
# 4.2.5 왜 손실 함수를 설정하는가?
# 신경망을 학습할 때 정확도를 지표로 삼아서는 안 된다.
# 정확도를 지표로 하면 매개변수의 미분이 대부분의 장소에서 0이 되기 때문이다.
# (매개변수의 미소한 변화에는 거의 반응을 보이지 않고 그 값이 분연속적으로 변화)
궁긍적인 목적이 높은 '정확도'를 끌어내는 매개변수 값을 찾는 것.
그런데 '손실함수'라는 우회적인 값을 사용하는 이유가 뭘까?
- 신경망 학습에서의 '미분'의 역할에 주목하면 된다. 손실함수의 값을 가능한 작게 하는 매개변수 값을 찾는다.
- 미분 값을 서서리 갱신하는 과정을 반복
가상의 신경망이 있고, 그 신경망의 어느 한 가중치 매개변수에 주목한다고 하자. 그 가중치 매개변수의 손실 함수의 미분이란 '가중치 매개변수의 값을 아주 조금 변화 시켰을 때, 손실함수가 어떻게 변하냐'의 의미.
미분 값이 음수면 그 가중치 매개변수를 양의 방향으로 변화시켜 손실 함수의 값을 줄일 수 있음.
반대로 미분 값이 양수면 미분 값이 0이면 어느 방향으로 움직여도 손실 함수의 값은 줄어들지 않는다. (여기서 매개변수 갱신은 멈춤)
정확도를 사용할 시, 대부분의 장소에서 미분 값이 0이 되어 매개변수를 갱신할 수 없기 때문.
수치 미분
수학적으로 근사 값으로 계산해야 한다.
이 차분은 x를 중심으로 그 전후의 차분을 계산한다는 의미에서 중심 차분 혹은 중앙 차분이라 한다.
(x+h)와 x의 차분은 전방 차분이라고 함.
해석적 미분: 우리가 수학 시간에 배운 그 미분
수치 미분: 근사치 미분
import numpy as np
import matplotlib.pylab as plt
# 4.3.1 미분
# 나쁜 구현 예
def numerical_diff_bad(f, x):
h = 10e-50
return (f(x + h) - f(x)) / h
# h값이 너무 작아 반올림 오차를 일으킬 수 있음 10e-4정도가 적당하다고 알려짐
# 전방 차분에서는 차분이 0이 될 수 없어 오차가 발생
# -> 오차를 줄이기 위해 중심 차분을 사용
def numerical_diff(f, x):
h = 10e-4
return (f(x + h) - f(x - h)) / (2 * h)
# 4.3.2 수치 미분의 예
# y = 0.01x² + 0.1x
def function_1(x):
return 0.01*x**2 + 0.1*x
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 = 5, 10일때 미분
print(numerical_diff(function_1, 5)) # 0.200000000000089
print(numerical_diff(function_1, 10)) # 0.29999999999996696
# 접선의 함수를 구하는 함수
def tangent_line(f, x):
d = numerical_diff(f, x)
# print(d)
y = f(x) - d*x
return lambda t: d*t + y
tf = tangent_line(function_1, 5)
y2 = tf(x)
plt.plot(x, y2)
plt.show()
# 4.3.3 편미분
# f(x0, x1) = x0² + x1²
def function_2(x):
return x[0]**2 + x[1]**2
# or return np.sum(x**2)
# x0 = 3, x1 = 4일 때, x0에 대한 편미분을 구하라.
def function_tmp1(x0):
return x0**2 + 4.0**2.0
# x0 = 3, x1 = 4일 때, x1에 대한 편미분을 구하라.
def function_tmp2(x1):
return 3.0**2.0 + x1 * x1
print(numerical_diff(function_tmp1, 3.0)) # 5.999999999998451
print(numerical_diff(function_tmp2, 4.0)) # 8.000000000000895
기울기
import numpy as np
import matplotlib.pylab as plt
# 앞 절에서 x0, x1에 대한 편미분을 변수별로 따로 계산했음.
# x0, x1의 편미분을 동시에 계산하고 싶다면?
# x0 = 3, x1 = 4일 때 (x0, x1) 양쪽의 편미분을 묶어 벡터로 정리한 것을 기울기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
# f(x0, x1) = x0² + x1²
def function_2(x):
return x[0]**2 + x[1]**2
# or return np.sum(x**2)
print(numerical_gradient(function_2, np.array([3.0, 4.0]))) # [ 6. 8.]
print(numerical_gradient(function_2, np.array([0.0, 2.0]))) # [ 0. 4.]
print(numerical_gradient(function_2, np.array([3.0, 0.0]))) # [ 6. 0.]
# 4.4.1 경사법(경사 하강법)
# x0 = x0 - η*∂f/∂x0
# x1 = x1 - η*∂f/∂x1
# η(eta) : 갱신하는 양, 학습률learning rate
# 위 식을 반복
# f:최적화하려는 함수
# init_x : 초깃값
# lr : 학습률
# step_num : 반복횟수
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)
# 경사법으로 f(x0, x1) = x0² + x1²의 최솟값을 구해라
init_x = np.array([-3.0, 4.0])
x, x_history = gradient_descent(function_2, init_x, lr=0.1)
print(x) # [ -6.11110793e-10 8.14814391e-10]
# 학습률이 너무 큼
init_x = np.array([-3.0, 4.0])
x, x_history = gradient_descent(function_2, init_x, lr=10.0)
print(x) # [ -2.58983747e+13 -1.29524862e+12] 발산함
# 학습률이 너무 작음
init_x = np.array([-3.0, 4.0])
x, x_history = gradient_descent(function_2, init_x, lr=1e-10)
print(x) # [-2.99999994 3.99999992] 거의 변화 없음
# 그래프
init_x = np.array([-3.0, 4.0])
x, x_history = gradient_descent(function_2, init_x, lr=0.1, step_num=20)
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()
기울기가 가리키는 쪽은 각 장소에서 함수의 출력 값을 가장 크게 줄이는 방향.
경사법(경사 하강법)
최적의 매개변수를 찾아냄. 신경망 역시 최적의 매개변수(가중치와 편향)를 학습 시에 찾음.
최적이란 손실 함수가 최솟값이 될 때의 매개변수 값.
매개변수 공간이 광대하여 어디가 최솟값이 되는 곳인지 짐작할 수 없음.
또한, 기울기가 가리키는 곳에 정말 함수의 최솟값이 있는지, 즉 그쪽이 정말 나아갈 방향
'AI 및 자동화' 카테고리의 다른 글
티스토리 크롤링 포스팅 제목과 링크 추출 자동화 (Python) (10) | 2024.10.01 |
---|---|
[DL] 신경망 (0) | 2024.04.17 |
[DL] 퍼셉트론 (0) | 2024.04.17 |