728x90
반응형
- 데이터셋 다운로드
github.com/WegraLee/deep-learning-from-scratch
- 이미지 사용 출처
github.com/ExcelsiorCJH/DLFromScratch
6.1 매개변수 갱신
- 신경망 학습의 목적 : 손실함수 낮추는 매개변수 찾는것 = 이 문제를 푸는 것을 '최적화 라고 함
- 확률적 경사 하강법(SGD) : 매개변수의 기울기를 구하여 기울어진 방향으로 매개변수 값을 갱신하는 것
6.1.2 확률적 경사 하강법(SGD)
- SGD 수식
- W는 갱신할 가중치 매개변수, aL/aW은 W에 대한 손실함수의 기울기 , n은 학습률
- <--는 우변의 값으로 좌변의 값을 갱신한다는 뜻
# 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]
- update 메서드는 SGD 과정에서 반복해서 호출됨
- params와 grads는 딕셔너리 변수 (각각 가중치 매개변수와 기울기 저장)
6.1.3 SGD의 단점
- 문제에 따라서는 비효율적일 때가 있음
- 위 그림에서 y축 방향은 가파른데 x축 방향은 완만
- 최솟값이 되는 장소는 (0,0)이지만, 기울기 대부분은 그곳을 가리키지 않음
- 갱신 경로가 상당히 비효율적인 움직임
- SGD의 단점은 비등방성 함수
- 방향에 따라 기울기(성질)가 달라지는 함수에서는 탐색경로가 비효율적
6.1.4 모멘텀
- 모멘텀은 운동량을 뜻하는 단어로, 물리와 관계가 있음
class Momentum:
"""Momentum SGD"""
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]
- SGD와 비교하면 '지그재그 정도'가 덜함
- x축의 힘은 아주 작지만 방향은 변하지 않아서 한 방향으로 일정하게 가속
- y축의 힘은 크지만 위아래로 번갈아 받아 상충하여 y축 방향의 속도는 안정적이지 않음
6.1.5 AdaGrad
- 신경망 학습에는 학습률이 중요 : 학습률을 정하는 효과적 기술로 학습률 감소 (학습을 진행하면서 학습률 점차 줄여나감)
- 학습률을 서서히 낮추는 가장 간단한 방법은 매개변수 '전체'의 학습률 값을 일괄적으로 낮추는 것 --> AdaGrad
- AdaGrad는 각각의 매개변수에 맞춤형 값을 만들어줌
- W는 갱신할 가중치 매개변수, aL/aW는 W에 대한 손실함수에 기울기, n은 학습률
- h는 기존 기울기값을 제곱하여 계속 더해줌
- 매개변수 갱신할 때 1/route(h)를 곱해 학습률 조정
# 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)
- AdaGrad는 과거의 기울기를 제곱하여 계속 더해감
- 어느 순간 갱신량이 0이 되어 갱신 X
- 문제개선기법 : RMSProp, 먼 과거의 기울기 서서히 잊고 새로운 기울기 정보를 크게 반영
- 지수이동평균(Exponential Moving Average,EMA) 사용하는 optimizer
6.1.6 Adam
- AdaGrad 와 모멘텀 기법 융합
- 하이퍼파라미터의 '편향 보정'이 진행
# Adam 구현
class Adam:
"""Adam (http://arxiv.org/abs/1412.6980v8)"""
def __init__(self, lr=0.001, beta1=0.9, beta2=0.999):
self.lr = lr
self.beta1 = beta1
self.beta2 = beta2
self.iter = 0
self.m = None
self.v = None
def update(self, params, grads):
if self.m is None:
self.m, self.v = {}, {}
for key, val in params.items():
self.m[key] = np.zeros_like(val)
self.v[key] = np.zeros_like(val)
self.iter += 1
lr_t = self.lr * np.sqrt(1.0 - self.beta2**self.iter) / (1.0 - self.beta1**self.iter)
for key in params.keys():
#self.m[key] = self.beta1*self.m[key] + (1-self.beta1)*grads[key]
#self.v[key] = self.beta2*self.v[key] + (1-self.beta2)*(grads[key]**2)
self.m[key] += (1 - self.beta1) * (grads[key] - self.m[key])
self.v[key] += (1 - self.beta2) * (grads[key]**2 - self.v[key])
params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)
#unbias_m += (1 - self.beta1) * (grads[key] - self.m[key]) # correct bias
#unbisa_b += (1 - self.beta2) * (grads[key]*grads[key] - self.v[key]) # correct bias
#params[key] += self.lr * unbias_m / (np.sqrt(unbisa_b) + 1e-7)
- Adam은 하이퍼파라미터를 3개 설정 (학습률, 일차모멘텀용 계수, 이차모멘텀용 계수)
6.1.7 어느 갱신 방법을 이용할 것인가?
- 네 기법의 결과비교
# coding: utf-8
import sys, os
sys.path.append('/deep-learning-from-scratch') # 親ディレクトリのファイルをインポートするための設定
import numpy as np
import matplotlib.pyplot as plt
from collections import OrderedDict
from common.optimizer import *
def f(x, y):
return x**2 / 20.0 + y**2
def df(x, y):
return x / 10.0, 2.0*y
init_pos = (-7.0, 2.0)
params = {}
params['x'], params['y'] = init_pos[0], init_pos[1]
grads = {}
grads['x'], grads['y'] = 0, 0
optimizers = OrderedDict()
optimizers["SGD"] = SGD(lr=0.95)
optimizers["Momentum"] = Momentum(lr=0.1)
optimizers["AdaGrad"] = AdaGrad(lr=1.5)
optimizers["Adam"] = Adam(lr=0.3)
idx = 1
for key in optimizers:
optimizer = optimizers[key]
x_history = []
y_history = []
params['x'], params['y'] = init_pos[0], init_pos[1]
for i in range(30):
x_history.append(params['x'])
y_history.append(params['y'])
grads['x'], grads['y'] = df(params['x'], params['y'])
optimizer.update(params, grads)
x = np.arange(-10, 10, 0.01)
y = np.arange(-5, 5, 0.01)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
# for simple contour line
mask = Z > 7
Z[mask] = 0
# plot
plt.subplot(2, 2, idx)
idx += 1
plt.plot(x_history, y_history, 'o-', color="red")
plt.contour(X, Y, Z)
plt.ylim(-10, 10)
plt.xlim(-10, 10)
plt.plot(0, 0, '+')
#colorbar()
#spring()
plt.title(key)
plt.xlabel("x")
plt.ylabel("y")
plt.show()
- 문제가 무엇인지, 하이퍼파라미터가 어떻게 설정되어있는지에 따라 결과 다름
- 모든 문제에서 항상 뛰어난 기법은 아직까지 없음
6.1.8 MNIST 데이터셋으로 본 갱신 방법 비교
# 100층 뉴런으로 구성된 5층신경망 / activation_func : ReLU
# coding: utf-8
import os
import sys
sys.path.append('/deep-learning-from-scratch')
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 *
# 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:optimizer 딕셔너리 설정==========
optimizers = {}
optimizers['SGD'] = SGD()
optimizers['Momentum'] = Momentum()
optimizers['AdaGrad'] = AdaGrad()
optimizers['Adam'] = Adam()
#optimizers['RMSprop'] = RMSprop()
networks = {}
train_loss = {}
for key in optimizers.keys():
networks[key] = MultiLayerNet(
input_size=784, hidden_size_list=[100, 100, 100, 100],
output_size=10)
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 optimizers.keys():
grads = networks[key].gradient(x_batch, t_batch)
optimizers[key].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 optimizers.keys():
loss = networks[key].loss(x_batch, t_batch)
print(key + ":" + str(loss))
# 3. 그래프==========
markers = {"SGD": "o", "Momentum": "x", "AdaGrad": "s", "Adam": "D"}
x = np.arange(max_iterations)
for key in optimizers.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, 1)
plt.legend()
plt.show()
6.2 가중치의 초깃값
6.2.1 초깃값을 0으로 하면?
- 가중치 감소 기법 : 가중치 매개변수의 값이 작아지도록 하여 오버피팅이 일어나지 않도록 하는 기법
- 가중치를 작게 만들고 싶으면 초깃값도 작은 값에서 시작 --> 0으로 설정은 안됨
- 초깃값을 0으로 설정하게 되면 오차역전파에서 모든 가중치값이 독같이 갱신되기 때문
- 순전파 때 모든 뉴런에 같은 값이 입력되므로, 역전파 두번째 층의 가중치가 모두 똑같이 갱신된다는 뜻(학습이 안됨)
- (가중치를 균일한 값으로 설정X) 가중치 초깃값을 무작위로 설정해야함
6.2.2 은닉층의 활성화값 분포
- 은닉층의 활성화값(활성화 함수의 출력데이터)의 분포를 관찰하면 중요한 정보를 얻을 수 있음
- 가중치 초깃값에 따라 은닉층 활성화값들이 어떻게 변화하는지 실험
- 시그모이드 함수 사용 5층 신경망에 무작위 생성 입력데이터를 흘리며각 층의 활성화 값 분포를 히스토그램으로 그림
import numpy as np
import matplotlib.pyplot as plt
def sigmoid(x):
return 1 / (1 + np.exp(-x))
x = np.random.randn(1000, 100) # 1000개의 데이터
node_num = 100 # 각 은닉층 노드의 수
hidden_layer_size = 5 # 은닉층 5개
activations = {} # 활성화 값 저장할 공간
for i in range(hidden_layer_size):
if i != 0:
x = activations[i-1]
w = np.random.randn(node_num, node_num) * 1 # 가중치 분포의 표준편차 = 1
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')
plt.hist(a.flatten(), 30, range=(0,1))
plt.show()
- 각 층의 활성화 값들이 0과 1에 치우쳐 있음
- 시그모이드 함수는 출력이 0 (또는 1)에 가까워지자 그 미분은 0에 다가감
- 기울기 소실 : 데이터가 0과 1에 치우쳐 분포하게 되면 역전파의 기울기 값이 점점 작아지다가 사라짐
# 가중치 표준편차를 0.01로 바꿔 진행
def sigmoid(x):
return 1 / (1 + np.exp(-x))
x = np.random.randn(1000, 100) # 1000개의 데이터
node_num = 100 # 각 은닉층 노드의 수
hidden_layer_size = 5 # 은닉층 5개
activations = {} # 활성화 값 저장할 공간
for i in range(hidden_layer_size):
if i != 0:
x = activations[i-1]
w = np.random.randn(node_num, node_num) * 0.01 # 가중치 분포의 표준편차 = 0.01
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')
plt.hist(a.flatten(), 30, range=(0,1))
plt.show()
- 0.5 부근에서 값 집중
- 기울기 소실 문제는 없으나, 활성화 값이 치우친 것은 표현력 관점에서 큰 문제
- 노드 100개가 거의 같은 값을 출력한다면 노드가 많아도 노드 하나 모델과 다를게 없음
- Xavier 초깃값 : 일반적인 딥러닝 프레임워크에서 표준적으로 이용하는 초깃값
- 앞 계층의 노드가 n개라면 표준편차가 1/sqrt(n) 분포를 사용하면 된다는 것을 이용
- 앞 층의 노드가 많을수록 대상 노드이 초깃값으로 설장하는 가중치가 좁게 퍼짐
# 가중치 표준편차를 Xavier초깃값(1/sqrt(n))로 바꿔 진행
def sigmoid(x):
return 1 / (1 + np.exp(-x))
x = np.random.randn(1000, 100) # 1000개의 데이터
node_num = 100 # 각 은닉층 노드의 수
hidden_layer_size = 5 # 은닉층 5개
activations = {} # 활성화 값 저장할 공간
for i in range(hidden_layer_size):
if i != 0:
x = activations[i-1]
w = np.random.randn(node_num, node_num) / np.sqrt(node_num) # 가중치 분포의 표준편차 1/sqrt(100) (모든 층의 노드 수 100개로 단순화)
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')
plt.hist(a.flatten(), 30, range=(0,1))
plt.show()
- 층이 깊어지며 형태는 일그러지지만 앞 방식보다 넓게 분포됨
- 각 층의 데이터가 적당히 퍼져있으므로 함수의 표현력 제한 받지 않고 효율적 학습 진행
6.2.3 ReLU를 사용할 때의 가중치 초깃값
- ReLU 이용 시에는 ReLU에 특화된 초깃값 이용 권장
- He 초깃값 : 앞 층의 노드가 n개 일 때, 표준편차가 sqrt(2/n)인 정규분포 사용
- ReLU는 음의 영역이 0이기 때문에 더 넓게 분포시키기 위해 2배의 계수가 필요
- ReLU를 이용한 경우 활성화 값 분포 (0.01std, Xavier, He)
- std=0.01일 때의 각 층의 활성화 값들은 아주 작은 값
- 신경망에 아주 작은 데이터가 흐른다는 것은 가중치의 기울기 역시 작아져 학습이 제대로 이루어지지 않음
- Xavier는 층이 깊어지면서 치우침이 조금씩 커짐 = 기울기 소실문제 일으킴
- He초깃값은 모든 층에서 균일분포 = 역전파 때도 적절값 나올 가능성 높음
- ReLU 사용 시 He, sigmoid, tanh 등의 S자 모양 곡선에서는 Xavier초깃값 사용
6.2.4 MNIST 데이터셋으로 본 가중치 초깃값 비교
[코드는 책 참고]
- 층별 뉴런수 100개 / 5층 신경망 / 활성화 함수 = ReLU 사용
- std=0.01에서는 전혀 학습되지 않음 (순전파 때 너무 작은 값이 흐르기 때문)
- Xavier와 He는 학습 원할, 학습 진도는 He가 빠름
- 가중치 초깃값에 따라 학습의 성패가 갈리는 경우 많음 = 초깃값 중요!
6.3 배치 정규화
- 각 층이 활성화를 적당히 퍼뜨리도록 하는 방법
6.3.1 배치 정규화 알고리즘
- 배치 정규화가 주목받는 이유
- 학습 속도 개선
- 초깃값에 크게 의존하지 않음
- 오버피팅 억제 (드롭아웃 등 필요성 감소)
- 배치 정규화 계층 신경망에 삽입
- 학습 시 미니배치를 단위로 정규화 (데이터 분포 평균 0, 분산이 1이 되도록)
- 배치 정규화 수식
- 미니배치 B = {x1, x2 ... , xm}이라는 m개의 입력 데이터 집합에 대해 평균(uB)와 분산(a2B) 구함
- 평균 0, 분산 1이 되도록 정규화
- //normalize 단계에서 e(삼지창기호, epsilon)는 매우 작은 값으로, 0으로 나누는 사태를 예방
- 배치처리 층을 활성화 함수 앞 또는 뒤에 삽입하여 데이터분포가 덜 치우치게 함
- 배치 정규화 계층마다 정규화데이터에 고유한 확대와 이동변환 수행 (마지막 줄)
- 단순히 평균0, 분산1로 만들어주면 활성화 함수의 비선형성이 없어질 수 있기 때문에 확대 및 이동변환 실시
6.3.2 배치 정규화의 효과
[코드 책 참고]
6.4 바른 학습을 위해
- 오버피팅 억제하는 기술이 중요
6.4.1 오버피팅
- 오버피팅은 다음의 경우에 일어남
- 매개변수가 많고 표현력이 높은 모델
- 훈련 데이터가 적음
- 오버피팅 일부러 발생시키기 (위 요건 충족시키기) [코드 책 참고]
- 100에폭 지나는 무렵부터 정확도 변화 거의 없음 (100%의 훈련 정확도)
- 시험데이터에 대해서는 큰 차이 보임
- 정확도가 크게 벌어지는 것은 훈련 데이터에만 적응 = 오버피팅
6.4.2 가중치 감소
- 오버피팅 억제용으로 가중치 감소 방법 예로부터 많이 이용
- 학습 과정에서 큰 가중치에 상응하는 큰 페널티 부과하여 오버피팅 억제
- 가중치의 제곱노름(L2노름)을 손실함수에 더해 가중치 커지는 것 억제
...
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)
...
- train acc와 test acc 차이는 여전히 있지만, 그 차이 줄음
- 훈련데이터 정확도가 100% 도달하지 못함
- 오버피팅 억제효과 있음
6.4.3 드롭아웃
- 신경망 모델이 복잡할 때 사용하는 기법
- 노드를 임의로 삭제하면서 학습하는 방법 (삭제된 노드는 신호전달 X)
- test에는 모든 뉴런 신호 전달, 각 뉴런의 출력에 훈련 때 삭제 안 한 비율 곱하여 출력
# 드롭아웃 구현
class Dropout:
def __init__(self, dropout_ratio=0.5):
self.dropout_ratio = dropout_ratio
self.mask = None
def forward(self, x, train_flg=True):
if train_flg:
self.mask = np.random.rand(*x.shape) > self.dropout_ratio # x와 형상 같은 배열 무작위 생성, dropout_ratio보다 큰 원소만 True로 설정
return x * self.mask
else:
return x * (1.0 - self.dropout_ratio)
def backward(self, dout):
return dout * self.mask
- 훈련 시에는 self.mask에 삭제할 뉴런을 False로 표시
- x와 형상 같은 배열 무작위 생성, dropout_ratio보다 큰 원소만 True로 설정
- 역전파 때는 순전파 때 신호 통과 노드는 통과, 그렇지 않으면 차단 (ReLU와 같음)
- MNIST 데이터셋으로 확인 (네트워크 학습 대신해주는 Trainer.py 이용)
...
use_dropout = True
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)
...
- 훈련데이터와 시험데이터에 대한 정확도 차이 줄음
- 훈련데이터 정확도 100% 도달X
6.5 적절한 하이퍼파라미터 값 찾기
6.5.1 검증 데이터
- 하이퍼파라미터 성능 평가시에는 test data 사용해서는 안됨
- 검증데이터(validation data) : 하이퍼파라미터 검증용 데이터
# 하이퍼 파라미터 평가용 검증데이터 분리하기 (mnist)
(x_train, t_train), (x_test, t_test) = load_mnist()
# 훈련데이터 섞기
x_train, t_train = shuffle_dataset(x_train, t_train)
# 그 중 20% 검증데이터로 분할
validation_rate = 0.20
validation_num = int(x_train.shape[0] * validation_rate)
x_val = x_train[:validation_num]
t_val = t_train[:validation_num]
x_train = x_train[validation_num:]
t_train = t_train[validation_num:]
6.5.2 하이퍼파라미터 최적화
- 하이퍼파라미터 최적화의 핵심은 하이퍼파라미터의 '최적 값'이 존재하는 범위를 조금씩 줄여간다는 것
- 대략적인 범위 정하고 그 범위에서 무작위로 값을 골라낸 후, 정확도 평가
- 실제로도 로그스케일(10^-3 ~ 10^3)로 지정하기도 함
- 하이퍼파라미터 최적화 단계
- 하이퍼파라미터 값의 범위 설정
- 설정된 범위에서 하이퍼파라미터 값 무작위 추출
- 1단계에서 샘플링한 하이퍼파라미터 값 사용하여 학습, 검증데이터로 정확도 평가 (에폭은 작게 설정)
- 1단계와 2단계를 특정횟수 반복하며, 정확도 결과보고 하이퍼파라미터의 범위 좁힘
베이즈 최적화
- 베이즈 정리를 중심으로 한 수학이론 구사하여 엄밀, 효율적으로 최적화 수행
6.5.3 하이퍼파라미터 최적화 구현하기
- 로그스케일 범위에서 무작위 추출
# 로그스케일 내 무작위추출 구현
weight_decay = 10 ** np.random.uniform(-8, -4) # 가중치 감소계수 10^-8 ~ 10^-4 범위
lr = 10 ** np.random.uniform(-6, -2) # 학습률 10^-6 ~ 10^-2 범위
[학습코드는 책 참고]
* 추가 팁 : Tensorflow Keras를 사용할 시 하이퍼파라미터 및 초깃값 설정 팁
https://www.tensorflow.org/api_docs/python/tf/keras/layers/Dense
728x90
'Minding's Reading > 밑바닥부터 시작하는 딥러닝' 카테고리의 다른 글
[밑바닥부터 시작하는 딥러닝] CH. 7 합성곱 신경망(CNN) (0) | 2021.05.09 |
---|---|
[밑바닥부터 시작하는 딥러닝] CH.5 오차역전파법 (2) | 2021.05.09 |
[밑바닥부터 시작하는 딥러닝] CH.4 신경망 학습 (0) | 2021.04.06 |
[밑바닥부터 시작하는 딥러닝] CH.3 신경망 (0) | 2021.03.15 |
[밑바닥부터 시작하는 딥러닝] CH.2 퍼셉트론 (2) | 2021.03.14 |