numerical differentiation
한순간의 변화량
x의 '작은 변화'가 함수 f(x)를 얼마나 변화시키는지를 의미
이때 시간의 작은 변화, 즉 시간을 뜻하는 h를 한없이 0에 가깝게 한다는 의미를 표현하기 위해 lim을 사용
이는 다음과 같이 구현이 가능하다
def numerical_diff(f, x):
h = 1e-50
return (f(x + h) - f(x)) / h
하지만 이 코드에는 개선해야 할 점이 2가지가 있다.
>>> np.float32(1e-50)
0.0
이 미세한 값 h로 1e-4를 이용하면 좋은 결과를 얻는다고 알려져 있음개선 후 코드는 다음과 같다.
def numerical_diff(f, x):
h = 1e-4 # 0.0001
return (f(x+h) - f(x-h)) / (2*h)
위의 함수는 변수가 2개인 함수이다.
이 코드는 다음과 같이 구현이 가능하다.
def function_2(x):
return x[0]**2 + x[1]**2
# 또는 return np.sum(x**2)
이 함수는 변수가 2개이므로 어느 변수에 대한 미분이냐, 즉 x0과 x1 중 어느변수에 대한 미분인가를 구분해야 함
이처럼 변수가 여럿인 함수에 대한 미분을 편미분이라고 함
여러 변수 중 목표 변수 하나에 초점을 맞추고 다른 변수는 값을 고정 후 목표 변수에 대해 미분 진행
ex ) x0=3, x1=4일 때, x0에 대한 편미분
>>> def function_tmp1(x0):
return x0*x0 + 4.0**2.0
...
...
>>> numerical_diff(function_tmp1, 3.0)
6.00000000000378
gradient
모든 변수의 편미분을 벡터로 정리한 것
다음과 같이 구현이 가능하다.
def numerical_gradient(f, x):
h = 1e-4 # 0.0001
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
위의 과정을 통해 (x0, x1, ..., xn)의 각 점에서의 기울기를 계산할 수 있다.
이 기울기가 의미하는 바를 그림으로 표현하면 다음과 같다.
** 그림은 기울기 결과에 마이너스를 붙인 벡터를 나타냄
기울기는 방향을 가진 벡터로 표현
위 그림을 보면 다음을 알 수 있다.
- 기울기는 함수의 '가장 낮은 장소(최솟값)'을 가리킨다고 볼 수 있음
- 또한, '가장 낮은 곳'에서 멀어질수록 화살표의 크기가 커짐
기울기는 각 지점에서 낮아지는 방향을 가리킴 (반드시 최솟값은 아님 - local minimum)
정확히는 기울기가 가리키는 쪽은 각 장소에서의 함수 출력 값을 가장 크게 줄이는 방향이라고 할 수 있다.
gradient method
기계학습은 학습에서 최적의 매개변수를 찾아야 함.
최적의 매개변수를 구하기 위해서는 손실 함수가 최소가 되는 때의 매개변수를 얻어야 하는데,
기울기를 활용하여 함수의 최솟값을 찾도록 하는 방법이 경사법
위의 과정을 반복하여 함수의 값을 점차 줄여 나감
η 기호(에타)는 갱신하는 양을 나타냄 = 학습률(learning rate)
→ 매개변수 값을 얼마나 갱신할지를 결정
위의 식은 1번의 갱신에 해당하고 이를 반복하면서 서서히 함수의 값을 줄이게 된다.
경사 하강법은 다음과 같이 구현이 가능하다.
# f : 최적화하녀는 함수
# init_x : 초깃값
# lr : learning rate
# step_num : 반복 횟수
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
경사법을 사용한 갱신 과정을 그림으로 나타내면 다음과 같다.
학습률이 너무 크면 발산할 수 있고, 반대로 너무 작으면 거의 갱신되지 않은 채로 학습이 끝나기 때문에 학습률을 적절히 설정하는 것이 중요하다.
학습률같은 매개변수를 하이퍼파라미터(hyper parameter)라고 함
가중치와 편향 같은 매개변수가 학습 알고리즘에 의해 '자동'으로 획득되는 반면,
하이퍼파라미터는 사람이 직접 설정해야 함
→ 여러 후보 값 중 시험을 통해 최적의 값을 선정하는 과정 필요
신경망에서는 가중치 매개변수에 대한 손실 함수의 기울기를 계산한다
신경망 학습의 절차는 다음과 같이 정리할 수 있다.
데이터를 미니배치로 무작위 선정하기 때문에 확률적 경사 하강법(stochastic gradient descent, SGD)이라고 부름
MNIST 데이터셋을 사용하여 손글씨 숫자를 학습하는 신경망을 구현해보자
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(x, 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'] = nemerical_gradient(loss_W, self.params['W1'])
grads['b1'] = nemerical_gradient(loss_W, self.params['b1'])
grads['W2'] = nemerical_gradient(loss_W, self.params['W2'])
grads['b2'] = nemerical_gradient(loss_W, self.params['b2'])
return grads
수치 미분 방식으로 각 매개변수의 손실 함수에 대한 기울기 계산까지 구현했다.
다음 장 오차역전파법 을 배우고 gradient(self, x, t)를 구현하여 기울기 계산을 고속으로 수행할 수 있도록 한다.
훈련 데이터 중 일부를 무작위로 꺼내고(미니배치), 그 미니배치에 대해 경사법으로 매개변수 갱신
import numpy as np
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 = []
# 하이퍼파라미터
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)
for i in range(iters_num):
# 미니배치 획득
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = x_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)
미니배치 크기를 100으로 함
→ 매번 60000 개의 훈련 데이터에서 임의로 100개의 데이터 추출
추출된 100개의 미니배치를 대상으로 확률적 경사하강법 수행, 매개변수 갱신
10000번 갱신을 반복하고, 갱신 때마다 훈련 데이터에 대한 손실 함수를 계산하여 배열에 저장한 뒤 그 값의 변화 추이를 그래프로 살펴보면 다음과 같다.
학습 횟수가 늘어나면서 손실 함수의 값이 줄어듦
데이터를 반복 학습하면서 최적 가중치 매개변수로 다가가고 있음을 알 수 있다.
신경망 학습에서는 훈련 데이터 외의 데이터를 올바르게 인식하는지를 확인해야 함
즉 '오버피팅'을 일으키지 않는지 확인해야 함
오버피팅은 훈련 데이터만 제대로 구분하고, 그렇지 않은 데이터들은 식별할 수 없는 경우를 말한다.
신경망 학습의 원래 목표가 범용적인 능력을 익히는 것이기 때문에 시험 데이터에 대한 정확도를 파악해야 한다.
1에폭별로 훈련 데이터와 시험 데이터에 대한 정확도를 기록하는 코드를 살펴보자
에폭(epoch)은 하나의 단위
1에폭 : 학습에서 훈련 데이터를 모두 소진했을 때의 횟수
ex)
훈련 데이터 10000개를 100개의 미니배치로 학습하는 경우, SGD를 100회 반복하면 훈련 데이터를 모두 '소진' 한 것
→ 100회가 1에폭
import numpy as np
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)
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 = []
# 정확도 추이 저장할 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 = x_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))
위의 코드로 얻은 결과는 다음과 같다.
훈련 데이터와 시험 데이터에 대한 정확도 추이를 보면
학습이 진행될수록 정확도가 높아짐을 알 수 있다.
또 두 정확도에 차이가 없는 것으로 보아 이 학습에서는 오버피팅이 일어나지 않음을 알 수 있다.