loading

cs231n 내용 요약 (3) - Optimization(최적화)


Lecture summary

Published on November 04, 2022 by JunYoung

AI Deep learning cs231n

11 min READ

신경망 학습

바로 이전 글에서는 linear classification에서 사용할 수 있는 두 classifier인 SVM(support vector machine)과 softmax에 대해 소개하였다. 대표적인 특징으로 SVM은 각 class 별 점수를 affine function $f$를 통해 구한 뒤, 정답에 해당되는 class의 점수를 다른 class의 점수보다 특정 margin($\Delta$) 이상 차이나게끔 구별하게끔 동작하였고, softmax는 이런 점수를 logistic 함수를 활용, normalized probability로 바꾸어 KL divergence를 최소화하게끔 동작하였다. 그래서 결론에서도 언급했던 것처럼, regularization을 제외한 loss term에서는 SVM은 특정 조건만 만족하면 loss가 0이 되어서 학습을 중단하는 반면에, softmax는 지속적인 학습이 가능하다는 특징이 있었다.
그렇다면, 구체적으로 loss function을 통해 parameter를 최적화하는 방법들에 대해서 알아보도록 하자. 해당 내용은 최적화 이론 관련된 수업을 듣게 되면 보다 이해하기 쉬운데, 딥러닝에서는 주로 gradient based approach만 다루기 때문에 가끔 몇몇 논문에서 penalty term이나 projection, 혹은 Jacobian, Hessian 등등 나오게 되면 당황하는 경우가 있다. 사실 generative model에서는 굉장히 유명한 논문 중 하나인 WGAN 또한 최적화 이론의 linear programming, duality form 등등 일반적인 딥러닝 지식으로는 이해할 수 없는 개념들이 많기 때문에 보다 엄밀하게 이해하고자 한다면 probability and random variables, optimization theory 관련 수업을 같이 수강하는 것을 추천한다.
서론이 길었고, 신경망의 학습법에 다루기 위해 두 개의 주요 개념을 먼저 가지고 오면, 하나는 score function이고, 또다른 하나는 loss function이다.

  1. Score function은 예제에서의 $f$와 같으며, parameter로 구성되어있고 raw image pixel(이미지 데이터)를 각 class별 점수로 치환하는/매칭하는 함수이다.
  2. Loss function은 특정 parameter를 통해 계산된 score 결과가 우리가 원하는 기준과 얼마나 가까운지(부합하는지)에 대한 척도가 된다. 측정하는 방식으로는 SVM의 hinge loss, softmax의 cross entropy loss를 설명했었다.

[ \begin{aligned} L_i =& \sum_{j \neq y_i} \max \left( 0, W_j^\top x_i - W_{y_i}^\top x_i + \Delta \right) \newline L_i =& -\log \left( \frac{e^{f_{y_i}}}{\sum_j e^{f_j}} \right) = -f_{y_i} + \log \sum_j e^{f_j} \end{aligned}
]

위의 식은 각각 SVM에서 사용되는 hinge loss와 softmax에서 사용되는 cross-entropy loss를 식으로 표현한 것이다. 결국 우리가 score function, loss function을 각 classifier에 대해서 정의한 이유는 현재 parameter($W,~b$)에서의 결과를 토대로 기준점에서 얼만큼 떨어져있는지 파악하고(loss function), 이를 활용하여 우리가 원하는 score function $f$를 찾아가고 싶은 것이다. Score function $f$를 구성하는 두 인자인 weight($W$)와 bias($b$)를 최적화하는 과정이므로, 이 부분을 해결할 수 있는 수학적 방법론이 곧 optimization theory고 지금부터 신경망 모델 및 딥러닝에서 사용되는 가장 기본적인 최적화 방법에 대해서 다룰 것이다.


Optimization

Loss function이 최솟값을 가지도록 하고 싶다면, 가장 간단한 방법은 loss function의 개형을 아는 것이다. 고등학교 수학에서 배웠던 이차함수 개형에서 최솟값 혹은 최댓값을 찾는 문제를 기억할 것이다. 우리가 해당 함수의 극값을 구할 때 공식을 사용하여 간단하게 구할 수 있었던 이유는 이차함수가 대표적인 convex(볼록), concave(오목) function에 해당되며, convex 및 concave optimization에서는 optimal solution이 유일하게 존재하고, 그 optimal solution은 gradient가 0인 꼭짓점에 존재했기 때문이다. 우리가 정의한 loss function도 이러했다면 보다 쉬운 문제가 되었겠지만, 안타깝게도 고차원의 데이터(이미지, 텍스트 임베딩 등등)을 다루는 상황에서는 간단한 함수의 형태를 가정할 수 없다는 것이 문제가 된다. 일반적으로 이렇게 non-analytic 함수의 형태를 분석할 때 주로 사용하는 방법은 feasible direction에 대한 constraints를 주고 분석하는 것이다.
예를 들어 우리가 개형을 알 수 없는 함수 $L(\cdot)$이 있고, 현재의 weight $W$에 대해 loss 값을 $L(W)$로 측정했다고 생각해보자. 어느 방향이 global optimal solution을 찾을 수 있는 방향일지는 모르지만, $W$와 같은 dimension을 가지는 방향 벡터 $W_1$을 정의할 수 있고, 임의의 step size $\alpha$에 대해 단계적으로 진행하면서 $L(W + \alpha W_1)$ 직선 상의 loss function value를 구할 수 있다.

그림에서 좌측에 보이는 것이 바로 위에서 언급했던 것과 같이 loss function의 현재 값($L(W)$)을 기준으로 특정 방향으로 진행했을 때의 함숫값을 분석한 경우의 예시가 된다. 이처럼 feasible direction을 가정하고, 해당 방향으로 진행하게 되면 함수 전체의 형태는 시각화할 수 없지만 특정 범위 내에서의 loss function을 분석할 수 있다. 마찬가지로 만약 feasible direction에 방향 벡터 하나를 더 추가한다면 $L(W + \alpha W_1 + \beta W_2)$가 될 수 있고, 이렇게 될 경우 관찰할 수 있는 축이 두 개가 생기기 때문에 우측이나 중앙과 같이 colormap으로 그 형태를 확인할 수 있는 것을 볼 수 있다. 같은 색을 가지는 부분이 그 높이(함숫값)이 같은 부분이 되고, 이를 contour line이라고 부른다. 축은 contour line이 변화하는 방향(contour line에 수직인 방향)으로 그려지게 된다. 실제로 SVM에서 사용되는 hinge loss를 예시로 들어보도록 하자.

[ L_i = \sum_{j \neq y_i} \max \left( 0, W_j^\top x_i - W_{y_i}^\top x_i + 1 \right) ]

만약 $\Delta = 1$인 경우 hinge loss는 위와 같이 표현되며, 데이터셋으로는 1차원의 점으로 구성된 3개의 샘플을 사용한다고 생각해보자. 각 샘플에 대해서 loss를 구하면 다음과 같다.

[ \begin{aligned} L_0 =& \sum_{j \neq y_0} \max \left( 0, W_j^\top x_0 - W_{y_i}^\top x_0 + 1 \right) \newline L_1 =& \sum_{j \neq y_1} \max \left( 0, W_j^\top x_1 - W_{y_i}^\top x_1 + 1 \right) \newline L_2 =& \sum_{j \neq y_2} \max \left( 0, W_j^\top x_2 - W_{y_i}^\top x_2 + 1 \right) \end{aligned} ]

보다 간단히 확인해보기 위해 regularization term은 우선 무시하도록 하자. 아무튼 SVM에서 사용되는 hinge loss는 모든 샘플에 대해서 구한 값을 기준으로 평균을 사용한다.

[ L = \frac{L_0 + L_1 + L_2}{3}
]

앞서 가정했던 상황은 1차원의 점으로 구성된 3개의 데이터 샘플이었기 때문에 $x_i, w_j$는 스칼라와 같다. 가로축을 weight라고 생각하고 세로축을 각 loss value($L_0,~L_1,~L_2$)라고 생각하면 다음과 같이 그래프를 그려볼 수 있다.

세 loss function 모두 최소화 될 수 있는 지점이 바닥과 평평하게 맞닿아 있는 부분의 모든 $W$에 해당될 것이다. 여기서 생각해볼 수 있는 사실은 SVM에서 사용된 hinge loss function이 고차원으로 올라가게 되더라도 convex function임을 유지할 수 있지 않을까라는 사실이다. 그러나 실질적으로 convex optimization을 가정하고 문제 풀이를 하기에는 고차원의 데이터에 대한 loss의 전체 구조는 불명확하기 때문에 적절하지는 않다고 판단된다. 그리고 convex optimization을 진행하는 과정에서 함수의 gradient 및 hessian을 구해야할 경우가 생기는데, 각각 모두 loss function이 weight 전체에 대해 미분이 가능하다는 전제가 있어야하기 때문에 이 또한 하나의 constraint로 작용할 수 있다.


Optimize methods

결국 특정 weight가 loss function을 최소화할 수 있게 하려면 다른 방법이 필요하다. 그 중 가장 먼저 생각해볼 수 있는 알고리즘은 일종의 brute-force인데, Weight를 무작위로 대입하고 이를 토대로 $L(W)$를 연산한 뒤에 기존에 연산했던 loss에 비해 작은 값이 나온다면 $W$를 업데이트하는 방식이다. 그러나 이 과정은 컨셉만 확인하더라도 알 수 있듯이, weight를 랜덤 값으로 할당하고 이 중에서 가장 좋은 성능을 보이는 weight를 찾는 방법이기 때문에 최적화가 거의 불가능하다고 말할 수 있다. 실수 차원에서의 weight parameter는 무한에 가까운 경우의 수를 가지기 때문에 단순히 랜덤한 예측으로 최적의 weight를 찾을 확률은 0에 가깝다.
그렇기 때문에, 우리는 랜덤한 기준으로 weight를 잡고(이를 초기화라고 부른다) 그 weight를 어떤 방향으로 변화시켰을 때 성능 개선이 이루어지는 지에 대해 초점을 맞출 것이다. 한번에 global optimal solution을 가지는 weight를 찾는 것은 불가능하지만 지금보다 나은 weight를 찾는 것은 가능하다. 흔히 눈을 가린 hiker가 하산하는 상황으로 비유하곤 한다. 눈을 가린 hiker는 산의 가장 밑부분이 어딘지는 모르지만 지금 서 있는 위치에서 조금씩 움직였을 때, 어떤 위치로 가야만 내려가는 길인지 알 수 있다.

위에서 언급한 것과 같이 지금 위치에서 모든 feasible direction으로 움직여본 뒤, 이 중 loss value를 가장 최소로 만드는 방향으로 이동하고 같은 과정을 반복한다. 하지만 움직인 모든 곳에서 모든 방향의 함숫값을 연산해야하기 때문에 이 또한 비효율적이라고 말할 수 있다. 이 정도면 눈을 가린 hiker라기 보다는 눈도 가리고 기울기를 느끼지 못하는 hiker라고 보는게 좀 더 그럴듯한 비유가 될 것 같다.

Following the gradient

굳이 모든 방향을 탐색해보지 않더라도 현재 위치에서 함숫값을 최소로 만들 수 있는 방향에 대해 알 수 있는 방법이 있다. 바로 gradient를 계산하는 것이다. Gradient의 단순한 정의는 함수의 특정 위치에서 변화량 혹은 기울기인데, 이를 다르게 표현하면 함수의 특정 위치에서 함숫값을 가장 크게 변화시킬 수 있는 방향으로 정의된다.

[ \frac{df(x)}{dx} = \lim_{h \rightarrow 0} \frac{f(x+h) - f(x)}{h}
]

도함수의 정의는 위와 같고, gradient는 이를 다차원으로 확장시킨 개념과 같다.

[ \nabla f(x) = \begin{bmatrix} \frac{\partial f(x)}{\partial x_1} \newline \frac{\partial f(x)}{\partial x_2} \newline \vdots \newline \frac{\partial f(x)}{\partial x_n} \end{bmatrix}
]

Gradient를 구하는 방법으로는 미소 단위인 $\delta$에 대해 도함수의 정의를 따라 계산하는 numerical gradient가 있고, 실제 function이 미분 가능한 형태로 주어질 경우 analytic하게 구하는 gradient가 있다.

[ \begin{aligned} \frac{df(x)}{dx} =& \frac{f(x+\delta) - f(x)}{\delta} \newline \frac{df(x)}{dx} =& \frac{f(x+\delta/2) - f(x-\delta/2)}{\delta} \end{aligned}
]

Numerical gradient를 구할 때 $\delta$에 대해 대칭으로 구하는 방법이 있고, 위와 같이 원래 도함수 정의에 맞게 구하는 방법이 있다. 아래 방법이 조금 더 tangential line의 기울기와 가깝지 않을까 추측해본다. 이렇게 구한 gradient는 앞서 설명했던 것처럼 함수의 특정 지점에서 함숫값을 최대로 만드는 방향에 대한 정보라고 했기 때문에, 이 방향에 negative value만큼 weight를 주게 되면 함숫값을 최소로 만드는 방향이 된다.

[ W_{i+1} = W_i - \alpha \nabla_W L(W_i))
]

다만 gradient의 반대 방향으로 얼마나 parameter를 update하는가는 hyperparameter로 정의되며(step size $\alpha$), 이 값이 너무 작을 경우 수렴 속도가 너무 느리거나 너무 클 경우 수렴하지 않고 발산하는 문제가 발생하게 된다. Gradient에 대한 step size를 너무 크게 주었을 경우 overshoot 되었다고 표현하며, 더이상 하강하지 못하고 고점에서 진동하는 경우나, 혹은 역으로 더 높은 위치로 이동하게 된다.

그렇기 때문에 적절한 step size를 정해주는 것이 중요하다. 앞서 말했던 것과 같이 gradient를 구하는 방법은 numerical과 analytic이 있다고 했었다. 그러나 사실 우리가 앞서 정의했던 hinge loss나 cross entropy loss의 경우 $W$에 대해서 미분이 가능하며, 이는 analytic한 형태로 표현이 가능하다.

[ L_i = \sum_{j \neq y_i} \max \left( 0, W_j^\top x_i - W_{y_i}^\top x_i + 1 \right) ]

위의 식은 sample $i$에 대한 hinge loss를 표현한 식이다. 이 식을 $W_{y_i}$(weight parameter 중 정답인 class의 score을 구하기 위한 요쇼)에 대해서 편미분하게 되면, 다음과 같이 표현할 수 있다. 다만 $\max$ 연산은 조건에 따라 미분이 달라지기 때문에 엄밀히 따지자면 gradient가 아닌 sub-gradient의 개념으로 접근하게 된다.

[ \nabla_{w_{y_i}} = -\sum_{j \neq y_i} \mathbb{1} \left(W_j^\top x_i - W_{y_i}^\top x_i + 1 > 0 \right) x_i ]

결국 loss 계산은 정답인 class에 대해서 나머지 class들이 점수 상으로 얼마나 거리가 떨어져있는지 계산하는 과정이므로, 만약 margin인 $1$보다 그 값이 크다면 $\max \left( 0, W_j^\top x_i - W_{y_i}^\top x_i + 1 \right) = W_j^\top x_i - W_{y_i}^\top x_i + 1$이 되기 때문에,

[ \nabla_{w_{y_i}}^j = \begin{cases} \frac{\partial}{\partial W_{y_i}} \left( W_j^\top x_i - W_{y_i}^\top x_i + 1 \right) = -x_i, & W_j^\top x_i - W_{y_i}^\top x_i + 1 > 0 \newline 0, & W_j^\top x_i - W_{y_i}^\top x_i + 1 \leq 0 \end{cases} ]

위와 같이 표현할 수 있다. 모든 $j$에 대해서 더한 것이 구하고자 하는 loss의 analytic form이 된다. 위의 식은 정답인 class에 대한 row를 기준으로 한 결과고, 만약 정답이 아닌 class에 대한 row를 기준으로 한다면

[ \nabla_{w_j} = \begin{cases} \frac{\partial}{\partial W_j} \left( W_j^\top x_i - W_{y_i}^\top x_i + 1 \right) = x_i, & W_j^\top x_i - W_{y_i}^\top x_i + 1 > 0 \newline 0, & W_j^\top x_i - W_{y_i}^\top x_i + 1 \leq 0 \end{cases} ]

와 같이 계산할 수 있다.

[ \nabla_{w_{y_i}}^j = \begin{cases} \frac{\partial}{\partial W_{y_i}} \left( W_j^\top x_i - W_{y_i}^\top x_i + 1 \right) = -x_i & W_j^\top x_i - W_{y_i}^\top x_i + 1 > 0 \newline 0 & W_j^\top x_i - W_{y_i}^\top x_i + 1 \leq 0 \end{cases} ]


Stochastic gradient descent, mini-batch gradient descent의 차이

이렇게 각 샘플당 gradient를 계산하는 과정은 상당히 오래 걸린다. 만약 모델이 무겁거나 더 복잡한 형태라면 샘플 하나하나의 gradient를 계산하는 과정은 비효율적이다. 따라서 샘플을 여러 묶음으로 분리하여 gradient를 계산하는 mini-batch gradient descent를 사용하기도 한다. 이 방법은 Stochastic Gradient Descent(SGD)의 특수한 버전이라고 생각하면 된다.
Stochastic이란 random한 프로세스를 의미하는데, 예를 들어 $N$개의 샘플에 대한 loss를 한 번에 연산하기 힘들기 때문에 학습 시에 랜덤한 비복원 추출로 샘플 하나씩 추출하고, 이 샘플 하나에 대한 gradient만 계산하면 된다고 생각해보자. 아래에서 보는 것과 같이 데이터셋 전체를 하나의 묶음(batch)로 보고 계산한다면, gradient가 향하는 방향이 절대적으로 전체 데이터셋에 대한 분포를 반영하기 때문에 가장 이상적인 방향으로 최적화가 가능하다(물론 convex optimization의 가정을 가지고 있어야 한다). 하지만 연산 환경에서 약 100만장에 달하는 대용량 데이터셋이 사용된다면 한번에 loss를 계산하는 것은 거의 불가능하기 때문에(메모리 문제, 속도 문제) 이를 분리해서 학습하게 된다. 하지만 이 경우에도 단순 stochastic gradient descent 방법은 noisy한 weight update가 진행되기 때문에 데이터셋 전체의 분포를 통한 최적화에 비해 비효율적으로 학습된다는 문제가 있다. 따라서 이를 적절히 타협을 본 mini-batch optimization을 통해 최적화하게 된다. 여기서 mini-batch란 랜덤하게 1개의 샘플을 비복원추출을 하는 것이 아니라, $K$개의 샘플을 추출함으로써 전체 데이터셋의 분포의 형태를 모집단을 통해 유추하면서 학습할 수 있게끔 한다는 개념이다.
이러한 방법이 학습에 도움이 되는 이유는 training dataset이 서로 correlation을 가지고 있기 때문이다. 만약 특수한 상황을 가정하여, 120만개의 image set이 모두 1000개의 이미지 데이터를 복사해서 만들어진 이미지라 한다면, 굳이 1200개의 같은 gradient를 똑같이 계산하는 것과 같다. 물론 실제 데이터셋을 이렇게 구성하지는 않지만, 각 데이터가 서로 어느 정도 연관성을 가진다는 전제 하에 mini batch의 gradient가 결국 데이터셋 전반의 틀을 모방하여 계산하는 것과 같기 때문에(앞서 말했던 모집단의 개념) 크게 문제될 것이 없다.


결론

마무리하자면, 신경망에서 softmaxSVM classifier에서 weight($W$)와 $x$를 연산한 score와 실제 label인 $y$와의 차이로 발생하는 loss가 있고, 이 data loss에 $W$의 정규화를 위한 loss가 더해져서 objective(학습 목표)가 되었다. 그리고 이 objective를 최소화시키는 방향으로 optimization을 진행할 수 있었으며, 최적화 방법은 analytic하게 계산된 gradient를 통해 parameter update를 하는 것이었다. 여기에 추가적으로 loss term의 gradient를 모든 샘플에 대해 연산하는 것은 복잡하기도 하고 overfitting의 위험이 있기 때문에 dataset 전체를 하나의 batch로 학습하는 것이 아닌 mini-batch gradient descent를 사용하는 것까지 배울 수 있었다.

A n o t h e r p o s t i n c a t e g o r y