학습
- 훈련 데이터로부터 가중치 매개변수의 최적값을 자동으로 획득하는 것
4.1 데이터에서 학습하다!
신경망의 특징
- 데이터를 보고 학습할 수 있다. 즉, 가중치 매개변수의 값을 데이터를 보고 자동으로 결정한다
기계 학습
- 기계 학습의 중심에는 데이터가 존재한다.
- 사람의 개입을 최소화하고 수집한 데이터로부터 패턴을 찾으려 시도한다
기계 학습의 접근법
- 이미지에서는 특징(feature)을 추출하고 그 특징의 패턴을 기계학습 기술로 학습하는 방법이 있다.
- 특징은 입력 데이터(입력 이미지)에서 본질적인 데이터(중요한 데이터)를 정확하게 추출할 수 있도록 설계된 변환기를 가리킨다
- 이미지 데이터를 벡터로 변환하고, 변환된 벡터를 가지고 지도 학습 방식의 대표 분류 기법인 SVM, KNN 등으로 학습할 수 있다
이미지 -> 사람이 생각한 특징(SIFT, HOG 등) -> 기계학습(SVM, KNN 등) -> 결과
신경망의 이점
- 모든 문제를 같은 맥락에서 풀 수 있다
- 즉, 신경망은 모든 문제를 주어진 데이터 그대로를 입력 데이터로 활용해 'end-to-end'(데이터(입력)에서 목표한 결과(출력)를 사람의 개입 없이 얻는다는 뜻)로 학습할 수 있다.
범용 능력
- 아직 보지 못한 데이터(훈련 데이터에 포함되지 않는 데이터)로도 문제를 올바르게 풀어내는 능력
- 이 능력을 획득하는 것이 기계학습의 최종 목표
- 이 능력을 평가하기 위해 훈련 데이터와 시험 데이터를 분리해야 한다
오버피팅(overfitting)
- 한 데이터셋에만 지나치게 최적화된 상태
- 오버피팅 피하기는 기계학습의 중요한 과제이다
4.2 손실 함수
손실 함수 (loss function)
- 신경망이 '하나의 지표'를 기준으로 최적의 매개변수 값을 탐색하는데 이 신경망 학습에서 사용하는 지표를 손실함수라고 한다
- 일반적으로 오차제곱합과 교차 엔트로피 오차를 사용한다
오차제곱합 (sum of squares for error, SSE)
- 가장 많이 쓰이는 손실 함수이다
- 아래는 오차제곱합의 수식이다.

import numpy as np
def sum_sqares_error(y,t):
return 0.5*np.sum((y-t)**2)
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(sum_sqares_error(np.array(y),np.array(t)))

교차 엔트로피 오차(cross entropy error, CEE)
- 자주 이용하는 손실 함수이다
- 아래는 교차 엔트로피 오차의 수식이다.

- 교차 엔트로피 오차는 정답일 때의 출력이 전체 값을 정하게 된다

- 이는 정답에 해당하는 출력이 커질수록 0에 다가가다가, 그 출력이 1일 때 0이된다. 반대로 정답일 때의 출력이 작을 수록 오차는 커진다
import numpy as np
def cross_entropy_error(y,t):
delta=1e-7
return -np.sum(t*np.log(y+delta))
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)))

- 위에 대한 수식은 데이터 하나에 대한 손실 함수인데 데이터가 N개일 때의 손실 함수 수식은 아래와 같다

미니배치(mini-batch)
- 신경망 학습에서도 훈련 데이터로부터 일부만 골라 학습을 수행하는데 그 일부를 미니배치라고 한다
- 그래서 이를 통한 학습 방법을 미니배치 학습이라고 한다
from dataset.mnist import load_mnist
import numpy as np
(x_train, t_train),(x_test,t_test) = load_mnist(normalize=True, one_hot_label=True)
train_size=x_train.shape[0]
batch_size=10
batch_mask=np.random.choice(train_size,batch_size)
print(batch_mask)

신경망 학습에서의 최적의 매개변수(가중치와 편향)를 탐색
- 이렇게 손실 함수를 설정하여 이 함수의 값을 가능한 한 적게 하는 매개변수의 값을 찾는다. 이 때 매개변수의 미분(기울기)을 계산하고, 그 미분 값을 단서로 매개변수의 값을 서서히 갱신하는 과정을 반복한다
- 정확도를 지표로 삼으면 미분 값이 대부분의 장소에서 0이 되어 매개변수를 갱신할 수 없기 때문에 정확도를 지표로 삼지 않고 계단 함수를 함수로 사용하지 않는다. 계단 함수는 대부분의 장소에서 0이기 때문이다
- 또한 계단 함수는 한순간만 변화를 일으키지만, 시그모이드 함수의 미분(접선)은 출력이 연속적으로 변하고 곡선의 기울기도 연속적으로 변한다.


4.3 수치 미분
h에 매우 작은 값을 대입할 때의 개선법
1. 너무 작은 값을 이용하면 컴퓨터로 계산하는데 문제가 되기 때문에 10^(-4) 정도의 값을 사용한다
2. 진정한 미분과 이번 구현의 값은 엄밀히는 일치하지 않아 오차를 줄이기 위해 함수의 차분을 계산하는 방법을 쓴다
import numpy as np
import matplotlib.pylab as plt
def function_1(x):
return 0.01*x**2+0.1*x
x=np.arange(0.0,20.0,0.1)
y=function_1(x)
plt.xlabel("x")
plt.ylabel("f(x)")
plt.plot(x,y)
plt.show()

중심 차분, 중앙 차분
- (x+h), (x-h)일 때의 함수 f의 차분을 계산할 때 x를 중심으로 그 전후의 차분을 계산한다
> (x+h)와 x의 차분은 전방 차분이라고 한다
def numberical_diff(f,x):
h=1e-4 # 0.0001
return (f(x+h)-f(x-h)) / (2*h)
편미분
- 변수가 여럿인 함수에 대한 미분을 편미분이라고 한다
- 여러 변수 중 목표 변수 하나에 초점을 맞추고 다른 변수는 값을 고정한다
import numpy as np
import matplotlib.pylab as plt
def numberical_diff(f,x):
h=1e-4 # 0.0001
return (f(x+h)-f(x-h)) / (2*h)
def function_tmp1(x0):
return x0*x0 + 4.0**2.0
print(numberical_diff(function_tmp1,3.0))

4.4 기울기
기울기 (gradient)
- 모든 변수의 편미분을 벡터로 정리하는 것
import numpy as np
import matplotlib.pylab as plt
def numberical_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
def function_2(x):
return x[0]**2+x[1]**2
print(numberical_gradient(function_2,np.array([3.0,4.0])))

- 기울기는 각 지점에서 낮아지는 방향을 가리킨다. 즉, 기울기가 가리키는 쪽은 각 장소에서의 함수의 출력 값을 가장 크게 줄이는 방향이다

경사법(gradient method)
- 매개변수 공간이 광대하여 어디가 최솟값이 되는 곳인지를 짐작할 수 없을 때 기울기를 이용해 함수의 최솟값(또는 가능한 한 작은 값)을 찾으려는 것
- 현 위치에서 기울어진 방향으로 일정 거리만큼 이동하고 이동한 곳에서도 마찬가지로 기울기를 구하고, 또 그 기울어진 방향으로 나아가기를 반복하여 함수의 값을 점차 줄이는 것이다
- 기울기가 가리키는 곳에 정말 함수의 최솟값이 있는지 보장할 수 없다
- 경사법을 아래와 같이 수식으로 나타낼 수 있다

학습률(learning rate)
- 에타는 갱신하는 양을 나타내어 학습률이라고 부른다
- 매개변수 값을 얼마나 갱신하느냐를 정하는 것이다
- 0.01이나 0.001 등 미리 특정 값으로 정해두어야 한다
import numpy as np
import matplotlib.pylab as plt
def numberical_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
def gradient_descent(f,init_x,lr=0.01,step_num=100):
x=init_x
for i in range(step_num):
grad = numberical_gradient(f,x)
x -= lr*grad # lr은 학습률
return x
def function_2(x):
return x[0]**2+x[1]**2
init_x=np.array([-3.0,4.0])
print(gradient_descent(function_2, init_x=init_x,lr=0.1,step_num=100))



- 위는 경사법을 사용하여 최솟값을 탐색한 것이다. 그래서 거의 (0,0)에 가까운 결과이고 실제로 진정한 최솟값 (0,0)이므로 거의 정확한 결과를 얻은 것이다
- 갱신 과정을 그림으로 나타내면 아래와 같다

신경망에서의 기울기
- 이 기울기는 가중치 매개변수에 대한 손실 함수의 기울기를 말한다
- 형상이 2x3일 때의 수식으로는 아래와 같이 나타날 수 있다

from dataset.mnist import load_mnist
import numpy as np
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
def cross_entropy_error(y,t):
delta=1e-7
return -np.sum(t*np.log(y+delta))
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
def numberical_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
def gradient_descent(f,init_x,lr=0.01,step_num=100):
x=init_x
for i in range(step_num):
grad = numberical_gradient(f,x)
x -= lr*grad # lr은 학습률
return x
def function_2(x):
return x[0]**2+x[1]**2
x = np.array([0.6, 0.9])
t = np.array([0, 0, 1])
net = simpleNet()
print(net.W)

from dataset.mnist import load_mnist
import numpy as np
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
def cross_entropy_error(y,t):
delta=1e-7
return -np.sum(t*np.log(y+delta))
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
def numberical_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
def gradient_descent(f,init_x,lr=0.01,step_num=100):
x=init_x
for i in range(step_num):
grad = numberical_gradient(f,x)
x -= lr*grad # lr은 학습률
return x
def function_2(x):
return x[0]**2+x[1]**2
x = np.array([0.6, 0.9])
net = simpleNet()
p=net.predict(x)
print(p)
t = np.array([0, 0, 1])
print(net.loss(x,t))

4.5 학습 알고리즘 구현하기
전체 (신경망 학습이 이뤄지는 순서)
- 신경망에는 적응 가능한 가중치와 편향이 있고, 이 가중치와 편향을 훈련 데이터에 적응하도록 조정하는 과정을 '학습'이라고 한다. 신경망 학습은 다음과 같이 4단계로 수행한다
1단계 - 미니배치
- 훈련 데이터 중 일부를 무작위로 가져온다. 이렇게 선별한 데이터를 미니배치라 하며, 그 미니배치의 손실 함수 값을 줄이는 것이 목표이다
2단계 - 기울기 산출
- 미니배치의 손실 함수 값을 줄이기 위해 각 가중치 매개변수의 기울기를 구한다. 기울기는 손실 함수의 값을 가장 작게 하는 방향을 제시한다
3단계 - 매개변수 갱신
- 가중치 매개변수를 기울기 방향으로 아주 조금 갱신한다
4단계 - 반복
- 1~3단계를 반복한다
확률적 경사 하강법(stochastic gradient descent)
- 경사 하강법으로 매개변수를 갱신하지만 데이터를 미니배치로 무작위로 선정하기 때문에 확률적 경사 하강법이라고 부른다
- 대부분의 딥러닝 프레임워크는 SGD라는 함수로 이 기능을 구현한다
2층 신경망 구하기
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
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
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
- 여기서 params 변수는 신경망에 필요한 매개변수가 모두 저장되고 이 값들은 순방향 처리에 사용된다.
- grads 변수는 params 변수에 대응하는 각 매개변수의 기울기가 저장된다.
미니배치 학습 구현하기
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 = 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)

시험 데이터로 평가하기
- 아래의 구현은 훈련 데이터 외의 데이터를 올바르게 인식하는지 확인. 즉, 오버피팅이 일어나지 않은 확인하기 위해서 학습을 할수록 정확도가 어떻게 되는 알기 위한 구현이다.
import sys, os
sys.path.append(os.pardir) # 부모 디렉터리의 파일을 가져올 수 있도록 설정
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)
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))
# 그래프 그리기
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()

'학교 공부 > 컴퓨터비전' 카테고리의 다른 글
밑바닥부터 시작하는 딥러닝 Chapter3-2 : 신경망 (0) | 2024.05.02 |
---|---|
밑바닥부터 시작하는 딥러닝 Chapter3-1 : 신경망 (0) | 2024.05.01 |
밑바닥부터 시작하는 딥러닝 Chapter2 : 퍼셉트론 (0) | 2024.05.01 |
밑바닥부터 시작하는 딥러닝 Chapter1 : 헬로 파이썬 (0) | 2024.04.29 |