Object detection
classification은 단일 객체에 대해 분류를 하는 것이다. 이 단일 객체의 위치를 찾아주는 것을 localization이라 하는데, 멀티 객체애 대해 탐색을 하고자 했고, 이 방법이 object detection이다.
localization에서의 출력은 classifer 및 객체 위치에 대한 x,y,w,h 이다.
예전에는 sliding window와 같은 방법으로 객체를 검출했다. 그러나 이는 너무 연산량도 많고, 속도도 느리다는 단점이 존재했다. 그래서 최근에는 classifier와 regressor, 두 개를 출력하는 two stage 모델과 이 두개를 한꺼번에 하는 one stage 모델이 개발되었다.
two stage에는 RCNN시리즈가 있고, one stage에는 SSD, Yolo 등이 있다.
이전의 classfication의 마지막 단은 fc layer였다. 그러나 object detection에서의 마지막 단은 conv layer로 구성되어 있다. 이 둘의 차이는 flatten를 하냐 마냐의 차이다. flatten을 하게 되면 위치 정도가 사라지게 되고, conv layer로 출력이 되면 위치 정보가 그대로 유지할 수 있다.
one stage의 object detection의 출력은 [H,W class num + box_offset_confidence]
One stage detection
SSD, YOLO 등이 이에 해당하는데, 속도는 빠르지만 정확도가 다소 낮은 편이다.
모델 구조
- backbone
classification 모델에서 봤듯이 input가 입력되면 feature map이 점점 작아지면서 더 함축적인 의미의 feature map을 만드는 역할을 하는 것을 backbone 또는 feature extractor이라 한다.
layer가 깊어질수록 추상화된다.
- neck
이렇게 추출된 각기 다른 해상도의 feature map들은 neck 단계에서 합쳐진다. 두개 의 feature map을 결합을 할 때는 동일한 크기의 feature map으로 만들어 준 후 elementwise concat 또는 add을 통해 결합하게 된다.
A matrix [C, H, W]와 B matrix [C, H, W]를 결합하면 concat matrix는 [2C, H, W]의 shape을 가지게 된다. add matrix는 [C, H, W]의 shape을 가진다.
- dense prediction
합쳐진 feature map들을 통해 prediction을 한다. 중요한 것은 하나의 feature map에서 결과가 나오는 것이 아니라 여러 개의 feature map에서 나온 결과를 합치는 것이다.
class와 bounding box를 출력하는 부분이다. 이를 header 또는 prediction layer이라 부른다.
Two stage detection
RCNN 시리즈가 two stage detection 모델에 해당하는데, 속도가 조금 느리나 정확도가 높은 편이다.
가장 큰 특징은 region proposal을 추출하고, CNN을 연산한 후 이 rego=ion proposal과 결합하여 예측한다.
dense prediction까지는 동일하나 뒤에 prediction 단이 하나 더 존재한다. dense prediction에서는 후보군을 출력하고, 이에 대해 NMS를 통해 박스의 개수를 줄여 출력한다.
용어
object detection을 하다보면 다양한 용어들이 나온다. 그 용어들을 차근차근 살펴보고자 한다.
Grid
header layer의 마지막 feature map에서의 픽셀 수를 grid라 한다. 즉, network를 통해 출력된 feature map이 13x13을 가진다면 grid도 13x13의 사이즈를 가지고 있다고 할 수 있다.
마지막 feature map은 이미지의 정보를 담고 있는 것이므로 이미지에 삽입해보면 전체 이미지를 grid로 나눈 그림을 볼 수 있다.
한 grid마다 box prediction, objectness score, class score의 정보를 anchor Box 개수만큼 가지고 있다. 즉, 각 BOX마다의 box prediction[tx,ty,tw,th], objectness score[confidence], class score[p1,p2,…,pc] 의 정보를 가지고, 이것이 box개수만큼 존재한다.
Anchor
feature map에서 bounding box를 예측할 떄 사용되는 detector이다. anchor는 각 grid안에 특정 개수로 존재하고, 1개의 anchor 당 1개의 object를 예측한다. 이 anchor의 크기는 미리 정의해줘야 한다.
이 때, 크기는 다양하게 정의해야 한다. 그 이유는 object의 모양이 어떻게 되어 있는지 확인하기 어려우므로 여러 개의 anchor size를 선언해줘야 잘 예측할 수 있다.
anchor가 5개라고 하면, 각 anchor마다 box offset, objectness confidence, class confidence가 5개씩 존재한다. box offset(4) + objectness confidence(1) + class confidence(n_classes)개수만큼의 element를 가지므로, class의 개수가 20개라 하면 25개의 element를 가진다. 1개의 grid안에 5개의 anchor가 있다고 하면 feature map의 채널은 (25*5=125)채널을 가진다.
objectness, class score
objectness score란 원하는 object에 대해서만 gt를 1로 두는 것이다. class score는 class 별로 gt를 구분해놓고 class별로 객체에 대한 class를 예측하는 부분에 대한 score이다.
IOU(Intersection over union)
두 bounding box를 얼마나 잘 예측했는지에 대해 비교하기 위해 IOU개념을 사용한다. 용어 그대로 영역의 결합 정도를 추론한다. 이 IOU가 threshold보다 크면 positive box, 작으면 negative box라 할 수 있다.
\[IOU = \frac{A \cap B}{A \cup B}\]NMS(Non-Maximum Suppression)
1개의 grid에 대해 n개의 anchor가 있고, 1개의 anchor마다 1개의 객체를 예측하면 n개의 bounding box가 나오게 된다. 같은 객체를 검출하는 bounding box들에 대해 anchor들을 filtering하는데, 가장 큰 score을 가진 것만 남긴다.
추가적으로 많이 사용되는 함수들에 대해 자세히 설명하려고 한다.
softmax
우리가 예측한 class score는 0.9,0.4,0.2 등의 값으로 존재한다. 이를 확률값으로 가지기 위해 softmax를 적용하면 total sum이 1이 되도록 만든다. 즉 [0.9,0.4,0.2]에 softmax를 적용하면 [0.4755, 0.2844, 0.2361]이 된다.
소프트맥스는 최대(max)를 모방하여 네트워크의 출력에 대해 0 또는 1로 만드는 것이 아닌, 출력값들을 $ \frac{e^i} {\sum_{i=1,c} e^i} $ 으로 계산한다. 즉, 지수함수로 치환한 상태로 다 더한 값에 대한 해당 index의 값으로 나타낸다.
loss function
MSE loss, MAE loss
이전에 얘기했던 n1 norm, n2 norm과 비슷하게 MAE는 절대값을 사용해서 loss를 구하는 방식이고, MSE는 제곱을 통해 Loss를 구하는 방식이다. 오차가 클수록 error 값이 커지므로 정량적 성능으로 활용된다. 이 방법의 문제는 두 벡터 사이의 거리를 비교하는 것이므로 error가 큰 경우임에도 불구하고, gradient가 작아서 갱신되는 폭이 작을 수 있다.
다시 말해 grdient를 구하는 방식을 간략하게 설명하면, $$ MSE = \frac{1}{2} | y - o | _2^2 $$ 일 때, network 구조가 wx + b = o 이고, ground truth = y일 때, 즉, 아래와 같은 구조를 가지고 있다고 가정하고, error에 대한 식을 세우면 $ e = \frac{1}{2}(y - o)^2 = \frac{1}{2}(y - \sigma(wx + b))^2 $ 이다. |
이 때, 각각의 gradient를 구해보면, w에 대한 error의 gradient는 $ \frac{\partial e}{\partial w} = -x\sigma’(wx + b) * x * (y - \sigma(wx + b)) = 0.048 $ 이다. 그러나 b = 3, w = 1.9, output = 0.9971, y = 0 이라면 $ \frac{\partial e}{\partial w} = 0.0043 $ 이 나온다. 후자의 경우가 더 큰 error값을 가지므로 더 큰 gradient를 가져야 학습이 잘 진행되지만, 계산의 결과 gradient가 더 작게 나왔다.
그 이유는 sigmoid의 함수 형태에 있다. 함수 형태는 0에 가까울수록 gradient가 크고, 0에서 멀어질수록 gradient가 작다. 따라서 값이 크면 더 큰 gradient를 가져야 하지만, sigmoid의 형태의 특성으로 인해 이상하게 작동한다.
Cross entropy loss
cross entropy 란 신경망이 예측할 수 있는 값이 0또는 1이고, 정답 또한 0또는 1이라 가정하고, gt가 0일 때, 신경망이 0으로 잘 출력된 확률이 0.7이라면 반대로 잘못 출력된 확률이 0.3이 된다. 또는 gt가 1일 때, 신경망이 1로 잘 출력된 확률이 0.9라면 잘못 출력된 확률은 0.1이 된다. 그렇다면 예측값이 s1, gt값이 t1이라 하면, 신경망의 출력을 식으로 나타내면 $ s1 = \sigma(z), z = wx + b $ 일테고, $ \sigma(wx + b) $ 를 f라고 하면, $ t1 = f(s1) $ 으로 생각할 수 있다. 이 때, t1, s1은 모두 확률값으로 생각을 하고, 만약 0일 때를 가정했는데, gt값이 0이 나올 확률을 t1, 0이 나오지 않을 확률을 (1-t1)이라 할 수 있다. 실제로는 0으로 생각했는데, gt가 0이 나오지 않을 확률은 거의 존재하지 않는다. 하지만 계산을 위해 그렇게 가정을 한다. 그래서 positive term에서 gt가 t1일 때, t1이 나올 확률 t1과 예측값 f(s1)을 곱하고, t1이 나오지 않을 확률 (1-t1)과 그에 대해 틀리게 예측한 확률 (1-f(s1))을 곱하여 이 둘을 더하면 loss가 된다.
만약 0과 1의 분류에서 오분류했을 때, 실제값에 대한 확률은 거의 1에 가깝고, 예측값은 거의 0에 가까울 것이다. 따라서 이에 대한 손실은 -(1 x log0.009) - (0 x log0.991) = 2.05 - 0가 된다. 반대로 잘 분류했을 때는 -(1 x log0.991) - (0 x log0.009) = 0.0039 - 0 이 된다. 즉 잘못된 부분에서의 손실은 크게, 잘된 부분에서의 손실은 작게 할 수 있다.
즉, cross entropy loss에는 잘 분류된 것에 대한 positive loss와 잘못 분류된 것에 대한 negative loss 텀이 존재하고, class에 대해 잘 예측했다면 positive loss 텀은 작아지고, 잘못 검출했다면 negative loss 텀이 커진다.
내가 원하는 label이었는지 아닌지에 대해 판단하기 위해 one hot encoding을 통해 내가 원했던 label에 대해서만 1로 두고, 아닌 label에는 0으로 두는 방식을 사용하여 조금 더 명확한 loss값을 가지도록 만들어준다. 아까 얻었던 softmax의 출력값 [0.4755, 0.2844, 0.2361]과 one hot label [1, 0, 0]을 통해 cross entropy를 적용하면 0index에 대해서는 1 * log(0.4755)
, 1index에 대해서는 (1-0) * log(1-0.2884)
, 2index는 (1-0) * log(1-0.2361)
이 되고, 이를 다 더하여 loss를 구한다.
Data preprocessing
normalize
데이터를 전처리한다는 것은 정규화(normalization)을 한다는 것이다. 데이터가 편향되어 있을 때, 각각의 클래스별로 정규화를 수행한다.
먼저 데이터에 대해 평균이 0이 되도록 이동시킨다. 그 후 x,y축 각각에 대해 동일하게 표준편차가 1이 되도록 만든다. 즉, x축과 y축이 동일한 폭을 가지는 데이터의 분포로 만든다.
one-hot encoding
또는, one-hot encoding으로 만드는 것도 전처리에 있어서 필요한 부분이다.
원핫인코딩이란 각각의 클래스에 맞는 index에만 1을 주고, 나머지는 0으로 만드는 과정을 말한다. 사진에서 볼 수 있듯이 각각의 클래스의 index에만 1, 나머지는 0으로 되어 있는 것을 볼 수 있다.
초기 환경 설정
Initialize weights
초기 가중치를 어떻게 설정하는지에 따라 학습의 성능이 달라질 수 있다. 그래서 예전에는 weight를 가우시안이나 균일 분포에서 난수를 추출했지만, 최근에는 xhavier, kaiming 등의 다양한 방법들을 사용해서 초기화한다.
초기 가중치를 초기화하는데, 너무 작은 값을 사용하면 추후 학습할 때는 모든 활성 값이 0이 된다. 즉 학습이 되지 않는다. 또는, 초기 가중치가 너무 큰 값을 사용해도 내적에 의한 출력 자체가 너무 큰 값이 나오게 될 것이고, 활성 함수에서의 gradient도 너무 커져서 saturation, 포화가 발생한다. 그렇게 되면 학습이 되지 않는다.
최근에는 가중치를 배치단위로 정규화를 진행해서 다음 층에 전달하게 된다. 그렇게 되면 훨씬 더 좋은 성능을 가질 수 있다.
Optimizer
- momentum
momentum, 관성이라는 것은 이전의 방향성을 현재의 학습에 사용하겠다는 것이다. 모멘텀을 활용해서 local minima나 saddle points를 넘어갈 수 있다. local minima란 전체의 최소값이 아니지만, 지역적으로 봤을 때 최소값이 되는 구간을 말한다. saddle points란 삼차원이상의 함수에서 극값을 가지지 않는 gradient가 극소한 점을 말한다. 어떤 방향에서보면 최소, 어떤 방향에서보면 최대가 된다.
원래 가중치 갱신 수식은 $ \theta = \theta - \rho g$ 이다. 이 때 관성을 추가하면 $ v = \alpha * prev_v + \rho \frac{\partial g}{\partial \theta} $, 즉, 이전의 속도와 현재의 gradient를 더해서 현재의 속도로 만든다. 그 후 가중치를 갱신할 때는 $ \theta = \theta - v $ 를 사용한다.
이 때, $ \alpha $는 과거 속도에 대한 정도를 얼마나 적용시킬지에 대한 값이다. 이 값이 1에 가까울수록 이전 경사도 정보에 큰 가중치를 주는 셈이고, 이를 통해 더 매끄러운 가중치의 궤적을 그릴 수 있다. 이전 위치에서의 이동 방향 벡터와 이전 위치에서의 gradient 벡터를 더해서 현재의 진행 방향으로 만든다.
이 때, nesterov momentum이라 해서 현재 위치에서 다음 이동할 방향을 예견하고, 그에 대한 경사도를 구해서 이전 벡터를 더하면 local minima나 saddle point에서의 멈춤 현상도 방지할 수 있고, 더 알맞은 방향으로 진행할 수 있도록 만든다. 이러한 방법이 원래의 momentum 방식보다 더 좋은 결과를 얻는다고 한다.
momentum값은 보통 0.5, 0.9, 0.99로 사용한다. 예전에는 0.5로 사용했지만, 현재에는 0.9나 0.99로 많이 사용한다.
- leraning rate
learning rate, $ \rho $ 가 너무 크면 최저점을 지나치는 overshooting이 발생할 수 있다. 그러나 너무 작으면 수렴이 느리므로 적절한 learning rate를 주는 것이 중요하다.
Stochastic Gradient Descent
1개의 데이터마다 gradient를 갱신한다. w = w - lr * diff
이렇게 하면 너무 많은 갱신이 일어나기 때문에 좀 더 느리고, 연산량이 많아질 수있다. 그래서 MSGD(mini batch gradient descent)를 사용하기도 한다. 즉, 1개의 데이터마다 gradient를 갱신하는 것이 아닌 mini batch단위로 gradient를 연산하고, 이에 대해 갱신한다.
이 SGD의 인자에는 learning rate, weight decay, momentum, dampening 등이 있다.
이 때, 위의 식을 보면, momuntem은 전의 위치에서의 속도를 얼마나 가중할지에 대한 값이다. 대체로 0.9 또는 0.99로 주고, dampening은 현재 위치에서의 gradient를 얼마나 가중할지에 대한 값이다. 이것을 높이면, 현재의 gradient의 비중이 줄어든다. weight decay가 regularization 텀인데, weight 텀을 gradient에 얼마나 반영할지에 대한 값이다.
1
optimizer = optim.SGD(model.parameters(), lr =1e-2, momentum=0.9, weight_decay=0.1)
AdaGrad
learning rate는 초기에는 큰 값을 통해 수렴을 빨리하고, 뒤로 갈수록 세밀한 조작에 의한 최저점 도달을 위해 작은 learning rate가 적절하다. 그렇다면 우리가 learning rate를 상황에 맞게 조정할 수 있다면 더 좋은 학습 성능을 보일 수 있다.
그래서 나오게 된 방법이 adaptive learning rate를 적용한 gradient방법인 AdaGrad
이다. 난수를 통해 초기화하고 학습을 통해 gradient를 구하는데, 과거의 값과 현재의 값을 비교하여 동일한 방향이라면 수렴을 하고 있다는 증거이므로 learning rate를 줄여서 더 세밀한 조작을 할 수 있도록 만든다.
식을 보면, dx는 gradient, grad_squared는 과거의 gradient를 누적한 함수이다. 가중치 갱신의 식은 정리하면 $ \theta = \theta -\frac{\rho}{\epsilon + \sqrt{r}} * g = \theta - \rho’ * g $ 이다. $ \rho $는 lr, $ \epsilon $ 은 분모가 0이 되는 것을 방지하기 위한 값으로 보통 10^(-5) ~ 10^(-7)의 값을 가진다. 이 때, r은 이전 gradient를 누적한 벡터에 대한 값이다. r을 구하는 식은 $ r = r + g * g $, 즉 현재 gradient의 제곱을 더한다.
r이 크면 전체적으로 곱해지는 값인 $ \rho’ $가 작아져서 조금만 이동할 것이고, r이 작으면 곱해지는 값이 커져서 많이 이동하게 된다.
RMSProp
AdaGrad 방식의 문제점은 현재의 gradient와 과거의 gradient가 같은 비중을 가지므로 r이 점점 커져서 수렴에 방해가 될 수 있다. 그래서 가중 이동 평균 기법을 사용하여 최근 것에 비중을 더 크게 두는 방식이다. r에 대한 수식으로는 $ r = \alpha r + (1 - \alpha) g * g $ 이고, $ \alpha $ 는 0.9,0.99,0.999를 많이 사용한다. 나머지는 AdaGrad와 동일한 방식이다.
Adam
RMSprop에 momentum(관성)을 추가한 알고리즘이다. 이전의 $ \theta = \theta -\frac{\rho}{\epsilon + \sqrt{r}} * g = \theta - \rho’ * g $ 식에서 관성까지 추가하여, 관성, v는 $ v = \alpha_1 v - (1 - \alpha_1)g $ 이고, 을 수행하고, 이를 다시 $ v = \frac{1}{1-(\alpha_1)^t}v $ 로 만든다. r 또한, $ r = \alpha_2 r + (1 - \alpha_2) g * g $ 을 한 후, $ r = \frac{1}{1-(\alpha_2)^t}r $ 로 만든다. 그 후 $ \theta = \theta -\frac{\rho}{\epsilon + \sqrt{r}} * v $ 을 통해 가중치를 갱신한다.
일반적으로 $ \alpha_1 $ 는 0.9, $ \alpha_2 $ 는 0.999, learning rate는 1e-3 또는 5e-4로 설정한다.
과거의 좋은 점들은 다 추가한 알고리즘이라 할 수 있다. Adam보다는 SGD를 커스텀하는 것이 더 좋은 성능을 가질 수 있지만, 잘 모르겠지만 좋은 걸 적용하고 싶다면 Adam을 사용하는 것이 좋다.
그러나 최근에 회자되고 있는 부분으로 SGD가 Adam보다 더 좋은지에 대한 의견이 있다. Adaptive method(Adam, RMS-prop) 등의 방법이 non-Adaptive method(SGD, Momentum)보다 더 안좋다라는 해석이 있으므로 데이터셋에 맞게 잘 사용하는 것이 중요하다.
위의 최적화 방법들은 1차 미분을 활용한 방법들이다. 빨간 점에서의 gradient를 계산하고, 그 정보를 이용해서 손실함수를 선형함수로 근사시킨다.
2차 미분 optimizer
그렇다면 2차 미분을 사용하면 더 좋은 loss함수가 생성되지 않을까?
2차 미분을 사용한다는 것은 gradient와 hessian matrix을 통해 2차 근사를 사용하게 된다는 것이다. 2차 미분을 활용한 방법에는 3가지가 있다.
뉴턴 방법
뉴턴 방법에서는 테일러 급수를 사용해서 2차 근사를 한다.
\[J(w + \alpha) \approx J(w) + J'(w)\alpha + \frac{J''(w)}{2} \alpha^2\]이를 현재 가중치에서 다음 가중치까지의 변화량, $ \alpha $ 에 대해 미분해서 최소점을 찾아보면, $ \frac{\partial J(w + \alpha)}{\partial \alpha} \approx J’(w) + \alpha J’’ (w) = 0 $이다. 이를 $ \alpha $ 에 대해 전개하면 다음과 같다.
\[\alpha = -\frac{J'(w)}{J''(w)} = -(J''(w))^{-1} J'(w)\]이는 w가 하나일 때의 식이고, w가 여러 개일 때의 수식은 Hessian matrix를 사용하여 표현된다. 2차 미분에 대한 값은 $ H = (\nabla J(w))’ $ 로 표현되고, 1차는 J의 미분인 gradient로 표현된다.
\[\alpha = -H^{-1} \nabla J\]이를 통해 다음 가중치 값 w2를 나타내면
\[w_2 = w_1 + \alpha\]한번에 최저점으로 도달하는 것이 불가능하므로 반복을 해서 적용해야 하는데, H를 구하는 과정에서 매개 변수의 개수가 m이라 할 때 O(m^3)의 복잡도를 가지므로 과다한 연산량이 필요하다.
켤레 경사도 방법
H를 구할 때 과다한 연산량이 필요했던 것을 해결하기 위해 켤레, 즉 현재의 gradient 방향과 이전의 gradient 방향을 더해서 새로운 방향의 직선을 탐색한 후 그에 대해 2차 근사를 진행한다. 뉴턴에 비해서는 더 빠르게 도달할 수 있게 되었다.
유사 뉴턴 방법(quasi-Newton methods)
경사 하강법, 1차 근사는 수렴의 효율성이 낮았지만, 뉴턴 방법의 경우 해시안 행렬 연산이 너무 부담이었다. 그래서 이 해시안 행렬의 역행렬을 근사하는 방법을 직접적으로 구하는 것이 아닌 근사 행렬 M을 통해 근사하는 것이 유사 뉴턴 방법이다.
원래의 방법에서는 해시안 H의 역행렬을 직접적으로 구해서 다음 위치를 찾았지만, 이 역행렬을 근사해서 구한다.
대표적으로 해시안을 근사화하는 LFGS가 많이 사용된다. 기계학습에서는 M을 저장하는 메모리를 적게 쓰는 L-BFGS 를 주로 사용한다.
2차 미분을 활용하는 방법이 있긴 하나, 아직 연구가 덜 되어 있는 부분이라 1차 미분을 활용한 방법들을 많이 사용한다.
activation function
활성함수에는 선형, 계단, tanh, sigmoid, relu, leakyrelu등이 있다. sigmoid나 tanh 함수는 입력값이 커지면 포화상태가 되고 gradient가 0에 가까운 값이 출력된다. 이에 대한 한계로 인해 현재에는 ReLU를 많이 사용한다.
ReLU의 수식은 max(0, x)로 간단하다. 이 ReLU의 변형으로 leaky ReLU 함수가 있다. ReLU의 경우 0보다 작을 때는 0이므로 gradient가 0이 나오게 된다. 조금 더 유연한 함수를 사용하기 위해 0보다 작은 구간에서 leaky, 즉 미세한 경사를 만들었고, 이것이 leaky ReLU이다. 수식으로는 max(0.1x, x)이다.
ReLU의 가장 큰 장점은 gradient vanishing 현상이 생기지 않고, 간단한 max함수로 구성되어 있기에 연산량이 줄어든다.
Batch Normalization
입력을 정규분포로 만들고 network에 넣으면, 층을 거듭할수록 각각의 layer가 가지는 weight의 분포에 따라 데이터의 분포가 달라질 것이다. 그렇다면 이 층이 깊어질수록 더더욱 변형되고 이는 학습을 방해하는 요인으로 작용한다. 즉 각 layer마다의 입력의 분포가 일정해야 출력도 일정하게 되어서 성능도 잘 나오게 된다.
그래서 층 단위로 정규화를 진행하는 것을 batch normalization
이라 한다. 이 때, 층은 convolution나 fc layer 이후, activation을 통과하기 이전에 정규화를 진행한다.
$ x = activation(batch_norm(convolution(x))) $
미니 배치 단위로 평균과 분산을 계산해서 이를 통해 정규화를 진행한다. 정규화의 장점은 신경망의 gradient 흐름도가 개선되고, 높은 학습률을 가능케 한다. 또, 초기화에 대한 의존성이 감소한다. 초기화를 이상하게 했을 때 정규화를 하지 않으면 출력도 이상하게 나올 가능성이 있다. 그래서 이상한 초기값이라도 보정을 통해 분산 정도를 감소시켜줄 수 있다. 이렇게 하면 dropout과 비슷한 효과를 발생시켜서 dropout을 사용하지 않아도 되는 효과가 있다.
정규화를 진행한 후에는 학습을 잘 할 수 있도록 이동과 비례를 시켜주는 과정을 거쳐야 한다. 이동 $ \beta $, 비례 $ \gamma $ 을 통해 최종값인 z를 구한다.
$ z’ = \gamma * z + \beta $
이 때, 이동을 사용하면 가중치 편향의 역할을 하므로 가중치 편향을 제거시켜 줘도 된다.
이러한 이동과 비례를 한 후 후처리로 전체 훈련집합에 대한 z’의 평균 $ \mu $ 과 분산 $ \simga^2 $ 를 구한다. 이는 학습이 다 끝난 후 구해진 $ \gamma, \beta, \mu, \sigma^2 $는 저장시켜놓고 예측 단계에서 사용된다.
훈련 시에 구한 평균과 분산, 그리고 이동과 비례 파라미터들에 대해 새로운 데이터에 적용을 해서 정규화를 수행하고 이동과 비례를 적용시킨다. 즉, 새로운 데이터의 값들을 학습을 통해 얻어진 가장 잘 진행된다고 생각되는 분포로 만들어주는 것이다.
CNN에서는 특징 맵 단위로 정규화를 진행하게 되므로 특징맵 크기가 w x h 라면 미니배치에 있는 샘플 n개에 대해 feature map마다 하나씩의 $ \gamma, \beta $ 값이 존재하고, 이 값들을 통해 나온 정규 분포를 가지고, 배치 단위의 평균 $ \mu, \simga^2 $ 를 구한다.
정규화를 통해 0을 기준으로 분포가 구성되므로 sigmoid를 사용해도 학습이 가능해졌다.
Regularization
규제란 일반화 능력을 높이기 위해 학습 알고리즘을 수정하는 방법들을 모두 일컫는다. 규제에는 가중치 감쇠나 드롭아웃과 같이 목적함수나 신경망 구조를 직접 수정하는 명시적 규제와 데이터 증강, 앙상블 등을 통해 간접적으로 영향을 미치는 암시적 규제가 있다.
overfitting이 발생하는 이유는 가지고 있는 데이터에 비해 용량, 즉 모델이 훈련집합을 단순 암기함으로써 파라미터의 수가 너무 많아지기 때문이다. 그래서 loss function에 regularization, 규제 항을 추가하여 가중치의 차원을 줄일 수 있다.
$ loss function’(\theta) = loss function(\theta) + \lambda R(\theta) $
규제 기법
weight decay
규제항은 훈련집합과 무관하고, 데이터 생성 과정에서 미리 생성되는 사전 지식에 해당한다. 규제항은 매개변수를 작게 유지시켜서 모델의 용량(매개변수의 수)을 제한하는 역할을 한다. 즉 쉽게 말해 10차원의 매개변수가 있는데, 이것이 모두 사용되면 과잉적합이 발생하기 쉽다. 그래서 10차 중 8차정도만 사용되도록 만들거나, 학습을 통해 계속해서 가중치의 값이 커져서 과잉적합이 발생하는 상황에서 큰 가중치의 값을 작게 눌러주는 것이 규제이다. 이 때, 작은 가중치를 사용하도록 눌러주기 위해 사용하는 것이 규제항이고, 규제항의 종류로는 L2 norm이나 L1 norm을 사용한다. L2 norm을 사용하는 규제 기법을 가중치 감쇠라 한다.
- L2 normalization weight의 제곱의 합으로 패널티를 적용한다. 규제항의 식은 $ || \theta ||_2^2 $ 이고, 이는 가중치 벡터들의 크기를 추출하고, 이를 규제항으로 넣는다. 그리고, 이 규제항도 학습에 의해 낮아져야 하므로 학습을 진행할수록 w = [w1, w2, w3, …] 의 크기가 전체적으로 작아진다.
그렇다면 원래의 가중치 갱신의 수식이 $ \theta = \theta - \rho * \nabla J(\theta) = \rho * \frac{\partial J}{\partial \theta} $ 인데, 여기에 규제항을 추가해서 다음과 같은 식으로 만든다.
\[\theta = \theta - \rho (\nabla J(\theta) + \lambda * 2 * \theta)\] \[= (1 - 2\rho \lambda)\theta - \rho \nabla J(\theta)\]원래의 식에서 \(-\rho \lambda \theta\) 가 추가되었다. 만약 $ \rho = 0.01, \lambda = 2.0 $ 이라면 $ (1 - 2 \rho \lambda) = 0.96 $ < 1 이므로 원래의 값보다 작은 값이 곱해지는 것이다.
이를 그림으로 생각을 해봤을 때 원래의 최저점 위치보다 원점 방향으로 \(2\rho \lambda\) 값만큼 가까워진 것이 된다. 그러면 최저점으로 가는 벡터의 크기가 작아지는 효과가 발생하게 되고, 이것이 가중치 감쇠라 할 수 있다.
- L1 normalization
L1 norm을 적용해줄 수도 있다. L1이란 weight값의 절대값의 합을 페널티 항으로 사용하여 loss function에 패널티를 부과한다. 규제항이 추가된 손실함수를 미분하면 다음과 같다.
\[\nabla J'(\theta) = \nabla J(\theta) + \lambda \sin(\theta)\]sin을 사용한 이유는 L1이 절대값이므로 음수면 -, 양수면 +여야 한다. 이를 적용시키기 위해 sin함수를 적용했다. 이 식을 매개변수 갱신 수식에 적용하면
\[\theta = \theta - \nabla J'(\theta)\] \[\theta = \theta - \rho(\nabla J(\theta) + \lambda \sin(\theta))\] \[\theta = \theta - \rho \nabla J(\theta) - \rho \lambda \sin(\theta)\]원래의 식에서 $ - \rho \lambda \sin(\theta) $ 가 추가되었다. 이 항의 역할은 가중치 중 1개를 없앤다. 즉, L1 norm의 형태는 2차원을 기준으로 사각형인데, L1 규제 안에서의 최저점에 가장 가까운 위치는 축 위의 점일 것이다. 그래서 1개의 weight를 삭제시키는 역할을 한다.
대체로 L2를 많이 쓰는데, L1를 사용하는 경우는 특정 feature에 대한 의미를 찾을 때, 즉 필요하지 않는 feature에 대해서는 weight를 0으로 만들어서 원하는 feature만 보고자 할 때 사용한다.
정리하면, L2 규제는 대체로 weight를 0에 가깝게 만들어주는 역할을 하고, L1 규제는 특정 weight를 0으로 만들어주는 역할을 한다.
early stopping
학습 시 일정 반복을 지나게 되면 과익 적합 현상이 발생한다. 그래서 loss가 줄어들다가 loss가 올라갈 때 멈추는 방식이다. 학습때마다 loss를 저장했다가 loss가 5~10번 정도가 값이 상승하는 구간이 발생되면 멈추도록 한다.
data Augmentation
과잉적합을 방지하는 가장 확실한 방법은 훈련 집합을 키우는 것이다. 데이터의 수를 늘릴 수 있지만, weight에 맞게 데이터의 수를 증가시키는 것은 현실적으로 불가능하다. 그래서 rotation, translation, reflection 등의 affine 변환을 통해 데이터 수를 증가시킬 수 있다. 그러나 모든 부류에 같은 변형이 발생하면 모델이 이 규칙까지 인식하고 학습할 수 있다.
그래서 모핑과 같이 비선형 변환을 통해 공간을 찌그러트리기도 한다. 중요한 것은 원래 데이터의 특징이 변형되지 않도록 주의해야 한다.
많이 사용되는 방법은 당연히 crop, affine, 색상 변환이다. 완벽히 방지하지는 못하지만, 어느정도의 과잉적합을 방지하는데 도움이 되고, 적용하기도 간편하다.
Dropout
fc layer의 노드 중 일정 비율을 임의로 선택해서 동작을 멈추게 만드는 것이 Dropout 방식이다. 완전 연결을 사용하여 학습을 많이하면 fc layer의 가중치들이 비슷해진다. 이를 방지해야 overfitting을 방지할 수 있고, 그를 위해 몇개씩을 연결을 끊어서 부분적으로 최적화하여 비슷해지는 현상을 방지한다. 또 하나의 신경망에 dropout을 적용하면 여러 형태의 부분 신경망을 만들수 있고, 이들의 결과를 앙상블할 수 있다.
batch normalization을 사용했을 때 dropout을 사용하지 않아도 되는 이유는 만약 원래는 양수로만 이루어져 있는 출력을 gradient하면 값이 수렴하지 못할 것이다. 그러나 이 값을 정규화를 하면 평균이 0에 가까워질 것이고, 그러면 양수로만 이루어져 있는 출력이 음수와 양수의 값을 이분화해서 가질 수 있게 된다. 이를 activation 함수에 넣으면 특정 값에 대해서만 활성이 될 것이다. 이러한 방식이 dropout을 했을 때 특정 연결을 끊어버림으로써 얻을 수 있는 효과와 비슷하다.
Ensemble
서로 다른 여러 개의 모델을 결합하여 성능을 향상시키는 기법인데, 현대 기계학습에서는 상상블도 규제로 여긴다. 앙상블에는 크게 두가지 2단계가 있다.
- 서로 다른 예측기를 학습
- 서로 다른 구조의 신경망 여러 개를 학습, 같은 구조를 사용하더라도 서로 다른 초기값과 매개변수를 설정하고 학습
- e.g.)
- bagging(훈련 집합을 여러 번 샘플링해서 서로 다른 훈련 집합을 구성)
- boosting(i번쨰 예측기가 틀린 샘플을 i+1번째 예측기가 잘 인식하도록 하는 연계성을 구축)
- 학습된 예측기를 결합(평균)
- 여러 모델의 출력을 평균하거나 투표하여 최종 결과를 결정
hyperparameter Optimization
모델에서 매개변수는 가중치와 하이퍼 파라미터로 두 종류가 있다. 학습에 의해 결정되는 매개변수는 가중치, 사람에 의해 결정되는 lr, filter size 등은 하이퍼파라미터라고 한다.
하이퍼파라미터를 선택하는 기준에는 디폴트 값을 설정해도 되고, 경험에 의한 특정 range를 설정하고, 그 안에서 최적화시킬 수도 있다. 하이퍼 파라미터가 중요한 이유는 이로 인해 학습이 결정되기 때문이다. 특히 learning rate를 잘 설정해야 학습이 잘 진행된다.
선택하는 방법은 여러 방법이 존재한다.
- grid search and random search
임의 탐색은 난수로 하이퍼파라미터 조합을 생성해서 어떤 것들이 좋은지 체크하는 방법이다. 격자 탐색은 고정된 간격으로 샘플링해서 값을 비교하는 방법이다. 결과적으로 따져봤을 때, 임의 탐색이 조금 더 좋다. random search를 할 경우 최적의 값을 찾을 수도 있지만, grid를 사용할 경우 최적의 값이 grid 사이에 존재하면 찾을 수가 없다. 또한, grid search가 연산이 더 오래 걸린다.
이 때 값을 설정할 때는 log스케일로 값을 주어야 한다. 즉 log를 사용하지 않고 찾으면 log(1e-3) ~ log(1e-6)의 범위를 찾기 떄문에 수많은 값들이 샘플링된다. 그 대신 난수를 (-3~-6)과 같이 설정한 후 정확도가 높은 범위를 재설정해서 다시 찾는다. log의 값 자체를 난수로 설정하게 되면 예를 들어 uniform(log(1e-3),log(1e-6))로 찾고자 할 때, 0,0.1,0.0001,0.52343 등의 너무 수많은 값이 존재하게 된다. 이렇게 설정하면 각각의 값들의 차별성이 모호할 수 있으므로 log(10**uniform(-5,5))의 방법으로 탐색한다.
Prepare Data
object detection에서의 data를 사용하려면 data annotation이 필요하다. 우리의 데이터를 사용하려면 데이터 전처리를 통해 각 bounding box와 label에 대한 파일을 만들어야 한다. labelimg
라는 툴을 통해 많이 사용한다.
실제로 딥러닝 개발자가 직접 이 label을 만들지 않더라도, 이 annotation들이 적절하게 잘 적용되어 있는지 확인하지 않으면 모델 학습 자체가 잘못될 수 있다. 그래서 직접 data annotation을 수행해보면서 생성해보는 것이 중요하다.
gt 파일에는 txt나 yaml파일로 구성되어 있고, coco dataset에서의 gt파일을 살펴보게 되면 순서는 다음과 같다.
45 0.479492 0.688771 0.955609 0.5955
45 0.736516 0.247188 0.498875 0.476417
50 0.637063 0.732938 0.494125 0.510583
45 0.339438 0.418896 0.678875 0.7815
49 0.646836 0.132552 0.118047 0.096937
49 0.773148 0.129802 0.090734 0.097229
49 0.668297 0.226906 0.131281 0.146896
49 0.642859 0.079219 0.148063 0.148062
첫번째는 class index일 것이고, 2:5번째는 bounding box의 좌표로 구성되어 있다. 어떤 coco dataset의 경우 조금 다른 포맷을 가지기도 한다.
car 0.80 0 -2.09 1013.39 182.46 1241.00 374.00 1.57 1.65 1.35 4.43 1.65 5.20 -1.42
첫번째는 class name, 두번째는 truncation, 즉 가려진 정도나 이미지에서 벗어난 정도를 나타내준다. 세번째는 occulation, 가려짐에 대한 레벨을 나타낸다. 그 다음은 [x min, y min, x max, y max]인 bounding box에 대한 정보를 나타낸다.
여기서 coco dataset의 경우 bounding box가 [x,y,w,h]로 되어 있지만, yolo의 경우 [x center, y center, w, h]로 구성되어 있다. 따라서 자신이 사용하는 dataset과 모델을 적절하게 조정하는 것이 중요하다.
아래는 kitti dataset의 gt파일이다.
Pedestrian 0.00 0 -0.20 712.40 143.00 810.73 307.92 1.89 0.48 1.20 1.84 1.47 8.41 0.01
첫번째는 object의 정보, 두번째는 truncated level, 세번째는 occluded level, 네번째는 object의 각도가 정의되어 있다. 마지막으로 bbox에 대한 4개의 value가 있다.
데이터를 불러온 후 dataset을 설정할 때는 train:valid:test = 8:1:1 정도로 맞춘다.
Evaluation metric
precision 내가 예측한 바운딩 박스들 중 참인 것에 대한 비율
recall gt박스들 중 내가 예측한 참인 바운딩 박스의 비율
precision과 recall은 trade-off 관계를 가지고 있다. 따라서 이 두 값을 종합해서 성능을 평가한다.
F1 score
$ F_1 = 2 \frac{precision x recall}{precision + recall} $
F1 score은 precision과 recall의 조화평균을 사용한 방법이다.
PR curve
precision과 recall에 대한 곡선을 그리는 방법으로, confidence가 높은 것을 맨 앞으로 두고, 차례대로 그림을 그린다. 누적 TP/FP를 통해 각각의 예측값마다의 precision과 recall을 구한 후 곡선을 그려준다. 그러나 이 PR 곡선은 서로 다른 두 알고리즘을 비교하는 데에는 적합하지 않다. 그래서 나온 것이 mAP이다.
- mAP
이는 Mean average precision을 말하는데, PR 곡선에서 아래 영역으로 계산할 수 있다. 모든 box를 confidence기준으로 내림차순하고, 이 모든 box에 대해 precision과 recall을 계산한다. 곡선 아래 면적을 계산해주면 되는데, 객체가 두 개 이상 검출되었을 경우 AP를 평균하여 계산한다.
Confusion matrix
각 클래스간 예측한 값들을 카운팅해서 matrix로 보여주는 방식이다. 파란색이 정답인 label이고, 나머지는 틀리게 예측한 label이다. 이를 통해 특정 label에 대해 어떤 class를 모델이 헷갈려하는지를 확인할 수 있다.