학습 : 훈련 데이터로부터 가중치 매개변수의 최적값을 자동으로 획득하는 것
손실함수 : 신경망이 학습할 수 있도록 해주는 지표
학습의 목표 : 손실함수의 결괏값을 가장 작게 만드는 가중치 매개변수를 찾는 것
4.1 데이터에서 학습한다!
4.1.1 데이터 주도 학습
데이터 -> 특징(feature) 추출 -> 패턴 학습 (기계학습: SVM, KNN)
특징 : 입력 데이터에서 본질적인 데이터를 정확하게 추출할 수 있도록 설계된 변환기 (보통 벡터로 기술)
한계 : 특징은 여전히 사람이 설계해야 함
-> 신경망은 특징까지 기계가 스스로 학습 (종단간 기계학습 : 처음부터 끝까지 기계가 학습)
4.1.2 훈련 데이터와 시험 데이터
- 기계학습 문제는 데이터를 훈련 데이터와 시험 데이터로 나눠 학습과 실험을 수행하는 것이 일반적
- 훈련 데이터만 사용하여 학습하면서 최적의 매개변수를 찾는다.
- 시험 데이터를 사용하여 앞서 훈련한 모델의 실력을 평가
- 한 데이터셋에만 지나치게 최적화된 상태인 오버피팅 피하는 것이 기계학습의 중요한 과제
4.2 손실 함수
- 신경망 학습에서 최적의 매개변수 값을 탐색하는 기준 지표
- 신경망 성능의 '나쁨'을 나타내는 지표
- 일반적으로 오차제곱합과 교차 엔트로피 오차 사용
4.2.1 오차제곱합
y_k : 신경망의 출력 (신경망이 추정한 값)
t_k : 정답 레이블 (one-hot-encoding)
k : 데이터의 차원 수
#오차제곱합 함수 정의
def sum_squares_error(y,t):
return 0.5*np.sum((y-t)**2)
#정답 : 2
t = [0,0,1,0,0,0,0,0,0,0] #정답 레이블 (원-핫 인코딩 표기됨)
#예1
y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0] #정답에 더 가까움, 2를 0.6으로 제일 큰 확률이라고 예측
print(sum_squares_error(np.array(y), np.array(t)))
#예2
y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
print(sum_squares_error(np.array(y), np.array(t)))
0.09750000000000003 (정답에 더 가까움, 더 작은 결과값)
0.5975
- y는 소프트맥스 함수의 출력 (확률, 예: 이미지가 0,1,2,,,,,일 확률)
- t는 정답을 가리키는 위치의 원소는 1로, 그 외에는 0으로 표기 (2가 정답, 1로 표기됨 )
4.2.2 교차 엔트로피 오차
y_k : 신경망의 출력 (신경망이 추정한 값)
t_k : 정답 레이블 (one-hot-encoding)
-> 정답일 때의 y의 자연로그를 계산하는 식 => 정답일 때의 출력이 전체 값을 정하게 됨
(정답이 아닌 t들은 모두 0이기 때문)
#교차엔트로피오차 함수 정의
def cross_entropy_error(y,t):
delta = 1e-7
return -np.sum(t*np.log(y+delta))
- 아주 작은 값인 delta를 y에 더해 줌
- 마이너스 무한대가 발생하지 않도록 하기 위해
#위와 똑같은 예
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]
print(cross_entropy_error(np.array(y), np.array(t)))
y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
print(cross_entropy_error(np.array(y), np.array(t)))
0.510825457099338
2.302584092994546
4.2.3 미니배치 학습
- N개의 데이터로 확장
- 마지막에 N으로 나누어 정규화 -> 평균 손실 함수
-> 모든 데이터를 대상으로 손실 함수의 합을 구하려면 상당한 시간 소요
- 미니배치 : 데이터 일부를 추려 전체의 '근사치'로 이용
미니배치 학습 : 훈련 데이터로부터 일부만 골라 학습 수행
(X_train, t_train), (X_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
print(X_train.shape)
print(t_train.shape)
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] #batch_mask인덱스의 X_train값
t_batch = t_train[batch_mask] #batch_mask인덱스의 t_train값
- np.random.choice()함수로 무작위 추출
4.2.4 (배치용) 교차 엔트로피 오차 구현하기
def cross_entropy_error(y,t):
if y.ndim ==1: #y가 1차원인 경우, 데이터 하나씩 오차 구하는 경우
t = t.reshape(1, t.size) #2차원으로 바꿔줌
y = y.reshape(1, y.size)
batch_size = y.shape[0] #y가 1차원이 아닌 경우, y의 행 수가 배치 사이즈
return -np.sum(t*np.log(y+1e-7))/batch_size #배치 사이즈로 나눔으로써 정규화 -> 데이터 1개의 평균 교차 엔트로피 오차
#정답 레이블이 원-핫 인코딩이 아니라 숫자 레이블로 주어졌을 때
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.arrange(batch_size), t]+1e-7))/batch_size
- np.arrange(batch_size) : 0부터 batch_size -1까지 배열 생성
batch_size가 5이면 [0,1,2,3,4] 넘파이 배열 생성 - y[np.arrange(batch_size), t]: t에 대한 y값(예측 확률) 추출
[y[0,2], y[1,7], y[2,0], y[3,9], y[4,4]]
4.2.5 왜 손실 함수를 설정하는가?
- 신경망 학습에서는 최적의 매개변수 (가중치와 편향)를 탐색할 때 손실 함수의 값을 가능한 한 작게 하는 매개변수 값을 찾음
- 매개변수의 미분 (기울기)을 계산하고, 그 미분 값을 단서로 매개변수 값을 서서히 갱신
- 가중치 매개변수의 손실 함수의 미분 : 가중치 매개변수의 값을 아주 조금 변화 시켰을 때, 손실 함수가 어떻게 변하나
- 미분 값 음수 -> 가중치 매개변수 양의 방향으로 변화 -> 손실 함수 값 줄임
- 정확도는 매개변수의 미소한 변화에는 거의 반응을 보이지 않고, 있더라도 불연속적으로 갑자기 변화
- 계단 함수를 활성화 함수로 사용하지 않는 이유와도 같음
- 계단 함수의 미분은 대부분의 장소 (0이외의 곳)에서 0 -> 손실 함수 값에 변화 x
- 시그모이드 함수의 미분은 연속적으로 변화 -> 미분이 어느 장소라도 0이 아님 => 신경망이 올바르게 학습 가능
4.3 수치 미분
4.3.1 미분
미분 : 한순간의 변화량
: x의 작은 변화가 함수 f(x)를 얼마나 변화시키느냐 (h: 작은변화)
- 구현
- h = 1e-4 (0.0001)
- 오차를 줄이기 위해 중심 차분 (중앙 차분) 사용
def numerical_diff(f, x):
h = 1e-4 #0.0001
return (f(x+h) - f(x-h)) / (2*h)
- 수치 미분 : 아주 작은 차분으로 미분하는 것 (해석적 미분을 근사치로 계산하는 방법)
- 해석적 미분 : 수식을 전개해 미분하는 것 ( 수학 시간에 배운 그 미분)
4.3.2 수치 미분의 예
#수치미분 예
def funtion_1(x):
return 0.01*x**2 + 0.1*x
print(numerical_diff(funtion_1, 5))
print(numerical_diff(funtion_1, 10))
4.3.3 편미분
: 변수가 여럿인 함수에 대한 미분
- 여러 변수 중 목표 변수 하나에 초점을 맞추고 다른 변수는 값을 고정(상수 취급)
#편미분
def funtion_2(x):
return x[0]**2 + x[1]**2
#문제 1: x0 =3, x1=4일 때 x0에 대한 편미분 구하라
def funtion_tmp1(x0):
return x0*x0 +4.0**2.0 #변수가 x0하나인 식으로 수정
print(numerical_diff(funtion_tmp1, 3.0))
#문제 2: x0 = 3, x1=4일 때 x1에 대한 편미분 구하라
def funtion_tmp2(x1):
return 3.0**2.0 + x1*x1
print(numerical_diff(funtion_tmp2, 4.0))
6.00000000000378
7.999999999999119 (해석적 미분의 결과와 거의 같음)
4.4 기울기
: 모든 변수의 편미분을 벡터로 정리한 것
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
#예시
numerical_gradient(funtion_2, np.array([3.0, 4.0]))
array([6., 8.])
- 기울기 의미 : 각 장소에서 함수의 출력 값을 가장 크게 줄이는 방향
4.4.1 경사법(경사 하강법)
- 학습 시에 최적의 매개변수(가중치와 편향)를 찾기 위해 손실 함수가 최솟값이 되는 곳을 찾아야 함.
-> 경사법 : 기울기를 이용해 함수의 최솟값을 찾는 것 - 현 위치에서 기울어진 방향으로 일정 거리만큼 이동, 이동한 곳에서도 마찬가지로 기울기를 구하고, 또 기울어진 방향으로 나아가기를 반복하는 것
- 기계학습 최적화에 흔히 쓰는 방법
- 신경망 학습에 많이 사용됨
- η(에타) : 갱신하는 양, 학습률(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 : 학습률
- step_num : 경사법에 따른 반복 횟수
#예시
def funtion_2(x):
return x[0]**2 +x[1]**2
init_x = np.array([-3.0, 4.0])
gradient_descent(funtion_2, init_x = init_x, lr = 0.1, step_num=100)
array([-6.11110793e-10, 8.14814391e-10])
4.4.2 신경망에서의 기울기
신경망 학습에서의 기울기 : 가중치 매개변수에 대한 손실 함수의 기울기
1행 1번째 원소 편미분 = w11을 조금 변경했을 때 손실 함수 L이 얼마나 변화하느냐
#신경망에서의 기울기
#간단한 신경망 예시로 기울기 구하는 코드 구현
import sys, os
sys.path.append("/content/drive/MyDrive/Colab Notebooks/밑바닥딥러닝/common")
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): #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) #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]) #정답 레이블
net.loss(x, t) #손실함수 값
def f(W):
return net.loss(x, t)
dW = numerical_gradient(f, net.W)
print(dW) #기울기
[[ 0.53901031 -2.65287024 1.01427435]
[-0.61315748 -1.30049799 -1.1916805 ]] ->가중치 매개변수
[-0.22843555 -2.76217034 -0.46394784] -> p
0 -> 최댓값 인덱스
[[ 0.32093654 0.02547019 -0.34640673]
[ 0.48140481 0.03820528 -0.51961009]] -> 기울기
기울기가 0.3 -> w11을 h만큼 늘리면 손실함수의 값은 0.3h만큼 증가 => 음의 방향으로 갱신
-0.5 -> w23을 h만큼 늘리면 손실함수의 값은 0.5h만큼 감소 => 양의 방향으로 갱신 (w11보다 크게 기여)
4.5 학습 알고리즘 구현하기
- 절차:
전제 : 신경망에는 적응 가능한 가중치와 편향이 있고, 이 가중치와 편향을 훈련 데이터에 적응하도록 조정하는 과정이 학습.
신경망 학습은 다음과 같이 4단계로 수행.
1단계 - 미니배치
훈련 데이터 중 일부를 무작위로 가져옴. 이렇게 선별된 데이터를 미니배치라 하며, 그 미니배치의 손실 함수 값을 줄이는 것이 목표.
2단계 - 기울기 산출
미니배치의 손실 함수 값을 줄이기 위해 각 가중치 매개변수의 기울기를 구함. 기울기는 손실 함수의 값을 가장 작게 하는 방향 제시.
3단계 - 매개변수 갱신
가중치 매개변수를 기울기 방향으로 아주 조금 갱신
4단계 - 반복
1~3단계를 반복.
- 확률적 경사 하강법(stochastic gradient descent, SGD) : 미니배치로 데이터를 무작위로 선정하고 경사 하강법으로 매개변수를 갱신하는 방법
4.5.1 2층 신경망 클래스 구현하기
import sys, os
sys.path.append("/content/drive/MyDrive/Colab Notebooks/밑바닥딥러닝/common")
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) #0으로 초기화
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
# x:입력 데이터, t: 정답 레이블
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
4.5.2 미니배치 학습 구현하기
- 미니배치 크기 100
- 매번 60000개의 훈련 데이터에서 임의로 100개의 데이터 추림
- 100개의 미니배치를 대상으로 확률적 경사 하강법을 수행해 매개변수 갱신
- 갱신 횟수 10000번으로 설정, 갱신할 때마다 훈련 데이터에 대한 손실 함수를 계산, 그 값을 배열에 추가
4.5.3 시험 데이터로 평가하기
- 오버피팅을 확인하기 위해 정기적으로 훈련 데이터와 시험 데이터를 대상으로 정확도 기록
- 1에폭별로 정확도 기록
- 에폭(epoch): 하나의 단위, 1에폭은 학습에서 훈련 데이터를 모두 소진했을 때의 횟수에 해당
예: 훈련 데이터 10000개를 100개의 미니배치로 학습할 경우, 확률적 경사 하강법을 100회 반복하면 모든 훈련데이터 소진
-> 100회가 1에폭 - 에폭이 진행될수록 훈련데이터와 시험데이터 정확도가 모두 좋아지면 오버피팅 x
- 오버피팅이 있으면 시험 데이터에 대한 정확도 점점 떨어짐 -> 조기종료 사용 (6장 오버피팅 예방법)
#미니배치 학습 구현하기
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from two_layer_net import TwoLayerNet
(X_train, t_train), (X_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
train_loss_list = []
train_acc_list = []
test_acc_list = []
#하이퍼파라미터
iters_num = 10000 #반복 횟수
train_size = X_train.shape[0]
batch_size = 100 #미니배치 크기
learning_rate = 0.1
network = TwoLayerNet(input_size= 784, hidden_size = 50, output_size= 10)
#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) #60000개의 훈련 데이터에서 임의로 100개의 데이터 추리기
x_batch = X_train[batch_mask]
t_batch = t_train[batch_mask]
#기울기 계산
grad = network.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))
# 그래프 그리기
markers = {'train': 'o', 'test': 's'}
x = np.arange(len(train_acc_list))
plt.plot(x, train_acc_list, label='train acc')
plt.plot(x, test_acc_list, label='test acc', linestyle='--')
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()
-> 에폭 진행될수록 정확도 좋아짐
4.6 정리
- 기계학습에서 사용하는 데이터셋은 훈련 데이터와 시험 데이터로 나눠 사용
- 훈련 데이터로 학습한 모델의 범용 능력을 시험 데이터로 평가
- 신경망 학습은 손실 함수를 지표로, 손실 함수의 값이 작아지는 방향으로 가중치 매개변수를 갱신
- 가중치 매개변수를 갱신할 때는 가중치 매개변수의 기울기를 이용하고, 기울어진 방향으로 가중치의 값을 갱신하는 작업을 반복
- 아주 작은 값을 주었을 때 차분으로 미분하는 것이 수치 미분
- 수치 미분을 이용해 가중치 매개변수의 기울기를 구할 수 있음
- 수치 미분을 이용한 계산에는 시간이 걸리지만, 그 구현은 간단함.
- 다음 장에서 구현하는 오차역전파법은 기울기를 고속으로 구할 수 있음.
책 참고 : 밑바닥부터 시작하는 딥러닝 (한빛미디어)
책 깃허브 : https://github.com/WegraLee/deep-learning-from-scratch
'딥러닝 > 밑바닥부터 시작하는 딥러닝' 카테고리의 다른 글
6장 : 학습 관련 기술들 (0) | 2023.09.15 |
---|---|
5장 : 오차역전파법 (0) | 2023.09.13 |
3장 : 신경망 (0) | 2023.09.05 |
2장 : 퍼셉트론 (0) | 2023.09.04 |
1장 : 헬로 파이썬 (0) | 2023.09.02 |