[딥러닝 기초] 신경망 학습!
신경망 학습이란?
신경망 학습 요약
- 여기서 학습이란 훈련 데이터로부터 가중치 매개변수의 최적값을 자동으로 획득하는 것을 뜻한다.
- 책에서는 신경망이 학습할 수 있도록 해주는 지표인 손실 함수가 소개된다.
- 이 손실 함수의 결괏값을 가장 작게 만드는 가중치 매개변수를 찾는 것이 학습의 목표다.
- 손실 함수의 값을 가급적 작게 만드는 기법으로 함수의 기울기를 활용하는 경사법이 소개된다.
데이터에서 학습한다는게 무슨 소리일까?
- 데이터에서 학습한다는 것은 가중치 매개변수의 값을 데이터를 보고 자동으로 결정한다는 뜻이다.
- 2장 퍼셉트론에서는 진리표를 보면서 사람이 수작업으로 매개변수 값을 설정했지만 이 때는 매개변수가 겨우 3개 였다.
- 신경망에서는 수억개가 넘는데 이를 수작업으로 하는 것은 불가능하다.
데이터 주도학습
여기서 숫자 5를 인식하려면 어떻게 알고리즘을 짜야할까?
- 신경망의 특징은 이미지를 ‘있는 그대로’ 학습한다.
- 이러한 이유로 딥러닝을 종단간 기계학습이라고도 한다.
- 사람의 개입없이 처음부터 끝까지라는 목표한 결과를 출력한다.
훈련 데이터와 시험 데이터
왜 훈련데이터와 시험 데이터를 나눌까?
- 데이터셋 하나만으로 매개변수의 학습과 평가를 수행하면 범용 능력 검증이 안된다.
- 현재 데이터셋은 잘 맞춰도 다른 데이터셋에서는 엉망일 수도 있는데 이렇게 한 데이터셋에만 지나치게 최적화된 상태를 오버피팅이라고 한다.
손실 함수
- 신경망 학습에서 현재의 상태를 하나의 지표로 표현한다.
- 이 지표를 가장 좋게 만들어주는 가중치 매개변수의 값을 탐색
- 이 손실 함수는 임의의 함수를 사용할 수도 있지만 일반적으로 오차제곱함과 교차 엔트로피 오차를 사용한다.
오차제곱합
- y_k는 신경망의 출력(신경망이 추정한 값)
- t_k 는 정답 레이블
- k 는 데이터의 차원 수를 나타낸다.
- 이를 테면 “3.6 손글씨 숫자 인식” 예에서 y_k 와 t_k는 다음과 같은 원소 10개 짜리 데이터다
- 여기에서 신경망의 출력 y는 소프트맥스 함수의 출력이다.
- 소프트맥스 함수의 출력은 확률로 해석할 수 있으므로 이미지가 0일 확률은 0.1 이다.
- t는 숫자 0부터 시작 따라서 2가 정답일 확률이 0.6, 정답은 2다.
파이썬으로 구현
def sum_squares_error(y, t):
return 0.5 * np.sum((y-t)**2)
예시
- 위에서 첫 번째의 예는 정답이 2 신경망의 출력도 2에서 가장 높은 경우다.
- 두 번째에서 정답은 똑같이 2지만 신경망의 출력은 7에서 가장 높다.
- 오차제곱합 기준으로 첫 번째 추정 결과가 정답에 더 가까울 것으로 판단할 수 있다.
- (첫 번째 오차가 더 작다)
교차 엔트로피 오차
- 여기에서 로그는 밑이 자연로그다.
- y_k는 신경망의 출력
- t_k는 정답 레이블
- t_k는 정답에 해당하는 인덱스의 원소만 0이고 나머지는 0 (원-핫 인코딩)
- 예를 들어 정답 레이블은 2가 정답이라고 하고 이 때의 신경망 출력이 0.6이라고 한다면
- 교차 엔트로피 오차는 -log0.6 = 0.51이 된다.
- 같은 조건에서 신경망 출력이 0.1 이라면 -log0.1 = 2.30이 된다.
- 즉 교차 엔트로피 오차는 정답일 때의 출력이 전체 값을 정하게 된다.
파이썬으로 구현
# 교차 엔트로피 오차
def cross_entropy_error(y, t):
delta = 1e-7
# 1e-7 = 0.0000001
return -np.sum(t * np.log(y + delta))
- np.log() 함수에 0을 입력하면 마이너스 무한대가 되어 더 이상 계산을 진행할 수 없다
- 따라서 아주 작은 값을 더해 절대 0이 되지 않도록 만들어준다.
예시
- 첫 번째 예는 정답일 때의 출력이 0.6 -> 교차 엔트로피 오차는 약 0.51
- 두 번째 예에서는 정답의 출력이 더 낮은 0.1 -> 교차 엔트로피 오차는 2.3
- 즉 결과(오차 값)이 더 작은 첫 번째 추정이 정답일 가능성이 높다고 판단한다.
미니배치 학습
- 기계학습 문제는 훈련 데이터를 사용해 학습한다.
- 훈련 데이터에 대한 손실 함수의 값을 구하고 그 값을 최대한 줄여주는 매개변수를 찾아낸다.
- 이렇게 하려면 모든 훈련 데이터를 대상으로 손실 함수 값을 구해야 한다.
- 즉 훈련 데이터가 100개가 있으면 그로부터 계산한 100개의 손실 함수 값들의 합을 지표로 삼는다.
- 데이터가 N개라면 t_nk는 n번째 데이터의 k번쨰 값을 의미한다. (y_nk는 신경망의 출력, t_nk는 정답 레이블이다.)
- N으로 나눔으로써 평균 손실 함수르 구한다.
- 평균을 구해 사용하면 훈련 데이터 개수와 상관없이 언제든 통일된 지표를 얻을 수 있다.
- 많은 데이터를 대상으로 일일이 손실함 수를 계산 하는 것이 아닌 훈련 데이터로부터 일부만 골라 학습을 수행한다.
- 이 일부를 미니배치라고 한다.
- 예를 들어 60,000 장의 훈련 데이터중에 100장을 무작위로 뽑아 100장만 사용하여 학습하는 학습을 미니배치 학습이라고 한다.
MNIST + 미니배치
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, one_hot_label=True)
print(x_train.shape) # (60000, 784)
print(t_train.shape) # (60000z 10)
train_size = x_train.shape[0]
batch_size = 10
batch_mask = np.random.choice(train_size, batch_size)
# 이전 배치는 스텝이였는데 이번 배치는 10개를 무작위로 뽑는거임
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
- 넘파이 random.choice로 train_size에 있는 데이터 무작위 10개를 뽑아온다.
- 손실 함수도 미니배치로 계산한다.
(배치용) 교차 엔트로피 오차 구현하기
import sys, os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist
# 배치용 교차 엔트로피 오차 구현하기
# 1번 원 핫코딩일때
def cross_entropy_error(y, t):
if y.ndim == 1:
# ndim 은 y 의 차원의 수, 배열의 축 수
# t는 정답테이블
# y는 신경망 출력
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
# 1e-7 = 0.0000001
# 2번
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
'''
reshape는 배열 변환 ->
a = [1,2,3,4,5,6,7,8]
b = np.reshape(a,(2,4))
c = np.reshape(a,(4,2))
[[1 2 3 4]
[5 6 7 8]]
[[1 2]
[3 4]
[5 6]
[7 8]]
print(b.shape[0])
print(b.shape[1])
2
4
'''
y = 신경망의 출력 t = 정답 테이블 t 가 0일 경우 교차 엔트로피 오차도 0, 그 계산은 무시함
왜 손실 함수를 설정하는가?
- 신경망 학습에서는 최적의 매개변수(가중치와 편향)을 탐색할 때 손실 함수의 값을 가능한 작게 만들어주는 매개변수 값을 찾는다.
- 이때 매개변수의 미분(정확히는 기울기)를 계산하고 그 미분 값을 단서로 매개변수의 값을 서서히 갱신하는 과정을 반복한다.
- 예를 들어 미분 갑이 음수면 가중치 매개변수를 양의 방햔으로 변화시켜 손실 함수의 값을 줄이고 반대로 미분 값이 양수면 가중치 매개변수를 음의 방향으로 변화시켜 손실 함수의 값을 줄일 수 있다.
- 하지만 미분 값이 0이면 가중치 매개벼수를 어느 쪽으로 움직여도 손실 함수의 값은 줄어들지 않는다.
- 이 때 가중치 매개변수의 갱신은 멈춘다.
- 정확도를 지표로 삼으면 안되는 이유는 미분 값이 대부분의 장소에서 0이 되어 매개변수를 갱신할 수 없기 때문이다.
정확도를 지표로 삼으면 매개변수의 미분이 대대분의 장소에서 0이 되는 이유는 무엇일까?
- 예시
- 한 신경망이 100장의 훈련데이터 중 32장을 올바로 인식한다면 정확도는 32%다.
- 만약 정확도가 지표였다면 가중치 매개변수의 값을 조금씩 바꾼다고 해도 정확도는 그대로 32%다.
- 즉 매개변수를 약간만 조정해서는 정확도가 개선되지 않고 일정하게 유지된다.
- 손실 함수를 지표로 삼는다면?
- 현재 손실 함수 값이 0.925313 같은 수치라면 매개변수의 값이 조금 변화하면 손실함수의 값도 0.93434 처럼 연속적으로 변화한다.
- 정확도는 매개변수의 미소한 변화에는 거의 반응을 보이지 않고 반응이 있더라도 그 값이 불연속적으로 갑자기 변화한다.
- 계단 함수를 활성화 함수로 사용하지 않는 이유와도 들어맞는다.
- 만약 활성화 함수로 계단 함수를 사용한다면 지금까지와의 이유로 신경망 학습이 잘 이뤄지지 않는다.
- 계단 함수의 미분은 다음 그림과 같이 대부분의 장소에서(0이 외의 곳) 에서 0이다.
- 그 결과 계단 함수를 이용하면 손실 함수를 지표로 삼는 게 아무 의미가 없다.
- 매개변수의 작은 변화가 주는 파장을 계단 함수가 말살하여 손실 함수의 값에는 아무런 변화가 나타나지 않기 때문이다.
- 계단 함수는 한순간만 변화를 일으키지만 시그모이드 함수의 미분(접선)은 그림과 같이 출력이 연속적으로 변하고 곡선의 기울기도 연속적으로 변한다.
- 즉 시그모이드 함수의 미분은 어느장소라도 0이 되지 않는다.
- 이는 신경망 학습에서 중요한 성질로, 기울기가 0이 되지 않는 덕분에 신경망이 올바르게 학습할 수 있는 점이다.
수치 미분
경사법에서는 기울기(경사) 값을 기준으로 나아갈 방향을 정한다.
미분
미분이란 한 순간의 변화량을 표시한 것
파이썬으로 구현
def numerical_diff(f, x):
h = 1e-4 # 0.0001
return (f(x+h) - f(x-h)) / (2*h)
- 파이썬에서는 반올림 오차 문제가 있다. 1e-50을 float32형으로 나타내면 0.0이 된다.
- 이 미세값을 1e-4 정도로 사용하면 문제 없다.
- h를 무한히 0으로 좁히는 것은 불가능하다.
- 이러한 오차를 줄이기 위해 (x+h) 와 (x-h)일때 함수의 차분을 계산해서 쓴다.
- 이를 중심차분 또는 중앙차분이라하며
- (x+h) 와 x의 차분은 전방차분이라고 한다.
- 요약하자면 우리가 알고 있는 미분은 해석적 미분 이고 수치미분은 근사치로 계산 하는 거라고 생각하면 된다.
수치 미분의 예
파이썬 구현
함수 그리기
- x 가 5일 때와 10일 때의 함수 미분을 계산해보자
- 진정한 미분은 0.2 와 0.3 이지만 수치미분과 비교 했을 때 오차가 매우 작다.
- x =5 , x = 10에서의 접선은 아래와 같다.
편미분
- 앞과 달리 변수가 2개다
- 그래프
-
- 문제 1에서는 x_1 = 4로 고정된 새로운 함수를 정의하고, 변수가 x_0 에 대해 수치 미분 함수를 적용했다.
- 이렇게 구한 문제 1의 결과는 6.0000…
- 문제 2의 결과는 7.99999…
- 편미분은 목표 변수를 제외한 나머지를 특정 값에 고정
기울기
import numpy as np
# 기울기
# x[0] x[1] 동시에 계산하고 싶다면?
def numerical_gradient(f, x):
h = 1e-4 # 0.0001
grad = np.zeros_like(x) # x와 형상이 같은 배열을 생성
# print(grad) array [0. 0.]
# print(x.size) -> 2
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 function_2(x):
return x[0]**2 + x[1]**2
- x = np.array[3.0, 4.0] 이라고 가정한다면 x.size 값은 2
- range 로 인해 idx 는 0 , 1
- x[0] 값은 3.0, x[1] 값은 4.0
- x[0], x[1] 값 갱신
- 파라미터 F 는 함수를 받아옴
- f(x) = function_2(x) 갱신된 값 fxh1, fxh2 각각 대입
- 세 점 (3,4) , (0,2), (3,0) 에서의 기울기는 위와 같다.
- 이 기울기를 백터로 그리면 아래와 같다.
- 각 기울기는 각 지점에서 낮아지는 방향을 가리킨다.
- 기울기가 가리키는 쪽은 각 장소에서 함수의 출력 값을 가장 크게 줄이는 방향이다.
경사법(경사 하강법)
신경망에서 최적의 매개변수 (가중치와 편향)을 학습 시에 찾아야 한다. 여기에서 최적이란 손실 함수가 최솟값이 될 때의 매개변수 값이다.
- 일반적으로 매개변수 공간이 광대하여 어디가 최솟값이 되는 곳인지 짐작하기 어렵다.
- 이런 상황에서 기울기를 잘 이용해 함수의 최솟값(또는 가능한 작은 값)을 찾으려는 것이 경사법이다.
- 주의할 점은 각 지점에서 기울기가 가리키는 곳이 정말 함수의 최솟값이 있는지, 그 쪽으로 나아갈 방향인지는 보장할 수 없다.
- 실제로 복잡한 함수에서 기울기가 가리키는 방향에 최솟값이 없는 경우가 대부분이다.
- 경사법은 현 위치에서 기울어진 방향으로 일정 거리만큼 이동한다.
- 그런 다음 이동한 곳에서도 마찬가지로 기울기를 구하고, 또 그 기울어진 방향으로 나아가기를 반복한다.
- 이런 방식으로 함수의 값을 점차 줄이는 것을 경사법이라 한다.
- 특히 신경망 학습에서 경사법을 많이 사용한다.
- 경사법의 수식은 다음과 같다
-
- 기호 에타 N 은 갱신하는 양을 나타낸다.
- 이를 신경망 학습에서는 학습률이라고 한다.
- 한 번의 학습으로 얼마만큼 학습해야할지, 즉 매개변수 값을 얼마나 갱신하느냐를 정하는 것이 학습률이다.
- 위 식에서는 1회에 해당하는 갱신이고 이 단계를 반복한다.
- 이 단계를 여러번 반복해서 서서히 함수의 값을 줄인다.
- 학습률 값은 0.01 이나 0.001등 미리 특정 값으로 정한다
- 일반적으로 값이 너무 크거나 작으면 좋은 장소를 찾아갈 수 없다.
- 보통 이 학습률 값을 변경하면서 올바르게 학습하고 있는지를 확인하면서 진행한다.
경사 하강법 구현
import numpy as np
from Chap_4_4_0_기울기 import numerical_gradient
# 경사하강법
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 late를 의미하는 학습률
- step_num 경사법에 따른 반복 횟수를 뜻함
# 경사하강법으로 f(x[0],f[1]) = x[0]^2 + x[1]^2 의 최솟값을 구하여라
def function_2(x):
return x[0]**2 + x[1]**2
init_x = np.array([-3.0, 4.0])
a = gradient_descent(function_2, init_x=init_x, lr=0.1, step_num=100)
print(a)
# array([ -6.11110793e-10, 8.14814391e-10])
- 초기값을 (-3.0, 4.0) 으로 설정
- lr = 0.1 x grad(기울기) = (array ([-6.0, 8.0]))
- 첫 return x 값은 [-2.4 3.2]
- (-3.0 , 4.0) - (-0.6 , 0.8)
- [-2.4, 3.2] 의 기울기로 다시 시작
- 반복문이 끝날 때까지 계속 차감하면서 값을 업데이트
-
학습률에 따른 차이
신경망에서의 기울기
- 의 각 원소는 각각의 원소에 관한 편미분이다.
- 예를 들어 1행 1번째 원소인 은 w_11 을 조금 변경했을 때 손실함수 L이 얼마나 변화했느냐를 나타낸다.
class simpleNet:
def __init__(self):
self.W = np.random.randn(2,3) # 정규분포로 초기화
self.Z = 1
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
- W는 random 으로 2행 3열의 행렬 생성
- x= 1x2, W = 2x3
- np.argmax() 배열에서 가장 높은 값의 인덱스 반환
- t= 임의의 정답 레이블
- 위에서는 1.1328074가 최대값이라 np.argmax(p) 값은 2
- loss의 z값은 p값과 동일
- p 값은 1차원
- 오버플로 대비해서 p의 max 값 제거해줌
- x값 = [1.05414809 0.63071653 1.1328074 ]
- np.max값 = 1.1328074
- x - np.max(x) 값 = [-0.07865931 -0.50209087 0. ]
- soft max 값은 np.exp(x) / np.sum(np.exp(x))
- 위 식에서
y = [0.36541271 0.23927078 0.39531651]
- y 값이 1 차원이므로
- t = t.reshape(1, t.size)
- t 값은 [2]
- y = y.reshape(1, y.size)
- y 값은 [[0.36541271 0.23927078 0.39531651]]
- batch_size = y.shape[0]
- return -np.sum(np.log(y[np.arange(batch_size), t] + 1e-7)) / batch_size
- t = t.reshape(1, t.size)
- 0.92806853663411326
출처 : 밑바닥부터 시작하는 딥러닝
댓글남기기