Data Science/Deep Learning
[Deep Learning from Scratch] chapter 4.Training Neural Network
sunnyshiny
2023. 2. 9. 21:57
728x90
- 학습: 훈련 데이터로부터 가중치 매개변수의 최적값을 자동으로 획득하는 것
- 손실함수는 신경망이 학습할 수 있도록 해주는 지표
- 종단간 기계학습 : 딥러닝을 종단간 기계학습(end-to-end machine learning)이라고도 함 즉 처음부터 끝까지, 데이터의 입력에서 목표한 결과를 사람의 개입없이 얻는다는 뜻
데이터 주도 학습¶
- 데이터에서 학습한다는 것은 가중치 매개변수의 값을 데이터를 보고 자동으로 결정한다는 것
- 기계학습은 데이터에서 답을 찾고, 패턴을 발견하고, 이야기를 만드는 것 따라서 기계학습을 중심에는 데이터가 존재
- 신경망(딥러닝)은 중요한 특징까지 스스로 학습
훈련 데이터와 시험 데이터¶
- 기계학습은 훈련데이터(training data)와 시험 데이터(test data)를 나누어 학습과 실험을 수행하는 것이 일반적
- 훈련 데이터로 최적의 매개변수를 찾음
- 시험 데이터로 훈련한 모델의 실력을 평가
- 훈련 데이터와 시험 데이터로 나누는 이유: 아직 보지 못한 데이터로 문제를 올바르게 풀어내는지 범용능력을 제대로 평가하기 위해서임.
손실함수¶
왜 손실함수를 설정하는 가?
- 신경망 학습은 최적의 매개변수(가중치와 편향)을 탐색하는 것으로 손실함수가 최소인 매개변수를 찾는것
- 손실함수 미분은 매개변수 값을 아주 조금 변화시켰을때 손실함수가 어떻게 변하는가를 관찰하는 것을 의미
- 계단함수를 활성화 함수로 하지 않는 이유도 대부분의 값에서 미분이 0이기 때문이다. 즉 매개 변수의 작은 변화가 주는 파장을 계단함수가 말살하여 손실 함수의 값에 아무런 변화가 나타나지 않음.
- 반면 시그모이드는 값이 연속적으로 변하며 함수의 미분이 0이 되지 않음.
- 신경망을 학습할 때 정확도를 지표로 삼아서는 안 된다. 정확도를 지표로 하면 매개변수의 미분이 대부분의 장소에서 0이 되기 때문
- 즉 정확도를 매개변수로 하면 작은 변화에는 거의 반응을 보이지 않다가 갑자기 불연속적인 변화를 보인다 (계단함수를 활성화 함수로 사용하지 않는 것과 동일$\rightarrow$ 대부분의 장소에서 미분 값이 0 이다. 매개변수가 주는 변화를 계단함수가 말살하여 손실함수의 값에 아무런 변화가 나타나지 않게 됨)
오차제곱합(Sum of squares for error, SSE)¶
$E= \frac{1}{2}\sum(y_{pred}-y)^2$
$y_{pred}$ : 신경망의 출력값
$y$ :정답 레이블
In [1]:
import numpy as np
In [2]:
def sum_squres_error(y, t):
return 0.5*np.sum((y-t)**2)
In [3]:
t = [0, 0, 1, 0, 0, 0, 0, 0, 0] # 정답
y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.0, 0.1, 0.0, 0.0] #2라고 추정
print(sum_squres_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] #틀린추정
print(sum_squres_error(np.array(y), np.array(t)))
0.09250000000000003 0.5975
In [4]:
def cross_entropy_error(y, t):
delta = 1e-7 # np.log()에 0을 넣으면 -inf가 되어 계산을 할 수 없기 때문
return -np.sum(t*np.log(y+delta))
In [5]:
t = [0, 0, 1, 0, 0, 0, 0, 0, 0] # 정답
y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.0, 0.1, 0.0, 0.0] #2라고 추정
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] #틀린추정
print(cross_entropy_error(np.array(y), np.array(t)))
0.510825457099338 2.302584092994546
Mini-batch¶
- 훈련 데이터로 부터 일부만 골라 학습 수행
In [6]:
np.random.choice(60000, 10) # 0이상 60,000미만의 수 중 무작위로 10개를 뽑음
Out[6]:
array([ 2324, 42164, 10912, 13455, 25609, 18090, 25591, 10044, 42262, 20433])
In [7]:
'''
# Batch 구현
train_size = x_train.shape[0]
batch_size =10
batch_mark = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mark]
t_batch = t_train[batch_mark]
'''
Out[7]:
'\n# Batch 구현\ntrain_size = x_train.shape[0]\nbatch_size =10\nbatch_mark = np.random.choice(train_size, batch_size)\nx_batch = x_train[batch_mark]\nt_batch = t_train[batch_mark]\n'
(배치용) 교차 엔트로피 구현¶
In [8]:
# 교차 엔트로피 구현 one-hot vector일때
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
In [21]:
# 정답레이블이 숫자 레이블로 주어졌을 때
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
수치미분( Numeric differenciation)¶
$\frac{df(x)}{dx}= \lim_{h->0}\frac{f(x+h)-f(x)}{h}$
- 너무 작은 값은 반올림 오차(rounding error)를 일으키므로 (중심차분, 중앙차분 으로 계산) $\frac{df(x)}{dx}= \lim_{h->0}\frac{f(x+h)-f(x-h)}{2h}$
- 해석적 미분(Analytic differenciation): 수식을 전개하여 미분
- 수치 미분(Numeric differenciation): 차분으로 미분하는 것
- Gradient: 모든 변수의 편미분을 벡터로 정리한 것
In [9]:
def numerical_diff(f, x):
h = 1e-4
return (f(x+h)-f(x-h))/(2*h)
In [10]:
def func(x):
return np.sum(x**2) #f(x1,x2) = x1**2 +x2**2
In [11]:
def numerical_gradient(f, x):
h = 1e-4
grad = np.zeros_like(x) # x와 같은 shape의 배열 생성
for idx in range(x.size):
tem_val = x[idx]
#f(x+h)계산
x[idx] = tem_val + h
fxh1 = f(x)
#f(x-h)계산
x[idx] = tem_val - h
fxh2 = f(x)
grad[idx] = (fxh1-fxh2)/(2*h)
x[idx] = tem_val # 값 복원
return grad
In [12]:
print(numerical_gradient(func, np.array([3.0, 4.0])))
print(numerical_gradient(func, np.array([0.0, 2.0])))
print(numerical_gradient(func, np.array([3.0, 0.0])))
[6. 8.] [0. 4.] [6. 0.]
기울기¶
Gradient descent¶
- 기울기를 활용하여 함수의 최솟값을 찾아낸는 것이 경사 하강법(gradient descent)
- 함수의 최댓값을 찾아낸는 것은 경사 상승법(gradient ascent)
- 함수가 극소, 최소 , 안장점(saddle point)가 되는 곳의 기울기 모두 0 이다.
- 극소는 국소적인 최소값, 즉 한정된 범위에서의 최소인 점
- 안장점은 어느 방향에서 보면 극대값이고 다른 방향에서 보면 극솟값이 되는 점
- 경사법은 기울기가 0 인 장소를 찾지만 반드시 최솟값이라 할수 없으며, 복잡하고 찌그러진 모양의 함수라면 plateau(고원)에 빠져 학습이 진행되지 않는 정체기에 빠질 수 있다.
$$ x_0 = x_0 -\alpha \frac{\partial f}{\partial x_0}\\x_1 = x_1 -\alpha \frac{\partial f}{\partial x_1}$$
$\alpha$(learning rate,학습률):한번의 학습으로 얼마만큼 학습할지, 즉 매개변수를 얼마나 갱신할지는 정하는 것
하이퍼파라미터(hyperprameter): $\alpha$와 같은 매개 변수를 일컬음. 가중치 매개변수와는 다르게 사람이 직접 설정해야 함
- 학습률이 너무 크면 큰 값을 발산하고 작으면 학습이 거의 갱신되지 못한 채 끝나버릴 수 있으므로 적절히 설정하는 것이 중요.
In [13]:
def gradient_descent(f, init_x, lr =0.1, step_num=100):
x = init_x
for i in range(step_num):
grad = numerical_gradient(f, x)
x -= lr*grad
return x
In [14]:
init_x = np.array([-3.0, 4.0])
gradient_descent(func, init_x=init_x)
Out[14]:
array([-6.11110793e-10, 8.14814391e-10])
In [18]:
def softmax(a):
C = np.max(a)
exp_a = np.exp(a-C) #오버플로 대책
sum_exp_a= np.sum(exp_a)
y = exp_a/sum_exp_a
return y
In [19]:
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 [20]:
net = SimpleNet()
print(net.W) #가중치 매개변수
x = np.array([0.6, 0.9])
p = net.predict(x)
print('prediction:',p)
print('최댓값 인덱스: ',np.argmax(p)) # 최댓값의 인덱스
t = np.array([0, 0 ,1])
net.loss(x, t)
[[0.048327 0.59915597 0.11820487] [0.33692047 1.05549058 1.86691939]] prediction: [0.33222462 1.30943511 1.75115038] 최댓값 인덱스: 2
Out[20]:
0.6338780242242011
Two layer Net¶
- 확률적 경사 하강법(Stochastic gradient descent): 데이터를 미니배치로 무작위로 선정, 확률적으로 무작위로 골라낸 데이터에 대해 수행하는 경사 하강법
In [1]:
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
def loss(self, x, t):
# t: 정답 레이블
y = self.predict(self, x)
return cross_entropy_error(y, t)
def accuracy(self, x, t):
y = self.predict(self, 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 numrical_gradient(self, x, t):
loss_W = lambda W: self.loss(x, t)
grad = {}
grad['W1'] = numrical_gradient(loss_W, self.params['W1'])
grad['b1'] = numrical_gradient(loss_W, self.params['b1'])
grad['W2'] = numrical_gradient(loss_W, self.params['W2'])
grad['b2'] = numrical_gradient(loss_W, self.params['b2'])
return grads
Summary
- 기계학습에서 사용하는 데이터 셋은 훈련 데이터와 시험 데이터로 나눠 사용한다
- 훈련데이터로 학습한 모델의 범용 능력을 시험 데이터로 평가한다
- 신경망 학습은 손실 함수를 지표로 손실함수의 값이 작아지는 방향으로 가중치 매개변수를 갱신한다
- 가중치 매개변수를 갱신할 때는 가중치 매개변수의 기울기를 이용하고, 기울어진 방향으로 가중치의 값을 갱신하는 작업을 반복한다.
- 아주 작은 값을 주었을 때의 차분으로 미분하는 것을 수치 미분이라고 한다.
- 수치 미분을 이용해 가중치 매개변수의 기울기를 구할 수 있다.
Reference
밑바닥부터 시작하는 딥러닝
728x90