그림 8.2는 $\tanh'(x)$와 $\sigma'(x)$의 그래프로 $\sigma'(x)$의 최대값은 $\sigma'(0)=0.25$였지만 $\tanh'(x)$의 최대값은 $\tanh'(0)=1$이기 때문에 시그모이드 함수와 비교했을 때 경사가 소실되기 힘들다는 것을 알 수 있다.
그림 8.2 $\tanh'(x)$와 $\sigma'(x)$의 그래프
쌍곡탄젠트 함수 코드 구현
TensorFlow
tf.nn.tanh()
Keras
Activation('tanh')
은닉층 4개로 MNIST 모델링하기
Keras 코드로 구현하면 정확도가 $93.2%$까지 올라가는 것을 알 수 있다.
from time import time
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation
from keras.optimizers import SGD
from sklearn import datasets
from sklearn.model_selection import train_test_split
start = time()
MNIST = datasets.fetch_mldata('MNIST original', data_home='.')
n = len(MNIST.data)
N = 10000
indices = np.random.permutation(range(n))[:N]
X = MNIST.data[indices]
Y = MNIST.target[indices]
Y = np.eye(10)[Y.astype(int)]
x_train, x_test, y_train, y_test = train_test_split(X, Y, train_size=0.8)
# 모델 설정
n_in = len(X[0])
n_hidden = 200
n_out = len(Y[0])
model = Sequential()
model.add(Dense(input_dim=n_in, units=n_hidden))
model.add(Activation('tanh'))
model.add(Dense(units=n_hidden))
model.add(Activation('tanh'))
model.add(Dense(units=n_hidden))
model.add(Activation('tanh'))
model.add(Dense(units=n_hidden))
model.add(Activation('tanh'))
model.add(Dense(units=n_out))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy', optimizer=SGD(lr=0.01), metrics=['accuracy'])
# 모델 학습
epochs = 1000
batch_size = 100
model.fit(x_train, y_train, epochs=epochs, batch_size=batch_size)
print(f'\nElapse training time : {(time() - start)//60}분 {(time() - start) % 60:.6}초\n')
# 정확도 측정
start = time()
loss_and_metrics = model.evaluate(x_test, y_test)
print(f'\nLoss : ')
print(f'Accuracy : %')
print(f'Elapse test time : 초')
Elapse training time : 4.0분 37.9497초
32/2000 [..............................] - ETA: 0s
2000/2000 [==============================] - 0s 32us/step
Loss : 0.396467
Accuracy : 93.2%
Elapse test time : 0.06404519081115723초
ReLU(Rectified Linear Unit) 함수
$\tanh(x)$ 함수를 사용하면 경사가 소실되기 어렵기 때문에 좋기는 하지만 고차원 데이터를 다룰 경우에는 값이 커질 수 있어 다시 경사가 소실될 수 있는 문제점이 발생한다
복잡한 데이터일 수록 고차원일 경우가 많은 이를 회피할 수 있는 활성화 함수가 ReLU 함수로 램프 함수 또는 정규화 선형 함수라고도 하는데 식 $(8.4)$와 같이 정의하며, 그래프는 그림 $8.3$과 같다.
LReLU의 그래프는 그림 $8.4$와 같으며, ReLU 함수와의 차이점은 $\alpha$에 의해 $x<0$일 때도 작은 경사(\alpha)를 갖는다. LReLU 함수를 미분하면 식 $(8.7)$과 같다.
$$\begin{align}f'(x) =\left\{ \begin{array}{lc} 1 & x > 0\\ \alpha & x \leqq 0\end{array}\right. \tag{8.7}\end{align}$$
그림 8.4 $f(x)=\max(\alpha x,x)$ 함수의 그래프
ReLU 함수는 $x\leqq 0$일 때 경사가 사라져버려 학습 과정이 불안해질 수 있는 문제가 있었지만 LReLU는 $x\leqq 0$일 때도 학습이 진행되기 때문에 ReLU 함수보다 효과적인 활성화 함수라고 생각할 수 있지만 실제로 사용하게 되면 효과가 있는 경우도 있고, 업는 경우도 있어 언제 효과가 나타나는지에 관해 아직 밝혀진 바가 없다.
LReLU 함수 코드 구현
TensorFlow에서는 아직 API가 제공되지 않아 직접 함수를 정의해 사용해야 한다.
def lrelu(x, alpha=0.01):
if x >= 0:
return x
else:
return alpha * x
from time import time
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation
from keras.optimizers import SGD
from sklearn import datasets
from sklearn.model_selection import train_test_split
from keras.layers.advanced_activations import LeakyReLU
start = time()
MNIST = datasets.fetch_mldata('MNIST original', data_home='.')
n = len(MNIST.data)
N = 10000
indices = np.random.permutation(range(n))[:N]
X = MNIST.data[indices]
Y = MNIST.target[indices]
Y = np.eye(10)[Y.astype(int)]
x_train, x_test, y_train, y_test = train_test_split(X, Y, train_size=0.8)
# 모델 설정
n_in = len(X[0])
n_hidden = 200
n_out = len(Y[0])
alpha = 0.01
model = Sequential()
model.add(Dense(input_dim=n_in, units=n_hidden))
model.add(LeakyReLU(alpha=alpha))
model.add(Dense(units=n_hidden))
model.add(LeakyReLU(alpha=alpha))
model.add(Dense(units=n_hidden))
model.add(LeakyReLU(alpha=alpha))
model.add(Dense(units=n_hidden))
model.add(LeakyReLU(alpha=alpha))
model.add(Dense(units=n_out))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy', optimizer=SGD(lr=0.01), metrics=['accuracy'])
# 모델 학습
epochs = 20
batch_size = 200
model.fit(x_train, y_train, epochs=epochs, batch_size=batch_size)
print(f'\nElapse training time : {(time() - start)//60}분 {(time() - start) % 60:.6}초\n')
# 정확도 측정
start = time()
loss_and_metrics = model.evaluate(x_test, y_test)
print(f'\nLoss : ') print(f'Accuracy : %')
print(f'Elapse test time : 초')
32/2000 [..............................] - ETA: 0s
1984/2000 [============================>.] - ETA: 0s
2000/2000 [==============================] - 0s 33us/step
Elapse training time : 0.0분 0.0660467초
Loss : 0.473356 Accuracy : 92.9%
Elapse test time : 0.06604671478271484초
Parametric ReLU 함수
LeakyReLU 함수는 $x<0$일 때, 경사 $\alpha$가 고정되었지만, 경사 $\alpha$도 학습으로 최적화하는 것이 Parametric ReLU 함수로 PReLU라고도 한다.
PReLU 함수는 활성화 이전의 값(벡터) $\mathbb=(p_1, p_2, \ldots, p_J)$에 대하여 식 $(8.7)$과 같이 정의하는데, 이 때 상수(스칼라) $\alpha$가 아닌 벡터 $\mathbb{\alpha}=(\alpha_1, \alpha_2, \ldots, \alpha_j, \ldots, \alpha_J)$가 주어진다.
우변에 있는 두 항 중에 $\frac{\partial E}{\partial f(p_j)}$는 앞쪽에 있는 층(순전파에서 다음 층)에서 역전파해오는 오차항이기 때문에 $\frac{\partial f(p_j)}{\partial \alpha_j}$는 식 $(8.8)$에 의해 다음과 같이 구할 수 있다.
import numpy as np
from keras.models
import Sequential
from keras.layers
import Dense, Activation from keras.optimizers
import SGD
from sklearn import datasets
from sklearn.model_selection import train_test_split
MNIST = datasets.fetch_mldata('MNIST original', data_home='.')
n = len(MNIST.data)
N = 10000
indices = np.random.permutation(range(n))[:N]
X = MNIST.data[indices]
Y = MNIST.target[indices]
Y = np.eye(10)[Y.astype(int)]
x_train, x_test, y_train, y_test = train_test_split(X, Y, train_size=0.8) # 모델 설정
n_in = len(X[0])
n_hidden = 200
n_out = len(Y[0])
model = Sequential()
model.add(Dense(input_dim=n_in, units=n_hidden))
model.add(Activation('sigmoid'))
model.add(Dense(units=n_out))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy', optimizer=SGD(lr=0.1), metrics=['accuracy'])
print(f'\nWhen neurons are , Elapse training time : seconds') # 정확도 측정
start = time()
loss_and_metrics = model.evaluate(x_test, y_test)
print(f'\nLoss : ') print(f'Accuracy : %')
print(f'When neurons are , Elapse test time : seconds')
은닉층 뉴런의 개수에 따른 정확도 비교
뉴런 200개일 때
When neurons are 200, Elapse training time : 161.34555006027222 seconds
32/2000 [..............................] - ETA: 0s
2000/2000 [==============================] - 0s 23us/step
Loss : 0.315761 Accuracy : 91.3%
When neurons are 200, Elapse test time : 0.046033620834350586 seconds
뉴런 400개일 때
When neurons are 400, Elapse training time : 338.7665045261383 seconds
32/2000 [..............................] - ETA: 0s
1408/2000 [====================>.........] - ETA: 0s
2000/2000 [==============================] - 0s 45us/step
Loss : 0.255655 Accuracy : 92.5%
When neurons are 400, Elapse test time : 0.09006357192993164 seconds
뉴런 800개 일 때
When neurons are 800, Elapse training time : 588.1928327083588 seconds
32/2000 [..............................] - ETA: 0s
1248/2000 [=================>............] - ETA: 0s
2000/2000 [==============================] - 0s 49us/step
Loss : 0.282248 Accuracy : 92.6%
When neurons are 800, Elapse test time : 0.09756922721862793 seconds
뉴런 1000개일 때
When neurons are 1000, Elapse training time : 737.0927028656006 seconds
32/2000 [..............................] - ETA: 0s
992/2000 [=============>................] - ETA: 0s
1920/2000 [===========================>..] - ETA: 0s
2000/2000 [==============================] - 0s 59us/step
Loss : 1.66926 Accuracy : 84.8%
When neurons are 1000, Elapse test time : 0.11908316612243652 seconds
뉴런 2000개일 때
When neurons are 2000, Elapse training time : 1447.6608402729034 seconds
32/2000 [..............................] - ETA: 0s
576/2000 [=======>......................] - ETA: 0s
1056/2000 [==============>...............] - ETA: 0s
1568/2000 [======================>.......] - ETA: 0s
2000/2000 [==============================] - 0s 105us/step
Loss : 1.61429 Accuracy : 87.55%
When neurons are 2000, Elapse test time : 0.21014928817749023 seconds
뉴런 4000개일 때
When neurons are 4000, Elapse training time : 2890.275902748108 seconds
32/2000 [..............................] - ETA: 1s
288/2000 [===>..........................] - ETA: 0s
544/2000 [=======>......................] - ETA: 0s
800/2000 [===========>..................] - ETA: 0s
1056/2000 [==============>...............] - ETA: 0s
1312/2000 [==================>...........] - ETA: 0s
1568/2000 [======================>.......] - ETA: 0s
1824/2000 [==========================>...] - ETA: 0s
2000/2000 [==============================] - 0s 217us/step
Loss : 9.80855 Accuracy : 37.95%
When neurons are 4000, Elapse test time : 0.4558277130126953 seconds
표 7.1과 그림 7.2를 보면 은닉층의 뉴런의 개수를 늘리더라도 신경망 모델의 예측 정확도가 높아지다가 떨어지는 것을 알 수 있다. 즉, 뉴런의 개수를 단순히 늘린다고 정확도가 높아진다고 볼 수가 없다
또한 뉴런의 개수를 늘리면 학습에 걸리는 시간 또한 무시할 수 없을 정도로 길어지는 것을 알 수 있다
각 층의 뉴런의 개수를 $n_i, n_h, n_o$로 나타낸다면 각각의 뉴런이 연결되는 가지의 수는 $n_i\times n_h + n_h\times n_o$이기 때문에 모델의 예측 정확도가 동일하다면 은닉층 뉴런의 개수 $n_h$가 작을 수록 좋다
은닉층 뉴런의 개수가 일정할 때 은닉층의 개수에 따른 정확도 비교
Keras에서 은닉층의 개수를 추가하는 것은 간단하다
from time import time
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation
from keras.optimizers import SGD
from sklearn import datasets
from sklearn.model_selection import train_test_split
start = time()
MNIST = datasets.fetch_mldata('MNIST original', data_home='.')
n = len(MNIST.data)
N = 10000
indices = np.random.permutation(range(n))[:N]
X = MNIST.data[indices]
Y = MNIST.target[indices]
Y = np.eye(10)[Y.astype(int)]
x_train, x_test, y_train, y_test = train_test_split(X, Y, train_size=0.8) # 모델 설정
n_in = len(X[0])
n_hidden = 200
n_out = len(Y[0])
model = Sequential()
model.add(Dense(input_dim=n_in, units=n_hidden))
model.add(Activation('sigmoid')) # 은닉층 1개 추가
model.add(Dense(units=n_hidden))
model.add(Activation('sigmoid'))
model.add(Dense(units=n_out))
model.add(Activation('softmax'))
model.compile(loss='categorical_crossentropy', optimizer=SGD(lr=0.1), metrics=['accuracy']) # 모델 학습
epochs = 1000
batch_size = 100
model.fit(x_train, y_train, epochs=epochs, batch_size=batch_size)
print(f'\nWhen hidden layers are 2, Elapse training time : seconds\n') # 정확도 측정
start = time()
loss_and_metrics = model.evaluate(x_test, y_test)
print(f'\nLoss : ')
print(f'Accuracy : %')
print(f'When hidden layers are 2, Elapse test time : seconds')
은닉층의 개수가 2개일 때
When hidden layers are 2, Elapse training time : 221.6898009777069 seconds
32/2000 [..............................] - ETA: 0s
2000/2000 [==============================] - 0s 26us/step
Loss : 0.314476 Accuracy : 91.1%
When hidden layers are 2, Elapse test time : 0.05203652381896973 seconds
은닉층의 개수가 3개일 때
When hidden layers are 3, Elapse training time : 4.0분 31.61421251296997초
32/2000 [..............................] - ETA: 1s
1408/2000 [====================>.........] - ETA: 0s
2000/2000 [==============================] - 0s 46us/step
Loss : 0.327307 Accuracy : 90.85%
When hidden layers are 3, Elapse test time : 0.0분 0.09306979179382324초
은닉층의 개수가 4개일 때
When hidden layers are 4, Elapse training time : 5.0분 7.0608086585998535초
32/2000 [..............................] - ETA: 0s
1920/2000 [===========================>..] - ETA: 0s
2000/2000 [==============================] - 0s 33us/step
Loss : 0.384374 Accuracy : 88.8%
When hidden layers are 4, Elapse test time : 0.0분 0.06604671478271484초 acy
은닉층의 개수가 5개일 때
When hidden layers are 5, Elapse training time : 5.0분 32.52758073806763초
32/2000 [..............................] - ETA: 0s
1856/2000 [==========================>...] - ETA: 0s
2000/2000 [==============================] - 0s 35us/step
Loss : 0.43979 Accuracy : 87.55%
When hidden layers are 5, Elapse test time : 0.0분 0.0695500373840332초
은닉층의 개수가 6개일 때
When hidden layers are 6, Elapse training time : 6.0분 29.71598792076111초
32/2000 [..............................] - ETA: 1s
1568/2000 [======================>.......] - ETA: 0s
2000/2000 [==============================] - 0s 43us/step
Loss : 0.454472 Accuracy : 86.25%
When hidden layers are 6, Elapse test time : 0.0분 0.0860595703125초
은닉층의 개수가 7개일 때
When hidden layers are 7, Elapse training time : 6.0분 53.46243381500244초
32/2000 [..............................] - ETA: 1s
1472/2000 [=====================>........] - ETA: 0s
2000/2000 [==============================] - 0s 44us/step
Loss : 0.90537 Accuracy : 70.2%
When hidden layers are 7, Elapse test time : 0.0분 0.08705806732177734초
은닉층의 개수가 8개일 때
When hidden layers are 8, Elapse training time : 7.0분 55.60267639160156초
32/2000 [..............................] - ETA: 1s
1312/2000 [==================>...........] - ETA: 0s
2000/2000 [==============================] - 0s 49us/step
Loss : 2.29931 Accuracy : 12.4%
When hidden layers are 8, Elapse test time : 0.0분 0.09706592559814453초
은닉층의 개수가 9개일 때
When hidden layers are 9, Elapse training time : 8.0분 43.596526861190796초
32/2000 [..............................] - ETA: 1s
1152/2000 [================>.............] - ETA: 0s
2000/2000 [==============================] - 0s 55us/step
Loss : 2.30193 Accuracy : 9.95%
When hidden layers are 9, Elapse test time : 0.0분 0.11157894134521484초
은닉층의 개수가 10개일 때
When hidden layers are 10, Elapse training time : 9.0분 0.5422961711883545초
32/2000 [..............................] - ETA: 1s
1216/2000 [=================>............] - ETA: 0s
2000/2000 [==============================] - 0s 50us/step
Loss : 2.30177 Accuracy : 10.05%
When hidden layers are 10, Elapse test time : 0.0분 0.10107207298278809초
은닉층의 개수가 50개일 때
When hidden layers are 50, Elapse training time : 37.0분 29.942996501922607초
32/2000 [..............................] - ETA: 6s
288/2000 [===>..........................] - ETA: 0s
544/2000 [=======>......................] - ETA: 0s
832/2000 [===========>..................] - ETA: 0s
1120/2000 [===============>..............] - ETA: 0s
1408/2000 [====================>.........] - ETA: 0s
1696/2000 [========================>.....] - ETA: 0s
1984/2000 [============================>.] - ETA: 0s
2000/2000 [==============================] - 0s 245us/step
Loss : 2.30093 Accuracy : 12.5%
When hidden layers are 50, Elapse test time : 0.0분 0.5073604583740234초
은닉층의 개수
학습 시간
오차
정확도
테스트 시간
$1$개
$2$분 $41.33$초
$0.3157$
$91.3\%$
$0.046$초
$2$개
$3$분 $41.69$초
$0.3144$
$91.1\%$
$0.052$초
$3$개
$4$분 $31.61$초
$0.3273$
$90.85\%$
$0.093$초
$4$개
$5$분 $7.06$초
$0.3844$
$88.8\%$
$0.066$초
$5$개
$5$분 $32.52$초
$0.4398$
$87.55\%$
$0.069$초
$6$개
$6$분 $29.71$초
$0.4545$
$86.25\%$
$0.086$초
$7$개
$6$분 $53.46$초
$0.9054$
$70.2\%$
$0.087$초
$8$개
$7$분 $55.60$초
$2.2993$
$12.4\%$
$0.097$초
$9$개
$8$분 $43.59$초
$2.3019$
$9.95\%$
$0.112$초
$10$개
$9$분 $0.54$초
$2.3017$
$10.05\%$
$0.101$초
$50$개
$37$분 $29.94$초
$2.3009$
$12.5\%$
$0.507$초
표 7.2 200개의 뉴런을 가진 은닉층의 개수에 따른 성능 비교
표 7.2의 결과는 표 7.1처럼 뉴런의 개수랑 은닉층의 개수를 무작정 늘린다고 예측 결과의 정확도가 높아지지는 않고 오히려 더 떨어지는 결과를 보여준다. 특히 은닉층이 8개인 경우에는 거의 학습이 되지 않았다는 것을 알 수 있다.
PyTorch 코드
학습을 위한 코드
import torch
import torchvision.datasets as dsets
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
import torch.nn as nn
import matplotlib.pyplot as plt
import random
from time import time
USE_CUDA = torch.cuda.is_available() # GPU를 사용가능하면 True, 아니라면 False를 리턴
device = torch.device("cuda" if USE_CUDA else "cpu") # GPU 사용 가능하면 사용하고 아니면 CPU 사용
print("다음 기기로 학습합니다:", device)
# for reproducibility
random.seed(777)
torch.manual_seed(777)
if device == 'cuda':
torch.cuda.manual_seed_all(777)
# MNIST dataset
mnist_train = dsets.MNIST(root='MNIST_data/',
train=True,
transform=transforms.ToTensor(),
download=True)
mnist_test = dsets.MNIST(root='MNIST_data/',
train=False,
transform=transforms.ToTensor(),
download=True)
# dataset loader
batch_size = 100
data_loader = DataLoader(dataset=mnist_train,
batch_size=batch_size, # 배치 크기는 100
shuffle=True,
drop_last=True)
# MNIST 데이터 이미지의 크기: 28 * 28 = 784
linear = torch.nn.Linear(784, 10, bias=True).to(device)
# 매개변수
training_epochs = 15
batch_size = 100
# cost/loss 함수와 optimizer 정의
criterion = torch.nn.CrossEntropyLoss().to(device) # 내부적으로 Softmax로 계산됨
optimizer = torch.optim.SGD(linear.parameters(), lr=0.1)
start = time()
for epoch in range(training_epochs): # 앞서 training_epochs의 값은 15로 지정함.
avg_cost = 0
total_batch = len(data_loader)
for X, Y in data_loader:
# 배치 크기가 100이므로 아래의 연산에서 X는 (100, 784)의 텐서가 된다.
X = X.view(-1, 28 * 28).to(device)
# 레이블은 원-핫 인코딩이 된 상태가 아니라 0 ~ 9의 정수.
Y = Y.to(device)
optimizer.zero_grad()
hypothesis = linear(X)
cost = criterion(hypothesis, Y)
cost.backward()
optimizer.step()
avg_cost += cost / total_batch
print(f'Epoch: {epoch + 1:>04d}/{training_epochs:2d}, cost = {avg_cost:>12.9f}')
print(f'Learning finished, Elapse test time : {time() - start:10.4f}seconds')
다층 퍼셉트론을 모델화했을 때처럼 식 $(7.1)$과 식 $(7.2)$, 식 $(7.3)$을 다음과 같이 놓으면 웨이트 행렬 $\mathbf{W}=(\mathbb{w}_1, \mathbb{w}_2,\ldots, \mathbb{w}_J)$에 대한 경사는 식 $(7.7)$과 같이 나타낼 수 있다.
식 $(7.7)$에서 우리는 $\frac{\partial E_n}{\partial \mathbb{p}_j}$만 계산하면 되는데, 신경망 모델이 $3$층인 경우 편미분의 연쇄법칙(chain rule)을 적용하면 식 $(7.8)$과 식 $(7.9)$, 식 $(7.10)$과 같이 계산할 수 있다.
이론적으로는 은닉층의 개수가 늘어도 편미분의 연쇄법칙을 반복해서 적용하면 정형화된 식을 통해 각 매개변수의 경사를 구할 수 있다.
그러나 식 $(7.22)$를 코드로 구현할 때에는 문제가 발생할 수 있는 부분이 있는데, 바로 뉴런의 활성화를 위한 시그모이드 함수가 사용되는 부분이다. 식 $(7.22)$ 오차역전파식에는 '시그모이드 함수를 미분한 것들의 곱' 부분이 있는데 우리는 시그모이드 함수를 미분하면 식 $(7.23)$이 되는 것을 알고 있으며, 이를 그래프로 나타내면 그림 $(7.3)$과 같다.
그림 7.3 시그모이드 함수 $\sigma(x)$와 시그모이드를 미분한 도함수 $\sigma'(x)$의 그래프
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
return 1 / (1 + np.exp(-x))
x = np.linspace(-10, 10, 1000)
y = np.array([sigmoid(x) for x in x])
z = np.array([sigmoid(x)*(1-sigmoid(x)) for x in x])
plt.plot(x, y, label='$\sigma(x)$')
plt.plot(x, z, label='$\sigma\'(x)$')
plt.grid(linestyle='--', color='lavender')
plt.legend()
plt.show()
그림 $(7.3)$에서 알 수 있듯이 시그모이드 함수의 도함수 $\sigma'(x)$는 $x=0$일 때 최댓값 $\sigma'(x)=0.25$가 된다. 은닉층이 2개 있는 4층 신경망 모델에서 오류역전파 식 (7.22)의 값을 계산할 때에는 $0.25^2$이 곱해지는 것을 알 수 있으며, 은닉층이 $N$개인 경우에 오차를 계산할 때 $\alpha_N\leqq 0.25^N < 1$ 범위에 있는 $\alpha_N$이 곱해진다는 것을 알 수 있으며 $N$이 커지면 $\alpha_N$ 또한 $0$에 수렴하는 것을 알 수 있다.
은닉층이 많아지면 오류역전파식을 통해 오차를 계산할 때 식 $(7.24)$로 인하여 오차항의 값이 $0$에 가까워지는 현상을 경사 소실 문제(vanishing gradient problem)이라고 하며, 이를 해결하기 위해서는 미분을 하더라도 값이 작아지지 않는 활성 함수를 사용해야만 한다.
또한 경사 소실 문제는 은닉층이 많지 않더라도 발생할 수 있는데, 각 층의 차원(뉴런의 수)이 높아지는 경우에 시그모이드 함수를 통해 활성화되는 $\mathbf{W}\mathbb{x}+\mathbb{b}$의 값이 커질 수 있어 경사가 소실될 수 있다.
$\mathbf{W}=(\mathbb{w}_1, \mathbb{w}_2, \ldots, \mathbb{w}_n)$이라고 하면 그림 $(7.3)$에서 알 수 있듯이 시그모이드 함수의 입력값이 커지기 때문에 식 $(7.25)$가 성립한다. 따라서 식 $(7.26)$으로 인해 경사가 소실될 수 있다.
$n=1$일 경우에 $\hat(x)=a_0+a_1x$이기 때문에 직선으로 표현되며, $n=2$인 경우에는 2차 함수 곡선으로 표현할 수 있다. 따라서 $n$을 크게하면 더 복잡한 곡선을 표현할 수 있는데, 실제로는 $n$을 크게 하더라도 정확하게 표현하는 것이 쉽지 않다.
그림 $7.5$는 $n=1, 4, 33$일 때 30개의 샘플 데이터를 사용해 회귀 분석한 것으로 $n=1$일 때에는 직선으로 표현할 수 밖에 없어 샘플 데이터를 제대로 표현할 수가 없으며, $n=33$일 때에는 더욱 복잡한 함수를 표현할 수 있지만 샘플 데이터만 표현하는 것에 맞춰 회귀되었기 때문에 결과적으로는 실제 분포인 $\cos(\frac{3\pi}{2}x)$와는 다소 거리가 있다.
그림 7.5 1차 함수 회귀(왼쪽), 4차 함수 회귀(중간), 33차 함수로 회귀(오른쪽)
이는 샘플 데이터의 개수가 한정돼 있기 때문에 주어진 데이터만 잘 표현할 수 있는 분포 함수를 찾았다고 하더라도 이 분포 함수가 새롭게 발생되는 미지의 데이터도 잘 표현할 수 있도는 장담을 할 수가 없다. $n=33$일 때처럼 샘플 데이터만을 과잉으로 회귀하는 상태를 오버피팅이라고 한다.
은닉층이나 은닉층의 뉴런 개수를 늘려도 신경망 모델을 구성하는 전체 뉴런의 개수가 늘어나기 때문에 더욱 복잡한 패턴을 표현할 수 있지만 학습 데이터만을 복잡한 방법으로 표현한다면 데이터의 실제 분포와는 다른 패턴 분류의 형태가 될 가능성이 높아진다.
신경망 모델을 학습시킬 때는 오차 함수 $E$값이 최소가 되도록 매개변수 값을 변경했지만 단순히 $E$를 최소화하면 오버피팅될 가능성이 있기 때문에 무조건 최소화시키기만 하면 좋은 것은 아니다.
학습 데이터는 예측이 잘 되지만 테스트 데이터는 예측이 잘 안된다면 오버피팅을 의심해봐야만 한다.
AND 게이트나 OR 게이트처럼 직선 하나($K$ 클래스인 경우에는 $K-1$개의 직선)으로 데이터를 분류하는 것을 '선형 분리 가능(linearly seperable)하다'고 하며 XOR 게이트처럼 직선 하나로 분류할 수 없는 경우를 '선형 분리 불가능(linearly non-seperable)하다'고 한다
단순 퍼셉트론이나 로지스틱 회귀처럼 선형 분리 가능한 문제에만 적용할 수 있는 모델을 선형 분류기(linear classifier)라고 한다
데이터가 $n$차원인 경우에는 $n-1$차원의 초평면으로 분리가 가능한지 알아봐야 하며, 분리가 안되는 경우에는 데이터의 차원을 강제로 높여서 더 높은 차원의 초평면으로 분리하는 방법 중의 하나가 SVM(Support Vector Machine) 알고리즘
XOR 게이트 구현을 위하여 게이트 조합하기
AND, OR, NOT의 기본 게이트들은 선형 분리 가능하기 때문에 이들을 아래 그림과 같이 조합하여 구현하면 선형 분리 불가능한 XOR 게이트를 선형으로 분리할 수 있다
XOR 게이트를 기본 게이트들의 조합으로 구성할 수 있다는 것은 신경망 모델에서 있어서 매우 중요
뉴런을 기본 게이트로 구성할 수 있기 때문에 비선형 분류를 할 수 있는 뉴런 모델을 기본 게이트의 조합으로 구현 가능
비선형 분류를 위해서는 여태까지 구현했던 것처럼 입력과 출력만 생각해서는 안된다
기존 신경 모델에 아래 그림 6.1처럼 점선 내부에 기본 게이트들을 추가한다
그림 6.1 기본 게이트를 조합해서 만든 XOR 게이트 모델
위 그림 6.1에서 점선 부분만 따로 뗴어내면 입력이 2개, 출력이 2개이 때문에 신경망 모델에서는 아래 그림 6.2처럼 2개의 뉴런을 추가하여 위 그림 6.1의 점선 부분처럼 입력이 2개, 출력도 2개로 나타낼 수 있다
그림 6.2 XOR 게이트를 신경망으로 구현한 모델
신경망 모델이 XOR 게이트를 제대로 구현한 것인가?
위에서 구현한 신경망 모델 그림 6.2가 XOR 게이트를 제대로 재현한 것인지 검증해보자
위 그림 6.2의 신경망 모델에서 뉴런의 구조는 우리가 앞에서 해왔던 것에서 변한 것이 없기 때문에 식 (6.1)과 식 (6.2), 식 (6.3)으로 나타낼 수 있다
식 (6.7)로 구한 $\mathbb{h}$는 출력층에 대하여 입력층으로 전기신호를 전달하기 때문에 '은닉층 - 출력층' 부분은 가중치 $\boldsymbol{V}$, 바이어스 $\mathbb{c}$, 활성화 함수 $g(\cdot)$일 때, 식 (6.8)과 같으며, 이를 가설식으로 만들면 식 (6.9)로 표기할 수 있다
\begin{eqnarray} y = g(\boldsymbol{V}\mathbb{h} + \mathbb{c})\tag{6.8}\end{eqnarray}
활성화 함수 $g$는 신경망 모델의 전체 출력이기 때문에 다중 클래스 분류를 할 때에는 소프트맥스 함수를 사용하면 되고, 이진 분류를 하는 경우에는 시그모이드 함수를 사용하면 된다
활성화 함수 $f$는 출력층에 신호를 전달하는 역할을 하며, $g(\cdot)$ 안에 있는 $\boldsymbol{V}\mathbb{h}+\mathbb{c}$는 임의의 실수이기만 하면 되기 때문에 $\mathbb{h}$를 출력하는 활성화 함수 $f$는 입력값이 작으면 출력도 작은 값을 출력하고, 입력값이 크면 출력도 큰 값을 출력하는 함수이면 충분하지만 계산의 편의성을 위해 시그모이드 함수를 사용한다
다층 신경망 모델식 최적화 문제와 역전파 모델
다층 신경망 모델에서 학습을 시킨다하는 것은 식 (6.9) 가설식과 실제의 값 $\mathbb{y}$의 차를 최소로 만드는 매개변수 $\boldsymbol{W}$와 $\boldsymbol{V}$, $\mathbb{b}$, $\mathbb{c}$를 찾는 최적화 문제가 되는데, 신경망 모델의 핵심이 되는 오차 역전파 모델에 대해 자세히 알아보기 위해 각 층에 있는 웨이트 행력을 벡터로 분해해서 경사하강법 모델을 해석해보자
오차함수를 $E$라고 하면 $E=E(\boldsymbol{W}, \boldsymbol{V}, \mathbb{b}, \mathbb{c})$가 되며, 이 때 경사하강법을 통해 가장 적합한 매개변수를 찾으면 된다
각 매개변수에 관한 경사를 구하기 위해여, 각각의 매개변수로 편미분을 구해야하기 때문에 지금까지 한 것처럼 $N$개의 입력 데이터가 있다고 가정하고 그 중에서 $n$번째에 해당하는 데이터 벡터를 $x_n$과 $y_n$이라고 하자
$N$개의 데이터 각각에서 발생하는 오차 $E_n$ $(n=1,2,\ldots,N)$은 서로 독립적으므로 식 (6.10)과 같이 나타낼 수 있다
\begin{eqnarray} E = \sum_{n=1}^N E_n \tag{6.10}\end{eqnarray}
은닉층과 출력층에서 활성화되기 이전의 값을 각각 식 (6.11)과 식 (6.12)로 표현할 수 있다
$\boldsymbol{W}=(\mathbb{w}_1,\mathbb{w}_2,\ldots,\mathbb{w}_J)$와 $\boldsymbol{V}=(\mathbb{v}_1,\mathbb{v}_2,\ldots,\mathbb{v}_K)$에 대해 각각 식 (6.11)과 식 (6.12)에 의해서 식 (6.13)과 식 (6.14)가 성립한다
식 (6.13)과 식 (6.14)에서 $\frac{\partial E_n}{\partial \mathbb{p}_j}$와 $\frac{\partial E_n}{\partial \mathbb{q}_k}$를 구하면 매개변수 $\boldsymbol{W}$와 $\boldsymbol{V}$에 대한 경사, 즉 편미분을 구할 수 있다
이제 '은닉층 - 출력층' 부분에서 입력 데이터 $\mathbb{h}_j=f(\boldsymbol{W}\mathbb{x}_n+\mathbb{b})$가 어떤 클래스($\mathbb{y}_k)$로 분류될 확률은 $g(\cdot)$이 소프트맥스 함수이기 때문에 식 (6.17)로 표현할 수 있다
$\mathbb{h}_j$ $(j=1, 2, \ldots, J)$가 $k$ 클래스에 속한다면 $\mathbb{y}_j$의 $k$번째 성분인 $\mathbb{y}_{j_k}$는 $1$이지만 그 외의 $\ell\neq k$번째 성분인 $\mathbb{y}_{j_\ell}$은 $0$이기 때문에 식 (6.18)과 같이 1-of-$K$ 표현으로 나타낼 수 있다
'은닉층 - 출력층' 부분에서 다중 클래스 분류 모델의 가설식이 식 (6.9)이고, 이 때 다중 클래스 분류 문제이기 때문에 $g(\cdot)$이 소프트맥스 함수이기 때문에 $\boldsymbol{V}$와 $\mathbb{c}$의 가장 좋은 값을 찾기 위한 가능도 함수는 식 (6.19)로 표현할 수 있다
각각의 $\mathbb{h}_j=f(\boldsymbol{W}\mathbb{x}_n+\mathbb{b})$에 대하여 신경망 모델의 출력값이 $\mathbb{y}_{j_k}$이 될 확률 $\textrm{Pr}(C=k|\mathbb{h}_j)$이 높아야 좋은 모델이다
식 (6.26)에서 $\sum_{k=1}^K \mathbb{v}_{k_j}\delta_k$ 부분만 따로 떼어내 살펴보면 지금까지 살펴봤던 뉴런의 출력을 나타내는 식과 모양이 같은 것을 알 수 있다. 이는 다시 말하면 신경망 모델에서 네트워크의 입력층에서 출력층으로 향하는 순방향전파(forward propagation) 출력 모델식을 고려해 해석학적으로 살펴봤지만 오차함수 최적화를 위해 매개변수를 업데이트하기 위한 경사하강법을 해석할 때에는 그림 6.4처럼 $\delta_k$가 네트워크 상에서 출력층에서 입력층으로 전파되는 모델(error backpropagation)로 생각할 수 있다
$\mathbb{w}_k=\left(\begin{array}{c} w_{k1} & w_{k2} & \cdots & w_{kM}\end{array}\right)$라고 하면 전체 웨이트 $\boldsymbol{W}$는 식 (5.10)과 되며 바이어스 $b$는 식 (5.11)과 같이 표현할 수 있다
실제의 값과 모델의 가설식의 오차를 계산하면서 매개변수 $\boldsymbol{W}$와 $\mathbb{b}$를 업데이트하기 위한 가능도 함수를 구하기 위해 입력 데이터 $\mathbb{x}_n$ $(n=1, 2, \ldots, N)$과 그에 대응하는 정답 데이터 $\mathbb{y}_n$이 있다고 하자
$\mathbb{x}_n$이 클래스 $k$에 속한다면 식 (5.15)와 같이 $\mathbb{y}_n$의 $k$번째 성분 $\mathbb{y}_{n_k}$는 $1$이지만 그 외의 $j$번째 성분인 $\mathbb{y}_{n_j}$는$0$이 될 것이다
전체 웨이트 행렬 $\boldsymbol{W}$와 바이어스 벡터 $\mathbb{b}$에 대한 편미분 식은 각각의 $j$번째 성분에 대한 편미분이 식 (5.19)와 식 (5.20)이기 때문에 각각의 성분을 모아 벡터로 묶으면 식 (5.21)과 식 (5.22)로 표현할 수 있다
각 클래스의 데이터는 평균 $\mu \neq 0$인 정규분포를 따르는 샘플 데이터를 생성해 사용
전체 데이터는 300개로 3개의 클래스는 각각 100개의 데이터로 구성되어 있어, 100개를 제대로 분류해야 한다
미니배치 확률 경사하강법(SGD)을 사용하려는데, SGD를 사용하기 위해서는 데이터를 무작위로 섞는 작업이 필요하다
sklearn 라이브러리의 sklearn.utils.shuffle 함수를 사용
import numpy as np
import matplotlib.pyplot as plt
import tensorflow as tf
M = 2 # 입력 데이터의 차원
K = 3 # 출력 클래스의 수
n = 100 # 각 클래스에 속한 데이터 수
N = n * K # 전체 데이터 수
# 샘플 데이터 생성
X1 = np.random.randn(n, M) + np.array([0, 10]) # 평균이 [0, 10]인 클래스
X2 = np.random.randn(n, M) + np.array([5, 5]) # 평균이 [5, 5]인 클래스
X3 = np.random.randn(n, M) + np.array([10, 0]) # 평균이 [10, 0]인 클래스
Y1 = np.array([[1, 0, 0] for _ in range(100)])
Y2 = np.array([[0, 1, 0] for _ in range(100)])
Y3 = np.array([[0, 0, 1] for _ in range(100)])
X = np.concatenate((X1, X2, X3), axis=0)
Y = np.concatenate((Y1, Y2, Y3), axis=0)
plt.scatter(X1[:, 0], X1[:, 1], marker='x', label='Class 1')
plt.scatter(X2[:, 0], X2[:, 1], marker='o', label='Class 2')
plt.scatter(X3[:, 0], X3[:, 1], marker='^', label='Class 3')
plt.grid(linestyle=':')
plt.legend()
plt.show()
W = tf.Variable(tf.zeros([M, K]))
b = tf.Variable(tf.zeros([K]))
x = tf.placeholder(tf.float32, shape=[None, M])
y = tf.placeholder(tf.float32, shape=[None, K])
hypothesis = tf.nn.softmax(tf.matmul(x, W) + b)
# 오차 함수는 미니배치로 계산을 하기때문에 각 미니배치의 평균값으로 오차를 계산
cost = tf.reduce_mean(-tf.reduce_sum(y * tf.log(hypothesis), reduction_indices=[1]))
train = tf.train.GradientDescentOptimizer(learning_rate=0.1).minimize(cost)
# 제대로 분류되는지 확인
correct_prediction = tf.equal(tf.argmax(hypothesis, 1), tf.argmax(y, 1))
batch_size = 50
n_batches = N // batch_size
# SGD에서는 각 에포크마다 데이터를 섞는다
with tf.Session() as sess:
sess.run(tf.global_variables_initializer())
for epoch in range(20):
X_, Y_ = shuffle(X, Y)
for i in range(n_batches):
start = i * batch_size
end = start + batch_size
sess.run(train, feed_dict={x: X_[start:end], y: Y_[start:end]})
X_, Y_ = shuffle(X, Y)
classified = correct_prediction.eval(session=sess, feed_dict={x: X_[0:10], y: Y_[0:10]})
prob = hypothesis.eval(session=sess, feed_dict={x: X_[0:10]})
print(f'분류 결과: {classified}')
print()
print(f'출력 결과 확률:\n{prob}')
입력 데이터가 2차원이기 때문에 분류의 기준이 되는 직선을 그릴 수 있는데, 소프트맥스 함수의 값이 같아지는 곳이 경계가 된다
클래스 1과 클래스 2를 분류하는 직선 : $w_{11}x_1 + w_{12}x_2 + b_1 = w_{21}x_1 + w_{22}x_2 + b_2$
클래스 2와 클래스 3을 분류하는 직선 : $w_{21}x_1 + w_{22}x_2 + b_2 = w_{31}x_1 + w_{32}x_2 + b_3$
Keras로 구현
from sklearn.utils import shuffle
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation
from keras.optimizers import SGD
M = 2 # 입력 데이터의 차원
K = 3 # 출력 클래스의 수
n = 100 # 각 클래스에 속한 데이터 수
N = n * K # 전체 데이터 수
# 샘플 데이터 생성
X1 = np.random.randn(n, M) + np.array([0, 10]) # 평균이 [0, 10]인 클래스
X2 = np.random.randn(n, M) + np.array([5, 5]) # 평균이 [5, 5]인 클래스
X3 = np.random.randn(n, M) + np.array([10, 0]) # 평균이 [10, 0]인 클래스
Y1 = np.array([[1, 0, 0] for _ in range(100)])
Y2 = np.array([[0, 1, 0] for _ in range(100)])
Y3 = np.array([[0, 0, 1] for _ in range(100)])
X = np.concatenate((X1, X2, X3), axis=0)
Y = np.concatenate((Y1, Y2, Y3), axis=0)
model = Sequential()
model.add(Dense(input_dim=M, units=K))
model.add(Activation('softmax'))
# 이진 분류는 loss='binary_crossentropy'
# one-hot encoding은 loss=categorical_crossentropy'
model.compile(loss='categorical_crossentropy', optimizer=SGD(lr=0.1))
minibatch_size = 50
model.fit(X, Y, epochs=20, batch_size=minibatch_size)
X_, Y_ = shuffle(X, Y)
classes = model.predict_classes(X_[0:10], batch_size=minibatch_size)
prob = model.predict_proba(X_[0:10], batch_size=minibatch_size)
print()
print('예측 결과:')
print(np.argmax(model.predict(X_[0:10]), axis=1) == classes)
print()
print(f'출력 확률:\n{prob}')
확률 밀도 함수(PDF; Probability Density Function) : 확률 변수의 분포를 나타내는 함수로 확률 변수 $X$가 $a$ 이상 $b$ 이하가 될 확률을 식 (4.2)와 같이 표현할 때 $f(x)$를 확률밀도함수라고 한다
\begin{eqnarray}\textrm{Pr}(a\leqslant X \leqslant b) = \int_a^b f(x) dx\tag{4.2}\end{eqnarray}
$f(x)$는 확률이기 때문에 다음 두 조건 식 (4.3)과 식 (4.4)를 만족해야 한다
모든 실수 $x$에 대하여\begin{eqnarray}f(x) \geqslant 0\tag{4.3}\end{eqnarray}
실수 범위를 가지는 확률 변수 $X$에 대하여\begin{eqnarray}\textrm{Pr}(-\infty \leqslant X \leqslant \infty) = \int_{-\infty}^{\infty}f(x)dx=1\tag{4.4}\end{eqnarray}
누적 분포 함수(CDF; Cumulative Distribution Function) : 어떤 확률 분포에서 확률 변수가 특정값보다 작거나 같은 확률로 실수 범위를 가지는 확률 변수 $X$의 누적 분포 함수를 $F(x)=P(X\leqslant x)$이라고 할 때, 식 (4.5)와 같이 표현된다
학습을 위해서는 실제의 값과 뉴런 모델의 출력값의 차가 최소가 되도록 웨이트와 바이어스를 조절해야하는데, 식 (4.7)를 사용하는 경우에는 이를 계산하는 것이 쉬운 일이 아니다
오차가 최소가 되도록 매개변수의 값을 업데이트하는 것이 쉬워야하기 때문에 다소 오차는 있지만 누적 분포 함수와 모양의 거의 흡사하면서도 계산은 매우 쉬운 시그모이드 함수를 사용하는 것이 편리하다. 아래 그림은 $\sigma=1$과 $\sigma=2$일 때의 누적 분포 함수와 시그모이드 함수의 그래프를 비교한 것이다
모든 일에서 이론도 매우 중요하지만 '현실적으로 계산이 가능한가?'라는 생각을 가지고 공학적으로 접근한 것도 중요하다
$N$개의 입력 데이터 $\mathbb{x}_n(n=1, 2,\ldots,N)$과 이 입력 데이터와 쌍을 이루는 출력 데이터 $\mathbb{y}_n$이 주어졌을 때 신경망의 매개변수인 $\mathbb{w}$와 바이어스 $b$의 가장 좋은 값을 찾기 위하여 가능도 또는 우도 함수(likelihood function)는 표준정규분포의 누적 분포 함수 대신에 시그모이드 함수를 사용한 가설식 (4.11)를 사용해 식 (4.12)과 같이 나타낼 수 있다
식 (4.12)의 가능도 함수 또는 우도 함수의 값이 최대가 되도록 매개변수를 업데이트하면 신경망 모델이 학습을 잘한 것이라고 볼 수 있다
함수의 최대나 최소가 되는 상태를 구하는 문제를 최적화 문제(optimization problem)라고 한다
함수의 최대화는 부호를 바꾸면 최소화가 되기 때문에 일반적으로 함수를 최적화한다고 하는 것은 함수를 최소로 만드는 매개변수를 구하는 것을 의미한다
함수 최적화와 미분 관계
함수 최적화 문제는 함수를 최대나 최소로 만드는 매개변수를 구하는 것이기 때문에 함수의 최대와 최소는 미분(differentiation)과 관계가 있기 때문에 우리는 식 (4.12)를 매개변수들로 편미분(partial differentiation)하는 문제를 생각해야 한다
식 (4.12)는 함수가 곱의 형태로 되어 있어 편미분을 계산하는 것이 쉽지 않기 때문에 편미분 계산이 쉽도록 식 (4.11)에 로그를 취해 덧셈 형태로 변환하고 최소가 되는 최적화 문제가 되도록 음수의 형태로 변환하면 식 (4.13)이 된다
식 (4.13)과 같은 형태의 함수를 교차 엔트로피 오차 함수(cross-entropy error fuction 또는 crosss-entropy cost function)이라고 하며, 이 함수를 최소화하는 것이 식 (4.12) 가능도 또는 우도 함수를 최대화하는 것이기 때문에 식 (4.12)는 최적의 상태에서 오차가 어느 정도 있는지를 알 수 있는 식이라고 할 수 있기 때문에 식 (4.13)를 오차 함수(cost function) 또는 손실 함수(loss function)이라고 한다
경사하강법
식 (4.13) 오차 함수에서 매개변수는 $\mathbb{w}$와 $b$이기 때문에 $\mathbb{w}$와 $b$로 편미분해서 $0$이 되는 값이 오차 함수가 최소가 되기 때문에 이 식을 직접 해석적으로 식을 풀어 해를 구하는 것은 쉬운 문제가 아니다
해석적으로 식을 푸는 방법보다는 단순 신경망 모델에서 처럼 오차를 계산하여 줄여나가는 방법으로 매개변수를 갱신하는 방법을 사용해야 하는데 매개변수를 업데이트하며 오차를 최소하는 방법 중 대표적인 것이 경사하강법(GDA; Gradient Descent Algorithm)이다
가중치 $\mathbb{w}$와 바이어스 $b$는 식 (3.9)와 식 (3.10)과 같은 방법으로 업데이트된다
식 (4.20)와 식 (4.21)의 경사하강법을 통해 로지스틱 회귀 문제를 해결할 수 있지만 실제 구현에 있어서는 한번 학습을 할 때마다 학습 데이터의 개수만큼 $N$번의 합을 구해야만 하는 문제가 발생하는데, $N$이 작으면 큰 문제가 없지만 $N$이 큰 경우에는 구현상에 문제가 발생
메모리에 적재할 때 용량의 문제로 메모리에 다 올려놓지 못하는 경우가 발생하는 문제
계산 시간이 증가하는 문제
이 문제를 해결하기 위한 대책으로 나오는 것이 확률적 경사하강법(SGD; Stochastic Gradient Descent Algorithm)
장점 : 경사하강법 1번의 계산량으로 확률적 경사하강법을 $N$번 계산할 수 있기 때문에 효율적으로 최적의 해를 찾을 수 있음
단점 : 경사가 $0$으로 수렴하는 일이 거의 없기 때문에 최소해를 찾기가 힘들 뿐만 아니라 $N$개의 데이터 전체에 대하여 반복해서 학습을 해야함
에포크(epoch) : $N$개의 데이터 전체에 대한 반복 학습 횟수
각 에포크마다 데이터를 무작위로 섞어서(shuffle) 학습을 하기 때문에 학습을 할 때 편향되지 않은 최적의 해를 찾기 쉬워짐
for epoch in range(epochs):
shuffle(data) # 에포크마다 데이터를 섞는다
for datum in data: # 데이터를 1개씩 사용해 매개변수를 업데이트한다
parameters_gradient = evaluate_gradient(cost_function, parameters, datum)
parameters -= learning_rate * parameters_gradient
미니배치 경사하강법(Minibatch Gradient Descent Algorithm) : 경사하강법과 확률적 경사하강법의 중간에 위치한 방법으로 $N$개의 데이터를 $M(\leqq N)$개씩으로 나눠서(미니배치) 학습하는 방법이며, $M$는 대략 $50 \leqq M \leqq 500$정도의 값을 사용하며, 이와 대비하여 일반 경사하강법을 배치 경사하강법이라고도 한다
미니배치 경사하강법을 사용하면 메모리의 부족함없이 선형대수를 사용하여 연산할 수 있기 때문에 1개씩 반복 계산하는 것보다 빠르게 연산이 가능
일반적으로 '확률적 경사하강법'이라고 하면 '미니배치 경사하강법'을 말하는데, $M=1$인 경우가 정확한 확률적 경사하강법에 해당함
for epoch in range(epochs):
shuffle(data) # 에포크마다 데이터를 섞는다
batches = get_batches(data, batch_size=M)
for batch in batches: # 각 미니배치마다 매개변수를 업데이트한다
parameters_gradient = evaluate_gradient(cost_function, parameters, datum)
parameters -= learning_rate * parameters_gradient
OR 게이트 로지스틱 회귀를 TensorFlow로 구현
파이썬 코드
import numpy as np
import tensorflow as tf
X = np.array([[0, 0],
[0, 1],
[1, 0],
[1, 1]])
Y = np.array([[0],
[1],
[1],
[1]])
W = tf.Variable(tf.zeros([2, 1]))
b = tf.Variable(tf.zeros([1]))
x = tf.placeholder(tf.float32, shape=[None, 2])
y = tf.placeholder(tf.float32, shape=[None, 1])
hypothesis = tf.nn.sigmoid(tf.matmul(x, W) + b)
cost = -tf.reduce_sum(y * tf.log(hypothesis) + (1-y)*tf.log(1-hypothesis))
train = tf.train.GradientDescentOptimizer(learning_rate=0.1).minimize(cost)
correct_predict = tf.equal(tf.to_float(tf.greater(hypothesis, 0.5)), y)
with tf.Session() as sess:
init = tf.global_variables_initializer()
sess.run(init)
for epoch in range(200):
sess.run(train, feed_dict={x: X, y: Y})
classified = correct_predict.eval(session=sess, feed_dict={x: X, y: Y}) # OR 게이트 예측값
print(f'OR 게이트 예측 결과: \n{classified}')
prob = hypothesis.eval(session=sess, feed_dict={x: X, y: Y}) # OR 게이트의 출력 확률
print(f'OR 게이트 출력 확률: \n{prob})
실행 결과
OR 게이트 예측 결과 :
[[ True]
[ True]
[ True]
[ True]]
OR 게이트 출력 확률 :
[[0.22355038]
[0.9142595 ]
[0.9142595 ]
[0.99747425]]
OR 게이트 로지스틱 회귀를 Keras로 구현
파이썬 코드
import numpy as np
from keras.models import Sequential
from keras.layers import Dense, Activation
from keras.optimizers import SGD
# 가설식 모델 정의 : 입력(input_dim) 2개, 출력(units) 1개
# 발화를 결정하는 활성화 함수는 누적 분포 함수가 아닌 sigmoid를 사용
model = Sequential()
model.add(Dense(input_dim=2, units=1))
model.add(Activation('sigmoid'))
# 가설식의 오차 함수는 cross-entropy 함수를 사용
model.compile(loss='binary_crossentropy', optimizer=SGD(learning_rate=0.1))
X = np.array([[0, 0],
[0, 1],
[1, 0],
[1, 1]])
Y = np.array([[0],
[1],
[1],
[1]])
# 가설식 모델 학습시키기
model.fit(X, Y, epochs=200, batch_size=1)
# 학습 결과 확인하기
classes = model.predict_classes(X, batch_size=1)
prob = model.predict_proba(X, batch_size=1)
print(f'\nOR 게이트 예측 결과 :\n{Y==classes})')
print()
print(f'OR 게이트 출력값 확률:\n{prob}')
식 (3.4)와 식 (3.5)를 사용하여 식 (3.3)을 변형하면 식 (3.6)과 같이 쓸 수 있는데, 뉴런의 출력을 식 (3.6)과 같이 표현한 신경망 모델을 퍼셉트론(perceptron)이라고 하며 단순 신경망 모델을 단순 퍼셉트론(simple perceptron)이라고 한다. 식 (3.6)에서 벡터 $\mathbb{w}$를 웨이트 벡터(weight vector)라고 하며, $b$를 편향 또는 바이어스(bias)라고 한다
단순 퍼셉트론을 모델화할 때 매개변수 $(w_1, w_2, \theta)$의 값을 조절하는 오차정정학습법을 사용하였다면 퍼셉트론을 모델화할 때에는 웨이트 벡터 $\mathbb{w}$와 바이어스 $b$를 조절하면서 오차정정학습법을 사용해야 하기 때문에 오차정정을 위한 차는 단순 퍼셉트론의 경우처럼 식 (3.7)~식 (3.10)과 같이 정리할 수 있다
뉴런이 발화하지 않는 데이터의 평균값은 $0$이고, 뉴런이 발화하는 데이터의 평균값은 $5$이며 각각 10개의 데이터를 생성하는 코드는 다음과 같다
import numpy as np
import matplotlib.pyplot as plt
rng = np.random.RandomState(123)
d = 2 # 데이터의 차원
N = 10 # 데이터 수
mean = 5 # 뉴런이 발화하는 데이터의 평균값
x1 = rng.randn(N, d) + np.array([0, 0])
x2 = rng.randn(N, d) + np.array([mean, mean])
plt.scatter(x1[:, [0]], x1[:, [1]], color='r', marker='o', label='x1')
plt.scatter(x2[:, [0]], x2[:, [1]], color='b', marker='x', label='x2')
plt.axis(xmin=-1, xmax=7, ymin=-1, ymax=7)
plt.grid()
plt.legend()
plt.show()
X = np.concatenate((x1, x2), axis=0) # x1과 x2의 데이터를 합쳐서 입력 데이터로 만든다
퍼셉트론 모델에 필요한 매개변수 웨이트 벡터 $\mathbb{w}$와 바이어스 $b$를 초기화하고, 출력값은 $y=f(\mathbb{wx}+b)$를 코드로 정의한다
W = np.zeros(d)
b = 0
def H(X): # 퍼셉트론 모델의 예측 출력값
return step(np.dot(W, X) + b)
def step(X): # 활성화 함수
return 1 * (X > 0)
def y(i): # i번째 학습의 결과의 실제의 값
if i < N:
return 0 # x1에 속하는 N-1개까지는 발화하지 않기 때문에 0
else:
return 1 # x2에 속하는 N개부터는 발화하기 때문에 1
오차정정학습법은 모든 데이터를 정확하게 분류할 때까지 학습을 반복하기 때문에 반복문을 다음과 같이 유사코드로 작성할 수 있다
while True:
#
# 매개변수 업데이트 처리
#
if '모든 데이터를 정확하게 분류했다면':
break
매개변수 업데이트 부분에는 식 (3.7)~식 (3.10)을 구현해야할 뿐만 아니라 데이터 분류가 제대로 되었는지 판별할 수 있도록 구현한 코드는 다음과 같다
while True:
classified = True:
for i in range(N * 2):
delta_W = (y(i) - H(X[i])) * X[i] # 식 (3.7)
delta_b = y(i) - H(X[i]) # 식 (3.8)
W += delta_W # 식 (3.9)
b += delta_b # 식 (3.10)
classified *= all(delta_W == 0) * (delta_b == 0)
if classified:
break
print(f'w:\n{W}')
print(f'b:\n{b}')
12라인 코드는 20개의 데이터 중에서 하나라도 $\Delta\mathbb{w}\neq 0$이거나 $\Delta b\neq 0$이면 classified 변수는 $0$, 즉 False가 되기 때문에 다시 학습을 반복하게 된다
classified *= all(delta_W == 0) * (delta_b == 0)
최종적으로 업데이트된 매개변수의 값은 다음과 같기 떄문에 두 종류의 데이터를 구분하는 식은 $2.14037745x_1 + 1.27639271x_2 -9=0$이며 그림과 같이 구분되는 것을 알 수 있다
AND 게이트는 그림과 같이 입력이 2개, 출력이 1개이며, 2개의 입력이 모두 $1$일 때만 $1$을 출력하고 그 외에는 $0$을 출력
Input
Output
$x_1$
$x_2$
$y$
$0$
$0$
$0$
$0$
$1$
$0$
$1$
$0$
$0$
$1$
$1$
$1$
AND 게이트로 만든 회로는 신경망 단순 모델과 동일한 형태(입력 2개, 출력 1개)이기 때문에 신경망으로 구현 가능
AND 게이트를 신경망으로 구현하기 위해서는 위의 표에 나온 $\mathbb{x}=(x_1,x_2)$과 $y$에 대한 각각의 값에 대하여 가설 모델(Hyperthesis model) 식 (2.1)를 만족하는 매개변수 $(w_1,w_2,\theta)$를 찾을 수 있어야 한다
먼저 매개변수 $(w_1,w_2,\theta)$에 임의의 값을 넣고 $(x_1,x_2)$의 모든 값에 대하여 예측 모델의 계산한 값 $H(\mathbb{x})$와 실제 값인 $y$의 차가 작아지도록 $(w_1,w_2,\theta)$ 값을 다시 조정해야한다
예를 들어, 매개변수를 $(w_1,w_2,\theta)=(1, 1, 0)$으로 하면 $(x_1,x_2)=(0,0)$일 때 식 (2.1)를 계산해보면 $H(\mathbb{x})=1\cdot 0 + 1\cdot 0 - 0 =0\geqslant 0$이 되어 $H(\mathbb{x})=1$이 되어 발화가 되나, 실제로는 $y=0\neq H(\mathbb{x})$이 되어 발화되어서는 안된다
이는 뉴런끼리의 결합이 강하거나 임계값이 낮아서 발생하는 일이기 때문에 가중치를 낮추거나 임계값을 높이는 방법으로 출력을 낮게 만들어야 함
오차정정학습법(Error Correction Learning)
위의 예처럼 임의의 매개변수 $(w_1,w_2,\theta)$에 대하여 식 (2.1)를 계산한 값 $H(\mathbb{x})$에 대하여 $H(\mathbb{x})\neq y$라면 매개변수$(w_1,w_2,\theta)$를 다시 수정하면서 $H(\mathbb{x})\cong y$가 되도록 하는 방법을 오차정정학습법이라고 하며 이와 같이 결과값을 보고 가중치를 수정하기 때문에 역전파 알고리즘(backward propagation algorithm)이라고 함
정답인 출력값을 $y$라 하고, 신경망 모델의 출력값을 $H(\mathbb{x})$라고 하면 오차정정학습법을 다음과 같이 정리할 수 있음
$y=H(\mathbb{x})$일 경우, 정확한 값이 출력되었기에 매개변수를 수정할 필요가 없다
$y=0$일 때 $H(\mathbb{x})=1$인 경우, 출력값이 크기 때문에 출력값을 작게하는 방향으로 매개변수를 조정한다
입력값이 양수이면 가중치를 작게, 임계값은 높게
입력값이 음수이면 가중치를 크게, 임계값은 높게
$y=1$일 때 $H(\mathbb{x})=0$인 경우, 출력값이 작기 때문에 출력값을 크게하는 방향으로 매개변수를 조정한다
입력값이 양수이면 가중치를 크게, 임계값은 낮게
입력값이 음수이면 가중치를 작게, 임계값은 낮게
이전 매개변수 $(w_1,w_2,\theta)$와 새로 업데이트된 매개변수 $(w'_1,w'_2,\theta')$와의 차이를 각각 $\Delta w_1$과 $\Delta w_2$, $\Delta \theta$라고 하면 각각의 차이는 식 (2.2), 식 (2.3), 식 (2.4)와 같이 정의 가능
오차정정학습법에 따라 $y=0$일 때 $H(\mathbb{x})=1$인 경우, 출력값이 크기 때문에 출력값을 작게하는 방향으로 매개변수를 조정해야 하는데, AND 게이트에서 입력값은 모두 0보다 크거나 같은 양수이기 때문에 가중치를 작게 만들고 임계값은 높게 만들어야 한다
오차정정학습법은 $k$번째 반복 학습에서 매개변수를 업데이트하고 다시 $k+1$번째 반복 학습을 하면서 매개변수를 다시 업데이트하면서 오차가 최대한으로 작아져 정답이 출력될 때까지 알고리즘을 반복하는 방식으로 진행
이 과정은 아래 표와 같이 각각의 매개변수들이 업데이트되면서 학습이 반복될 수록 오차가 줄어들면서 정확한 값을 출력하는 것을 알 수 있다
$k$
$x_1$
$x_2$
$y$
$w_1$
$w_2$
$\theta$
$H(\mathbb{x})$
$y-H(\mathbb{x})$
$\Delta w_1$
$\Delta w_2$
$\Delta \theta$
$1$
$0$
$0$
$0$
$0$
$0$
$0$
$1$
$-1$
$0$
$0$
$1$
$2$
$0$
$1$
$0$
$0$
$0$
$1$
$0$
$0$
$0$
$0$
$0$
$ 3$
$1$
$0$
$0$
$0$
$0$
$1$
$0$
$0$
$0$
$0$
$0$
$4$
$1$
$1$
$1$
$0$
$0$
$1$
$0$
$1$
$1$
$1$
$-1$
$5$
$0$
$0$
$0$
$0$
$1$
$0$
$1$
$-1$
$0$
$0$
$1$
$6$
$0$
$1$
$0$
$0$
$1$
$1$
$1$
$-1$
$0$
$-1$
$1$
$7$
$1$
$0$
$0$
$0$
$0$
$2$
$0$
$0$
$0$
$0$
$0$
$8$
$1$
$1$
$1$
$0$
$0$
$2$
$0$
$1$
$1$
$1$
$-1$
$9$
$0$
$0$
$0$
$0$
$1$
$1$
$0$
$0$
$0$
$0$
$0$
$10$
$0$
$1$
$0$
$0$
$1$
$1$
$1$
$-1$
$0$
$-1$
$1$
$11$
$1$
$0$
$0$
$0$
$0$
$2$
$1$
$-1$
$-1$
$0$
$1$
$12$
$1$
$1$
$1$
$0$
$0$
$3$
$0$
$1$
$1$
$1$
$-1$
$13$
$0$
$0$
$0$
$0$
$1$
$2$
$0$
$0$
$0$
$0$
$0$
$14$
$0$
$1$
$0$
$0$
$1$
$2$
$0$
$0$
$0$
$0$
$0$
$15$
$1$
$0$
$0$
$0$
$1$
$2$
$1$
$-1$
$-1$
$0$
$1$
$16$
$1$
$1$
$1$
$0$
$1$
$3$
$0$
$1$
$1$
$1$
$-1$
$17$
$0$
$0$
$0$
$2$
$2$
$2$
$0$
$0$
$0$
$0$
$0$
$18$
$0$
$1$
$0$
$2$
$2$
$2$
$1$
$-1$
$0$
$-1$
$1$
$19$
$1$
$0$
$0$
$2$
$1$
$3$
$0$
$0$
$0$
$0$
$0$
$20$
$1$
$1$
$1$
$2$
$1$
$3$
$1$
$0$
$0$
$0$
$0$
$21$
$0$
$0$
$0$
$2$
$1$
$3$
$0$
$0$
$0$
$0$
$0$
$22$
$0$
$1$
$0$
$2$
$1$
$3$
$0$
$0$
$0$
$0$
$0$
$23$
$1$
$0$
$0$
$2$
$1$
$3$
$0$
$0$
$0$
$0$
$0$
$24$
$1$
$1$
$1$
$2$
$1$
$3$
$1$
$0$
$0$
$0$
$0$
오차정정학습법은 '정확한 출력이 나올 때까지 매개변수를 업데이트를 반복하는 과정'을 말하며 '신경망을 학습시킨다'라고 부르며 학습의 결과로 매개변수 $(w_1, w_2, \theta) = (2, 1, 3)$이 구해졌기 때문에 뉴런이 발화하는 경계는 식 (2.8)에 의해 결정된다는 것을 알 수 있으며, 아래 그림과 같다
아래 그림을 보면 식 (2.8)의 직선 이 외에도 다양한 직선으로도 데이터를 분류할 수 있음을 알 수 있는데, 이는 오류정정학습법을 통해 얻은 모델이 데이터를 잘 분류할 수 있는 모델 중의 하나인 것을 알 수 있다