티스토리 뷰
# 파이썬 ≥3.5 필수
import sys
assert sys.version_info >= (3, 5)
# 사이킷런 ≥0.20 필수
import sklearn
assert sklearn.__version__ >= "0.20"
# 텐서플로 ≥2.0 필수
import tensorflow as tf
from tensorflow import keras
assert tf.__version__ >= "2.0"
%load_ext tensorboard
# 공통 모듈 임포트
import numpy as np
import os
# 노트북 실행 결과를 동일하게 유지하기 위해
np.random.seed(42)
# 깔끔한 그래프 출력을 위해
%matplotlib inline
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.rc('axes', labelsize=14)
mpl.rc('xtick', labelsize=12)
mpl.rc('ytick', labelsize=12)
plt.style.use('default')
심층 신경망 훈련하기¶
인공 신경망에서 층이 깊어질 수록 복잡한 문제를 다룰 수 있는 것은 잘 알려져있다.
하지만 기울기 소실, 폭주 문제 와 같이 하위층으로 갈수록 생기는 문제와 훈련속도 저하,
훈련 데이터/레이블 부족 + 오버피팅에 대해 취약해지는 등 다양한 문제가 발생한다.
기울기 소실 / 폭주¶
역전파 알고리즘은 출력층에서 입력층까지 오차 기울기를 전파하면서 진행된다. 모든 파라미터에 대해 손실함수에 대한
기울기를 구해서 손실함수가 적어지는 쪽으로 수정하는데 문제는 이 기울기가 하위층으로 갈 수록 작아지는 경우가 많다.
이 문제를 기울기 소실 문제라고 부르고 반대로 기울기가 점점 커져서 비정상적으로 커진다면 기울기 폭주 라고 부른다.
(기울기 폭주의 경우에는 순환 신경망에서 주로 나타난다)
이런 불안정한 기울기는 층마다 학습 속도를 다르게 만들면서 심층 훈경망 훈련을 어렵게 만든다.
이 문제에 대한 해결책으로 가중치 파라미터에 대한 초기화 방법이 있다.
Xavier 초기화 (Glorot)¶
예측을 할 때는 정방향으로, 기울기를 역전파 할 때는 역방향으로 신호가 적절하게 흘러야한다.
이 과정에서 신호가 사라지거나 폭주하지 않으려면 입력과 출력에 대한 분산값이 일치해야한다.
보통 그래서 층의 입력과 출력의 연결 개수가 다르다면 이를 보장하기가 힘들었지만 각 층의 가중치를
다음과 같은 수식을 따르는 방식대로 무작위로 초기화 한다면 분산이 일정해짐을 보였다.
$fan_{avg}$ = 층의 입력과 출력 연결 연결 개수의 평균 ($fan_{in}$ + $fan_{out}$) / 2 이라 할 때
\begin{equation} (E=0,\;\sigma^2\;=\;\frac{1}{fan_{avg}})\;인\;정규분포 \;또는 \\ \\ r\;=\;\sqrt{\frac{3}{fan_{avg}}} 일 때,\;-r\;과 +r\;사이의 \;균등분포 \end{equation}
Xavier 초기화는 활성화 함수가 없거나 하이퍼볼릭 탄젠트, 로지스틱, 소프트맥스가 활성화 함수인 경우 적용된다.
케라스에서 이용하고 싶다면
keras.layers.Dense(10, activation="relu", kernel_initializer = "GlorotNormal")
(GlorotUniform 으로 두면 균등분포를 이용가능)
He 초기화 (lecun 초기화)¶
Xavier 초기화 이후 나온 다른 활성화 함수에 대한 전략들 모두 Xavier 초기화에 베이스를 둔다.
ReLU 활성화 함수를 위해 도입된 He초기화 역시 Xavier 초기화와 유사하다.
\begin{equation} (E=0,\;\sigma^2\;=\;\frac{2}{fan_{in}})\;인\;정규분포 \;또는 \\ \\ r\;=\;\sqrt{\frac{6}{fan_{in}}} 일 때,\;-r\;과 +r\;사이의 \;균등분포 \end{equation}
케라스에서 이용하고 싶다면
keras.layers.Dense(10, activation="relu", kernel_initializer = "he_normal")
(he_uniform 으로 두면 균등분포를 이용가능)
수렴하지 않는 활성화 함수¶
활성화 함수를 잘못 선택하면 기울기 소실과 폭주를 일으킬 수 있다. (글로럿, 벤지오 2010)
그 이전까지는 생물학적 뉴런과 유사한 시그모이드가 최선이라 생각했지만 아니었고 그 대체재로
특정 양수값에 수렴되지 않는 ReLU 함수가 각광받기 시작했다.
다만, ReLU 함수가 0 이하의 값을 출력하지 않기 때문에 죽은 ReLU 문제가 등장했고 이로 인해
기울기가 0이 되면서 경사 하강법을 이용할 수 없게되는 문제가 발생하였다.
LeakyReLU¶
이런 죽은 ReLU문제와 같이 수렴문제를 해결하기 위하여 LeakyReLU 라는 함수가 등장하게 되었다.
\begin{equation} \text{LeakyReLU}_{a}\;(z)\;=\;max(az, z) \end{equation} $a$를 (0,1) 구간에 두어서 z가 음수일 때 어느정도의 기울기로 값을 얻을 것인지를 설정하는데 보통 0.01로 둔다.
이와 유사하게 훈련중에 $a$ 값을 무작위로 선택하고 테스트시에 평균값을 이용하는 RReLU 방식 이 있다.
ELU¶
훈련시간도 적고 왠만한 ReLU 변종 함수들의 성능을 앞지른 ELU 함수이다.
\begin{equation} \text{ELU}_a(Z) = \begin{cases} \alpha\;(exp(z)-1) \; & \text{if} \;z<\; 0\\ z \; & \text{if} \;z\;\geq\;0 \end{cases} \end{equation}
z가 음수인 경우에도 함수가 $-a$에 수렴하기 때문에 죽은 뉴런을 생성하지 않는다.
특히 $a=1$ 이라면 z=0에서 급격히 변동되지 않는 특성을 가지므로 z=0을 포함한 모든 구간에서 매끄럽기 때문에
전 구간에서 미분이 가능해지고 이 덕분에 경사 하강법을 빠르게 이용하는 것이 가능하다.
다만 훈련시에는 수렴할 수 있어서 속도가 빠르지만 테스트 시에는 지수함수 때문에 속도가 느려지는 단점이 있다.
SELU¶
완전 연결층만 쌓아서 모든 은닉층을 SELU 함수만 이용한다면 알아서 네트워크가 자기 정규화 되는 함수이다.
따라서 훈련하는 동안 각 층의 출력이 평균이 0이고 표준편차가 1인 정규분포를 따르기에 기울기관련 문제를 해결한다.
다만 자기 정규화를 위한 몇가지 규칙이 존재한다.
- 입력 특성이 표준화 되어야함
- 모든 은닉층의 가중치가 르쿤 정규분포로 초기화되어야함 (kernel_initializer = lecun_normal)
- 네트워크가 일렬로 쌓여져야함 (RNN, skip connection 과 같이 순차적이지 않으면 X)
(일부 연구자들은 CNN 에서도 SELU가 성능 향상이 가능하다고 주장한다)
배치 정규화¶
초기화를 잘 이용하면 훈련 초기 단계에서 기울기가 난리나는 것을 막을 수 있지만 나중에 다시 발생하지 않는다는 보장이 없다.
따라서 배치 정규화 (Batch Normalization, BN)을 이용하곤 하는데 이 기법은 활성화 함수에 값을 넣기 전 또는 후에 적용되며
데이터를 원점에 맞추고 정규화한다음 스케일을 조정하고 이동시킨다.
3번째 줄 까지는 정규화 단계이며 마지막 4번째 단계에서 스케일을 조정하고 이동시킨다.
다만 테스트 시에는 샘플의 배치가 아닌 샘플 하나에 대한 예측을 만들어야 한다.
이 경우 입력의 평균과 표준편차를 계산할 방법이 없기에 전체 훈련 세트에 대한 평균과 표준편차를 이용하거나
(주로) 배치들의 입력 평균과 표준편차의 이동 평균 (moving average)를 사용하여 최종 통계치를 추정한다.
(케라스의 BatchNormalization 층은 이를 자동으로 수행한다)
model = keras.models.Sequential([
keras.layers.Flatten(input_shape=[28, 28]),
keras.layers.BatchNormalization(),
keras.layers.Dense(300, activation="relu"),
keras.layers.BatchNormalization(),
keras.layers.Dense(100, activation="relu"),
keras.layers.BatchNormalization(),
keras.layers.Dense(10, activation="softmax")
])
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
flatten (Flatten) (None, 784) 0
batch_normalization (BatchN (None, 784) 3136
ormalization)
dense (Dense) (None, 300) 235500
batch_normalization_1 (Batc (None, 300) 1200
hNormalization)
dense_1 (Dense) (None, 100) 30100
batch_normalization_2 (Batc (None, 100) 400
hNormalization)
dense_2 (Dense) (None, 10) 1010
=================================================================
Total params: 271,346
Trainable params: 268,978
Non-trainable params: 2,368
_________________________________________________________________
케라스에서 정규화층의 입력 이동 평균 $\gamma$ 와 입력 이동 표준편차 $\sigma$ 는 역전파로 훈련되지 않고
입력값에만 의존하기 때문에 non-trainable 하다고 뜬다.
bn1 = model.layers[1]
[(var.name, var.trainable) for var in bn1.variables]
[('batch_normalization/gamma:0', True),
('batch_normalization/beta:0', True),
('batch_normalization/moving_mean:0', False),
('batch_normalization/moving_variance:0', False)]
논란의 여지가 좀 있긴하지만 배치 정규화를 활성화 함수 이전에 사용하는 것을 추천한다고 한다.
은닉층에서 활성화 함수를 지정하지 않고 배치 정규화 층 뒤에 별도의 층으로 분리하면 구현이 가능하다.
model = keras.models.Sequential([
keras.layers.Flatten(input_shape=[28, 28]),
keras.layers.BatchNormalization(),
# 배치 정규화 층이 입력 평균 이동 파라미터를 사용하면서
# Dense층의 편향을 무효화 시키므로 필요가 없어진다.
keras.layers.Dense(300, use_bias=False),
keras.layers.BatchNormalization(),
keras.layers.Activation("relu"),
keras.layers.Dense(100, use_bias=False),
keras.layers.BatchNormalization(),
keras.layers.Activation("relu"),
keras.layers.Dense(10, activation="softmax")
])
그레디언트 클리핑¶
역전파에서 일정 임곗값을 넘어서지 못하도록 일부러 가중치를 제한하는 방법을 통해 기울기 폭주를 막을 수 있다.
이를 그레디언트 클리핑이라고 하고, 순환 신경망의 경우 배치 정규화가 적용이 어려워 클리핑 기법이 자주 이용된다.
만약 값을 기준으로만 넘는 값을 잘라주면 그레디언트 벡터의 방향이 당연하게도 바뀐다.
이게 싫다면 clipvalue 대신 clipnorm을 이용하여 전체적으로 스케일을 조정할 수 있도록 설정하자.
보통 그레디언트의 l2 norm이 임곗값을 넘게되면 l2 norm으로 나누어서 클리핑 해주는 것이 일반적이다.
optimizer = keras.optimizers.SGD(clipvalue=1.0)
optimizer = keras.optimizers.SGD(clipnorm=1.0)
사전훈련된 층 재사용 (전이학습)¶
큰 규모의 심층신경망 DNN을 매번 처음부터 학습시키면 비용이 크다.
대신 비슷한 유형의 문제를 해결한 신경망의 하위층을 재사용하면 비용도 줄고 훈련 데이터도 많이 필요 없어진다.
이를 전이학습이라 부르고, 보통 원본 모델에서 하위층을 이용하여 유용한 학습된 특성을 뽑아낸다.
층을 얼마나 재사용할지가 중요한데 비슷할 수록 많이 뽑아내면 된다.
그런데 만약 사용할 수 있는 레이블된 데이터가 적다면 GAN이나 오토인코더를 활용하여 비지도 학습을 진행하고
학습된 모델의 하위층을 데려와서 이용해도 된다.
고속 옵티마이저¶
SGD보다 빠른 옵티마이저를 쓰면 심층 신경망 DNN에서 학습속도를 가속할 수 있다.
모멘텀 최적화¶
일정한 크기(학습률)로 내려가는 경사 하강법과 달리 모멘텀을 적용한 방식이 존재한다.
이전 그레디언트가 얼마였는지 고려해서 빠르게 지역 국소값을 탈출 할 수 있도록 도와준다.
모멘텀 벡터를 $m$ 이라고 부르고, 모멘텀을 $\beta$ 이라고 부르는데,
모멘텀 벡터는 이전 그레디언트를 반영해주고, 모멘텀은 이 모멘텀 벡터가 너무 빠르게 증가하는 것을 막는다.
(마치 마찰 저항과 같은 역할을 해준다.)
\begin{align*} & 1. \;m \gets \beta m\;-\; \eta\nabla_{\theta}J(\theta) \\ & 2. \;\theta \gets \theta \;+\;m \\ & \textrm{만약 그레디언트가 일정하다면 그레디언트에 $\frac{1}{1-\beta}$를 곱한 것과 같다} \end{align*}
마치 그레디언트가 가속도처럼 작용하게 되며 경사가 가파른 곳을 지나면서 가속도가 증가하고 속도가 빨라지다가,
경사가 완만한 곳 (예를 들어 지역적 극소값)을 지날 때, 이 가속도를 이용하여 빠르게 탈출할 수 있게 된다.
optimizer = keras.optimizers.SGD(learning_rate=0.001, momentum=0.9)
네스테로프 가속 경사¶
모멘텀과 비슷하긴 한데 아예 그레디언트 계산 자체를 현재 위치 $\theta$ 가 아닌,
현재 위치보다 앞선 $\theta + \beta m$ 을 기준으로 계산한다.
\begin{align*} & 1. \;m \gets \beta m\;-\; \eta\nabla_{\theta}J(\theta + \beta m) \\ & 2. \;\theta \gets \theta \;+\;m \\ \end{align*}
당연히도 모멘텀이 적용된 위치에서 그레디언트를 계산하는 것이 더 정확하므로, 최적점을 더 빠르게 찾을 수 있다.
AdaGrad¶
경사 하강법은 곧바로 전역 최적점으로 가지 않고 현재 위치에서 가장 가파른 경사를 따라 이동한다.
따라서 최적점으로 느리게 이동하는데 처음부터 전역 최적점으로 이동한다면 빠르게 최적값을 얻을 수 있을 것이다.
AdaGrad는 가장 가파른 차원 을 따라서 그레디언트 벡터의 스케일을 감소시키며 문제를 접근한다.
\begin{align} & 1. \;s \gets s\;+\;\nabla_{\theta}J(\theta)\otimes\nabla_{\theta}J(\theta)\\ & 2. \; \theta \gets\;\theta\;-\;\eta\nabla_{\theta}J(\theta)\oslash\sqrt{s+\varepsilon}\\ \\ & \otimes\;:\;\textrm{원소 별 곱셈}\\ & \oslash\;:\;\textrm{원소 별 나눗셈}\\ \end{align}
우선 그레디언트의 제곱을 벡터 S에 누적시킨다. 즉, 파라미터에 대한 손실 함수의 편미분을 제곱하여 누적시키는데
만약 비용 함수가 i번째 차원을 따라 가파르다면 반복이 진행되면서 값이 점점 커진다.
그 다음으로 그레디언트 벡터를 $\sqrt{s+\varepsilon}$ 로 나누어서 경사하강법을 진행한다.
($\varepsilon$ 값은 0 으로 나누는 것을 막기위한 값이다.)
이렇게 되면 경사가 가파른 곳일 수록 s값이 커지고 학습률이 따라서 감소하는 효과를 가져오게 된다.
이를 적응적 학습률 이라고 부르고 전역 최적점 방향으로 나아가는데 도움이 된다.
덕분에 학습률 $\eta$ 값을 튜닝하지 않아도 된다.
AdaGrad는 간단한 2차 방정식 문제에서는 잘 작동하지만 조기 종료되는 경우가 종종 있다.
이는 학습률이 너무 빠르게 감소해서 생긱는 문제여서 심층 신경망에는 사용되지 않는다.
(선형 회귀 같은 간단한 작업에는 효율적일 수 있다.)
RMSProp¶
학습률이 너무 빠르게 감소해서 조기종료되는 AdaGrad의 문제를 개선하기 위하여
훈련 시작부터 그레디언트를 누적시키는 것이 아닌 최근의 그레디언트만 누적시키는 방식을 RMSProp은 채택한다.
우선 알고리즘의 첫 단계에서 AdaGrad와 달리 지수 감소를 사용한다.
\begin{align} & 1. \;s \gets \beta s\;+\;(1-\beta)\nabla_{\theta}J(\theta)\otimes\nabla_{\theta}J(\theta)\\ & 2. \; \theta \gets\;\theta\;-\;\eta\nabla_{\theta}J(\theta)\oslash\sqrt{s+\varepsilon}\\ \end{align}
보통 감쇠율 $\beta$ (== rho) 는 0.9로 설정한다. (이 자체만으로 잘 작동해서 튜닝은 잘 안함)
optimizer = keras.optimizer.RMSprop(lr=0.001, rho=0.9)
Adam과 Nadam 최적화¶
Adaptive momentum estimation을 의미하는 Adam은 모멘텀 최적화와 RMSProp을 합친 것이다.
모멘텀 최적화처럼 지난 그레디언트의 지수 감소 평균을 따르고
RMSProp 처럼 최근 그레디언트 제곱의 지수 감소된 평균을 따른다.
\begin{align} & 1. \;m \gets \beta_{1} m\;-\; (1-\beta_{1})\eta\nabla_{\theta}J(\theta) \\ & 2. \;s \gets \beta_{2} s\;+\;(1-\beta_{2})\nabla_{\theta}J(\theta)\otimes\nabla_{\theta}J(\theta)\\ & 3. \;\hat{m} \gets \frac{m}{1-\beta_{1}^{t}}\\ & 4. \;\hat{s} \gets \frac{s}{1-\beta_{2}^{t}}\\ & 5. \; \theta \gets\;\theta\;+\;\eta\hat{m}\oslash\sqrt{\hat{s}+\varepsilon}\\ \end{align}
m과 s가 0으로 초기화되기 때문에 초반에는 이전 값들이 크게 영향을 주지 못한다.
이를 보상하기 위하여 반복 초기에 3,4 단계에서 m과 s값들을 증폭시키지만 점점 분모가 1에 가까워지며 증폭되지 않는다.
모멘텀 감쇠 파라미터인 $\beta_{1}$ 은 0.9로 초기화 되고, 스케일 감쇠 파라미터 $\beta_{2}$ 는 0.999로 초기화된다.
optimizer = keras.optimizer.Adam(lr=0.001, beta_1=0.9, beta_2=0.999)
이외에도 Adam에서 3,4 단계를 건너뛰고 2,5 단계의 결과인 스케일의 $l_2$ 노름을 $l_{\infty}$ 로 바꾼 AdaMax 가 있고
(Adam보다 더 안정적이라는 이야기가 있지만 데이터마다 다르다. Adam이 안되면 이용하자)
네스테로프 가속 경사 기법을 적용한 Nadam 이 있다. (종종 Adam보단 조금 더 빨리 수렴한다)
학습률 스케줄링¶
최적의 학습률을 찾는 것은 중요하다. 학습률이 너무 작으면 시간이 오래 걸리고 반대로 너무 크다면 최적점 근처에서 진동한다.
보통 그래서 최적의 학습률을 찾기 위해 큰 학습률로 시작해서 학습률을 감소시켜 가는 것이 가장 낫다고 알려져있다.
이런 전략을 학습 스케줄링이라고 하며 다음과 같은 방식이 있다.
거듭제곱 기반 스케줄링¶
학습률을 반복 횟수 $t$ 에 대한 함수로 지정하고 각 스텝마다 감소시킨다. \begin{equation} \eta(t)\;=\;\eta_{0}\;/\;(1+t/s)^{c} \end{equation} 여기서 $\eta_{0}$ 는 초기 학습률, $c$ 는 거듭제곱 수 (보통 1), $s$ 는 스텝횟수 하이퍼파라미터이다.
식을 보면 초반에는 학습률이 빠르게 감소하다가 점점 느리게 감소함을 알 수 있다.
# decay는 스텝횟수 하이퍼파라미터인 s의 역수이다.
optimizer = keras.optimizers.SGD(lr=0.01, decay=1e-4)
지수 기반 스케줄링¶
학습률을 다음과 같은 반복 횟수 $t$에 관한 함수로 지정하고 각 스텝마다 감소한다. \begin{equation} \eta(t)\;=\;\eta_{0}\cdot(0.1)^{t/s} \end{equation} 파라미터 자체는 거듭제곱 기반 스케줄링과 비슷하지만 스텝마다 학습률이 10배씩 줄어드는 것을 확인할 수 있다.
def exponential_decay(lr0, s):
# 스케줄러의 첫번째 파라미터로 에포크, 두번째 파라미터로 현재 학습률을 받아온다.
def exponential_decay_fn(epoch):
return lr0 * 0.1**(epoch / s)
return exponential_decay_fn
exponential_decay_fn = exponential_decay(lr0=0.01, s=20)
그 다음 이 스케줄링 함수를 전달하여 콜백을 만들어 fit() 메서드에 전달하면 된다.
# LearningRateScheduler는 에포크가 시작될 때마다
# 옵티마이저의 learning rate를 업데이트한다.
lr_scheduler =
keras.callbacks.LearningRateScheduler(exponential_decay_fn)
history = model.fit(X_train_scaled, y_train,
[...],
callbacks=[lr_scheduler])
모델을 저장할 때 옵티마이저와 학습률은 저장되지만 현재 에포크는 저장되지 않는다.
fit()을 호출할 때 마다, epoch가 0으로 초기화 되므로 매우 큰 학습률이 만들어 질 수 있다.
따라서 fit()의 파라미터중 initial_epoch를 수동으로 지정하자.
구간별 고정 스케줄링¶
일정 횟수의 에포크 동안 일정한 학습률을 사용하고 그 다음에는 줄어든 학습률을 일정한 에포크 동안 사용하는 기법이다.
잘 작동할 수 있지만 적절한 학습률과 에포크 횟수의 조합을 찾는 비용이 크다.
def piecewise_constant_fn(epoch):
if epoch < 5:
return 0.01
elif epoch < 15:
return 0.005
else:
return 0.001
성능 기반 스케줄링¶
매 N 스텝마다 검증 오차를 확인하고 오차가 줄어들지 않으면 $\lambda$ 배 만큼 학습률을 감소시킨다.
# 5번 기다리고 그 이후 학습률에 0.5를 곱해준다.
lr_scheduler =
keras.callbacks.ReduceLROnPlateau(factor=0.5, patience=5)
1사이클 스케줄링¶
다른 방식과 달리 훈련 절반 동안은 초기 학습률 $\eta_0$ 을 선형적으로 $\eta_1$ 까지 증가시킨다.
그 다음 나머지 절반 동안 다시 $\eta_1$ 에서 선형적으로 초기 학습률 $\eta_0$ 까지 줄여버린다.
마지막 몇 번의 에포크에서는 학습률을 소수점 몇 째 자리까지 선형적으로 줄인다.
(최대 학습률 $\eta_1$ 은 다른 기법처럼 정하고 초기 학습률 $\eta_0$ 는 최대 학습률의 10배 정도 낮은 값을 선택한다.)
모멘텀을 사용하는 경우, 높은 모멘텀으로 시작해서 절반동안 선형적으로 낮췄다가 다시 원상태로 선형적으로 복구한다.
이 방식이 훈련 속도도 줄여주고 높은 성능을 보여주는 것을 2018년 레슬리 스미스가 보여주었다.
K = keras.backend
class ExponentialLearningRate(keras.callbacks.Callback):
def __init__(self, factor):
self.factor = factor
self.rates = []
self.losses = []
def on_batch_end(self, batch, logs):
self.rates.append(K.get_value(self.model.optimizer.lr))
self.losses.append(logs["loss"])
K.set_value(self.model.optimizer.lr, self.model.optimizer.lr * self.factor)
def find_learning_rate(model, X, y, epochs=1, batch_size=32, min_rate=10**-5, max_rate=10):
init_weights = model.get_weights()
iterations = math.ceil(len(X) / batch_size) * epochs
factor = np.exp(np.log(max_rate / min_rate) / iterations)
init_lr = K.get_value(model.optimizer.lr)
K.set_value(model.optimizer.lr, min_rate)
exp_lr = ExponentialLearningRate(factor)
history = model.fit(X, y, epochs=epochs, batch_size=batch_size,
callbacks=[exp_lr])
K.set_value(model.optimizer.lr, init_lr)
model.set_weights(init_weights)
return exp_lr.rates, exp_lr.losses
def plot_lr_vs_loss(rates, losses):
plt.plot(rates, losses)
plt.gca().set_xscale('log')
plt.hlines(min(losses), min(rates), max(rates))
plt.axis([min(rates), max(rates), min(losses), (losses[0] + min(losses)) / 2])
plt.xlabel("Learning rate")
plt.ylabel("Loss")
keras.optimizers.schedules¶
사실 tr.keras에는 학습률 스케줄링을 위한 거듭제곱 기반, 구간별 고정 스케줄링을 지원한다.
이러면 스케줄러가 학습률을 정의해주고 이 학습률이 옵티마이저에 전달된다.
tf.keras.optimizers.schedules.ExponentialDecay(
initial_learning_rate,
decay_steps,
decay_rate,
staircase=False,
name=None
)
# number of steps in 20 epochs (batch size = 32)
s = 20 * len(X_train) // 32
learning_rate =
keras.optimizers.schedules.ExponentialDecay(0.01, s, 0.1)
optimizer = keras.optimizers.SGD(learning_rate)
왠 깡구현이 이리 많은지 모르겠지만 내용만 숙지하고 넘어가자ㅠ
규제로 오버피팅 피하기¶
심층 신경망에는 파라미터가 워낙 많다보니 네트워크의 자유도가 상당히 높다.
이는 모델이 복잡한 데이터셋을 학습할 수 있음을 의미하지만 불필요한 특성도 학습하게 만들며 과대적합에 취약함을 뜻한다.
이를 위해 조기 종료나 배치 정규화를 배웠지만 더 좋은 규제 방법들이 있다.
L1과 L2 규제¶
앞서 다뤘던 라쏘, 릿지 방식이 가중치의 크기가 커지는 것을 막았듯 (사실 같다)
신경망에서도 가중치를 제한 하기 위해 $l1$과 $l2$ 규제를 이용하여 손실함수에 더해줄 수 있다.
kernel_regularizer=keras.regularizers.l1(규제 정도)
kernel_regularizer=keras.regularizers.l2(규제 정도)
kernel_regularizer=keras.regularizers.l1_l2(l1=규제 정도, l2=규제 정도)
일반적으로 신경망 내의 모든 은닉층에 동일한 활성화 함수와 초기화 전략 및 규제가 적용되기에
동일한 파라미터 값을 반복하는 경우가 많다. 이러면 보기가 어렵기에 functools.partial()로 묶어주자.
from functools import partial
# default 값들을 미리 정해준다.
RegularizedDense = partial(keras.layers.Dense,
activation="elu",
kernel_initializer="he_normal",
kernel_regularizer=keras.regularizers.l2(0.01))
model = keras.models.Sequential([
keras.layers.Flatten(input_shape=[28, 28]),
RegularizedDense(300),
RegularizedDense(100),
RegularizedDense(10, activation="softmax")
])
드롭아웃¶
출력 뉴런을 제외한 모든 뉴런들은 임시적으로 드롭아웃될 확률 p를 가진다.
매 훈련마다 각 뉴런들이 이 확률 p에 따라 활성화되거나 비활성화되며 훈련이 끝나면 더이상 적용하지 않는다.
이렇게되면 뉴런들이 계속해서 학습되지 않기 때문에 학습데이터에 편향되지 않게된다.
따라서 입력값의 변화에 덜 민감해지게 되고 일반화 성능이 좋아지게 된다. 다르게 생각하면 매 훈련마다 뉴런이 있거나 없어지기 때문에 $2^N$ 개의 네트워크가 생긴다고 생각할 수 있다.
다만, p=50%로 훈련을 했다면 테스트 때에는 하나의 뉴런이 훈련 때보다 평균적으로 두 배 많이 연결된다.
이 점을 보상하기 위하여 훈련하고 나서 각 뉴런의 가중치 파라미터에 0.5를 곱해줄 필요가 있다.
일반화 시키면, 훈련이 끝난뒤 각 가중치 파라미터에 보존 확률 (1-p) 값을 곱해야 한다.
$\bullet\;$ SELU 함수를 써서 자기 정규화를 하는 네트워크를 규제하는 경우, alpha 드롭아웃을 이용하자.
keras.layers.AlphaDropout(rate=0.2),
# 활성화 함수 이후에 드롭아웃을 적용하는 모델
# 값을 통과 시키고 그 이후에 드롭아웃을 적용한다.
model = keras.models.Sequential([
keras.layers.Flatten(input_shape=[28, 28]),
keras.layers.Dropout(rate=0.2),
keras.layers.Dense(300, activation="elu", kernel_initializer="he_normal"),
keras.layers.Dropout(rate=0.2),
keras.layers.Dense(100, activation="elu", kernel_initializer="he_normal"),
keras.layers.Dropout(rate=0.2),
keras.layers.Dense(10, activation="softmax")
])
몬테 카를로 드롭아웃 (MC 드롭아웃)¶
드롭아웃 네트워크와 근사 베이즈 추론 사이의 관련성과 모델 재훈련 필요없이 성능을 향상 시킬 수 있는 방식이다.
y_probas = np.stack([model(X_test_scaled, training=True)
for sample in range(100)])
y_proba = y_probas.mean(axis=0)
테스트셋에서 하나의 셋에 대한 서로다른 100개의 예측을 드롭아웃과 함께 얻어낸다.
그 이후 드롭아웃으로 얻어낸 예측을 평균을 때리면 드롭아웃 없이 예측한 결과보다 안정적으로 뽑혀나온다.
맥스-노름 규제¶
각각의 뉴런에 대해 가중치 파라미터 $w$의 $l2$노름을 $\Vert w\Vert_2\;\le\;r$ 이 되도록 규제한다.
여기서 $r$ 은 맥스-노름 하이퍼파라미터이다. 전체 손실 함수에 규제 손실 항을 추가하지 않지만,
매 훈련 스텝이 끝날 때 마다 알아서 $\Vert w\Vert_2$ 를 계산하고 필요에 따라 스케일을 조정한다.
keras.layers.Dense(100, activation="selu",
kernel_initializer="lecun_normal",
kernel_constraint=keras.constraints.max_norm(1.))
# bias_constraint 에도 적용해서 편향을 규제할 수도 있다.
max_norm() 함수는 기본값이 0인 axis 파라미터가 있다.
Dense 층은 보통 [샘플 개수, 뉴런 개수] 크기의 가중치를 가지기에 axis=0 를 적용하면
각 뉴런의 가중치 벡터에 독립적으로 적용된다.
이와 비슷하게 [샘플 개수, 높이, 너비, 채널 개수]로 구성된 합성곱층은 axis=[0,1,2] 를 적용하면
각 채널축에 독립적으로 적용된다.
'AI' 카테고리의 다른 글
[핸즈온 머신러닝] chapter 13. 텐서플로에서의 데이터 적재와 전처리 (0) | 2022.05.04 |
---|---|
[핸즈온 머신러닝] chapter 12. 텐서플로에서의 사용자 정의 모델과 훈련 (0) | 2022.04.28 |
[핸즈온 머신러닝] chapter 10. 케라스를 이용한 인공 신경망 (0) | 2022.04.22 |
[핸즈온 머신러닝] chapter 9. 비지도 학습 (0) | 2022.04.22 |
[핸즈온 머신러닝] chapter 8. 차원 축소 (0) | 2022.04.22 |
- Total
- Today
- Yesterday