일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 에이블스쿨5기
- 밑바닥부터시작하는딥러닝
- KTAIVLESchool5기
- KT
- 우하하!!!
- NeuralNetwork
- 딥러닝
- c언어
- javascript
- C
- UiPathStudio
- 딥러닝기초
- AIVLE
- 커피는역시
- 파이팅!!!!
- 신경망
- 사전설명회
- 우하하!!!!
- AivleSchool
- coding
- Programming
- KT에이블스쿨
- deeplearning
- 자바스크립트
- 에이블스쿨
- C언어프로젝트
- 자바스크립트기초문법
- Doit!자바스크립트+제어쿼리입문
- Doit!자바스크립트+제이쿼리입문
- KTAIVLESchool
- Today
- Total
삶은 계란
딥러닝 기초 부수기 - 신경망 학습 본문
딥러닝 기초 부수기 - 신경망 학습
본 게시글은 한빛미디어 출판사의 '밑바닥부터 시작하는 딥러닝(저자: 사이토 고키)' 도서 내용을 바탕으로 작성하였습니다.
1. 서론
전 게시글에서는 신경망(neural network)에 대해 알아보았다. 이번에는 신경망 학습에 대해 정리해보겠다. 학습의 의미는 훈련 데이터로부터 가중치 매개변수의 최적값을 스스로 획득하는 것이다. 여기서 학습의 지표로 손실 함수를 사용하는데, 이 손실 함수의 결과값을 가장 작게 하는 가중치 매개변수 값을 찾는 것이 신경망 학습의 목표이다.
2. 신경망 학습의 기본 개념
MNIST 데이터셋은 손글씨 숫자 이미지를 담고 있는 데이터셋이다. 사람은 해당 숫자 이미지의 특징(feature)을 기계한테 알려줘야 한다. 기계는 사람이 알려준 특징을 통해 스스로 학습을 진행하여 해당 숫자 이미지가 어떤 숫자인지 예측한다. 이것이 신경망 이전의 기계학습(machine learning)이다. 그렇게 신경망이 등장하게 되고, 신경망은 처음부터 끝까지 사람의 개입 없이 특징까지도 기계가 스스로 학습하여 결과를 출력한다. 이러한 학습을 딥러닝(deep learning)이라 부른다.
(1) 훈련 데이터와 시험 데이터
신경망은 훈련 데이터(training data)를 사용하여 학습을 진행한다(최적의 매개변수 값을 찾는다). 학습이 끝나면 시험 데이터(test data)를 사용하여 해당 신경망의 성능을 평가한다. 데이터를 2개로 나누는 이유는 신경망의 범용 능력을 제대로 평가하기 위해서이다. 범용 능력은 훈련 데이터가 아닌 아직 보지 못한 데이터로도 문제를 올바르게 풀어내는 능력이다. 신경망이 한쪽 데이터에만 지나치게 최적화된 상태는 오버피팅(overfitting)이라고 한다.
3. 손실 함수
신경망에서는 학습의 지표로 손실 함수(loss function)를 사용한다. 손실 함수는 신경망 성능의 '나쁨'을 나타내는 지표이다. 이것으로 해당 신경망이 최적의 매개변수 값을 올바르게 찾았는지 확인할 수 있다. 손실 함수 값이 높을수록 신경망이 예측한 값과 정답 값의 오차가 크다는 의미가 된다. 이번 절에서는 손실 함수의 종류에 대해 살펴보자.
(1) 오차제곱합(sum of squares for error, SSE)
위 수식에서 yk는 신경망이 추정한 값, tk는 정답 레이블, k는 데이터의 차원 수이다. 제곱을 한 이유는 음수 값이 나오지 않게 하기 위해서이다. 상수 1/2은 추후 미분을 편리하게 하기 위한 값이다. 이처럼 오차제곱합은 신경망의 추정 값과 정답 값의 차를 제곱하고 그 총합을 구한다.
# 오차제곱합 구현
import numpy as np
# 오자제곱합 함수
def sum_squares_error(y, t):
return 0.5 * np.sum((y-t)**2)
# 정답은 '2'이다. 2번째 인덱스 원소만 1이기 때문. -> 원-핫 인코딩
t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
# 예시1 : 신경망이 '2'일 확률이 가장 높다고 추정(0.6)
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)) # 출력 결과 : 0.097500000000000031
# 예시2 : 신경망이 '7'일 확률이 가장 높다고 추정(0.6)
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)) # 출력 결과 : 0.597500000000000003
(2) 교차 엔트로피 오차(cross entropy error, CEE)
위 수식에서 log는 밑이 자연상수 e이다. 오차제곱합과 마찬가지로 yk는 신경망이 추정한 값, tk는 정답 레이블, k는 데이터 차원 수이다. '-'를 붙인 이유는 음수 값을 양수로 바꾸기 위해서이다. 교차 엔트로피 오차는 정답일 때의 출력이 전체 값을 정한다. tk는 정답에 해당하는 인덱스의 원소만 1이고 나머지는 0인 원-핫 인코딩 배열인데, 0을 log에 곱하면 무조건 0이 되므로 결론적으로 tk가 1일 때의 yk에 log를 씌운 값만 나온다.
# 교차 엔트로피 오차 구현
import numpy as np
# 교차 엔트로피 오차 함수
def cross_entropy_error(y, t):
delta = 1e - 7 # log의 진수 부분이 0이 되지 않도록 아주 작은 값을 더한다.
return -np.sum(t * np.log(y+delta))
# 정답은 '2'이다. 2번째 인덱스 원소만 1이기 때문. -> 원-핫 인코딩
t = [0, 0, 1, 0, 0, 0, 0, 0, 0, 0]
# 예시1 : 신경망이 '2'일 확률이 가장 높다고 추정(0.6)
y = [0.1, 0.05, 0.6, 0.0, 0.05, 0.1, 0.0, 0.1, 0.0, 0.0]
cross_entropy_error(np.array(y), np.array(t)) # 출력 결과 : 0.51082545709933802
# 예시2 : 신경망이 '7'일 확률이 가장 높다고 추정(0.6)
y = [0.1, 0.05, 0.1, 0.0, 0.05, 0.1, 0.0, 0.6, 0.0, 0.0]
cross_entropy_error(np.array(y), np.array(t)) # 출력 결과 : 2.3025840929945458
(3) 미니 배치 학습
지금까지 본 것은 데이터 하나에 대한 손실 함수이다. 이번에는 훈련 데이터 전체에 대한 손실 함수의 합을 구하는 것을 살펴보자.
하나에 대한 교차 엔트로피 오차를 데이터의 개수인 N개로 확장한 식이다. N으로 나누는 것은 평균 손실 함수를 구한다는 의미이다. 신경망 학습은 훈련 데이터에서 일부를 골라 학습을 수행하여 전체의 근사치로 사용한다. 이때 이 일부를 미니 배치(mini-batch)라고 한다.
(4) 배치용 교차 엔트로피 오차 구현
# 배치용 교차 엔트로피 오차
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
4. 수치 미분
(1) 수치 미분
아주 작은 차분으로 미분하는 것을 수치 미분(numerical differentiation)이라 한다. 수학 시간에 배운 미분은 해석적(analytic) 미분이라 한다. 수치 미분이 해석적 미분과 다른 점은 근사치로 계산한다는 것이다.
# 수치 미분
def numerical_diff(f, x):
h = 1e-4
return (f(x+h) - f(x-h)) / (2*h) # 해석적 미분과의 오차를 줄이기 위해 중앙 차분 사용
# 중앙 차분: x를 중심으로 전후의 차분 계산
(2) 편미분
변수가 2개 이상인 함수에 대한 미분을 편미분이라 한다. 편미분 문제는 목표 변수를 제외한 나머지 변수 값을 고정하고 목표 변수에 대한 수치 미분을 적용하여 해결한다.
# 편미분 문제 예시
def function(x): # 변수가 2개인 함수
return x[0]**2 + x[1]**2
def function_tmp(x0): # x1 값이 4로 고정된 새로운 함수 정의
return x0*x0 + 4.0**2.0
numerical_diff(function_tmp, 3.0) # 수치 미분 계산! 결과는 6.00000000000378
5. 기울기
변수가 2개 이상인 함수에서 모든 변수의 편미분을 동시에 계산하는 방법도 있다. 기울기(gradient)는 모든 변수의 편미분을 벡터로 정리한 것이다. 기울기를 이용해 손실 함수가 최솟값이 될 때의 매개변수 값(최적값)을 찾을 수 있다.
# 기울기
def numerical_gradient(f, x):
h = 1e-4
grad = np.zeros_like(x) # x와 같은 형상인 0 배열 생성
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(function, np.array([3.0, 4.0])) # 출력결과: array([6., 8.])
# 점 (3, 4)의 기울기는 (6, 8)
기울기가 가리키는 쪽은 각 장소에서 함수의 출력 값을 가장 크게 줄이는 방향이다.
(1) 경사 하강법
현재 위치에서 기울어진 방향으로 일정 거리만큼 이동하여 기울기를 구하고 이동하는 것을 반복하면서 함수의 최솟값을 찾는 최적화 방법을 경사 하강법(gradient descent method)이라 한다.
위 수식은 변수가 2개인 경우를 보여준 수식이다. 수식을 반복하면서 서서히 함수의 값을 줄인다.
# 경사 하강법
def gradient_descent(f, init_x, lr=0.01, step_num=100): # lr: 학습률, step_num: 반복 횟수
x = init_x
for i in range(step_num):
grad = numerical_gradient(f, x)
x -= lr * grad
return x
학습률 같은 매개변수를 하이퍼 파라미터(hyper parameter)라고 한다. 하이퍼 파라미터는 사용자가 직접 설정해줘야 하는 매개변수이다.
(2) 신경망에서의 기울기
신경망에서의 기울기는 가중치 매개변수에 대한 손실 함수의 기울기이다.
W는 가중치이고, L은 손실 함수이다. 기울기는 ∂L/∂W로 나타낼 수 있다. 1행 1번째 원소인 ∂L/(∂w11 )는 w11을 조금 변경했을 때 손실 함수 L이 얼마나 변화하느냐를 나타낸다.
# 신경망에서 기울기 구하기
import numpy as np
# 간단한 신경망
class simpleNet:
def __init__(self):
self.W = np.random.randn(2,3) # 2x3 형상의 가중치 매개변수
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를 인수로 받아 손실 함수 계산하는 함수
def f(W):
return net.loss(x, t)
# main문
if __name__ == "__main__":
net = simpleNet() # 가중치 매개변수 초기화
x = np.array([0.6, 0.9]) # 입력
p = net.predict(x) # 예측 수행
t = np.array([0, 0, 1]) # 정답 레이블
net.loss(x, t) # 출력결과: 0.92806853663411326
6. 학습 알고리즘 구현
신경망 학습 절차는 아래와 같다.
- 전제 : 신경망에 적응 가능한 가중치, 편향이 존재한다.
- 1단계 - 미니 배치 : 훈련 데이터 중 일부를 무작위로 선별한다.
- 2단계 - 기울기 산출 : 미니 배치의 손실 함수 값을 줄이기 위해 각 가중치 매개변수의 기울기를 구한다.
- 3단계 - 매개변수 갱신 : 가중치 매개변수를 기울기 방향으로 아주 조금 갱신한다.
- 4단계 - 반복 : 1단계 ~ 3단계를 반복한다.
위 방법은 확률적으로 무작위로 골라낸 데이터에 대해 수행하는 경사 하강법이다. 이를 확률적 경사 하강법(stochastic gradient descent, SGD)라고 한다.
(1) 2층 신경망 클래스 구현
# 2층 신경망 클래스
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
(2) 미니 배치 학습 후 시험 데이터로 평가
# TwoLayerNet 클래스와 MNIST 데이터셋을 사용하여 미니배치 학습 후 시험 데이터로 평가
# MNIST 데이터 읽기
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True, one_hot_label=True)
# 2층 신경망 클래스
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)
# 매개변수 갱신
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()
'딥러닝' 카테고리의 다른 글
딥러닝 기초 부수기 - 합성곱 신경망(CNN) (0) | 2022.03.19 |
---|---|
딥러닝 기초 부수기 - 학습 관련 기술들 (0) | 2022.03.14 |
딥러닝 기초 부수기 - 오차역전파법(backpropagation) (2) | 2022.03.05 |
딥러닝 기초 부수기 - 신경망(neural network) (4) | 2022.02.07 |
딥러닝 기초 부수기 - 퍼셉트론(perceptron) (4) | 2022.02.03 |