728x90
◎ 밑바닥부터 시작하는 딥러닝(1)¶
● chapter 4. 신경망 학습¶
- 학습이란 훈련 데이터로부터 가중치 매개변수의 최적값을 자동으로 획득하는 것을 뜻한다.
- 손실 함수는 신경망이 학습할 수 있도록 해주는 지표이다.
- 손실 함수의 결괏값을 가장 작게 만드는 가중치 매개변수를 찾는 것이 학습의 목표
4.1 데이터에서 학습한다!
- 신경망의 특징은 가중치 매개변수의 값을 데이터를 보고 자동으로 결정하는 특징이 있다.
- +신경망은 모든 문제를 주어진 데이터 그대로를 입력 데이터로 활용해 'end-to-end'로 학습할 수 있다.
- 기계학습은 사람의 개입을 최소화하고 수집한 데이터로부터 패턴을 찾습니다.
- 딥러닝을 종단간 기계학습(end-to-end machine learning)이라고도 한다.
- 기계학습 문제는 데이터를 훈련 데이터와 시험 데이터로 나눠 수행한다.
그 이유는 범용 능력을 제대로 평가하기 위해 분리한다.
- 범용 능력은 아직 보지 못한 데이터로도 문제를 올바르게 풀어내는 능력을 일컫는 말
- 한 데티어셋에만 지나치게 최적화된 상태를 오버피팅(overfitting)이라 한다.
- 범용 능력을 획득하는 것이 기계학습의 최종 목표
- 오버피팅 피하기는 기계학습의 중요한 과제입니다.
4.2 손실 함수
- 손실 함수는 일반적으로 오차제곱합과 교차 엔트로피 오차를 사용합니다.
- 오차제곱합(SSE) = 1/2 * ∑(yk-tk)**2
- 오차제곱합은 추정 값과 참 값의 차를 제곱한 후, 그 총합을 구합니다.
In [1]:
import numpy as np
# 오차제곱합 구현하기
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 : '2'일 확률이 가장 놑다고 추정함
y=[0.1,0.05,0.6,0.0,0.05,0.1,0.0,0.1,0.0,0.0]
sum_squares_error(np.array(y),np.array(t)) # 추정 결과가 정답에 더 가까울 것으로 판단할 수 있다.
Out[1]:
0.09750000000000003
In [2]:
#예2 : '7'일 확률이 가장 높다고 추정함
y=[0.1,0.05,0.1,0.0,0.05,0.1,0.0,0.6,0.0,0.0]
sum_squares_error(np.array(y),np.array(t))
Out[2]:
0.5975
첫 번째 추정 결과가 (오차가 더 작으니) 정답에 더 가까울 것으로 판단할 수 있다.
교차 엔트로피 오차
- 또 다른 손실 함수로서 교차 엔트로피 오차(CEE)도 자주 사용
- 엔트로피 오차(E) = -∑tklogyk
- yk는 신경망 출력, tk는 정답 레이블
In [3]:
# 엔트로피 구현하기
def cross_entropy_error(y,t):
delta = 1e-7 # log0는 무한대이기 때문에 무한대의 오류를 피하고자 아주 작은 값 delta를 더한 것
return -np.sum(t*np.log(y+delta))
In [4]:
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]
# 추정1
cross_entropy_error(np.array(y), np.array(t))
Out[4]:
0.510825457099338
In [5]:
y=[0.1,0.05,0.1,0.0,0.05,0.1,0.0,0.6,0.0,0.0]
# 추정2
cross_entropy_error(np.array(y), np.array(t))
Out[5]:
2.302584092994546
결과가 더 작은 첫 번째 추정이 정답일 가능성이 높다고 판단 가능
미니배치 학습
- 신경망 학습에서도 훈련 데이터로부터 일부만 골라 학습을 수행하며 그 일부를 미니배치(mini-batch)라고 한다.
- 가령 60,000장의 훈련 데이터 중에서 100장을 무작위로 뽑아 그 100장만을 사용하여 학습하는 것을 미니배치 학습이라고 한다.
In [6]:
import sys, os
sys.path.append('C:/Users/설위준/Desktop/deep-learning-from-scratch-master') # 부모 디렉터리의 파일을 가져올 수 있도록 설정
from dataset.mnist import load_mnist
(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)
# 각 데이터의 형상 출력
print(x_train.shape)
print(t_train.shape)
(60000, 784) (60000,)
In [7]:
# 훈련 데이터에서 무작위 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]
In [8]:
# 미니배치와 같은 배치 데이터를 지원하는 교차 엔트로피 오차 구현하기 (원-핫 인코딩일때 사용)
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
신경망을 학습할 때 정확도를 지표로 삼지 않는 이유는 매개변수의 미분이 대부분 0이 되기 때문이다.
정확도는 계단 함수와 같이 불연속적인 변화를 나타내지만 손실 함수는 시그모이드 함수와 같이 연속적인 변화를 나타낸다.
매개변수의 작은 변화가 주는 파장을 계단 함수가 말살하여 손실 함수의 값에는 아무런 변화가 나타나지 않기 때문에 손실 함수를 사용
4.3 수치 미분
- 수치 미분 : 아주 작은 차분으로 미분하는 것
- 해석적 미분 : 수식을 전개해 미분하는 것, 오차를 포함하지 않는 '진정한 미분' 값을 구해준다.
- 차분 : 임의 두 점에서의 함수 값들의 차이
- 반올림 오차 : 작은 값이 생략되어 최종 계산 결가에 오차가 생기게 하는 것
In [9]:
# 수치 미분 구현하기
def numerical_diff(f,x):
h=1e-4 # 반올림 오차 문제 해결
return (f(x+h)-f(x-h)) / (2*h)
In [10]:
# 수치 미분 계산
def function_1(x):
return 0.01*x**2 + 0.1*x
# 5일때 미분값
numerical_diff(function_1,5)
# 5일때 해석적 해는 2
Out[10]:
0.1999999999990898
In [11]:
# 편미분 계산
def function_2(x):
return x[0]**2 + x[1]**2 # f(x) = x1^2 + x2^2
# x0=3, x1=4일 때 x0에 대한 편미분 계산
def function_tmp(x0):
return x0*x0 + 4.0**2.0
numerical_diff(function_tmp,3.0)
Out[11]:
6.00000000000378
4.4 기울기
- 모든 변수의 편미분을 벡터로 정리한 것을 기울기(gradient)라고 합니다.
- 기울기가 가리키는 쪽은 각 장소에서 함수의 출력 값을 가장 크게 줄이는 방향
In [12]:
# 기울기 구하는 함수 구현하기
def numerical_gradient(f,x):
h = 1e-4
grad = np.zeros_like(x) # x와 형상이 같은 배열을 생성
for i in range(x.size):
tmp_val = x[i]
x[i] = tmp_val + h
fxh1 = f(x)
# f(x+h) 계산
x[i] = tmp_val - h
fxh2 = f(x)
# f(x-h) 계산
grad[i] = (fxh1 - fxh2)/(2*h)
x[i] = tmp_val
return grad
numerical_gradient(function_2, np.array([3.0,4.0]))
Out[12]:
array([6., 8.])
경사법(경사 하강법)
- 신경망은 최적의 매개변수를 학습 시에 찾아야 합니다.
- 극솟값, 최솟값, 또 안장점(saddle point)이 되는 장소에서는 기울기가 0 (안장점은 어느 방향에서 보면 극댓값이고 다른 방향에서 보면 극솟값이 되는 점)
- 매개변수 공간이 관대하여 어디가 최솟값인지 짐작할 수 없어 기울기를 이용해 함수의 최솟값을 찾는 '경사법'을 사용합니다.
- 경사법은 현 위치에서 기울어진 방향으로 일정 거리를 이동하고 기울기를 구하고, 또 기울어진 방향으로 나아가기를 반복합니다. 이렇게 해서 함수의 값을 점차 줄이는 것이 경사법(gradient method)입니다.
- 매개변수 값을 얼마나 갱신하느냐를 정하는 것이 '학습률'입니다.
In [13]:
# 학습률을 활용한 경사 하강법 구현하기 (최솟값 찾기는 경사 하강법, 최댓값 찾기는 경사 상승법이라고 합니다.)
def gradient_descent(f,init_x,ir=0.01,step_num=100): # init_x : 초깃값, ir : 학습률, f : 최적화하려는 함수, ,step_num : 반복 수
x=init_x
for i in range(step_num):
grad = numerical_gradient(f,x)
x -= ir*grad
return x
In [14]:
# 경사 하강법을 활용해 f(x) = x0^2 + x1^2의 최솟값 구하기
def function_2(x):
return x[0]**2 + x[1]**2
init_x = np.array([-3.0,4.0])
gradient_descent(function_2, init_x=init_x, ir=0.1, step_num=100)
# 실제 최솟값이 (0,0)이므로 경사법으로 거의 정확한 결과를 얻는 것을 확인할 수 있다.
Out[14]:
array([-6.11110793e-10, 8.14814391e-10])
In [15]:
# 학습률을 너무 크거나 작으면 좋은 결과를 얻을 수 없다.
# 학습률이 너무 큰 경우 : ir = 10.0
init_x = np.array([-3.0,4.0])
gradient_descent(function_2, init_x=init_x, ir=10.0, step_num=100)
# 너무 크면 큰 값으로 발산
Out[15]:
array([-2.58983747e+13, -1.29524862e+12])
In [16]:
init_x = np.array([-3.0,4.0])
gradient_descent(function_2, init_x=init_x, ir=1e-10, step_num=100)
# 너무 작으면 갱신되지 않은 채 끝난다.
Out[16]:
array([-2.99999994, 3.99999992])
학습률 같은 매개변수를 하이퍼파라미터라고 합니다.
신경망의 가중치 매개변수는 훈련 데이터와 학습 알고리즘에 의해서 '자동'으로 획득되는 매개변수인 반면, 학습률은 사람이 직접 설정해야하는 매개변수입니다.
신경망에서의 기울기
-
In [17]:
import sys, os
sys.path.append('C:/Users/설위준/Desktop/deep-learning-from-scratch-master') # 부모 디렉터리의 파일을 가져올 수 있도록 설정
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
In [18]:
net = simpleNet()
print("가중치 매개변수")
print(net.W) # 가중치 매개변수
x = np.array([0.6,0.9])
p = net.predict(x)
print("예측값")
print(p)
np.argmax(p) # 최댓값의 인덱스
가중치 매개변수 [[ 0.83415356 -0.4100993 -1.71641231] [-0.23792743 1.11819652 -1.05432913]] 예측값 [ 0.28635745 0.76031729 -1.9787436 ]
Out[18]:
1
In [19]:
t = np.array([0,0,1]) # 정답 레이블
net.loss(x,t) # 손실 함수값
Out[19]:
3.2621068602868752
In [20]:
def f(W):
return net.loss(x,t)
dW = numerical_gradient(f,net.W)
print(dW)
[[ 0.2213884 0.35562559 -0.57701399] [ 0.3320826 0.53343839 -0.86552099]]
위의 dW를 통해 (1,1)은 양의 값을 가지므로 h만큼 늘리면 손실 함수의 값은 0.13만큼 커진다는 뜻이다.
다른 값들도 동일하게 해석 가능하며 양의 값은 음의 방향으로 음의 값은 양의 방향으로 갱신해야 함을 알 수 있다
4.5 학습 알고리즘 구현하기
- 데이터를 미니배치로 무작위로 선정하면 확률적 경사 하강법(SGD)이라고 부른다.
In [21]:
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
# 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
# x : 입력 데이터, t : 정답 레이블
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
def gradient(self, x, t):
W1, W2 = self.params['W1'], self.params['W2']
b1, b2 = self.params['b1'], self.params['b2']
grads = {}
batch_num = x.shape[0]
# forward
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
y = softmax(a2)
# backward
dy = (y - t) / batch_num
grads['W2'] = np.dot(z1.T, dy)
grads['b2'] = np.sum(dy, axis=0)
da1 = np.dot(dy, W2.T)
dz1 = sigmoid_grad(a1) * da1
grads['W1'] = np.dot(x.T, dz1)
grads['b1'] = np.sum(dz1, axis=0)
return grads
실험 데이터로 평가하기
- 훈련 데이터의 손실 함수 값이 작아지는 것은 신경망이 잘 학습하고 있다는 방증이지만, 이 결과만으로는 다른 데이터셋에도 비슷한 실력을 발휘할지는 확실하지 않다.
- 에폭(epoch)은 하나의 단위이며 1에폭은 학습에서 훈련 데이터를 모두 소진했을 때의 획수에 해당한다.
In [22]:
# 데이터 읽기
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
network = TwoLayerNet(input_size=784, hidden_size=50, output_size=10)
# 하이퍼파라미터
iters_num = 10000 # 반복 횟수를 적절히 설정한다.
train_size = x_train.shape[0]
batch_size = 100 # 미니배치 크기
learning_rate = 0.1
train_loss_list = []
train_acc_list = []
test_acc_list = []
# 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)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
# 기울기 계산
#grad = network.numerical_gradient(x_batch, t_batch)
grad = network.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))
train acc, test acc | 0.10441666666666667, 0.1028 train acc, test acc | 0.7906333333333333, 0.7966 train acc, test acc | 0.8777666666666667, 0.8826 train acc, test acc | 0.89805, 0.9033 train acc, test acc | 0.90785, 0.9125 train acc, test acc | 0.9144833333333333, 0.9181 train acc, test acc | 0.9200333333333334, 0.9221 train acc, test acc | 0.92385, 0.9252 train acc, test acc | 0.92775, 0.9288 train acc, test acc | 0.9302833333333334, 0.9311 train acc, test acc | 0.9329833333333334, 0.9336 train acc, test acc | 0.9360333333333334, 0.9359 train acc, test acc | 0.9381166666666667, 0.9388 train acc, test acc | 0.9398666666666666, 0.9402 train acc, test acc | 0.9415166666666667, 0.9407 train acc, test acc | 0.9431, 0.9431 train acc, test acc | 0.9456, 0.9444
In [23]:
import matplotlib.pyplot as plt
# 그래프 그리기
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()
에폭이 진행될수록 훈련 데이터와 시험 데이터를 사용한 평가 모두 좋아지고 있다.
또한, 두 정확도에는 차이가 없어 학습에서는 오버피팅이 일어나지 않았다.
In [24]:
from IPython.core.display import display, HTML
display(HTML("<style>.container {width:80% !important;}</style>"))
728x90
'Book report > Deep Learning from Scratch' 카테고리의 다른 글
Chapter 6. 학습 관련 기술들 (0) | 2021.10.10 |
---|---|
Chapter 5. 오차역전파법 (0) | 2021.10.09 |
Chapter 3. 신경망 (0) | 2021.08.16 |
Chapter 2. 퍼셉트론 (0) | 2021.08.15 |