본문 바로가기
CVLab/cs231n (2016)

cs231n - lecture06(Trainit NN Part 2)

by print_soo 2024. 9. 28.

⭐️⭐️⭐️ NN(신경망)을 Training하는 프로세스 ⭐️⭐️⭐️

 
 
 

1. 데이터의 미니배치를 샘플링

전체 데이터 셋에서 미니배치를 샘플링(무작위로 선택)하여 학습에 사용한다.
이는 계산 효율성을 높이고, 매 스텝마다 모델이 다른 데이터에 대해 학습할 수 있도록 한다.
 
 

2. Forward pass

입력 데이터를 신경망에 통과시켜 출력 값을 얻고, 그 출력 값과 실제 라벨 간의 차이(손실, Loss)를 계산한다.
이를 통해 모델의 예측 성능을 평가한다.
 
 

3. Backward pass (역전파)

출력층부터 시작해서 각 층의 노드(뉴런)들이 손실에 얼마나 기여했는지를 계산한다.
이를 위해 chain rule(연쇄 법칙)을 사용하여, 출력층에서 손실에 대한 기울기를 먼저 구하고, 그 값을 기반으로 한 층씩 거꾸로 올라가면서 이전 층의 기울기를 계산한다.
 
기울기가 클 수록 손신에 많이 기여한 뉴런이다. 
 
 

4. 파라미터 업데이트

계산된 gradient를 기반으로 파라미터를 업데이트한다. 일반적으로 경사 하강법(Gradient Descent)을 사용해 파라미터를 조금씩 수정하여 손실을 최소화하도록 한다.
 
 


Fancier Optimization - 가중치 업데이트를 어떻게 효율적으로 할 것인지

SGD의 성능 문제

SGD는 데이터의 일부만을 가지고 학습을 한다. 따라서, 계속 조금씩 업데이트를 하면서 가중치의 최적 값을 찾아간다.
이 과정에서 방향을 정확히 잡지 못하고 헤메는 경향이 있기 때문에 전체적으로 학습이 오래 걸린다는 문제가 있다.
 
아래의 사진에서 처럼 빨간 점에서 스마일까지 일직선으로 가는게 아니라 지그재그로 가기 때문에 오래 걸린다.

 
 
 
이러한 문제를 개선해보자. 
 
우선 첫 번째 방법은 Momentum이다.
 
 

SGD 성능 저하 문제를 해결하기 위한 방안들

 
[우리가 앞으로 배울 방안들을 시각화 한것]
 

 
 
 
 
 
 

1. SGD + Momentum - 이전 변화량 감지

 
기본적인 경사하강법의 코드는 아래와 같다. 
 

 
X(t)는 현재 가중치, X(t+1)은 다음 가중치를 의미한다.
 
"다음 가중치 = 현재 가중치 -  학습률 * 현재 가중치의 기울기"
이 방식은 가중치를 손실을 줄이는 방향으로 조금씩 수정해나가는 것을 의미한다.
 
따라서, 해당 부분을 왼쪽처럼 코드로 구현할 수 있다.
 
 
 
 
 
 
 
 
 
모멘텀을 추가한 경사하강법은 아래와 같다.
 
 

V(속도) 라는 건 이전 스텝(파라미터를 수정하는 한번의 과정)의 기울기 정보를 나타낸다.
 
p는 모멘텀 계수이다. 이전 속도 정보가 얼마나 반영될지를 결정한다.
보통 0.9나 0.99를 설정한다.
 
f(xt)는 현재 스텝에서 계산된 손실 기울기를 나타낸다. 
 
따라서 다음 가중치를 계산할 때 아래와 같이 계산한다.
"다음 가중치 = 현재 가중치 -  학습률 * 현재 속도"
 
 
코드를 보면 아래와 같이 프로세스를 설명할 수 있다. 
 
1. 속도를 0으로 초기화한다.
 
2. 파라미터를 지속적으로 업데이트한다.
 
3. 현재 가중치 x에서 기울기 dx를 계산한다. 
 
4. 이전 속도에 모멘텀 계수 p를 곱합 값에 현재 기울기 dx를 더해 속도를 갱신한다. 
 
5. 갱신 속도에 학습률을 곱해 가중치를 업데이트한다.
 
 
 
[ 아래는 모멤텀 유무를 시각적으로 보여주는 사진 ]
 
 

 
이 사진은 모멘텀이 없는 경로이다.
 
가중치가 최적의 값에 도달할 때 진동하면서 천천히 수렴하는 모습을 보여준다.
즉, 공이 빨간점으로 도달해야할 때 좌우로 움직이다가 도달하는 것처럼
 
 
이 사진은 모멘텀이 있는 경로이다.
 
가중치가 최적의 값에 도달할 떄 진동이 적고 빠르고, 부드럽게 수렴는 모습을 보여준다.
즉, 공이 빨간점에 도달해야할 때 거의 바로 도달 하는 것 처럼
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

모멘텀을 추가한다는 것은

이전 학습 단계에서의 속도(기울기 변화량)를 반영하여,
더 빠르고 안정적으로 최적의 가중치를 찾아가는 방법

 
 
 


 

2. Nesterov Momentum update - 이미 가중치를 이동 시킨 후 기울기 계산

: SGD + Momentum 방식을 개선한 방식
 
 
SGD + Momentum를 공식을 시각화 하면 아래와 같다.
 

 
속도와 기울기를 더하면 다음 스텝의 속도가 도출되는데 이걸 벡터로 보면 왼쪽과 같이 그릴 수 있다.
 
위의 사진을 해석하면 예를 들어 (0, 0)에서 속도가 간 지점(이전 스텝)과 또 (0, 0)에서 구한 기울기를 더해 계산된 새로운 지점이 다음 스텝에서 사용할 속도가 되는 것이다.
 
 
 
Nesterov Momentum update는 다르게 속도를 도출한다.
 

 
 
예를 들어 (0, 0)에서 속도가 간 지점(이전 스텝)이 (3, 4) 라고하면 (3, 4)에서 구한 기울기를 더해 계산된 새로운 지점이 다음 스텝에서 사용할 속도가 되는 것이다.
 
 
 
 
 
 
하지만 이런 기존과 다른 방식으로 기울기를 계산하기 때문에 호환성 문제가 생길 수 있다. 
 
 
 
기존의 모멘텀 방식은 현재 위치에서 기울기를 계산하는 반면, Nesterov 방식은 예측한 위치에서 기울기를 계산한다. 이로 인해 기존 경사 하강법이나 모멘텀 알고리즘과 구조가 다르다.
 
따라서, Nesterov 방식은 기존의 최적화 알고리즘과 쉽게 통합되기 어렵고, 기존 알고리즘에서 Nesterov 방식으로 바로 전환할 때 호환성 문제가 발생할 수 있다.
 
 


 

3. AdaGrad update - 기울기의 제곱

: 각 가중치에 대해 기울기 제곱합을 사용해 학습률을 조정하는 방법
 
 

 

grad_squared = 0

    가중치의 기울기 제곱의 합을 저장할 변수 grad_squard를 0으로 초기화
 

dx = compute_gradient(x)

    현재 가중치 x에 대한 기울기(gradient)를 계산한다. 기울기는 손실 함수의 경사를 나타내며, 가중치를 어느 방향으로 얼마나 조정할지를 알려준다.
 

grad_squared += dx * dx

    기울기 dx의 제곱값을 누적해서 합한다. grad_squared는 각 가중치에 대한 기울기의 제곱합을 저장한다. 
    즉, 특정 가중치에서 기울기 값이 커질수록 기울기 제곱값이 더  많이 누적된다.
 
    이 과정이 AdaGrad에서 가장 핵심인 부분이다. 
 

x -= learning_rate * dx / (np.sqrt(grad_squared) + 1e-7)

    가중치를 업데이트할 때, 기울기 제곱합에 따라 학습률을 조정한다. 
    1e-7은 0으로 나누는 오류를 방지하기 위해 추가된 작은 값이다.
 
 
 

[장점]

    희소한 특징에 대한 학습이 효과적: 자주 등장하는 가중치는 학습률이 낮아지고, 잘 나타나지 않는 특징의 가중치는 학습      률이 높아져 효율적인 학습이 가능해진다.
 
 
 

[단점]

    학습률이 너무 빠르게 줄어듦: 시간이 지남에 따라 기울기 제곱합이 계속 누적되면 학습률이 매우 작아져 더 이상 진행되지 않을 수 있다.
 
 
 

❓의문점 1. 왜 가중치의 기울기 제곱을 누적하는건가? (그냥 가중치의 기울기를 누적하면 안되는건가?)

 
그냥 가중치의 기울기를 제곱하는 경우 음수와 양수가 나와 값이 상쇄될 수 있다. 
 
 

❓의문점 1-1. 그렇다면 가중치의 제곱이 아니라 절대값을 누적하면 되는거 아닌가?

 
가중치 기울기 제곱을 사용하는 이유는 기울기에 더 크게 반응하기 때문이다. 예를 들어 2에서 4로 가중치의 기울기가 변경했을 때 절대 값을 사용한다면 2배만 증가된 것이 된다. 하지만 제곱을 한다면 4에서 16으로 늘어난 것이기 때문에 4배가 증가된 것이다. 
 
이처럼 제곱을 사용하면 큰 기울기에 더 민감하게 반을할 수 있기에 제곱을 사용한다.
 
 

❓의문점 2. 어떻게 기울기 제곱합에 따라 각 가중치마다 다른 학습률을 적용하는가?

가중치를 적용하는 방식은 위와 같다. 이 방식은 기울기 제곱합이 큰 가중치일수록 학습률이 더 작아지고, 기울기 제곱합이 작은 가중치는 학습률이 크게 유지되도록 만든다.
 
 
 
예를 들어 수치를 대략적으로 설정해서 수식을 풀어보겠다.
 

 
이렇게 누적된 기울기의 제곱합이 크면 다음 가중치가 덜 줄어든다. 
 
 
 
 
 

❓의문점 3. 왜 자주 업데이트되는 가중치의 학습률은 감소하는가?

 
자주 업데이트가 되는 가중치의 경우 기울기 제곱합이 계속 증가한다. 그 결과 기울기 제곱합의 제곱근이 커지면서 다음 스텝의 가중치의 변화 폭은 점점 감소된다. 따라서 학습률도 감소된다.
 
 
 
 
 
AdaGrad의 단점인 시간이 지나면 학습률이 매우 떨어지게 된다는 단점을 보안한 방법이 바로 RMSProp update이다.
 


4. RMSProp update - 기울기의 크기(기울기가 얼마나 빠르게 변하는지에 대한 값)

: AdaGrad와 방식은 같지만, 가중치 기울기 제곱합을 누적해서 더해주는 게 아니라 기울기 제곱의 "지수 이동 평균"을 사용한다.
 
 

 
 
이렇게 변경해주면 AdaGrad의 장점은 유지하고 단점은 보완할 수 있다.
 


 

5. Adam update - 가중치 변화 + 기울기의 크기 모두 고려

: 모멘텀과 RMSProp의 장점을 결합한 방식이다. 
 
 
 

 
 

first_moment = 0, second_moment = 0

    기울기의 이동평균(모멘텀)을 저장하는 first_moment과 기울기 제곱의 이동 평균(분산)을 저장하는 second_moment를 0으로 초기화한다.
 
 

dx = compute_gradient(x)

    현재 가중치 x에 대한 기울기를 계산한다.
 
 

first_moment = beta1 * first_moment + (1 - beta1) * dx

    first_moment는 모멘텀을 반영하여 기울기의 이동 평균을 업데이트한다.
 
 

second_moment = beta2 * second_moment + (1 - beta2) * dx * dx

    second_moment는 기울기 제곱의 이동 평균을 계산한다. 이는 RMSProp처럼 각 가중치마다 적응형 학습률을 조정할 수 있게 해준다.
 
 

x -= learning_rate * first_unbias / (np.sqrt(second_unbias) + 1e-7)

    편향 보정을 통해 학습 초기 단계에서 모멘텀과 기울기 제곱의 이동 평균이 0에서 시작하는 문제를 해결한다.
 
 
 

first_unbias = first_moment / (1 - beta1 ** t)
second_unbias = second_moment / (1 - beta2 ** t)

    first_moment와 second_moment를 결합하여 업데이트한다.
 
 
 
 
 

[장점]
 

    빠르고 안정적인 학습: Adam은 모멘텀과 RMSProp의 장점을 결합하여 빠르고 안정적인 학습을 제공한다.
 
    적응형 학습률: 가중치마다 학습률이 달라지기 때문에 복잡한 신경망에서도 효과적으로 학습할 수 있다.

 
 
[단점]
 

    매우 작은 학습률: 때로는 너무 작은 학습률을 적용할 수 있어서, 충분한 학습이 이루어지지 않는 경우가 발생할 수 있다
 
 


Q. 우리가 배운 방법들 중 어떤 방식을 사용하는게 좋을까?

정답은 이 중에 어떤 것도 최선이 아니다.
 
학습률은 시간의 경과에 따라서 점차 줄이는 방법(Decay)이 좋다.
 
 
[주요 학습률 감쇠 방식]
 

 


 

First-Order and Second-Order Optimization

 
우리가 현재까지 Loss function을 구하기 위해서 Gradient만 사용했다. 이러한 방식을 First-Order Optimization 라고 한다.
 
 
그럼 Second-Order Optimization라는 건 뭘까?
 

Second-Order Optimization

: 기울기(gradient)뿐만 아니라, 곡률(curvature) 정보까지 사용하여 최적화하는 방식
 

 

Second-Order Optimization가 Learning rate가 필요하지 않은 이유?

1차 최적화(SGD)에서는 기울기(gradient)만 사용하여, 어느 방향으로 가야 하는지는 알지만, 얼마나 멀리 가야 하는지는 모른다.
 
그래서 학습률이라는 파라미터로 그 이동 거리를 결정한다. 하지만 2차 최적화는 기울기와 곡률(Hessian)을 모두 사용해서 가야할 방향과 속도를 한번에 결정할 수 있다.
 
따라서 학습률은 필요없다.
 
 
 

Second-Order Optimization의 작동방식

 
1. 기울기(gradient) 계산
    1차 미분을 통해서, 현재 위치에서 기울기가 어디로 향하는지 계산한다. 
    기울기는 함수의 최솟값으로 가기 위해 어느 방향으로 이동해야하는지 알려준다.
 
2. 곡률(curvature) 계산
    2차 미분을 통해서 함수가 얼마나 휘어져있는지 계산한다. 
    곡률은 기울기가 얼마나 가파른지, 완만한지를 알려준다.
 
3. 가중치 업데이트
    기울기와 곡률을 모두 사용하여, 최적의 이동 거리와 방향을 결정한다.
 
 
하지만 이 방식은 굉장히 복잡하고 그렇기 때문에 메모리 사용량을 많이 사용한다. 
 
따라서 대규모 신경망에서는 잘 사용되지 않지만, 상대적으로 작은 문제에서는 유용한 방법이다.
 
 
 
 
따라서 이러한 문제를 해결하기 위해 L-BFGS(근사 기법을 사용해 메모리 사용량과 계산 비용을 줄인 방법)라는 방식을 도입했다. 

 

L-BFGS

L-BFGS에서는 정확한 곡률 정보를 반영하지 못하는 한계가 있고, 큰 모델이나 비선형 문제에서는 여전히 First-Order Optimization(SGD)에 비해서 비효율적일 수 있다. 또한 Full-batch에서는 잘 작동하지만 mini - batch에서는 잘 작동하지 않는다.
 
따라서 해당 방식도 큰 모델에서는 제약이 있다.
 

 
 
 
 

First-Order vs Second-Order Optimization 간단한 비유
 

 
 
 
First-Order Optimization는 언덕을 내려갈 때 경사만 보고 방향을 정하지만, 속도는 적절하게 조절해야 한다. 이때 학습률이라는 파라미터로 속도를 직접 조정한다.
 
Second-Order Optimization는 언덕의 경사와 곡률(휘어짐)을 동시에 보고, 어디로 얼마나 내려가야 할지 자동으로 계산한다. 즉, 속도 조절(학습률)이 필요 없는 방식이다.
 
 
 
 

따라서 대부분의 경우에는 Adam을 사용하고,
Full-batch를 사용할 수 있는 경우에는 L-BFGS를 사용하면된다.

 
 
 


 
 

Test Data에서 성능을 올리기 위한 방법

 

Model Ensembles

: 여러 모델을 결합하여 개별 모델보다 더 나은 성능을 내는 방법
 

 
 

방식:  독립된 여러 모델 학습시키기
 

10개의 독립적인 모델을 학습시켜, 그 결과를 평균 내어 예측을 수행한다.
성능이 약 2% 정도 향상된다. -> 여러 모델의 결과를 평균함으로써 개별 모델의 약점을 보완하고 더 안정적인 예측을 만들기 때문
 
 

이런 방식이 성능향상이 되는 이유는?

 
이 방식은 여러 모델이 서로 다른 지점에 도달한 결과를 결합함으로써, 개별 모델의 실수나 지나침을 보완하는 방식이다.
 
 
개별 모델은 학습 과정에서 최적의 값을 지나칠 수 있지만, 이 여러 결과를 평균내면 더 낮은 최적의 값에 가까워질 수 있습니다.
복잡한 손실 함수에서도, 각 모델이 서로 다른 부분을 탐색하므로 더 나은 성능을 얻을 수 있습니다.
 
 

Regularization

Model Ensembles말고 모델의 성능을 올리려면 Regularization을 잘 활용해야한다.
 
 

Dropout

: 일부 뉴런을 임시로 꺼버리는 방법
 

일반적인 NN / Dropout이 적용된 NN

 
 
일반적인 NN은 모든 뉴런끼리 연결이 되어있다. 하지만 Dropout을 적용하면 특정한 뉴런은 0으로 만들어 일부를 연결 끊김 상태로 만든다.
 
이 방식은 특정한 뉴런이 특정한 Feature만 학습하는게 아니라, 뉴런이 여러 Feature를 고르게 학습할 수 있게 해준다.
 

[작동 방식]

 

Train
 

매 스텝마다 랜덤으로 일부 뉴런들을 무작위로 0으로 만든다.
이렇게 하면, 모델이 특정 뉴런만 사용하여 학습하는 것을 방지하고, 다양한 조합으로 학습하게 만든다. 
 

Test
 

학습 때 처럼 일부 뉴런만 0을 만드는 것이 아니라, 모든 뉴런을 0으로 만들지 않는다.
하지만 Train과 동일한 환경을 유지하기 위해서 dropout의 비율을 곱하여 출력 값을 조정한다. 즉, Train 때 일부 뉴런을 껐던 것을 고려해서 출력을 보정한다.
 
 
[장단점]
 
장점: Dropout은 과적합을 방지하고, 모델의 일반화 성능을 크게 향상
 
 
단점: 매 스텝마다 일부 뉴런을 꺼서 학습하므로, 학습 시간이 다소 길어질 수 있음
 
 
 


Dropconnect

Dropout이 뉴런 자체를 꺼버리는 것이라면, Dropconnect는 "가중치(weight)"를 임의로 0으로 만든다.
 
 
 
즉, 뉴런 간 연결(가중치)을 무작위로 끊어서 학습하는 방법이다. 이를 통해 모델이 특정 가중치에 너무 의존하지 않도록 도와준다.
 
뉴런들은 서로 선으로 연결되어 있는데, 학습할 때 그 선(가중치) 중 일부를 임의로 끊어버린다고 생각하면 된다. 이렇게 해서 어떤 연결에만 의존하는 걸 방지하는 것 이다.
 


 

Fractional Max Pooling - 다양한 풀링 크기로 더 많은 정보 추출

일반적인 Max Pooling은 이미지의 특정 영역을 정해 그 안에서 가장 큰 값을 선택하는 방식이다.
 
 
 
Fractional Max Pooling은 어디서 pooling을 할지 랜덤하게 선택하는 방식이다. 즉, 일정한 규칙대로 고정된 위치가 아닌, 매번 다른 위치에서 pooling을 적용한다.
 
원래는 이미지를 정해진 격자로 나눠서 큰 값을 선택했다면, 이제는 랜덤하게 격자를 선택해 큰 값을 고른다. 고정된 패턴이 아니라 랜덤한 방식이기 때문에, 더 다양한 패턴을 학습할 수 있게 된다.
 


 

Stochastic Depth - 일부 레이어 생략해 학습 속도를 상승

깊은 신경망(Deep Neural Network)에서는 여러 레이어(층)이 있다. Stochastic Depth는 학습할 때 일부 레이어를 랜덤하게 생략하는 방식이다.
 
 
 
즉, 네트워크 전체를 학습하는 대신, 몇 개의 레이어를 무작위로 끄고 나머지 레이어만 학습한다. 테스트할 때는 모든 레이어를 다 사용한다.
 
신경망의 여러 층 중에서 몇 개 층은 임시로 생략하고 나머지만 학습한다. 이 과정에서 모델이 특정 레이어에만 의존하지 않고, 다양한 경로를 학습하게 되어 더 견고한 모델이 된다.
 

'CVLab > cs231n (2016)' 카테고리의 다른 글

cs231n - lecture08(Localization and Detection)  (1) 2024.10.03
cs231n - lecture07(CNN)  (0) 2024.10.01
cs231n - lecture05(Trainit NN Part 1)  (7) 2024.09.28
2024.09.27 Q&A  (1) 2024.09.27
cs231n - lecture04(역전파, 신경망)  (0) 2024.09.27