- 5강 리뷰
1) History of CNN
2) CNN
- pooling
- padding
- stride
- 1x1 conv layer
Training Neural Networks
NN 학습을 위해 필요한 기본 설정들에는 Activation Functions, Data Preprocessing, Weight Initialization, Batch Normailzation 등이 있다.
또한 hyperparameter optimization, model ensemble, 그리고 학습이 잘 되고 있는지 확인하는 Training Dynamics도 있다.
part 1에서는 activation function, data preprocessing, weight initialization, batch normailzation, learning precess, hyperparameter optimization을 배울 것이다.
Activation Function
FC/CNN 을 학습할 때, 데이터 입력이 들어오면 가중치와 곱하고 bias를 더한다.
그 후 actviation 연산을 거치게 된다.(activation function)
활성함수의 예시로는 다음과 같다.
이번 강의에서는 다양한 활성함수와 그들간의 trade-off를 다뤄볼 것이다.
sigmoid
먼저 sigmoid를 살펴보자면, 1/(1+e^-x) 의 형태를 가지고 있다.
입력을 받아서 그 입력을 [0,1] 사이의 값이 되도록 만든다. 입력의 값이 크면 sigmoid의 출력은 1에 가깝고, 입력값이 작으면 0에 가까워진다.
sigmoid는 뉴런의 firing rate를 포화(saturation)시키는 것으로 해석할 수 있기에 과거에는 많이 사용되었지만, 크게 3가지의 문제점을 가지고 있어 지금은 잘 사용하지 않는다.
sigmoid의 문제점
- 포화된 뉴런(0이나 1에 가까운 값을 내는 뉴런)이 gradient를 없앤다.(= vanishing gradient)
sigmoid를 computational graph의 형태로 살펴보면, 데이터 x와 출력이 존재한다.
backpropagation를 위해 gradient를 계산한다.
먼저 (dL/dσ)가 있을 것이고, sigmoid gate를 지나 local sigmoid function의 gradient인 (dL/dx)를 구하기 위해 chain rule를 적용한다.
즉, dL/dx = (dσ/dx) * (dL/dσ) 가 된다.
x = -10 일 때의 gradient(dL/dx)는 그림에서 보듯이 0 이다. 그렇게 되면, 거의 0에 가까운 값들이 backpropagation 될 것이다. 때문에, 그 뒤에 전달되는 모든 gradient는 모두 죽어(=0)버린다.
x = 0 일 때는 backprop가 잘 진행될 것이다.
x = 10 일 때는 x = -10일 때와 같이 모든 gradient가 0 이 된다.
따라서 x가 아주 크거나 아주 작다면 gradient가 계속 0으로 죽어버린다.
- sigmoid의 출력값이 0을 중심으로 하지 않는다.(not zero-centered)
neural network에서, 입력값 x가 항상 양수일 경우를 가정해보자.
x는 가중치 w와 곱해지고 활성함수를 통과할 것이다.
wixi + b 가 활성함수 f를 통과하면 f(∑wixi + b)가 된다.
항상 양수를 가정했으므로 sigmoid를 거친 값들은 항상 양수이다.
backpropagation 시에 local gradient를 생각해보면, 우선 dL/df를 계산한다.
w에 대한 gradient는 다음과 같다.
dL/dw = dL/df * df/dw
여기서 L은 loss 함수, f = w^T * x + b 이다. f 식을 통해
df/dw = x 를 구할 수 있다.
따라서 dL/dw = (dL/df) * x 이다.
파라미터의 gradient는 입력값에 의해 영향을 받으며, 만약 입력값이 모두 양수라면 파라미터의 부호는 일정하게 된다.
이렇게 되면 파라미터를 업데이트할 때 계속 증가하거나 계속 감소할 수 밖에 없기 때문에 매우 비효율적이다.
w를 이차원적 예제로 생각해보자. w에 대한 두개의 축으로 이루어져 있다.
전부 양수 또는 음수로 업데이트된다는 것은 gradient가 이동 가능한 방향은 두 방향뿐이다.
파란선이 최적의 w 라고 한다면 빨간 화살표는 파란색 방향으로 내려갈 수가 없다.
- exp()의 계산 비용이 비싸다. (= 계산이 오래 걸린다)
tanh
그 다음으로 tanh 활성함수를 보고자 한다.
sigmoid와 비슷하지만 범위가 [-1,1] 이라는 것이 차이점이다. 또한, zero-centered 형태이다.
이를 통해 sigmoid의 두번째 문제를 해결할 수 있다. 하지만 여전히 gradient가 죽는다.
ReLU
ReLU를 살펴보자.
ReLU는 f(x) = max(0,x)의 형태를 가지고 있다.
이 함수는 element-wise 연산을 수행하며 입력이 음수면 값이 모두 0이 되고, 양수면 입력값 그대로 출력한다.
ReLU의 가장 큰 장점은 양의 값에서는 포화(saturation)되지 않는다.
또한, 계산 효율이 아주 뛰어나다. ReLU는 단순히 max 연산이기 때문이다.
실제 뉴런의 입/출력 값을 확인했을 때에도 sigmoid보다 ReLU가 더 가깝다.
ReLU의 문제점은 zero-centered가 아니라는 것이다.
또한, 음수의 경우 saturation된다. 즉, x<=0의 경우 gradient가 0이 된다.
초록,빨강 선이 ReLU를 의미하고, DATA CLOUD는 training data이다.
ReLU가 Data Cloud와 겹치지 않을 경우에는 dead ReLU가 발생할 수 있다.
dead ReLU에서는 activate가 일어나지 않고 업데이트되지 않는다.
반면 active ReLU는 일부는 active되고 일부는 active되지 않을 것이다.
dead ReLU가 발생하는 경우는 2가지인데, 첫번째 경우는 가중치의 초기화가 잘못되어 가중치 평면이 data cloud와 멀리 떨어져 있을 때다.
이때는 ReLU가 절대 활성화되지 않고, gradient가 업데이트되지 않는다.
두번째는 흔한원인으로 learning rate가 지나치게 클 경우이다. 처음에 적절한 ReLU로 시작할 수 있어도 update를 지나치게 크게하면 갑자기 죽어버리는 경우가 생긴다.
실제로 ReLU를 사용할 때는 10-20%가 dead ReLU가 되지만 이는 학습에 크게 영향을 주지 않는다.
추가적으로, update시에 active ReLU가 될 가능성을 조금이라도 더 높여주기 위해서 ReLU를 초기화할 때 positive bias를 추가해주는 경우가 있다.
하지만 이 방법은 잘 사용하지 않고 zero-bias를 사용한다.
leaky ReLU / PReLU / ELU
ReLU를 약간 변형한 leaky ReLU 가 있다. negative에도 기울기를 살짝 주어 ReLU의 문제점인 saturation 되는 문제가 해결한다.
여전히 계산도 효율적이다. 또한, 수렴도 빨리 할 수 있을 뿐더러 dead ReLU 현상도 더 이상 없다.
다른 모델로 pararmetric ReLU 도 있다.
negative space에 기울기가 있다는 점에서 Leaky ReLU와 유사하나 backpropagation가 α라는 파라미터로 결정된다.
α는 정해놓는 것이 아니라 backprop로 학습시키는 파라미터이다.
또 다른 모델로 ELU 라는 것이 있다. ReLU와 leaky ReLU의 중간 정도로 보면 된다. ReLU의 이점을 그대로 가져오면서 zero-mean에 가까운 출력값을 보인다.
문제는 negative에서 기울기를 가지지 않고 saturation 된다. 또한, 복잡한 exp()를 계산해야 한다는 단점이 있다.
장점으로는 좀 더 noise(잡음)에 강인하다. 이런 deactivation(불활성화)이 좀 더 강인함을 줄 수 있다고 주장한다.
Maxout Neuron
Maxout Neuron 은 입력을 받아드리는 특정한 기본 형식을 미리 정의하지 않고, α라는 파라미터를 추가한다. α는 backprop를 통해 학습된다.
w1에 x를 내적한 값 + b1, w2에 x를 내적한 값 + b2 의 최댓값을 사용한다.
Maxout은 ReLU와 leaky ReLU의 일반화된 형태이다.
위의 두 개의 선형함수를 취하기 때문이다.
이 때문에, 선형적인 형태를 띄지만 뉴련이 포화되지 않고 gradient도 죽지 않는다.
그러나 뉴런당 파라미터가 2배가 된다는 단점이 있다.
실제로는 ReLU를 가장 많이 사용하고 작동도 잘 된다.
다만 ReLU를 사용하려면 learning rate를 아주 조심스럽게 결정해야 한다.
Data Preprocessing
이제는 실제 네트워크를 훈련시켜 볼 것이다.
일반적으로 입력 데이터는 전처리를 해야 한다.
가장 대표적인 전처리 과정은 zero-mean(데이터를 전체의 평균값으로 빼준다)으로 만들거나 normalize(데이터를 표준 편차로 나눈다)한다.
zero-centered 하는 이유는 sigmoid함수에서 다룬 것과 같이 입력이 모두 양수/음수인 경우 모든 뉴런이 양/음수인 gradient를 갖기 때문이다. 하지만, 모두 양/음수인 gradient를 갖는다는 것은 좋은 최적화가 아니다.
정규화를 하는 이유는 모든 차원이 동일한 범위 안에 있게 하여 전부 동등한 기여를 하도록 만드는 것이다.
이미지의 경우 실제로는 전처리로 zero-centering 정도만 한다. 이미지는 이미 각 차원 간에 스케일이 어느정도 맞춰져 있기 때문에 정규화는 하지 않는다.
또한, 이미지를 다룰 때는 굳이 입력을 더 낮은 차원으로 만들지 않는다.
CNN에서는 원본 이미지 자체의 spatial 정보를 이용해서 이미지의 spatial structure을 얻을 수 있도록 한다.
보통 입력이미지의 사이즈를 서로 맞춰주는데 네트워크에 들어가기 전에 평균값을 빼준다. 평균값은 미니배치 단위로 학습을 시킨다고 해도 평균은 train data 전체의 평균을 계산하여 사용한다.
하지만, 채널마다 평균을 독립적으로 계산하는 경우도 있다.
VGGNet의 경우 RGB별로 평균을 구하여 학습데이터와 테스트데이터의 각 채널에 뺀다.
이미지 전처리 zero-mean이 sigmoid의 문제점을 해결해주지는 않는다. 처음에는 zero-mean이기에 해결되지만 deep network에서의 구동이라면 점점 갈수록 non-zero-mean이 될 것이다.
Weight Initialization
2-layer Neural Network 의 경우에 가중치를 어떻게 초기화시켜야 하는지 알아보자.
만약 모든 w=0이라면 동일한 w를 사용하므로 모든 뉴런이 같은 연산을 하게 된다. 따라서 출력값은 모두 같고 gradient도 모두 같게 될 것이다.
따라서 0이 아닌 임의의 작은 값으로 초기화해야 한다.
이 경우 초기 w를 표준정규분포(standard gaussian)에서 샘플링한다.
좀 더 작은 값을 위해 0.01을 나눠 표준편차를 1e-2로 만든다.
이런 식으로 모든 가중치를 임의의 값으로 초기화한다.
하지만 이 방법은 deep Network에서 문제가 발생한다.
10개의 layer로 이루어진 network가 있고 layer당 500개의 뉴런이 있다고 하자.
activation function으로 tanh를 사용하고 가중치를 임의의 작은 값으로 초기화 시킨다.
데이터를 랜덤하게 생성하여 forward pass 시킨다.
각 layer별 출력의 평균과 표준편차를 볼 수 있다. 평균은 tanh의 zero-centered 특성상 거의 0에 수렴한다. 표준편차를 보면 가파르게 줄어든다.
밑의 그래프는 layer별 평균과 표준편차를 나타낸 것이다.
w가 너무 작은 값들이라 출력값이 계속 수렴되다가 결국 0이 된다.
backward pass로 넘어가서 gradient를 구해보자.
각 layer의 입력값이 매우 작기에 입력값은 층을 지날수록 점점 0에 수렴한다.
upstream gradient와 w에 대한 local gradient인 x를 서로 곱하여 gradient를 업데이트하는데, x가 너무 작으면 업데이트되는 gradient도 점점 작아지고 결국 0에 수렴하여 업데이트되지 않는다.
그래서 w를 0.01 대신 1을 넣어 가중치 w를 초기화해보자.
가중치가 크기 때문에 w*x를 tanh에 집어넣으면 값들이 saturation(포화)될 것이다.
saturation되면 gradient는 0이 되어 가중치가 업데이트되지 않는다.
출력이 항상 -1 또는 1이 된다.
Xavier initialization
합리적인 w를 찾을 수 있는 좋은 방법 중 하나로 standard gaussian에서 랜덤으로 뽑은 값을 입력의 수로 나누어 스케일링한다.
기본적으로 Xavier initialization이 하는 일은 입/출력의 분산을 맞춰주는 것이다.
입력의 수가 많으면 w가 작아지고, 입력의 수가 작으면 더 큰 값을 얻게 된다.
각 layer의 입력이 unit gaussian이길 원하면 이런 형태의 초기화 기법을 사용할 수 있다.
여기서 가정하는 것은 linear activation이 있다고 가정하는 것이다. tanh의 경우 tanh의 active 영역(gadient가 0이 아닌 곳은 선형적 형태를 띔)안에 있다고 가정하는 것이다.
ReLU에서 실제로 ReLU의 특성때문에 반 밖에 작동하지 않기 때문에, Xavier initialization을 사용하면 잘 동작되지 않는다.
그래서 입력데이터의 개수를 2로 나눠줘야 잘 동작된다고 한다.
Batch Normalization
gaussian의 범위로 activation을 유지시키는 것에 관한 다른 아이디어가 있다. 레이어의 출력이 unit gaussian이길 바란다.
따라서 강제로 layer의 출력을 unit gaussian(0~1 사이에 존재)로 만들어준다.
즉, 각 layer에서 나온 batch 단위의 activation을 현재 batch에서 계산한 평균과 분산을 이용해 정규화함으로써 unit gaussian(평균이 0, 분산이 1 의 분포)으로 만든다.
batch당 N개로 구성된 D차원인 학습 데이터 X가 있다고 하자.
각 차원별로 실제 평균과 분산을 독립적으로 구한 후, 한 batch내에서 전부 계산해서 정규화한다. unit guassian범위를 가진 activation으로 만든다.
batch normalization은 FC나 conv layer 직후, 활성함수 직전에 넣어준다.
1
2
3
4
5
6
7
8
9
def forward(self,x):
x = x.view(-1, 28*28)
x = self.fc1(x)
x = self.batch_norm1(x)
x = F.relu(x)
x = F.dropout(x, training = self.training, p = self.dropout_prob) # dropout은 학습 과정 속에서 랜덤으로 노드를 선택해 가중값이 업데이트되지 않도록 하지만 평가 과정 속에서는 모든 노드를 이용해 output을 계산하기 때문에 학습 상태와 검증 상태에서 다르게 적용돼야 한다.
x = self.fc2(x)
x = F.log_softmax(x, dim = 1)
return x
깊은 네트워크의 경우 각 layer의 가중치 w가 지속적으로 곱해져서 bad scaling effect가 발생하는데 정규화가 이를 상쇄시켜준다.
Batch normalization은 입력의 스케일만 살짝 조정해주는 역할이기 때문에 FC나 Conv 어디든 적용할 수 있다.
Conv layer에서는 normalization을 차원마다 하는 것이 아니라 같은 activation maps들은 같이 normalize해준다. 즉 activation map마다 평균과 분산을 하나씩만 구한다. CNN이 데이터의 spatial structure을 잘 유지하기를 원하기 때문이다.
batch normalization에서는 입력값을 normalize 연산을 한 후, scaling/shifting 연산을 추가한다.
이를 통해 normalize 된 값들을 γ(분산)는 scaling의 효과를, β(평균)는 shifting 연산하여 unit gaussian으로 정규화된 값을 다시 원상복구할 수 있도록 해준다. 즉 BN하기 전 값으로 돌아간다.
이 때, γ와 β는 학습 가능한 파라미터이다. 학습시켜서 다시 indentity function(Y=X)가 되도록 하는 이유는 유연성을 얻기 위해서이다. unit gaussian이 필요하면 사용하지 않고, 아니라면 unit gaussian을 다시 scaling 하고 shifting 할 수 있다.
Q. shift와 scale을 추가시키고 학습시키면 indentity mapping(원래 상태로 돌아옴)이 되서 BN이 사라지는 것이 아닌가? 감마와 베타를 학습시키게 되면 indentity가 되지는 않는다. 일정량 변하긴 하지만 원래 상태로 돌아올 정도는 아니다. 때문에 여전히 효과를 볼 수 있다.
BN 과정을 요약하자면
- mini-batch에서의 평균, 분산을 계산
- 그것을 통해 normalize
- 다시 scaling, shifting
우리가 BN을 하는 이유는 layer의 입력을 normalization하기 위함이다. normalization하여 데이터를 gaussian distribution으로 만들어주는 것이다.
BN의 장점으로는
- batch normalization은 gradient의 흐름을 보다 원활하게 해주어 학습이 더 잘되도록 해준다.
- learning rate를 더 키울 수 있게 해준다.
- 초기화 기법을 다양하게 사용할 수 있게 해준다.
- 각 layer의 출력은 해당 데이터 하나 뿐만 아니라 batch 안에 존재하는 모든 데이터들(평균,분산)에 영향을 받기 때문에 regularization의 역할도 한다. 이는 오직 하나의 샘플에 대한 값이 아니라 batch 내의 모든 데이터가 하나로 묶일 수 있다.
BN의 평균과 분산은 학습데이터에서 구한 것이므로 test time에서는 추가적인 계산을 하지 않고, training에서 계산한 평균과 분산을 test에도 사용한다.
Babysitting the Learning Process
지금까지는 네트워크를 설계하는 것에 대해 배웠다. 이제는 학습과정을 어떻게 모니터링하고 hyperparameter을 조절할 것인지 배워보자.
data preprocessing
우선 첫 단계는 데이터 전처리다. 전처리에는 zero-mean을 사용한다.
Choose architecture
그 후로는 architecture을 선택해야 한다.
CNN, LSTM 등 다양하게 선택 가능하다.
Sanity check
네트워크를 초기화하고 forward pass를 하고난 후 loss를 구한다. 여기서 regularization은 0으로 준다.
만약 softmax classifier를 사용할 때 가중치가 작으면 loss는 -log(1/N) (N:number of classes)이 되어야 한다.
초기 loss가 정상이라면 regularization에 1e3으로 설저한다. 그 결과로 loss가 증가한다. 손실함수에 regularization term이 추가되기 때문(일반화되기 때문)이다.
sanity check(Training)
학습을 시작한다. 우선 데이터의 일부만 학습시킨다.
데이터가 적으면 당연히 overfit되고 loss가 많이 줄어든다.
이때는 regularization을 사용하지 않고 loss가 내려가는지만 확인한다.
Training
학습 시에 가장 중요하고, 가장 먼저 찾아야할 hyperparameter는 learning rate이다.
전체 데이터셋에 regularization을 약간씩 주면서 적절한 learning rate를 찾아본다.
처음에는 1e-6로 설정한다. 그 결과 loss가 잘 변하지 않는 것을 확인할 수 있다.
loss가 잘 줄어들지 않는 가장 큰 요인은 learning rate가 지나치게 작은 경우다. 지나치게 작으면 gradient 업데이트가 충분히 일어나지 않게 되기 때문이다.
loss가 잘 변하지 않음에도 training/calidation accuracy가 급상승하는 경우가 있다. 출력인 확률 값들이 퍼져있어서 loss는 잘 변하지 않지만 학습을 하고 있기에 이 확률 값들이 조금씩 옳은 방향으로 바뀌고 있다.
가중치는 서서히 변하지만 accuracy는 가장 큰 값만 취하기 때문에 급상승할 수 있는 것이다.
learning rate를 1e6으로 바꿔보자.
cost가 nan이라 함은 cost가 발산한다는 것이다.
이것은 learning rate가 너무 높을 경우 발생한다.
보통 learning rate는 1e-3~1e-5 사이의 값을 사용한다. 이 범위 사이의 값을 이용해서 cross-validation을 하여 learning rate가 적절한지 판단한다.
Hyperparameter Optimization
Cross-Validation
하이퍼파라미터를 최적화시키는 방법에는 cross-validation이 있다.
cross-validation은 training set으로 학습시키고 validation set으로 평가하는 방법이다.
이는 3강에 잘 설명되어 있다.
우선 coarse stage(first stage)에서는 넓은 범위에서 값을 골라낸다.
epoch 몇 번 만으로도 현재 값이 잘 작동하는지 알 수 있다. 따라서 epoch을 적게 설정하여 Nan이 나오거나 loss가 줄지 않느지를 보면서 빠르게 찾는 것이 좋다.
coarse가 끝나면 어느 범위가 잘 동작하는지 알 수 있다.
fine stage(seconde stage)에서는 좀 더 좁은 범위를 설정하고 학습을 길게 시켜 최적의 값을 찾는다.
Nan으로 발산하는 징조를 미리 감지할 수 있다. train동안 cost가 어떻게 변하는지 살펴봐야 한다. 이전의 cost보다 더 커진다면 학습이 잘못되고 있는 것이다.
5 epoch을 돌며 coarse search하는 과정이다.
여기서 확인할 것은 validation accuracy이다. 높은 val_acc에는 빨간색으로 표시해놨다.
빨간색으로 표시해둔 지역이 바로 fine-stage를 시작할만한 범위가 된다.
범위를 좁혀 reg는 0~10^-4, lr는 10^-4~10^-3로 설정한다.
한 가지 중요한 점은 하이퍼파라미터 최적화시에는 log scale로 값을 주는 것이 좋다.
파라미터 값을 샘플링할 때, (10^-3 ~ 10^-6)이 아닌 (-3~-6)와 같이 10의 차수 값만 샘플링해야 한다는 말이다.
여기서 가장 좋은 acc인 빨간색 박스에 포함되는 learning rate는 전부 10e-4 사이에 존재한다. 이는 최적값이 범위(0~10e-4)의 경계부분에 존재한다. 최적의 값이 10e-5나 10e-6일 수도 있기 때문에 효율적으로 탐색할 수 없다.
따라서, 최적의 값이 정한 범위의 중앙에 위치하도록 즉, (10e-2~10e-6)과 같이 범위를 잘 설정해주는 것이 중요하다.
Grid Search
하이퍼파라미터를 찾는 또 다른 방법으로 grid search라는 방법도 있다.
하이퍼파라미터를 고정된 값과 간격으로 샘플링하는 것이다.
하지만 이는 random search보다 좋지 않다.
random search하는 경우 오른쪽에 있는 것과 같이 최적의 값을 찾을 수 있지만, 왼쪽의 경우 겹치는 grid가 많아 최적의 값을 찾기 어려운 상태다. 또한 grid search가 더 오래 걸린다.
하이퍼파라미터에는 learning rate말고도 decay schedule, update type, regularization, network architecture 등이 있다.
실제로 cross validation을 통한 하이퍼파라미터 최적화를 엄청 많이 돌려봐야 한다.
cross validation으로 많은 하이퍼파라미터를 직접 돌려보고 어떤 값이 좋은지 판단해야 한다.
loss가 발산하거나 가파르게 내려가다가 정체기가 생기면 learning rate가 높은 것이고, 너무 평평하면 너무 낮은 것이다.
또한, loss가 평평하다가 갑자기 가파르게 내려가면 초기화의 문제다. backprop가 초기에는 잘 되지 않다가 학습이 진행되면서 회복되는 경우에 해당된다.
train_acc와 va_acc가 큰 차이를 보인다면 overfitting일 것이다. 따라서 regularization의 강도를 높여야 한다.
gap이 없다면 overfit되지 않은 것이기에 정확도를 높일 수 있는 충분한 여유가 존재한다.
가중치의 크기 대비 가중치 업데이트의 비율을 지켜볼 필요도 있다.
우선 파라미터의 norm을 구해서 가중치의 규모를 계산한다.
업데이트 사이즈도 norm을 통해 구할 수 있고, 업데이트 사이즈를 통해 크게 업데이트되는지를 알 수 있다.
가중치의 크기 대 가중치 업데이트 비율은 대략 0.001정도가 적당하다.
이는 디버깅할 때 유용하다.