전체 학습 내용
1. 초기화 - 활성화 함수, 전처리, 가중치 초기화, regularization, gradient checking
2. Training dynamics - babysitting the learning process, 파라미터 업데이트, 하이퍼파라미터 최적화
3. 평가 - 모델 앙상블
오늘 다룰 내용
활성화 함수
데이터 전처리
가중치 초기화
배치정규화
babysitting the learning process
하이퍼파라미터 최적화
Activation Functions
Sigmoid

$$ \sigma(x) = 1/(1+e^{-x}) $$
- 각 입력을 받아 그 입력을 [0,1] 사이의 값이 되도록 해줍니다.
- 입력의 값이 크면 출력은 1에 가깡루 것이고 값이 작으면 0에 가까울 것입니다.
- 0 근처 구간을 보면 선형함수처럼 보입니다.
문제점 1. Saturate된 뉴런은 gradient를 없앱니다.
만약 한 뉴런에서의 입력 x가 큰 음수 값이라면 역전파 과정에서 dL/dσ 는 0이 됩니다. gradient가 죽어버리게 되고 밑으로 0이 계속 전달되게 됩니다.
문제점 2. Sigmoid의 출력이 zero-centered 하지 않습니다.

뉴런의 입력 x가 항상 양수일 경우를 생각해봅시다. 이 x는 어떤 가중치랑 곱해져 활성함수를 통과하게 됩니다.
이때 뉴런의 Local Gradient는 그냥 x 이고 모든 x가 양수란는 것은 gradient가 전부 양수 또는 전부 음수가 됩니다.
위에서 음 또는 양의 값을 가지는 dL/df(활성함수) 가 넘어와 local gradient인 df/dW와 곱해지게 됩니다. 이때 df/dW는 x이고 x는
항상 양수이기 때문에 gradient의 부호는 그저 위에서 내려온 gradient의 부호와 같아집니다.
이로 인해 모든 W가 같은 방향으로만 움직일 것입니다. 파라미터를 업데이트 할 때 다 같이 증가하거나 다 같이 감소하거나 할 수 밖에 없습니다. 이런 gradient 업데이트 방식은 아주 비효율적입니다.

W가 이차원일 경우에 전부 양수 또는 음수로 업데이트된다면 gradient가 이동할 수 있는 방향은 4분명 중 두 영역뿐입니다.
만약 이론상 최적의 해가 파란색 벡터라면 gradient는 파란색 방향으로 내려갈 수 없습니다 (오른쪽-아래). 위의 빨간색 화살표 방향과 같이 gradient가 이동 가능한 방향(오른쪽-위, 왼쪽-아래)으로만 이동할 수 있게 됩니다. 지그재그로 내려가야해서 굉장히 비효율적이어집니다.
이런 이유로 일반적으로 zero-mean data를 원합니다. 입력 x가 양수/음수 모두 가지고 있으면 전부 같은 방향으로 움직이는 일은 발생하지 않을 것입니다.
문제점 3. exp()로 인해 계산 비용이 큽니다.
그렇게 큰 문제는 아닙니다. 오히려 내적의 계산 비용이 더 큽니다. 근데 굳이 문제를 뽑자면 얘도 문제가 될 수 있겠습니다.
tanh

- sigmoid와 유사하지만 범위가 [1,-1]입니다.
- zero-centered 합니다.
- 여전히 saturation때문에 gradient가 죽습니다.
- sigmoid보다 조금 좋지만 여전히 문제점이 있습니다.
ReLU (Rectified Linear Unit)

$$ f(x) = \max(0,x) $$
- ReLU는 element-wise 연산을 수행합니다.
- 입력이 음수면 0을 출력하고 양수면 입력 값 그대로 출력합니다.
- 양의 범위에서 Saturate하지 않습니다. 적어도 입력 스페이스의 절반은 saturation 되지 않습니다.
- 계산이 아주 효율적입니다. 시그모이드는 exp 연산을 해야했던 반면에 ReLU는 max 연산이므로 계산이 매우 빠릅니다.
- Sigmoid나 tanh보다 수렴속도가 거의 6배정도 빠릅니다.
문제점 1. zero-centered 하지 않습니다.
문제점 2. 양수에서 saturation 되지 않지만 음의 경우에선 그렇지 않습니다.

x가 큰 음수값이거나 정의되진 않지만 0이라면 gradient가 0이 됩니다. 이로 인해 ReLU는 gradient의 절반을 죽여버립니다.
이로인해 dead ReLU라는 현상을 겪을 수 있습니다.
이런일은 몇가지 상황에서 발생할 수 있습니다.

ReLU에서는 평면의 절반만 activate 됩니다. ReLU가 data cloud에서 떨어져있는 경우에 dead ReLU 발생할 수 있는데 해당 ReLU에서는 activate가 일어나지 않고 update되지 않습니다. 반면 active ReLU에서는 일부는 active 되고 일부는 active 되지 않을 것입니다.
이런일이 발생하는 이유는 몇가지 존재합니다.
첫번째, 초기화를 잘못한 경우
위 그림의 dead ReLU처럼 생긴 경운데 data cloud에서 멀리 떨어져 있는 경우입니다. 이런 경우 어떤 데이터 입력에서도 activate되는 경우가 존재하지 않을 것이고 backprop이 일어나지 않습니다. 이런 경우 update도 activate도 되지 않습니다.
두번째, learning rate가 지나치게 높은 경우
더 흔한 경우입니다. 처음에 적절한 ReLU로 시작할 수 있다고 해도 만약 update를 지나치게 크게 해 버려 가중치가 날뛰면 ReLU가 데이터의 manifold를 벗어나게 됩니다. 그래서 처음에는 학습이 잘되다가 갑자기 죽어버리는 경우가 생깁니다.
그리고 실제로 학습을 다 시켜놓은 네트워크를 살펴보면 10~20% 가량은 dead ReLU가 됩니다. 이게 문제긴 하지만 ReLU를 사용하고 있다면 대부분의 네트워크가 이 문제를 격을 수 있음. 하지만 그 정도로는 네트워크 학습에 큰 지장을 줄 수 없습니다.
그래서 실제로 ReLU를 초기화할 때 update시에 active ReLU가 될 가능성을 조금이라도 더 높혀주기 위해 postivie biases를 추가해 주는 경우가 있습니다. 효과가 있다는 사람들도 있고 없다는 사람들도 있습니다. 이 방법보다 대부분은 zero-bias로 초기화합니다.
Leaky ReLU

$$ f(x) = \max(0.01x, x) $$
- ReLU와 거의 유사하지만 음수 구간에서 0이 아닙니다.
- 음의 구간에서 saturation되지 않습니다.
- 계산이 효율적이고 수렴이 빠릅니다.
- dead ReLU 현상이 발생하지 않습니다.
PReLU (Parametric Rectifier)
$$ f(x) = \max(\alpha x, x) $$
- 음의 구간에서 기울기가 있다는 점에서 Leaky ReLU와 유사하지만 기울기가 alpha라는 파라미터로 결정됩니다.
- alpha를 딱 정해놓는 것이 아닌 backprop으로 학습시키는 파라미터로 만든 것입니다.
ELU (Exponential Linear Units)

- ReLU의 이점을 가집니다.
- zero mean에 가까운 출력을 가집니다.
- 음의 구간에서 saturation 됩니다.
- ELU는 이런 saturation(deactivation)이 noise에 더 robust할 수 있다고 주장합니다.
- ReLU와 Leaky ReLU의 중간 정도로 보면 좋을 것 같습니다.
Maxout "Neuron"
$$ \max(w_1^Tx+b_1, w_2^Tx + b_2) $$
- 지금까지 본 활성함수들과 다르게 입력을 받아드리는 특정한 기본형식을 미리 정의하지 않습니다.
- 두 함수의 결과중 최적값을 출력합니다.
- Maxout은 ReLU와 Leaky ReLU의 좀 더 일반화된 형태입니다.
- Maxout 또한 선형이기 때문에 saturation되지 않으면 gradient가 죽지 않을 것입니다.
- 뉴런 당 파라미터의 수가 두배가 된다는 단점이 있습니다.
실제로는 어떻게 사용할까
ReLU가 표준으로 많이 사용되며 보통 잘 동작합니다. 다만 Learning Rate를 아주 조심히 결정해야합니다.
Leaky ReLU / Maxout / ELU도 써볼 수 있겠지만 아직 실험적입니다.
tanh도 써볼 수 있겠지만 다른 활성함수들이 더 좋을 것 입니다.
sigmoid는 사용하지 않는 것이 좋을 것 같습니다.
DATA PREPROCESSING

가장 대표적인 전처리 과정은 'zero-mean'으로 만들고 'normalize'하는 것입니다. 보통 표준편차로 normalization 해줍니다.
앞서 활성함수를 다룰 때 본 것처럼 입력이 전부 양수이면 모든 뉴런이 양수인 기울기를 얻게 되고 이는 최적하지 못한(suboptimal) 최적화가 됩니다. 이런 일은 데이터가 전부 양수일 때 뿐만 아니라 0이거나 전부 음수일 경우에도 발생합니다.
normalization을 해주는 이유는 모든 차원이 동일한 범위 안에 있게 해줘 전부 동등하게 contribute 할 수 있게 합니다.
실제로 이미지의 경우 전처리로 zero-centering 정도만 해줍니다.
여러 ML 문제와는 다르게 이미지는 이미 각 차원 간에 스케일이 어느정도 맞춰져 있기 때문에 normalization은 하지 않습니다.

ML에는 PCA나 whitening같은 더 복잡한 전처리 과정이 있지만 이미지에서는 단순히 zero-mean 정도만 사용하고 그 밖의 여러 복잡한 방법은 잘 다루지 않습니다. 일반적으로 이미지를 다룰 때는 굳이 입력을 더 낮은 차원으로 projection 시키지 않습니다.
기본적으로 이미지를 다룰 때는 zero-mean을 전처리 해줍니다. 평균값은 전체 training data에서 계산하고 test data에도 동일한 값으로 계산하게됩니다. 보통 입력이미지의 사이즈를 서로 맞춰주고 네트워크에 들어가기 전에 평균값을 빼주게 됩니다.
실제로 일부 네트워크는 채널 전체의 평균을 구하지 않고 채널(RGB)마다 평균을 독립적으로 계산하는 경우도 있습니다.
채널별로 평균이 비슷할지 아닐지는 본인이 판단하기 나름입니다.
zero-mean을 사용해야하는 이유를 추가적으로 설명드리겠습니다.

위의 이진분류 문제를 푸는 상황이고 왼쪽은 normalization/zero-centered 되지 않은 경우이고 오른쪽은 적용된 경우입니다.
물론 단순하게 생각해서 왼쪽의 경우에도 classification이 가능하지만 선이 조금만 움직여도 classification이 잘 되지 않습니다. 이는 손실함수가 아주 약간의 가중치 변화에도 엄청 예민하다는 것을 의미합니다. 왼쪽의 경우에 Loss가 파라미터에 너무 민감하기 때문에 동일한 함수를 쓰더라도 오른쪽에 비해 학습 시키기 아주 어렵습니다.
반면 오른쪽에서 선의 기울기가 살짝식 왔다갔다 하는 경우를 생각해보면 손실함수가 가중치의 변동에 왼쪽 경우보다 덜 민감하다는 것을 할 수 있습니다. 이 경우가 최적화가 더 쉽습니다.
이는 Linear Classification의 경우에만 국한되는 것이 아닙니다. Neural Network 내부에도 다수의 linear classifier가 있다고 생각할 수 있기 때문에 입력이 zero-centered/Unit variance가 아닌 경우라면 레이어의 Weight Matrix가 아주 조금만 변해도 출력은 엄청 심하게 변하게 돼 학습이 어려워집니다.
Weight Initialization
Q. 만약 모든 가중치를 0으로 초기화하면 어떻게 될까
A. 모든 뉴런이 똑같은 일을 할 것입니다. 가중치가 0이기 때문에 모든 뉴런은 동일한 연산을 수행합니다.
출력, gradient 전부 같습니다. 결국 모든 가중치가 똑같은 값으로 업데이트 됩니다.
가중치를 동일한 값으로 초기화 시켰을 경우 이런 일이 발생합니다.
해결책 1. 임의의 작은 값으로 초기화 하기
W = 0.01 * np.random.randn(D,H)
이 경우 초기W를 표준정규분포에서 샘플링합니다. 좀 더 작은 값을 위해 0.01을 곱해 표준편차를 1e-2로 만들어줍니다.
네트워크의 크기가 작다면 symmetry breaking하기에 충분하지만 더 깊은 네트워크에서 문제가 생길 수 있습니다.
문제점을 살펴봅시다.
D = np.random.randn(1000, 500)
hidden_layer_sizes = [500] * 10
nonlinearities = ['tanh'] * len(hidden_layer_sizes)
act = {'relu':lambda x: np.maximum(0,x), 'tanh':lambda x:np.tanh(x)}
Hs = {}
for i in range(len(hidden_layer_sizes)):
X = D if i==0 else Hs[i-1]
fan_in = X.shape[1]
fan_out = hidden_layer_sizes[i]
W = np.random.randn(fan_in, fan_out) * 0.01
H = np.dot(X,W)
H = act[nonlinearities[i]](H)
Hs[i] = H
10개의 레이어로 이루어진 네트워크고 레이어당 500개의 뉴런이 있습니다. nonlinearities로는 tanh를 사용하고 가중치는 임의의 작은 값으로 초기화시킵니다. 데이터를 랜덤으로 만들어주고 forward pass 시켜보겠습니다. 그리고 각 레이어별 activations 수치를 통계화 시켜보겠습니다.
print('input layer had mean %f and std %f'%(np.mean(D), np.std(D)))
layer_means = [np.mean(H) for i,H in Hs.items()]
layer_stds = [np.std(H) for i,H in Hs.items()]
for i,H in Hs.items():
print('hidden layer %d had mean %f and std %f'%(i+1, layer_means[i], layer_stds[i]))
plt.figure()
plt.subplot(121)
plt.plot(Hs.keys(), layer_means, 'ob-')
plt.title('layer mean')
plt.subplot(122)
plt.plot(Hs.keys(), layer_stds, 'or-')
plt.title('layer std')
plt.figure()
for i,H in Hs.items():
plt.subplot(1, len(Hs), i+1)
plt.hist(H.ravel(), 30, range=(-1,1))

좌상단에 보이는 값들은 각 레이어의 출력의 평균과 표준편차를 계산한 것입니다. 레이어들의 평균은 tanh의 특성상 항상 0 근처에 있습니다. 하지만 표준편차를 보게되면 아주 가파르게 줄어 0에 수렴하게 됩니다.
첫번째 레이어에서는 가우시안스럽게 생긴 좋은 분포를 가지지만 W를 곱하면 곱할수록 W가 너무 작은 값들이라 출력 값이 급격히 줄어듭니다. 그리고 결국 0이 됩니다.
backward pass 할때 upstream gradient를 local gradient와 곱하게 됩니다. 그때 local gradient를 구해보면 WX를 W에 대해 미분해 결국 X가 될텐데 X가 엄청 작은 값이기 때문에 gradient도 작을 것이고 결국 업데이트가 잘 일어나지 않을 것입니다.
이번엔 가중치를 큰 값으로 초기화하면 어떻게 될지 알아보겠습니다.

가중치의 편차를 0.01이 아니라 더 큰 값인 1로 했을 경우에 레이어의 출력이 항상 -1 이거나 +1 일 것입니다. 그렇기 때문에 tanh의 출력은 saturation될 것이고 gradient는 0이 될 것입니다. 따라서 가중치의 업데이트가 일어나지 않게 됩니다.
가중치가 너무 작으면 사라져버리고 너무 크면 saturation되어 버립니다. 그래서 사람들이 초기화하기 좋은 방법을 찾으려했습니다.
그 중 하나가 Xavier Initialization입니다.
Xavier Initialization
W = np.random.randn(fan_in, fan_out) / np.sqrt(fan_in)
Standard Gaussian으로 뽑은 값을 입력의 수로 스케일링 해줍니다. 기본적으로 Xavier Initilization가 하는 일은 입/출력의 분산을 맞춰주는 것입니다.
이 수식을 통해 직관적으로 이해할 수 있는 것은 입력의 수가 작으면 더 작은 값으로 나눠 더 큰 값을 얻게됩니다. 이는 입력의 수가 작을 경우 분산이 작아지는 것을 막기 위해 더 큰 가중치로 초기화해 더 큰 값을 얻을 수 있기 때문입니다. 반대로 입력의 수가 많을 경우에는 더 작은 가중치가 필요합니다.
각 레이어의 입력이 Unit gaussian이길 원한다면 이런 류의 초기화 기법을 사용해 볼 수 있습니다.
여기서 가정하는 것은 현재 linear activation이 있다는 것입니다. 가령 tanh의 경우 tanh의 active region 안에 있다고 가정하는 것입니다.

Xavier Initilization + ReLU

Xavier Initilization과 ReLU를 함께 사용하면 잘 동작하지 않습니다. ReLU는 출력의 절반을 죽입니다. 그 절반은 매번 0이됩니다. 따라서 점점 더 많은 값들이 0이 되고 비활성 됩니다. 결국 분포가 계속 줄어 결국 0이 돼버리고 맙니다.
W = np.random.randn(fan_in, fan_out) / np.sqrt(fan_in/2)
이 문제를 해결하기 위한 일부 논문에서는 뉴런 들 중 절반이 없어진다는 사실을 고려해 추가적으로 2를 더 나눠줍니다.

실제로 잘 동작합니다. 결과를 보시면 전체적으로 좋은 분포를 형성하고 있는 것을 볼 수 있습니다.
이런 작은 변화는 트레이닝에 있어서 엄청난 차이를 보입니다. 가령 일부 논문을 보면 그런 차이가 트레이닝이 정말 잘되거나 하나도 안되거나를 결정하는 결과를 보이기도 합니다.
초기화는 우선 Xavier Initilization을 해보고 나서 그 밖에 다른 방법을 시도해 봅시다.
Batch Normalization
$$ \hat{x}^{(k)} ={x^{(k)} - E[x^{(k)}]\over{\sqrt{Var[x^{(k)}]}}} $$
레이어의 출력을 강제로 unit gaussian으로 만듭니다.
어떤 레이어로부터 나온 Batch 단위 만큼의 activation이 있다고 했을 때 이 값들을 현재 Batch에서 계산한 평균과 분산으로 normalization해 Unit Gaussian으로 만듭니다.
가중치를 잘 초기화 시키는 것 대신에 학습할 때마다각 레이어에 이런 일을 해줘서 모든 레이어가 unit gaussian이 되게 합니다.
그래서 이제부터할일은 네트워크의 forward pass 동안에 그렇게 되도록 명시적으로 만들어 주는 것입니다.
또한 평균과 분산을 상수로 가지고만 있으면 언제든지 미분이 가능해 backprop이 가능하게 됩니다.

Batch당 N개의 학습 데이터가 있고 각 데이터가 D차원이라고 해봅시다.
각 차원(feature element)별로 평균을 각각 구해줍니다. 한 Batch 내에 이걸 전부 계산해서 Normalize합니다.

보통 배치정규화는 FC나 Conv Layer 직후에 넣어줍니다. 깊은 네트워크에서 각 레이어의 W가 지속적으로 곱해져서 Bad scling effct가 발생했지만 Normalizationd는 그 bad effect를 상쇄시켜줍니다. batch normalization은 입력의 스케일만 살짝 조정해주는 역할이기 때문에 FC와 Conv 어디에든 적용할 수 있습니다.
Conv Layer에서 차이점이 있다면 Normalization을 차원(feature)마다 독립적으로 수행하는 것이 아니라 Activation Map의 같은 채널에 있는 요소들을 같이 Normalize 해줍니다. 왜냐하면 Convolutional Property 때문에 같은 방식으로 normalize 시켜줘야 하기 때문입니다. 따라서 Conv Layer의 경우엔 Activation map(채널, Depth)마다 평균과 분산을 하나만 구합니다.
제대로된 흐름도 살펴보고 gradient 어떻게 구하는지 궁금하면 논문을 한번 봅시다.
http://proceedings.mlr.press/v37/ioffe15.html
Batch Normalization: Accelerating Deep Network Training by Reducing Internal Covariate Shift
Training Deep Neural Networks is complicated by the fact that the distribution of each layer’s inputs changes during training, as the parameters of the previous layers change. This slows down the t...
proceedings.mlr.press
Normalization이 하는 일은 입력이 tanh의 linear한 영역에만 존재하도록 강제하는 것입니다. 그렇게 되면 saturation이 전혀 일어나지 않지만 그것보단 '얼마나' saturation이 일어날지를 우리가 조절하면 더 좋을 것 같습니다.

왼쪽 아래 수식에서 볼 수 있듯이 Batch Normalization에서는 Scaling 연산을 추가합니다. 이를 통해 Unit Gaussian으로 normalize된 값들을 감마로는 스케일링의 효과를, 베타로는 이동의 효과를 줍니다. 매우 극단적인 상황에서 네트워크가 감마는 분산을 베타는 평균을 학습한다면 네트워크가 값들을 원상복구 시킬 수 있습니다. 하지만 실제로는 잘 일어나지 않습니다.
이를 통해 네트워크가 데이터를 tanh에 얼마나 saturation 시킬지를 학습하기 때문에 유연성을 얻을 수 있습니다.

BN을 다시 모든 mini-batch마다 각각 평균과 분산을 계산해줍니다. 그리고 평균과 분산으로 Normalize한 이후에 다시 추가적인 scaling, shifting factor를 사용합니다. BN은 gradient의 흐름을 보다 원활하게 해주며 결국 학습이 더 잘되게(robust) 해줍니다.
BN을 쓰면 learning rates를 더 키울 수도 있고 다양한 초기화 기법들도 사용해 볼 수 있습니다.
또한 BN은 Regularization의 역할도 합니다. 각 레이어의 출력은 batch안에 존재하는 모든 데이터들의 평균과 분산으로 normalize되기 때문에 batch안에 존재하는 모든 데이터들에 영향을 받습니다.
bn은 평균과 분산을 학습데이터에서 구하고 test time에서 추가적인 계산을 하지 않습니다. trainig time에 running averages 같은 방법으로 평균, 분산을 계산하고 test time에 사용합니다.
Babysitting the Learning Process
학습 과정을 다루는 방법에 대해 알아봅시다. 지금까지는 네트워크 설계를 배웠고 이제는 학습과정을 어떻게 모니터링하고 하이퍼파라미터를 조절할 것인지 배워보겠습니다.
1단계 : 데이터전처리
앞서 배운 것처럼 zero-mean을 사용합니다.
2단계 : 아키텍처 선택

우선 하나의 Hidden Layer와 50개의 뉴런을 가진 모델로 해보겠습니다. 그 밖에 어떤 모델을 선택해도 상관 없습니다.

이제 Loss가 잘 동작하는지 알아봅시다. Forward pass를 하고난 후에 Loss가 그럴듯 해야 합니다. 가령 우리가 softmax를 사용하고자 한다면 우리는 가중치가 작은 값일 때 Loss가 대강 어떻게 분포해야하는지를 이미 알고 있습니다. Softmax classifier의 Loss는 negative log likelihood 가 되어야합니다. 가령 10개의 클래스라면 Loss는 -log(1/10)이 될 것입니다. 위에서 loss가 약 2.3 정도 되는 것을 볼 수 있는데 이를 통해 Loss가 원하는 대로 동작한다는 것을 알 수 있습니다. 이는 꽤 유용한 sanity check입니다.

이번엔 Regularization을 추가해보겠습니다. 손실함수에 Regularization term이 추가되기 때문에 Loss가 증가했습니다.
이 또한 유용한 sanity check입니다.
이제 학습할 준비가 끝났고 학습을 시작해봅시다.

처음 시작할 때 좋은 방법은 데이터의 일부만 우선 학습시켜 보는 것입니다. 데이터가 적으면 당연히 overfit이 생길 것이고 loss가 많이 줄어들 것입니다. 이때는 regularization을 사용하지 않고 Epoch마다 Loss가 0을 향해 잘 내려가는지 확인합니다.
지금까지의 sanity check가 잘 끝났다면 이제부터 진짜 학습을 시작해보겠습니다.
이젠 전체 데이터셋을 사용할 것이고 regularization을 약간만 주면서 적절한 learning rate를 찾아야 합니다.
learning rate는 가장 중요한 하이퍼파라미터 중 하나이고 가장 먼저 정해야만 하는 하이퍼파라미터입니다.
learning rate를 몇가지로 정하고 실험해봅시다.

우선 위에서 learning rate를 1e-6으로 정해봤습니다. loss가 좀처럼 변하지 않았습니다. loss가 잘 줄어들지 않는 가장 큰 요인은 learning rate가 지나치게 작은 경우입니다. learning rate가 지나치게 작으면 gradient 업데이트가 충분히 일어나지 않게되고 cost가 안 변하게 됩니다.
여기서 유심히 살펴봐야할점은 loss가 잘변하지 않음에도 training/validation accuracy가 20%까지 급상승합니다.
이는 현재 확률 값들이 아직까지 멀리 퍼져있기 때문에 loss가 여전히 비슷비슷한 것입니다. 하지만 이 확률은 조금씩 옳은 방향으로 바뀌고잇습니다. 우리가 지금 학습을 하고 있기 때문입니다.
가중치와 loss는 서서히 변하고 있지만 accuracy는 갑자기 뛰어버릴 수 있습니다. Loss는 각 클래스에 대한 확률값들을 비교하지만 accuracy는 단지 예측한 클래스가 정답이면 1 아니면 0이기 때문에 갑자기 뛸 수 있는 것입니다.

이번엔 더 큰 값인 1e6으로 learning rate를 바꿔보겠습니다. 결과를 보면 cost가 NaN임을 알 수 있습니다. 이는 cost가 발산했음을 의미합니다. 주된 이유는 learning rate가 지나치게 높기 때문입니다. 이 경우에는 learning rate를 낮춰야 합니다.
보통 learning rate는 1e-3 에서 1e-5 사이의 값을 사용합니다. 이 범위 사이의 값들을 이용해서 cross-validation을 수행해 줍니다. 이 사이의 값들을 이요해서 learning rate가 지나치게 작은지 아니면 큰지 정할 수 있습니다.
Hyperparameter Optimization
그렇다면 하이퍼 파라미터를 최적화시키고 그중 가장 좋은 것을 선택하려면 어떻게 해야할까요?
취할 수 있는 한가지 전략은 바로 cross-validation 입니다.
두가지 스테이지를 통해 확인해볼 수 있습니다.
1. Coarse Stage
넓은 범위에서 값을 골라냅니다. 적은 Epoch 수만으로도 현재 값이 잘 작동하는지 알 수 있습니다. NaN이 뜨거나 Loss가 줄지 않거나 하는 것을 보면서 적절히 잘 조절할 수 있을 것입니다. 이 Stage가 끝나면 어느 범위에서 잘 동작하겠다를 대충 알게 될 것입니다.
2. Fine Stage
Coarse Stage 이후에 좀 더 좁은 범위를 설정해 학습을 더 길게 시켜보면서 최적의 값을 찾습니다.
NaNs로 발산하는 징조를 미리 감지할 수도 있습니다. Train 동안에 Cost가 어떻게 변하는 지를 살펴보는 것입니다. 이전의 cost 보다 매우 커졌다면 잘못 되고 있는 것입니다. Cost가 엄청 크고 빠르게 오르고 있다면 loop를 멈춰버리고 다른 하이퍼파라미터를 선택하면 됩니다.
예시를 한번 보겠습니다.
Coarse Search

위 예시는 5 epochs를 돌며 coarse search를 하는 과정입니다. 네트워크 구조는 앞서 만든 것과 유사합니다. 여기에서 확인해야할것은 validation accuracy 입니다. 높은 val_acc에는 빨간색으로 표시해놨습니다. 빨간색으로 표시해둔 지역이 바로 fine-stage를 시작할 만한 범위가 될 것입니다. 한 가지 주목할 점은 하이퍼파라미터 최적화 시에는 Log scale로 값을 주는 것이 좋습니다. 파라미터 값을 샘플링할때 10^-3 ~ 10^-6을 샘플링하지 말고 10의 차수 값만 샘플링하는 것이 좋습니다 (-3 ~ -6) 왜냐하면 learning rate는 gradient와 곱해지기 때문에 learning rate의 선택 범위를 log scale을 사용하는 편이 좋습니다. 따라서 차수(orders of magnitude)를 사용하는 것이 좋습니다.
Fine Search

범위를 다시한번 조절해보겠습니다. reg는 범위를 10^-4 에서 10 ^0 정도로 좁히면 좋을 것 같습니다.
하지만 여기엔 문제가 있습니다. 우리는 여기에서 가장 좋은 Accuracy를 찾았습니다(빨간 화살표). 잘 보면 good learning rates는 전부 10e-4 사이에 존재합니다. learning-rate의 최적값들이 우리가 다시 좁혀 설정한 범위의 경계부분에 집중되어 있다는 것을 알 수 있습니다. 이렇게 되면 최적의 learning rate를 효율적으로 탐색할 수 없습니다. 실제로 최적의 값이 1E-5나 1E-6 쯤에 존재할 수도 있습니다. 탐색 범위를 조금 이동시킨다면 더 좋은 범위를 찾을 수 있을지도 모릅니다.
당연히 최적의 값이 내가 정한 범위의 중앙 쯤에 위치하도록 범위를 잘 설정해 주는 것이 중요합니다.

하이퍼파라미터를 찾는 또 다른 방법은 grid search를 이용하는 것입니다. 하이퍼 파라미터를 고정된 값과 간격으로 샘플링하는 것입니다. 하지만 실제로는 grid search 보다는 random search를 하는 것이 더 좋습니다. random이 더 좋은 이유는 바로 내 모델이 어떤 특정 파라미터의 변화에 더 민감하게 반응을 하고 있다고 생각해보면 이 함수가 더 비효율적인 dimentionality를 보인다고 할 수 있고 (노랑에는 별로 영향을 받지 않음) Random Search는 중요한 파라미터에게도 더 많은 샘플링이 가능하므로 위에 그려놓은 초록색 함수를더 잘 찾을 수 있을 것입니다. grid layout에서는 정해진 샘플링 밖에 할 수 없으므로 good region을 제대로 찾을 수 없습니다.
앞서 corase search ->fine search 순으로 진행해야한다고 했습니다. coarse search 단계에서는 하이퍼 파라미터를 조금 더 넓게 설정하고 iteration도 작게 설정해 학습시켜봅니다. 그리고 fine search 단계에서는 결과가 좋은 범위로 좁혀 iteration을 조금 더 돌면서 다시 탐색합니다. 적절한 하이퍼파라미터를 찾을 때까지 이 과정을 반복합니다.
가장 중요한 점은 coarse range를 설정할 때 가능한 최대로 범위를 설정해 줘야 한다는 것입니다. 그 범위가 하이퍼파라미터의 전범위를 살펴볼 수 있도록 충분히 넓어야합니다.
Q. 보통 하이퍼파라미터를 몇개 선택하나요?
A. 모델에 따라 다릅니다. 선택한 하이퍼파라미터의 수가 많을수록 기하급수적으로 경우의 수가 늘어납니다. 따라서 한번에 너무 많이 테스트 할 수 없습니다. 또 이는 얼마나 많은 자원을 학습에 사용할 수 있는지도 중요합니다. 이는 사람마다 다르고 실험마다 다릅니다. 보통 두세가지 정도를 고르는 편이고 많아도 네가지 정도만 선택하고 그 이상은 통제하기 어려워집니다.
일반적으로 learning rate가 가장 중요해 learning rate를 가장 먼저 선택해 놔야만 합니다. regularization, learning rate decay, model size 같은 것들은 lr보단 덜 중요합니다.
Q. 어떤 하이퍼파라미터의 값을 변경할 시에 다른 하이퍼파라미터의 최적 값이 변해버리는 경우가 빈번하나요?
A. 가끔 발생합니다. learning rates가 이런 문제에 덜 민감함에도 실제로 발생하곤 합니다. learning rates가 좋은 범위 내에 속하길 원하지만 보통은 optimal 보다는 작은 값이고 학습 속도가 길어지곤 합니다. 이를 해결할 더 좋은 최적화 방법은 다음 강의에서 알아봅시다.
다양한 하이퍼파라미터가 존재합니다.
- 네트워크 아키텍처
- learning rate, decay scehdule, update type, regularization
- hidden unit, depth
등이 있는데 일부는 이번 시간에 다뤘고 나머지는 다음 강의에서 다뤄봅시다.
Cross Validation

실제로 하이퍼파라미터 최적화와 Cross-validation을 정말 많이 해야 할 것입니다. cross validation으로 엄청나게 많은 하이퍼파라미터를 직접 돌려보고 모니터해서 어떤 값이 좋고 나쁜지를 확인해야합니다. 위에 보이는 loss curve를 보면서 좋은 것을 찾아서 시도해보는 일을 계속 반복해야합니다.
Loss Curve로 Learning rate를 알아보자

loss curve를 모니터링 하는데 있어서 learning rate이 정말 중요합니다. 그리고 loss curve를 보고 어떤 learning rate가 좋고 나쁜지를 알 수 있습니다. loss가 발산하면 learning rate가 너무 높은 것이고 너무 평평하다 싶으면 너무 낮은 것입니다. 또 가파르게 내려가다가 어느 순간 정체기가 생기면 이 또한 여전히 너무 높다는 의미입니다. learning step이 너무 크게 점프해서 적절한 local optimum에 도달하지 못하는 경우입니다. 최적의 learning rate는 보통 파란색의 Loss curve 형태를 보입니다. 비교적 가파르게 내려가면서도 지속적으로 잘 내려간다면 현재 learning rate를 유지해도 좋습니다.
Loss curve

Loss가 평평하다가 갑자기 가파르게 내려가는 것을 보게 된다면 이는 초기화의 문제일 수 있습니다. gradient의 backprop이 초기에는 잘 되지 않다가 학습이 진행되면서 회복되는 경우입니다.
Accuracy 모니터링하기

accuracy를 모니터링 하다보면 겪을 수 있는 일인데 만일 train_acc와 va_acc가 큰 차이를 보인다면 overfit일지도 모릅니다 따라서 regularization의 강도를 높혀야 할지도 모릅니다. gap이 없다면 아직 overfit 하지 않은 것이고 capacity를 높힐 수 있는 충분한 여유가 있다는 것을 의미합니다.
가중치 크기 대비 가중치 업데이트 추적하기
# assume parameter vector W and its gradient vector dW
param_scale = np.linalg.norm(W.ravel())
update = -learning_rate * dW
update_scale = np.linalg.norm(update.ravel())
W += update
print(update_scale / param_scale)
가중치의 크기 대비 가중치 업데이트 비율을 지켜볼 필요가 있습니다. 우선 파라미터의 norm을 구해서 가중치의 규모를 계산합니다. 그리고 업데이트 사이즈 또한 norm을 통해 구할 수 있고 이를 통해 얼마나 크게 업데이트 되는지를 알 수 있습니다. 대충 이 비율이 0.001 정도 되길 원합니다. 이 값은 변동이 커서 정확하지 않을 수 있지만 업데이트가 지나치게 크거나 작은지에 대한 감을 어느정도 가질 수는 있습니다. 업데이트가 너무 지나치게 커서도 너무 없어도 안됩니다. 문제가 뭔지 디버깅할때 유용하게 사용할 수 있습니다.
요약
활성화함수는 ReLU 사용하기
이미지 데이터 전처리는 평균 빼기
가중치 초기화는 xavier initialization 사용하기
배치 정규화 사용하기
하이퍼파라미터 최적화는 random search 사용하기
'인공지능 > cs231n' 카테고리의 다른 글
| [cs231n 정리노트] 5. Covolutional Neural Networks (0) | 2025.03.31 |
|---|---|
| [cs231n 정리노트] 4. Back-Propagation & Neural Networks (0) | 2025.03.21 |
| [cs231n 정리노트] 3. Loss Functions and Optimization (3) Optimization (0) | 2025.03.16 |
| [cs231n 정리노트] 3. Loss Functions and Optimization (2) Regularization (0) | 2025.03.10 |
| [cs231n 정리노트] 3. Loss Functions and Optimization (1) (0) | 2025.03.10 |