신경망 학습의 핵심 개념
- 가중치 매개변수의 최적값을 탐색하는 최적화 방법
- 가중치 매개변수 초깃값
- 하이퍼파라미터 설정 방
오버피팅(overfitting, 과대적합)의 대응책 : 가중치 감소, 드롭 아웃
배치 정규화
→ 딥러닝(신경망) 학습의 효율과 정확도를 높인다.
6.1 매개변수 갱신
신경망의 목적 : 손실 함수의 값을 가능한 한 낮추는 매개변수를 찾는 것
= 매개변수의 최적값 찾기
→ 최적화(optimization)
이전까지 사용한 최적의 매개변수 값 찾는 방법 : 확률적 경사 하강법(SGD)
매개변수의 기울기(미분) 이용
매개변수의 기울기를 구해, 기울어진 방향으로 매개변수 값 갱신을 반복하여 최적의 값에 접근
6.1.1 모험가 이야기
최적의 매개변수를 탐색하는 것은 지도를 보지않고 눈을 가린 채 골짜기의 가장 깊은 곳을 찾는 것과 같다.
이때 단서가 되는 것은 '땅의 기울기'
→ 현재 서 있는 곳에서 가장 크게 기울어진 방향으로 반복하여 조금씩 계속 가다보면 '깊은 곳'에 도달할 지도 모른다
이러한 방법이 SGD의 전략
6.1.2 확률적 경사 하강법(SGD)
SGD 수식
W : 갱신할 가중치 매개변수
∂L/∂W : W에 대한 손실 함수의 기울기
n : 학습률 (0.01이나 0.001과 같은 값을 미리 정함)
← : 우변의 값으로 좌변의 값 갱신
즉, SGD의 수식은 기울어진 방향으로 일정 거리만 가겠다는 (단순한) 방법
SGD 파이썬 클래스로 구현
class SGD:
def __init__(self, lr=0.01):
self.lr = lr
def update(self, params, grads):
for key in params.keys():
params[key] -= self.lr*grads[key]
|
초기화때 받는 인수 lr = learning rate(학습률) : 인스턴스 변수
update(params, grads) 메서드 : SGD 과정에서 반복해서 불림
인수인 params와 grads : 딕셔너리 변수
params['W1'], grads['W1']와 같이 각각 가중치 매개변수와 기울기 저장
- SGD 클래스를 사용한 신경망 매개변수 진행(의사코드)
network = TwoLayerNet(...)
optimizer = SGD()
for i in range(10000):
...
x_batch, t_batch = get_mini_batch(...) # 미니배치
grads = network.gradient(x_batch, t_batch)
params = network.params
optimizer.update(params, grads)
...
|
optimizer : 최적화를 행하는 자
위의 코드에서는 SGD가 최적화를 행하는 역할
- 매개변수 갱신은 optimizer가 수행
- 우리는 optimizer에 매개변수와 기울기 정보만 넘겨주면 됨
최적화를 담당하는 클래스를 분리해 구현하면 기능을 모듈화하기 좋음
6.1.3 SGD의 단점
SGD는 단순하고 구현도 쉽지만 문제에 따라 비효율적일 때도 있음
SGD의 단점을 알아보고자 아래 함수의 최솟값을 구하는 문제 비교
이 함수의 특징 : y축 방향은 크고, x축 방향은 작음 (y축 방향은 가파른데, x축 방향은 완만)
최솟값이 되는 장소는 (x,y) = (0,0) 이지만, 기울기의 대부분이 (0,0)을 가리키지 않음
초기값(탐색을 시작하는 장소) : (x,y) = (-7.0, 2.0)으로 하고 함수 SGD 적용하면
SGD는 심하게 굽이진 모습을 보임 → 상당히 비효율적
즉, SGD의 단점
: 비등방성(anisotropy) 함수 (방향에 따라 성질(기울기)이 달라지는 함수) 이기 때문에
탐색 경로가 비효율적
(오차율이 큼)
SGD가 지그재그로 탐색하는 근본 원인은 기울어진 방향이 본래의 최솟값과 다른 방향을 가르키기 때문
이러한 SGD의 단점을 보안하여 모멘텀, AdaGrad, Adam 세 가지 방법으로 SGD 대체 가능
6.1.4 모멘텀
모멘텀(Momentum) = 운동량
모멘텀 수식
: 기울기 방향으로 힘을 받아 물체가 가속된다는 물리 법칙을 나타냄
W : 갱신할 가중치 매개변수
∂L/∂W : W에 대한 손실 함수의 기울기
n : 학습률 (0.01이나 0.001과 같은 값을 미리 정함)
v : 물체의 속도(vectority)
모멘텀은 바닥을 구르는 듯한 움직임을 보임
αv : 물체가 아무런 힘을 받지 않을 때 서서히 하강시키는 역할 (α는 0.9 등의 값으로 설정)
모멘텀 구현 코드
class Momentum:
def __init__(self, lr=0.01, momentum=0.9):
self.lr = lr
self.momentum = momentum
self.v = None
def update(self, params, grads):
if self.v is None:
self.v = {}
for key, val in params.items():
self.v[key] = np.zeros_like(val)
for key in params.keys():
self.v[key] = self.momentum*self.v[key] - self.lr*grads[key]
params[key] += self.v[key]
|
인스턴스 변수 v : 물체의 속도
v는 초기화 때는 아무 값도 담지 않고, 대신 update()가 처음 호출될 때 매개변수와 같은 구조의 데이터를 딕셔너리 변수로 저장
SGD와 비교하여 지그재그의 정도가 덜함
x축의 힘은 아주 작지만 방향은 변하지 않아서 한 방향으로 일정하게 가속
y축의 힘은 크지만 위아래로 번갈아 받아서 상충 : y축 방향의 속도는 안정적이지x
(매개변수를 찾는 방법)
6.1.5 AdaGrad
신경망에서는 학습률 값(n)이 중요
n이 너무 작으면 학습하는 시간이 길어지고 너무 크면 발산
→ 학습률 감소(learning rate decay) : 학습률을 정하는 효과적 기술
학습률을 낮추는 방법
: 매개변수 전체의 학습률 값을 일괄적으로 낮추는 것
→ 이를 발전시켜 AdaGrad에서는 각각의 매개변수에 적응적(adaptive)으로 학습률 조정
AdaGrad 수식
h : 기존 기울기 값을 제곱하여 계속 더함
매개변수를 갱신할 때는 h^(1/2)를 곱하여 학습률 조정
즉, 매개변수의 원소 중 많이 움직인 원소는 학습률이 낮아짐
AdaGrad 구현 코드
class AdaGrad:
"""AdaGrad"""
def __init__(self, lr=0.01):
self.lr = lr
self.h = None
def update(self, params, grads):
if self.h is None:
self.h = {}
for key, val in params.items():
self.h[key] = np.zeros_like(val)
for key in params.keys():
self.h[key] += grads[key] * grads[key]
params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)
|
1e-7을 더해줌으로써 0으로 나누는 사태를 막아줌
최솟값을 향해 효율적으로 움직임
y축 방향은 기울기가 커서 처음에는 크게 움직이지만, 큰 움직임에 비례해 갱신 정도도 큰 폭으로 작아지도록 조정
따라서 y축 방향으로 갱신 강도가 빠르게 약해지고, 지그재그 움직임이 줄어듦
6.1.6 Adam
모멘텀 + AdaGrad : 공이 구르듯 각 매개변수 별 학습률 조정
6.1.7 어느 갱신 방법을 이용할 것인가?
상황에 맞는 갱신 방법을 사용해야 한다.
6.1.8 MNIST 데이터셋으로 본 갱신 방법 비교
손글씨 숫자 인식을 대상으로 네 기법 비교
SGD(느림)<모멘텀=Adam<=AdaGrad(빠름)
- 각 층이 100개의 뉴런으로 구성된 5층 신경망에서 ReLU를 활성화 함수로 사용하여 측정
- 하이퍼파라미터인 학습률과 신경망의 구조(층 깊이 등)에 따라 결과가 달라짐
6.2 가중치의 초기값
가중치 초깃값을 무엇으로 설정하느냐에 따라 신경망의 성패를 가를정도로 매우 중요
6.2.1 초깃값을 0으로 하면?
가중치 감소(weight decay)기법 : 오버피팅을 억제해 범용 성능을 높이는 기법
가중치 매개변수의 값이 작아지도록 학습하는 방법
가중치 값을 작게 하여 오버피팅이 일어나지 않도록 함
여태껏 초깃값은 정규분포에서 생성되는 값을 0.01배 한 작은 값 사용
가중치의 초깃값을 다 0으로 한다면? (정확히는 가중치를 균일한 값으로 설정 하면 xxx)
>> 올바른 학습이 이루어 지지 않음
오차역전파법에서 모든 가중치의 값이 똑같이 갱신
n번째 층 뉴련과 n+1번째 층 뉴런에 모두 같은 값 전달
= 역전파 때 n+1번째 층의 가중치가 모두 똑같이 갱신
따라서 가중치들은 같은 초깃값에서 시작하고 갱신을 거쳐도 여전히 같은 값 유지
이는 가중치를 여러 개 갖는 의미를 사라지게 함
이러한 '가중치가 고르게 되어버리는 상황'을 막으려면 (정확히는 가중치의 대칭적인 구조를 무너뜨리려면) 초깃값을 무작위로 설정해야 함
6.2.2 은닉층의 활성화값 분포
은닉층의 활성화 값(활성화 함수의 출력 데이터)의 분포를 관찰하면 중요한 정보를 얻을 수 있음
>> 가중치의 초깃값에 따라 은닉층 활성화값들이 어떻게 변화하는지 간단한 실험
활성화 함수로 시그모이드 함수를 사용하는 5층 신경망에 무작위로 생성한 입력 데이터를 흘리며 각 층의 활성화값 분포를 히스토그램으로 그림
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
return 1 / (1 + np.exp(-x))
input_data = np.random.randn(1000, 100) # 1000개의 데이터
node_num = 100 # 각 은닉층의 노드(뉴런) 수
hidden_layer_size = 5 # 은닉층이 5개
activations = {} # 이곳에 활성화 결과를 저장
x = input_data
for i in range(hidden_layer_size):
if i != 0:
x = activations[i-1]
# 초깃값을 다양하게 바꿔가며 실험
w = np.random.randn(node_num, node_num) * 1
# w = np.random.randn(node_num, node_num) * 0.01
# w = np.random.randn(node_num, node_num) * np.sqrt(1.0 / node_num)
# w = np.random.randn(node_num, node_num) * np.sqrt(2.0 / node_num)
a = np.dot(x, w)
z = sigmoid(a)
activations[i] = z
# 히스토그램 그리기
for i, a in activations.items():
plt.subplot(1, len(activations), i+1)
plt.title(str(i+1) + "-layer")
if i != 0: plt.yticks([], [])
# plt.xlim(0.1, 1)
# plt.ylim(0, 7000)
plt.hist(a.flatten(), 30, range=(0,1))
plt.show()
|
입력데이터 1000개의 데이터를 정규분포로 무작위로 생성하여 5층 신경망에 흘림
활성화 함수로는 시그모이드 함수 이용

- 가중치 값 1 → 0.01로 변경
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
return 1 / (1 + np.exp(-x))
input_data = np.random.randn(1000, 100) # 1000개의 데이터
node_num = 100 # 각 은닉층의 노드(뉴런) 수
hidden_layer_size = 5 # 은닉층이 5개
activations = {} # 이곳에 활성화 결과를 저장
x = input_data
for i in range(hidden_layer_size):
if i != 0:
x = activations[i-1]
# 초깃값을 다양하게 바꿔가며 실험
# w = np.random.randn(node_num, node_num) * 1
w = np.random.randn(node_num, node_num) * 0.01
# w = np.random.randn(node_num, node_num) * np.sqrt(1.0 / node_num)
# w = np.random.randn(node_num, node_num) * np.sqrt(2.0 / node_num)
a = np.dot(x, w)
z = sigmoid(a)
activations[i] = z
# 히스토그램 그리기
for i, a in activations.items():
plt.subplot(1, len(activations), i+1)
plt.title(str(i+1) + "-layer")
if i != 0: plt.yticks([], [])
# plt.xlim(0.1, 1)
# plt.ylim(0, 7000)
plt.hist(a.flatten(), 30, range=(0,1))
plt.show()
|
표준편차를 0.01로 한 정규분포의 각 층의 활성화값 분포

각 층의 활성화 값이 적당히 분포되어 있어야 함
층과 층사이에 적당한 데이터가 흘러야 신경망 학습이 효율적으로 이루어지기 때문
>>> Xavier 초깃값 사용
각 층의 활성화값들을 광범위하게 분포시킬 목적으로 가중치의 적절한 분포를 찾고자 함
→ 앞 계층의 노드가 n개이면 표준편차가 1/(n)^(1/2)인 분포를 사용하면 됨
Xavier 초깃값 : 앞 층의 노드가 n개일 때, 초깃값의 표준편차가 1/(n)^(1/2)이 되도록 설정
Xavier 초깃값을 사용하면 앞 층의 노드가 많을수록 대상 노드의 초깃값으로 설정하는 가중치가 좁게 퍼짐
가중치 초깃값 설정을 Xavier 초깃값을 이용하도록 바꿔준 구현 코드
(모든 층의 노드 수가 100개라고 단순화)
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
return 1 / (1 + np.exp(-x))
def ReLU(x):
return np.maximum(0, x)
def tanh(x):
return np.tanh(x)
input_data = np.random.randn(1000, 100) # 1000개의 데이터
node_num = 100 # 앞 층의 노드 수
hidden_layer_size = 5 # 은닉층이 5개
activations = {} # 이곳에 활성화 결과를 저장
x = input_data
for i in range(hidden_layer_size):
if i != 0:
x = activations[i-1]
w = np.random.randn(node_num, node_num) * np.sqrt(1.0 / node_num)
# w = np.random.randn(node_num, node_num) * np.sqrt(2.0 / node_num)
a = np.dot(x, w)
z = sigmoid(a)
activations[i] = z
# 히스토그램 그리기
for i, a in activations.items():
plt.subplot(1, len(activations), i+1)
plt.title(str(i+1) + "-layer")
if i != 0: plt.yticks([], [])
# plt.xlim(0.1, 1)
# plt.ylim(0, 7000)
plt.hist(a.flatten(), 30, range=(0,1))
plt.show()
|
활성화 값들이 넓게 분포되었음을 확인
각 층에 흐르는 데이터는 넓게 퍼져있으므로, 시그모이드 함수의 표현력도 제한받지 않고 학습이 효율적으로 이뤄질 것으로 기대
(오른쪽으로 갈수록 약간씩 일그러짐
이는 시그모이드 함수 대신 tahn 함수(쌍곡선 함수)를 적용하면 개선
sigmoid와 tahn은 'S'자 모양의 곡선 함수이지만
tahn 함수는 원점 (0,0)에서 대칭인 'S'자 모양 곡선인 반면
sigmoid는 (x,y) = (0, 0.5)에서 대칭인 S 곡선)
6.2.3 ReLU를 사용할 때의 가중치 초깃값
Xavier 초깃값은 활성화 함수가 선형인 것을 전제로 이끈 결과
sigmoid 함수와 tanh 함수는 좌우 대칭이라 중앙 부근이 선형인 함수
따라서 Xavier 초깃값이 적당
반면 ReLU를 이용할 때는 ReLU에 특화된 초깃값을 이용하라고 권장
이 특화된 초깃값을 찾아낸 카이밍 히(Kaiming He)의 이름을 따 He 초깃값이라고 함
He 초깃값 : 앞 계층의 노드가 n개일 때 표준편차가 (2/n)^(1/2)인 정규분포를 사용
Xavier 초깃값의 2배 → ReLU 함수는 음의 영역이 0이라서 더 넓게 분포시키기 위해 2배의 계수가 필요하다고 해석
활성화 함수로 ReLU를 이용한 경우의 활성화 값 분포
1) 표준편자 0.01인 정규분포(std=0.01)
구현 코드(일부)
def ReLU(x):
return np.maximum(0, x)
input_data = np.random.randn(1000, 100) # 1000개의 데이터
node_num = 100 # 각 은닉층의 노드(뉴런) 수
hidden_layer_size = 5 # 은닉층이 5개
activations = {} # 이곳에 활성화 결과를 저장
x = input_data
for i in range(hidden_layer_size):
if i != 0:
x = activations[i-1]
# w = np.random.randn(node_num, node_num) * 1
w = np.random.randn(node_num, node_num) * 0.01
# w = np.random.randn(node_num, node_num) * np.sqrt(1.0 / node_num) # Xavier
# w = np.random.randn(node_num, node_num) * np.sqrt(2.0 / node_num) # He
a = np.dot(x, w)
z = ReLU(a)
activations[i] = z
|
std = 0.01일 때 각 층의 활성화값들은 아주 작은 값들
신경망에 작은 데이터가 흐른다는 것은 역전파 때 가중치의 기울기 역시 작아진다는 뜻
학습이 잘 이루어 지지 음
2) Xavier 초깃값
구현코드(일부)
def ReLU(x):
return np.maximum(0, x)
input_data = np.random.randn(1000, 100) # 1000개의 데이터
node_num = 100 # 각 은닉층의 노드(뉴런) 수
hidden_layer_size = 5 # 은닉층이 5개
activations = {} # 이곳에 활성화 결과를 저장
x = input_data
for i in range(hidden_layer_size):
if i != 0:
x = activations[i-1]
# w = np.random.randn(node_num, node_num) * 1
# w = np.random.randn(node_num, node_num) * 0.01
w = np.random.randn(node_num, node_num) * np.sqrt(1.0 / node_num) # Xavier
# w = np.random.randn(node_num, node_num) * np.sqrt(2.0 / node_num) # He
a = np.dot(x, w)
z = ReLU(a)
activations[i] = z
|
층이 깊어지면서 치우짐이 조금씩 커짐
실제로 층이 깊어지면 활성화값들의 치우침도 커지고, 학습할 때 '기울기 소실' 문제를 일으킴
3) ReLU 전용 He 초깃값일 때의 실험 결과
구현코드(일부)
def ReLU(x):
return np.maximum(0, x)
input_data = np.random.randn(1000, 100) # 1000개의 데이터
node_num = 100 # 각 은닉층의 노드(뉴런) 수
hidden_layer_size = 5 # 은닉층이 5개
activations = {} # 이곳에 활성화 결과를 저장
x = input_data
for i in range(hidden_layer_size):
if i != 0:
x = activations[i-1]
# w = np.random.randn(node_num, node_num) * 1
# w = np.random.randn(node_num, node_num) * 0.01
# w = np.random.randn(node_num, node_num) * np.sqrt(1.0 / node_num) # Xavier
w = np.random.randn(node_num, node_num) * np.sqrt(2.0 / node_num) # He
a = np.dot(x, w)
z = ReLU(a)
activations[i] = z
|
모든 층에서 균일하게 분포
층이 깊어져도 분포가 균일하게 유지되기에 역전파 때도 적절한 값이 나올 것으로 기대
6.2.4 MNIST 데이터셋으로 본 가중치 초깃값 비교
실제 데이터를 가지고 가중치의 초깃값을 주는 방법이 신경망 학습에 얼마나 영향을 주는지 확인
경우 1. std = 0.01
경우 2. Xavier 초깃값
경우 3. He 초깃값
코드
import os
import sys
sys.path.append(os.pardir)
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from common.util import smooth_curve
from common.multi_layer_net import MultiLayerNet
from common.optimizer import SGD
# 0. MNIST 데이터 읽기==========
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)
train_size = x_train.shape[0]
batch_size = 128
max_iterations = 2000
# 1. 실험용 설정==========
weight_init_types = {'std=0.01': 0.01, 'Xavier': 'sigmoid', 'He': 'relu'}
optimizer = SGD(lr=0.01)
networks = {}
train_loss = {}
for key, weight_type in weight_init_types.items():
networks[key] = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100],
output_size=10, weight_init_std=weight_type)
train_loss[key] = []
# 2. 훈련 시작==========
for i in range(max_iterations):
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
for key in weight_init_types.keys():
grads = networks[key].gradient(x_batch, t_batch)
optimizer.update(networks[key].params, grads)
loss = networks[key].loss(x_batch, t_batch)
train_loss[key].append(loss)
if i % 100 == 0:
print("===========" + "iteration:" + str(i) + "===========")
for key in weight_init_types.keys():
loss = networks[key].loss(x_batch, t_batch)
print(key + ":" + str(loss))
# 3. 그래프 그리기==========
markers = {'std=0.01': 'o', 'Xavier': 's', 'He': 'D'}
x = np.arange(max_iterations)
for key in weight_init_types.keys():
plt.plot(x, smooth_curve(train_loss[key]), marker=markers[key], markevery=100, label=key)
plt.xlabel("iterations")
plt.ylabel("loss")
plt.ylim(0, 2.5)
plt.legend()
plt.show()
|
층별 뉴련수가 100개인 5층 신경망에서 활성화 함수 ReLU를 사용
경우 1. std = 0.01 : 학습이 전혀 이뤄지지 않음 - 앞서 활성화값의 분포에서 본 것처럼 순전파 때 너무 작은 값(0 근처로 밀집한 데이터)이 흐르기 때문. 그로 인해 역전파 때의 기울기도 작아져 가중치가 거의 갱신되지 않음
경우 2. Xavier 초깃값 : 학습이 순조롭게 이뤄짐
경우 3. He 초깃값 : 학습이 순조롭게 이뤄지며 Xavier보다 더 빠르게 진행
가중치의 초깃값은 신경망 학습에 아주 중요한 포인트
가중치의 초깃값에 따라 신경망 학습의 성패가 갈리는 경우가 많음
6.3 배치 정규화
앞 절에서는 각 층의 활성화값 분포를 관찰해보며, 가중치의 초깃값을 적절히 설정하면
각 층의 활성화값 분포가 적당히 퍼지면서 학습이 원활하게 수행됨을 배움
그렇다면 각 층이 활성화를 적당히 퍼뜨리도록 '강제' → 배치 정규화(Batch Normalization)
6.3.1 배치 정규화 알고리즘
배치정규화가 주목받는 이유
- 학습을 빨리 진행할 수 있다(학습 속도 개선)
- 초깃값에 크게 의존하지 않는다(골치 아픈 초깃값 선택 장애 안녕)
- 오버피팅을 억제한다(드롭아웃 등의 필요성 감소)
배치정규화의 기본 아이디어는 각 층에서의 활성화값이 적당히 분포되도록 조정하는 것
데이터 분포를 정규화하는 '배치 정규화(Batch Norm)계층'을 신경망에 삽입
배치 정규화는 그 이름과 같이 학습 시 미니배치를 단위로 정규화
구체적으로는 데이터 분포가 평균이 0, 분산이 1이 되도록 정규화
수식
- 미니배치 B = {x1, x2, ..., xm}이라는 m개의 입력 데이터의 집합에 대해
평균 μB와 분산 σ^2B를 구함
- 입력 데이터를 평균이 0, 분산이 1이 되게 정규화
- ε : 작은 값으로, 0으로 나누는 사태 예방
위 식은 단순히 미니배치 입력 데이터 {x1, x2, ..., xm}을 평균 0, 분산 1인 데이터 {x'1, x'2, ..., x'm} (' = 햇)으로 변환하는 일을 함
이 처리를 활성화 함수 앞(또는 뒤)에 삽입함으로써 데이터 분포가 덜 치우치게 할 수 있음
또 배치 정규화 계층마다 이 정규화된 데이터에 고유한 확대(scale)와 이동(shift) 변환을 수행
수식
γ(감마) : 확대 담당
β : 이동 담당
두 값은 처음에 γ = 1, β = 0 부터 시작하고 학습하면서 적합한 값으로 조정해 나감
배치 정규화의 알고리즘
(이 알고리즘이 신경망에서 순전파 때 적용)
6.3.2 배치 정규화의 효과
배치 정규화 계층을 사용한 실험
MNIST 데이터셋을 사용하여 배치 정규화 계층을 사용할 때와 사용하지 않을 때의 학습 진도 확인
구현 코드
- 배치 정규화가 학습을 빨리 진전
초깃값 분포를 다양하게 줘가며 학습 진행 확인
가중치 초깃값의 표준편차를 다양하게 바꿔가며 학습 경과를 관찰한 그래프
구현코드
import sys, os
sys.path.append(os.pardir)
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from common.multi_layer_net_extend import MultiLayerNetExtend
from common.optimizer import SGD, Adam
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)
# 학습 데이터를 줄임
x_train = x_train[:1000]
t_train = t_train[:1000]
max_epochs = 20
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.01
def __train(weight_init_std):
bn_network = MultiLayerNetExtend(input_size=784, hidden_size_list=[100, 100, 100, 100, 100], output_size=10,
weight_init_std=weight_init_std, use_batchnorm=True)
network = MultiLayerNetExtend(input_size=784, hidden_size_list=[100, 100, 100, 100, 100], output_size=10,
weight_init_std=weight_init_std)
optimizer = SGD(lr=learning_rate)
train_acc_list = []
bn_train_acc_list = []
iter_per_epoch = max(train_size / batch_size, 1)
epoch_cnt = 0
for i in range(1000000000):
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
for _network in (bn_network, network):
grads = _network.gradient(x_batch, t_batch)
optimizer.update(_network.params, grads)
if i % iter_per_epoch == 0:
train_acc = network.accuracy(x_train, t_train)
bn_train_acc = bn_network.accuracy(x_train, t_train)
train_acc_list.append(train_acc)
bn_train_acc_list.append(bn_train_acc)
print("epoch:" + str(epoch_cnt) + " | " + str(train_acc) + " - " + str(bn_train_acc))
epoch_cnt += 1
if epoch_cnt >= max_epochs:
break
return train_acc_list, bn_train_acc_list
# 그래프 그리기==========
weight_scale_list = np.logspace(0, -4, num=16)
x = np.arange(max_epochs)
for i, w in enumerate(weight_scale_list):
print( "============== " + str(i+1) + "/16" + " ==============")
train_acc_list, bn_train_acc_list = __train(w)
plt.subplot(4,4,i+1)
plt.title("W:" + str(w))
if i == 15:
plt.plot(x, bn_train_acc_list, label='Batch Normalization', markevery=2)
plt.plot(x, train_acc_list, linestyle = "--", label='Normal(without BatchNorm)', markevery=2)
else:
plt.plot(x, bn_train_acc_list, markevery=2)
plt.plot(x, train_acc_list, linestyle="--", markevery=2)
plt.ylim(0, 1.0)
if i % 4:
plt.yticks([])
else:
plt.ylabel("accuracy")
if i < 12:
plt.xticks([])
else:
plt.xlabel("epochs")
plt.legend(loc='lower right')
plt.show()
|
거의 모든 경우에서 배치 정규화를 사용할 때의 학습 진도가 빠른 것으로 나타남
실제로 배치 정규화를 이용하지 않은 경우엔 초깃값이 잘 분포되어 있지 않으면 학습이 전혀 진행되지 않는 모습도 확인 가능
>> 배치 정규화를 사용하면 학습이 빨라지며, 가중치 초깃값에 크게 의존하지 않아도 됨
배치 정규화는 이처럼 장점이 많으니 다양한 분야에서 활약할 것
6.4 바른 학습을 위해
오버피팅 : 신경망이 훈련 데이터에만 지나치게 적응되어 그 외의 데이터에는 제대로 대응하지 못하는 상태
기계학습은 범용 성능을 지향
훈련 데이터에는 포함되지 않은, 아직 보지 못한 데이터가 주어져도 바르게 식별해내는 모델이 바람직
오버피팅 억제하는 기술 중요
6.4.1 오버피팅
오버피팅이 일어나는 경우
1. 매개변수가 많고 표현력이 높은 모델
2. 훈련 데이터가 적은 모델
60,000개의 MNIST 데이터셋의 훈련 데이터 중 300개만 사용하고
7층 네트워크를 사용하여 복잡성을 높임
각 층 뉴런은 100개
활성화 함수 ReLU 함수 사용
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)
# 오버피팅을 재현하기 위해 학습 데이터 수를 줄임
x_train = x_train[:300]
t_train = t_train[:300]
|
훈련을 수행하는 코드
에폭마다 모든 훈련 데이터와 모든 시험 데이터 각각에서 정확도 산출
network = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100, 100, 100], output_size=10)
optimizer = SGD(lr=0.01) # 학습률이 0.01인 SGD로 매개변수 갱신
max_epochs = 201
train_size = x_train.shape[0]
batch_size = 100
train_loss_list = []
train_acc_list = []
test_acc_list = []
iter_per_epoch = max(train_size / batch_size, 1)
epoch_cnt = 0
for i in range(1000000000):
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
grads = network.gradient(x_batch, t_batch)
optimizer.update(network.params, grads)
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("epoch:" + str(epoch_cnt) + ", train acc:" + str(train_acc) + ", test acc:" + str(test_acc))
epoch_cnt += 1
if epoch_cnt >= max_epochs:
break
|
train_acc_list와 test_acc_list에는 에폭 단위(모든 훈련 데이터를 한 번씩 본 단위)의 정확도 저장
훈련 데이터를 사용하여 측정한 정확도는 100 에폭을 지나는 무렵부터 거의 100%
그러나 시험 데이터에 대해서는 큰 차이를 보임
이처럼 정확도가 크게 벌어지는 것은 훈련 데이터에만 적응(fixing)해버린 결과
훈련 때 사용하지 않은 범용 데이터(시험 데이터)에는 제대로 대응하지 못하는 것을 이 그래프에서 확인 가능
6.4.2 가중치 감소
오버피팅 억제용 : 가중치 감소(weight decay)
학습 과정에서 큰 가중치에 대해서는 그에 상응하는 큰 패널티를 부과하여 오버피팅을 억제하는 방법
원래 오버피팅은 가중치 매개변수의 값이 커서 발생하는 경우가 많기 때문
가중치의 제곱 법칙(L2 법칙 / 많이 사용됨)을 손실함수에 더해 손실 함수 값이 더 커지게 함
가중치 감소 : 모든 가중치 각각의 손실 함수에 1/2λW^2을 더하는 것
따라서 가중치의 기울기를 구하는 계산에서는 그동안의 오차역전파법에 다른 결과에 정규화 항을 미분한 λW를 더하는 것
λ : 정규화의 세기를 조절하는 하이퍼 파라미터
λ를 크게 설정할 수록 가중치에 대한 패널티가 커짐
구현 코드
import os
import sys
sys.path.append(os.pardir)
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from common.multi_layer_net import MultiLayerNet
from common.optimizer import SGD
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)
# 오버피팅을 재현하기 위해 학습 데이터 수를 줄임
x_train = x_train[:300]
t_train = t_train[:300]
# weight decay(가중치 감쇠) 설정 =======================
#weight_decay_lambda = 0 # weight decay를 사용하지 않을 경우
weight_decay_lambda = 0.1
# ====================================================
network = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100, 100, 100], output_size=10,
weight_decay_lambda=weight_decay_lambda)
optimizer = SGD(lr=0.01) # 학습률이 0.01인 SGD로 매개변수 갱신
max_epochs = 201
train_size = x_train.shape[0]
batch_size = 100
train_loss_list = []
train_acc_list = []
test_acc_list = []
iter_per_epoch = max(train_size / batch_size, 1)
epoch_cnt = 0
for i in range(1000000000):
batch_mask = np.random.choice(train_size, batch_size)
x_batch = x_train[batch_mask]
t_batch = t_train[batch_mask]
grads = network.gradient(x_batch, t_batch)
optimizer.update(network.params, grads)
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("epoch:" + str(epoch_cnt) + ", train acc:" + str(train_acc) + ", test acc:" + str(test_acc))
epoch_cnt += 1
if epoch_cnt >= max_epochs:
break
# 그래프 그리기==========
markers = {'train': 'o', 'test': 's'}
x = np.arange(max_epochs)
plt.plot(x, train_acc_list, marker='o', label='train', markevery=10)
plt.plot(x, test_acc_list, marker='s', label='test', markevery=10)
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()
|
훈련데이터와 시험데이터에 대한 정확도 차이는 있지만 가중치 감소를 이용하지 않은 그래프와 비교하면 그 차이가 줄어듦
즉, 오버피팅이 억제됨
그리고 앞서와 달리 정확도가 100%(1.0)에 도달하지 못함
>> 오버피팅을 억제하는 방식으로 손실 함수에 가중치 L2 노름을 더한 가중치 감소 방법
6.5.3 드롭 아웃
앞 절의 가중치 감소는 간단하게 구현할 수 있고 어느 정도 지나친 학습을 억제할 수 있음
그러러나 신경망 모델이 복잡해지면 가중치 감소만으로 대응하기 어려움
>> 드롭아웃 사용(drop out)
드롭아웃(Dropout) : 뉴런의 연결을 임의로 삭제하는 것
훈련할 때 임의의 뉴런을 골라 삭제하여 신호를 전달하지 않게 함
테스트 할 때는 모든 뉴런사용
import os
import sys
sys.path.append(os.pardir) # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from common.multi_layer_net_extend import MultiLayerNetExtend
from common.trainer import Trainer
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)
# 오버피팅을 재현하기 위해 학습 데이터 수를 줄임
x_train = x_train[:300]
t_train = t_train[:300]
# 드롭아웃 사용 유무와 비울 설정 ========================
use_dropout = True # 드롭아웃을 쓰지 않을 때는 False
dropout_ratio = 0.2
# ====================================================
network = MultiLayerNetExtend(input_size=784, hidden_size_list=[100, 100, 100, 100, 100, 100],
output_size=10, use_dropout=use_dropout, dropout_ration=dropout_ratio)
trainer = Trainer(network, x_train, t_train, x_test, t_test,
epochs=301, mini_batch_size=100,
optimizer='sgd', optimizer_param={'lr': 0.01}, verbose=True)
trainer.train()
train_acc_list, test_acc_list = trainer.train_acc_list, trainer.test_acc_list
# 그래프 그리기==========
markers = {'train': 'o', 'test': 's'}
x = np.arange(len(train_acc_list))
plt.plot(x, train_acc_list, marker='o', label='train', markevery=10)
plt.plot(x, test_acc_list, marker='s', label='test', markevery=10)
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()
|
훈련 시에는 순전파 때마다 self.mask에 삭제할 뉴런을 False로 표시
self.mask는 x와 형상이 같은 배열을 무작위로 생성하고 그 값이 dropout_ratio보다 큰 원소만 True로 설정
역전파때의 동작은 ReLU와 같음
즉, 순전파때 신호를 통과시키는 뉴런은 역전파 때도 신호를 그대로 통과시키고, 순전파 때 통과시키지 않은 뉴런은 역전파 때도 신호 차단
6.5 적절한 하이퍼파라미터 값 찾기
하이퍼파라미터 : 각 층의 뉴런 수, 배치 크기, 매개변수 갱신시의 학습률, 가중치 감소 등
이러한 하이퍼파라미터의 값을 잘 설정해야 모델의 성능이 좋아짐
하이퍼파라미터의 값을 최대한 효율적으로 탐색하는 방법
6.5.1 검증 데이터
이전까지는 데이터셋을 훈련 데이터와 시험 데이터 두 가지로 분리
하이퍼파라미터를 다양한 값으로 설정하고 검증 할때에는 시험 데이터를 사용하면 안됨
왜냐, 시험 데이터를 사용하여 하이퍼파라미터를 조정하면 하이퍼파라미터 값이 시험 데이터에 오버피팅 되기 때문
(하이퍼파라미터 값이 시험데이터에 적합하도록 조정됨)
이렇게 훈련 시킨 모델은 범용 성능이 떨어지는 모델이 됨
따라라서 하이퍼파라미터를 조정할 때는 하이퍼파라미터 전용 확인 데이터 필요
검증 데이터(validation data) : 하이퍼파라미터 조정용 데이터
하이퍼 파라미터의 적절성 평가
**
훈련 데이터 : 매개변수(가중치와 편향)의 학습에 이용
검증 데이터 : 하이퍼파라미터의 성능 평가하는데 이용
시험 데이터 : 시험망의 범용 성능을 확인하기 위해 마지막에 (이상적으로는 한 번만) 이용
데이터 셋에 훈련 데이터와 시험 데이터만 있는 경우 훈련 데이터 중 20% 정도를 검증 데이터로 먼저 분리
import sys, os
sys.path.append(os.pardir) # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from common.multi_layer_net import MultiLayerNet
from common.util import shuffle_dataset
from common.trainer import Trainer
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)
# 결과를 빠르게 얻기 위해 훈련 데이터를 줄임
x_train = x_train[:500]
t_train = t_train[:500]
# 20%를 검증 데이터로 분할
validation_rate = 0.20
validation_num = int(x_train.shape[0] * validation_rate)
x_train, t_train = shuffle_dataset(x_train, t_train)
x_val = x_train[:validation_num]
t_val = t_train[:validation_num]
x_train = x_train[validation_num:]
t_train = t_train[validation_num:]
def __train(lr, weight_decay, epocs=50):
network = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100, 100, 100],
output_size=10, weight_decay_lambda=weight_decay)
trainer = Trainer(network, x_train, t_train, x_val, t_val,
epochs=epocs, mini_batch_size=100,
optimizer='sgd', optimizer_param={'lr': lr}, verbose=False)
trainer.train()
return trainer.test_acc_list, trainer.train_acc_list
# 하이퍼파라미터 무작위 탐색======================================
optimization_trial = 100
results_val = {}
results_train = {}
for _ in range(optimization_trial):
# 탐색한 하이퍼파라미터의 범위 지정===============
weight_decay = 10 ** np.random.uniform(-8, -4)
lr = 10 ** np.random.uniform(-6, -2)
# ================================================
val_acc_list, train_acc_list = __train(lr, weight_decay)
print("val acc:" + str(val_acc_list[-1]) + " | lr:" + str(lr) + ", weight decay:" + str(weight_decay))
key = "lr:" + str(lr) + ", weight decay:" + str(weight_decay)
results_val[key] = val_acc_list
results_train[key] = train_acc_list
# 그래프 그리기========================================================
print("=========== Hyper-Parameter Optimization Result ===========")
graph_draw_num = 20
col_num = 5
row_num = int(np.ceil(graph_draw_num / col_num))
i = 0
for key, val_acc_list in sorted(results_val.items(), key=lambda x:x[1][-1], reverse=True):
print("Best-" + str(i+1) + "(val acc:" + str(val_acc_list[-1]) + ") | " + key)
plt.subplot(row_num, col_num, i+1)
plt.title("Best-" + str(i+1))
plt.ylim(0.0, 1.0)
if i % 5: plt.yticks([])
plt.xticks([])
x = np.arange(len(val_acc_list))
plt.plot(x, val_acc_list)
plt.plot(x, results_train[key], "--")
i += 1
if i >= graph_draw_num:
break
plt.show()
|
훈련 데이터를 분리하기 전에 데이터가 치우쳐져 있을 수 있으니 입력 데이터와 정답 레이블을 섞음
6.5.2 하이퍼파마니터 최적화
하이퍼파라미터를 최적화할 때의 핵심은 하이퍼파라미터의 '최적 값'이 존재하는 범위를 조금 씩 줄여나가는 것
범위를 조금씩 줄이려면 우선 대략적인 범위를 설정하고 그 범위에서 무작위로 하이퍼 파라미터 값을 골라낸(샘플링) 후 그 값으로 정확도 평가
정확도를 잘 살피면서 작업을 여러 번 반복하며 하이퍼파라미터의 '최적 값'의 범위를 좁혀 나감
하이퍼파라미터의 범위는 대략적으로
0.001~1,000 사이와 같이 10의 거듭제곱 단위로 범위 지정 → 로그 스케일(log scale)로 지정
하이퍼파라미터의 최적화는 오래 걸림
ㄸㅏ라서 학습을 위한 에폭을 작게 하여, 1회 평가에 걸리는 시간을 단축
하이퍼파라미터 최적화
0단계. 하이퍼파라미터의 값의 범위 설정
1단계. 설정된 범위에서 하이퍼파라미터의 값을 무작위로 추출
2단계. 1단계에서 샘플링한 하이퍼파라미터 값을 사용하여 학습하고, 검증 데이터로 정확도를 평가(단, 에폭은 작게 설정)
3단계. 1단계와 2단계를 특정 횟수(100회 등) 반복하여, 그 정확도의 결과를 보고 하이퍼파라미터의 범위를 좁힘
위 단계를 반복하여 하이퍼파라미터의 범위를 좁혀가고, 어느 정도 좁아지면 그 압축한 범위에서 값을 하나 고름
이것이 하이퍼파라미터를 최적화하는 하나의 방법
6.5.3 하이퍼파라미터 최적화 구현하기
MNIST 데이터셋 사용하여 하이퍼파라미터 최적화
하ㄱ습률과 가중치 감소의 세기를 조절하는 계수(가중치 감소 계수)를 탐색하는 문제
하이퍼파라미터 검증 값 : 0.001~1,000 사이의 로그 스케일 범위에서 무작위로 추출하여 수행
>> 10**np.random.uniform(-3,3) 과 같이 파이썬으로 작성 가능
예제에서는 가중치 감소 계수를 10^(-8)~10^(-4), 학습률을 10^(-6)~10(-2) 범위부터 시작
하이퍼 파라미터의 무작위 추출 코드
weight_decay = 10 ** np.random.uniform(-8, -4)
lr = 10 ** np.random.uniform(-6, -2)
|
이렇게 무작위로 추출한 값을 사용하여 학습 수행
그 후 여러 차례 다양한 하이퍼파라미터 값으로 학습을 반복하며 신경망에 좋을 것 같은 값이 어디에 존재하는지 관찰
결과 구현 코드드
import sys, os
sys.path.append(os.pardir)
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from common.multi_layer_net import MultiLayerNet
from common.util import shuffle_dataset
from common.trainer import Trainer
(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)
# 결과를 빠르게 얻기 위해 훈련 데이터를 줄임
x_train = x_train[:500]
t_train = t_train[:500]
# 20%를 검증 데이터로 분할
validation_rate = 0.20
validation_num = int(x_train.shape[0] * validation_rate)
x_train, t_train = shuffle_dataset(x_train, t_train)
x_val = x_train[:validation_num]
t_val = t_train[:validation_num]
x_train = x_train[validation_num:]
t_train = t_train[validation_num:]
def __train(lr, weight_decay, epocs=50):
network = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100, 100, 100],
output_size=10, weight_decay_lambda=weight_decay)
trainer = Trainer(network, x_train, t_train, x_val, t_val,
epochs=epocs, mini_batch_size=100,
optimizer='sgd', optimizer_param={'lr': lr}, verbose=False)
trainer.train()
return trainer.test_acc_list, trainer.train_acc_list
# 하이퍼파라미터 무작위 탐색======================================
optimization_trial = 100
results_val = {}
results_train = {}
for _ in range(optimization_trial):
# 탐색한 하이퍼파라미터의 범위 지정===============
weight_decay = 10 ** np.random.uniform(-8, -4)
lr = 10 ** np.random.uniform(-6, -2)
# ================================================
val_acc_list, train_acc_list = __train(lr, weight_decay)
print("val acc:" + str(val_acc_list[-1]) + " | lr:" + str(lr) + ", weight decay:" + str(weight_decay))
key = "lr:" + str(lr) + ", weight decay:" + str(weight_decay)
results_val[key] = val_acc_list
results_train[key] = train_acc_list
# 그래프 그리기========================================================
print("=========== Hyper-Parameter Optimization Result ===========")
graph_draw_num = 20
col_num = 5
row_num = int(np.ceil(graph_draw_num / col_num))
i = 0
for key, val_acc_list in sorted(results_val.items(), key=lambda x:x[1][-1], reverse=True):
print("Best-" + str(i+1) + "(val acc:" + str(val_acc_list[-1]) + ") | " + key)
plt.subplot(row_num, col_num, i+1)
plt.title("Best-" + str(i+1))
plt.ylim(0.0, 1.0)
if i % 5: plt.yticks([])
plt.xticks([])
x = np.arange(len(val_acc_list))
plt.plot(x, val_acc_list)
plt.plot(x, results_train[key], "--")
i += 1
if i >= graph_draw_num:
break
plt.show()
|
결과
검증 데이터의 학습 추이를 정확도가 높은 순서로 나열
Best-5까지 학습이 순조롭게 진행
Best-5까지의 하이퍼파라미터값(학습률과 가중치 감소 계수) 확인
학습이 잘 진행될때의 학습률은 0.001~0.01, 가중치 감소 계수는 ~ 정도
이처럼 잘될것 같은 값의 범위를 관찰하고 범위를 좁혀 나감
그런 다음 그 축소된 범위로 똑같은 작업을 반복
이렇게 적절한 값이 위치한 범위를 좁혀나가다 특정 단계에서 최종 하이퍼파라미터 값을 하택
'AI' 카테고리의 다른 글
Deep Learning for Coders with fastai & PyTorch 1장 (0) | 2023.07.31 |
---|---|
[Deep Learning from Scratch] CHAPTER 7 합성곱 신경망(CNN) (0) | 2023.03.09 |
[Deep Learning from Scratch] CHAPTER 5 오차역전파법 (0) | 2023.02.21 |
[Deep Learning from Scratch] CHAPTER 3 신경망 (0) | 2023.02.09 |