-
[cs231n] 7강 신경망 훈련하기 (1/4, 더 멋진 최적화 (fancier optimization))AI 2021. 3. 23. 12:43
지난시간에 했던 것들을 다시 보죠. 지난 시간에 우리는 신경망을 훈련시키는데 핵심적인 세부사항과 관련된 팁 (tip)과 트릭 (trick)에 대해서 얘기했습니다. 오늘은 지난 시간에 배운 것들을 다시 보고 이것들을 학습시키는 것에 대해서, 이런 종류의 핵심적인 세부사항에 대해 더 많이 얘기하겠습니다.
빨리 다시 요약해 보죠. 지난시간 활성 함수에 대해서 얘기했죠. 여러 활성 함수들의 동물원 (zoo)을 보고 그들의 여러 특징에 얘기했죠. 우리는 시그모이드에 대해 봤는데 10년 전에 신경망을 학습할 때는 꽤 자주 썼지만, 활성 함수의 양 끝에서 경사가 사라지는 문제가 있죠. 탠에이치 (tanh)도 이런 종류의 문제가 있죠.
일반적으로 추천하는 것은 대부분의 경우에서는 기본값으로 렐루 (ReLU)를 그냥 쓰는거죠. 여러 아키텍처에서 잘 동작하는 경향이 있으니까요.
가중치 초기화에 대해서도 얘기했죠. 위쪽에 보면, 훈련 시작할 때, 가중치를 초기화할 때, 이런 아이디어가 있습니다. 만약 가중치가 너무 작게 초기화되어 있으면, 망을 거쳐감에 따라서 활성이 사라질 겁니다. 왜냐면 반복해서 이 작은 숫자들을 곱해감에 따라, 0으로 줄어들게 되니까요. 그래서 모든 것이 0이 되고 학습은 일어나지 않고, 여러분들은 슬퍼지죠. 반면에, 가중치를 너무 크게 초기화하면, 망을 거쳐가면서 가중치 행렬을 계속 곱해가면, 결국 그것들은 폭발할 겁니다. 그럼 여러분은 불행해질거고, 학습은 없을거고, 나쁜 일이 될 겁니다. 그러나 초기화를 딱 맞게 하면, 예를 들면, 자비에 (Xavier) 초기화나 엠에스알에이 (MSRA) 초기화 같은 것을 사용하면, 좋은 활성 분포를 유지하면서 망을 거쳐갈 겁니다. 기억할 점은 망이 점점 깊어짐에 따라 이런 것들이 점점 더 중요하고 치명적이 될 것이라는 겁니다. 왜냐면, 망이 점점 더 깊어지면, 더 많은 이런 곱셈항으로 그 가중치 행렬을 계속 곱할거니까요.
데이타 전처리에 대해서도 얘기를 했는데요. 합성곱 망에선 영으로 중심을 맞추고 데이타를 정규화 (normalization)해서 0 평균과 단위 분산을 가지도록 하는 것이 꽤 일반적이라고 얘기했죠.
여러분들이 왜 이걸 원하게 될 건지에 대한 약간의 추가적인 직관을 제공하려고 하는데요. 바이너리 (binary) 분류 문제가 있는 간단한 셋업 (setup)을 생각해 보죠. 여기서 우리는 선을 그려서 이 빨간 점들을 파란 점들과 구별하고 싶을 겁니다. 왼쪽을 보면, 그 데이타 점들이 정규화되어 있지 않고, 중심이 맞춰져 있지 않고, 원점에서 멀리 있으면, 그것들을 분리하기 위해서 선을 사용할 수 있다는 것을 알수 있죠. 그러나 그 선이 약간 움직인다면 (wiggle), 우리의 분류는 완전히 망가지게 됩니다. 그게 의미하는 건, 왼쪽의 예에서, 손실 함수는 우리의 가중치 행렬에서 선형 분류기의 작은 변화에 매우 민감하다는 거죠. 우리는 여전히 같은 함수를 표현할 수 있지만, 그건 학습을 매우 어렵게 합니다. 왜냐면, 다시 얘기하지만, 그들의 손실이 우리의 파라미터 벡터에 매우 민감하니까요. 반면에, 오른쪽의 상황에선, 데이타 클라우드를 취해서 원점으로 옮기고, 그걸 단위 분산으로 만들면, 그 데이타를 꽤 잘 분류할 수 있고, 또 그 선을 약간 움직여도 우리의 손실 함수는 파라미터 값의 작은 변화에 덜 민감합니다. 그게 아마도 최적화를 좀 더 쉽게 합니다. 진행하면서 조금 보게 될 겁니다. 그런데 이 상황은 선형 분류의 경우만 있는 건 아닙니다. 신경망 안에는, 우리는 이 선형 행렬 곱셉, 즉 합성곱이 끼워져 있고, 뒤에 비선형 활성 함수가 온다는 것을 기억해야죠. 만약 신경망의 어떤 계층에 대한 입력이 중심이 맞춰져 있지 않고 0 평균이 아니고, 단위 분산이 아니면, 그 계층에서 가중치 행렬의 작은 변화가 그 계층의 출력의 큰 변화를 유발할 수 있습니다. 이것이 또한 학습을 어렵게 합니다. 이것이 왜 정규화가 중요한지에 대한 일종의 직관이죠.
정규화가 매우 중요하다는 이 직관을 가지고 있기 때문에, 배치 정규화에 대해 이야기 했죠. 여기서는 이 추가적인 계층을 우리 망에 더해서 모든 중간 활성이 0 평균이 되고 단위 분산이 되도록 합니다. 배치 정규화 방정식을 모양 (shape)과 함께 여기에서 좀 더 명시적으로 다시 요약했는데요. 다시 배치 정규화에선, 순전파 (forward pass)에서 우리는 미니 배치의 통계를 사용하여 평균과 표준 편차를 사용한다는 것을 알고 있고, 그 추정치를 순방향에서 데이타를 정규화하는데 사용하죠. 그다음 우리는 또한 레이어의 표현력을 높이기 위해 배율 (scale) 파라미터와 이동 (shift) 파라미터를 다시 도입합니다.
또한 지난시간에 학습 프로세스를 베이비 시팅하는 것에 대해 얘기했죠. 훈련동안 손실 곡선을 어떻게 봐야 하는지에 대해서요. 여기 어떤 망에 대한 예제가 있는데, 주말동안 제가 훈련시킨 겁니다. 이건 보통 제가 이런 작업을 할 때 하는 셋업입니다. 왼쪽엔 시간에 따른 훈련 손실을 보여주는 플롯 (plot)이 있죠. 이게 내려가는 것을 볼 수 있는데, 제 망이 손실을 줄이고 있다는 것을 보여주는 거죠. 잘 되고 있습니다. 오른쪽엔 플롯이 있는데, x 축은 시간 혹은 이터레이션 (iteration) 숫자고 , y축은 훈련셋과 검증셋에 대한 성능 측정이죠. 볼 수 있듯이, 시간이 지나면서, 훈련셋 성능은 위로 위로 위로 위로 올라가고, 손실 함수는 내려갑니다. 그러나 어떤 지점에서, 검증셋 성능은 일종의 평원을 이루죠. 이건 어쩌면 제가 이 상황에서는 과적합되었다는 (overfitting) 것을 암시합니다. 어쩌면, 저는 추가적인 정규화 (regularization)를 추가하려고 했어야 할 것 같네요.
우리는 또한 지난 시간에 하이퍼파라미터 탐색에 대해서 얘기했었죠. 이 모든 망들은 일종의 커다란 하이퍼파라미터 동물원 (zoo)을 갖고 있죠. 그것들을 모두 정확하게 설정하는 것이 꽤 중요합니다. 그리드 탐색 대 (vs.) 랜덤 탐색에 대해서 얘기 했고, 어떻게 랜덤 탐색이 이론적으로는 좀 더 나을 수 있는지 대해서 얘기했죠. 왜냐면 이런 상황에서는 성능이 다른 파라미터들보다 하나의 파라미터에 대해 더 민감할 수 있으니까요. 그리고 랜덤 탐색이 그 공간을 좀 더 잘 커버하도록 해 줍니다. 우리는 또한 굵은 (coarse) 탐색에서 미세한 (fine) 탐색으로 이동하는 아이디어를 얘기했고, 하이퍼파라미터 최적화를 할 때는 여러분은 아마도 하이퍼파라미터에 대해서 매우 넓은 범위로 시작하고 싶을 거고, 2번 정도의 이터레이션 동안 훈련하고 그 결과에 따라서 좋은 하이퍼파라미터 범위로 좁히는 거죠. 이제 다시 몇 번 이터레이션 동안 더 작은 범위에서 탐색하는 거죠. 이 과정을 반복해서 하이퍼파라미터를 위해 맞는 영역에 집중하는 겁니다. 다시 얘기하지만, 시작할 때 범위를 크게 가지는 것이 매우 중요합니다. 여러분은 모든 하이퍼파라미터에 대해서 매우 매우 넓은 범위를 원할 겁니다. 이상적으로는 그것들에 대한 충분히 넓은 범위를 탐색했다는 것을 알기 위해서는 그 범위들이 매우 넓어서 망이 범위의 양쪽 끝에서 폭파해야 합니다.
오늘 신경망을 학습할 때, 정말 재미있고 중요한 몇 가지 주제를 얘기하려고 합니다. 특히 얘기하고 싶은 건, 우린 더 멋진, 더 강력한 최적화 알고리즘이 있다는 것을 몇 번 암시했었는데요. 오늘 시간을 좀 써서 이것들에 대해서 파고 들고 요즘 사람들이 사용하는 최적화 알고리즘이 뭔지 얘기하겠습니다. 또한 앞선 강의에서 정규화 (regularization)도 건드렸는데요. 이 개념은 여러분의 망을 훈련과 테스트 오류 사이의 간격을 줄이도록 추가적인 것을 하는 거죠. 실제에서 사람들이 신경망에 관한 정규화에 사용하는 전략을 몇 가지 더 얘기하고 싶습니다. 마지막으로 저는 또한 전이 학습에 대해서도 약간 이야기하고 싶습니다. 가끔씩 여러분은 하나의 문제에서 다른 문제로 이동함으로써 생각보다 적은 데이타를 사용하면서도 잘 할 수가 있습니다.
몇 강의 앞의 내용을 기억해 보면, 신경망을 훈련시키는 핵심 전략은 최적화 문제이죠. 우리는 손실 함수를 작성하고, 그 함수는 가중치의 값들이 우리의 문제에 대해 얼마나 좋은지 나쁜지를 얘기해 줍니다. 이 손실 함수는 우리에게 가중치에 대한 어떤 좋은 지형 (landscape)를 준다고 생각할 수 있습니다. 오른쪽에 제가 작은 2차원 문제를 보여줬는데요. x와 y축은 가중치의 두 가지 값입니다. 그리고 플롯의 색깔은 손실 값을 나타냅니다. 이 2차원 문제의 만화 그림에서, 우리는 이 w1과 w2의 두 가지 값에 대해서만 최적화를 하는 거죠. 목표는 이 경우 가장 빨간 영역을 찾아서 낮은 손실로 가중치를 설정하는 것입니다. 기억할 건, 지금까지 우리는 이 극도로 단순한 최적화 알고리즘, 확률적 경사 하강으로 작업했다는 겁니다. 이건 매우 간단해서 3줄짜리 코드죠. 참 (true)인 동안, 우리는 먼저 데이타의 미니배치에서 경사의 손실을 평가하고 그다음 우리의 파라미터 벡터를 경사의 음의 방향으로 업데이트합니다. 왜냐면, 이것이 손실 함수가 가장 크게 감소하는 방향을 알려주니까요. 그다음 우리는 이걸 계속 반복하고 바라건대 빨간 영역으로 수렴해서 커다란 에러를 얻고 행복해지는거죠. 그러나 불행하게도 , 이 비교적 간단한 최적화 알고리즘은 실제에서 발생할 수 있는 많은 문제가 있습니다.
확률적 경사하강의 문제중 하나는, 우리의 목표 함수가 이것처럼 생겼다고 생각해 보죠. 다시, 우리는 두 개의 값인 w1과 w2를 그리고 있습니다. 우리가 그것들 중 하나를 변경하면, 손실 함수는 매우 느리게 변합니다. 우리가 수평값을 변경하면, 우리의 손실은 천천히 변하죠. 이 지형에서 우리가 위 아래로 움직이면, 이제 손실은 수직적 방향의 변화에 매우 민감합니다. 그런데, 이건 이 지점에서 나쁜 조건 숫자 (bad condition number)를 가진 손실로 언급됩니다. 이건 그 지점에서 해세 행렬 (Hessian matrix)의 가장 큰 특이값 (singular value)과 가장 작은 특이값 사이의 비율이죠. 그러나 직관적인 아이디어는 손실 지형이 타코 쉘 (taco shell)처럼 생겼다는 겁니다. 그건 하나의 방향으론 매우 민감하고 다른 방향으로는 민감하지 않죠. 궁금한 점은 함수에 대한 확률적 경사하강이 어떻게 생겼냐는 겁니다.
이런 종류의 함수에 확률적 경사 하강을 실행하면, 여러분은 이런 특징적인 지그재그 (zigzag) 동작을 얻게 되는데, 이런 종류의 목표 함수에 대해서는 경사의 방향이 최소값을 향한 방향으로 정렬되지 (align) 않았기 때문입니다. 경사를 계산하고 한 걸음 가면, 이런 앞뒤로 지그재그하는 선 위를 걷는거죠. 사실, 여러분은 이 수평 차원을 따라서는 매우 느린 진행을 하는 거고, 이 차원은 덜 민감한 차원이죠. 빠르게 변화하는 차원에 걸쳐서 이런 끔찍한 지그재그 동작을 얻게 됩니다. 이건 바람직하지 않은 동작이죠. 그런데, 이 문제는 고차원에서는 훨씬 더 흔해 집니다. 이 만화 그림에서는, 우리는 단지 2차원 최적화 지형을 보여주고 있지만, 실제에선, 우리의 신경망은 수백만, 수천만, 수억개의 파라미터를 가질 수도 있습니다. 이런 것들이 움직일 수 있는 방향이 수억개라는 거죠. 그 수억개의 움직일 수 있는 방향중에서, 만약 가장 큰 것과 가장 작은 것의 비율이 나쁘다면, 그럼 SGD (stochasitic gradient descent, 확률적 경사 하강)는 좋은 성능이 안나옵니다. 우리가 1억개의 파라미터를 가지는 것을 상상해 보면, 그 둘의 최대 비율은 아마도 꽤 클 겁니다. 실제에서 많은 고차원 문제들에 대해서 이건 꽤 큰 문제라고 생각합니다.
SGD의 또 다른 문제는 극소 (local minima) 혹은 안장점 (saddle point)라는 아이디어와 관련이 있습니다. 그래프를 약간 바꿨는데요. x축은 하나의 파라미터의 값을 보여주고 있고, y축은 손실을 보여주고 있습니다. 위의 예에서, 우리는 곡선의 목표 함수가 잇는데, 중간에 계곡이 있습니다. 이 상황에서 SGD에 무슨 일이 벌어질까요? 이 경우에 SGD는 멈춥니다.
왜냐면 이 극소에서, 지역적으로 평평하니까 경사가 0이기 때문이죠. SGD에서 기억할 건, 경사를 계산하고 경사의 반대 방향으로 걸어가는데, 현재 지점에서 반대 경사가 0이라면, 그럼 우리는 더이상 전진하지 않을 겁니다. 이지점에서 멈추는 거죠. 이 안장점 아이디어의 문제가 있는데요. 극소가 되는 대신, 한 지점을 생각할 수 있습니다. 한 방향으로 우리는 올라가고, 다른 방향으로 우리는 내려갑니다. 우리의 현재 지점에서 경사는 0입니다. 이지점에서 다시, 이 함수는 안장점에서 멈출 겁니다. 왜냐면 경사가 0이니까요.
한가지 지적하고 싶은 점은, 이와 같은 1차원 문제에서는 극소가 큰 문제인것 같고, 안장점이 걱정할 일이 아닌 것 같아 보입니다. 그러나 사실 매우 높은 차원 문제로 가면, 그건 반대입니다. 만약 1억 차원 공간에 있다고 생각해 보면, 안장점은 뭘 의미할까요? 그건 어떤 방향으로는 손실이 올라가고, 어떤 방향으로는 손실이 내려갑니다. 1억 차원이 있다면, 사실상 그건 거의 모든 곳에서 발생할 겁니다. 반면에 극소는 움직일 수 있는 모든 1억개의 방향중에서, 모든 방향이 손실을 올라가게 하죠. 사실, 이런 매우 높은 차원문제를 생각하면, 그건 꽤 드물어 보입니다. 최근 몇 년 동안 밝혀진 것은, 이런 매우 큰 신경망을 훈련할 때, 안장 지점에 대한 문제가 더 많고 지역 최소에 대한 문제가 적다는 겁니다. 그런데, 정확히 안장점에서만 문제가 있는 것이 아니라, 안장 지점 근처에도 문제가 있습니다. 아래쪽 예를 보면, 안장 지점 근처 영역에서, 경사는 0이 아니지만, 기울기가 매우 작죠. 그게 의미하는 건 만약 우리가 경사 방향으로 걸어가면 경사가 매우 작을 거고, 현재 파라미터값이 목표 지형에서 안장 지점 근처에 있을 때마다 우리는 매우 매우 느리게 전진할 겁니다. 이건 사실 큰 문제죠.
SGD의 또 다른 문제는 S에서 옵니다. SGD가 확률적 경사 하강이라는 것을 기억해야죠. 우리의 손실 함수는 전형적으로 수많은 여러 예제에 대한 손실을 계산하여 정의된다는 것을 회상해 보죠. 이 경우 만약 N이 전체 훈련 셋이면, 약 1백만이 될 겁니다. 매번 손실을 계산하는 것은 매우 비싸죠. 실제에선, 작은 예제의 미니배치를 이용해서 종종 손실과 경사를 추정한다는 것을 기억해야죠. 이것이 의미하는 바는 매번 걸음에서 경사에 대한 진짜 정보를 사실 얻고 있지 않다는 겁니다. 그 대신, 우리는 현재 지점에서 약간의 노이즈가 있는 경사 추정값을 얻고 있는 겁니다. 여기 오른쪽에, 이 플롯을 약간 조작했는데요. 저는 매 지점에서 임의의 균일한 노이즈를 경사에 추가했습니다. 이 노이즈로 SGD를 실행했더니 경사를 망쳐놨네요. 이것은 어쩌면 SGD 과정에서 정확히 일어나는 일이 아닐 수 있습니다. 그러나 경사 추정에 노이즈가 있으면, 바닐라 (vanilla) SGD는 공간을 이리저리 다니다가 최소를 향해 가기까지 사실 오랜 시간이 걸릴 수 있다는 것을 느끼게 해줍니다.
고맙게도, 이 문제들 중 많은 부분을 잘 해결해 주는 아주, 아주 간단한 전략이 있습니다. 그건 우리의 확률적 경사하강에 모멘텀 (momentum) 항을 추가하는 아이디어입니다. 왼쪽에 우리의 고전적인 오랜 친구인 SGD가 있고, 여기서 우리는 그냥 항상 경사의 방향으로 가는 거죠. 오른쪽에는 이 작고 작은 변화가 있는데, SGD + 모멘텀이라고 불리는 것입니다. 이건 이제 2개의 방정식이고 코드 5줄이라서 2배로 복잡하죠. 그러나 아주 간단합니다. 아이디어는 시간에 따른 속도 (velocity)를 유지하고, 속도에 경사 추정값을 더합니다. 그다음 경사의 방향으로 걸어가는게 아니라 속도의 방향으로 걸어가죠. 이건 매우 매우 간단합니다. 또한 하이퍼파라미터 로 (rho)가 있는데, 마찰 (friction)에 해당합니다. 이제 매걸음마다, 우리는 현재 속도를 취해서, 현재의 속도를 마찰 상수, 로 만큼 줄입니다. 종종 높은 값인데, 0.9가 자주 쓰이죠. 현재 속도를 취하고 마찰로 줄인다음 경사에 더합니다. 이제 우리는 원래 경사 벡터 방향이 아니라 속도 벡터 방향으로 걸어갑니다.
이 슈퍼 슈퍼 간단한 전략이 우리가 얘기한 이 모든 문제에 대해서 사실상 도움을 줍니다. 극소나 안장점에서 무슨 일이 벌어지는지 생각해 보면, 이 시스템의 속도를 생각해 보면, 언덕을 굴러 내려가는 공이 내려갈 수록 속도를 더해 간다는 물리적 해석을 할 수 있습니다. 일단 속도를 가지면, 극소를 지나가는 경우에도, 그 지점은 경사는 없더라도 속도를 가질 겁니다. 그리고나면 바라건데, 우리는 이 극소를 극복할 수 있고 계속 아래로 내려갈 수 있죠. 안장점 근처에서도 이런 비슷한 직관이 있는데, 안장점 근처의 경사가 매우 작지만, 경사를 내려가면서 우리가 만든 이 속도 벡터를 가지게 됩니다. 바라건데 이것이 우리가 안장점을 지나가도록 해 주고 아래로 쭉 굴러가게 해주는 거죠. 나쁜 조건 (poor conditioning)에서 무슨 일이 벌어지는 지를 생각해 보면, 만약 우리가 경사에 대한 이런 지그재그한 추정을 가지려고 한다면, 바라건데 우리가 모멘텀을 사용하면 그 지그재그는 서로 서로를 꽤 빠르게 지워서 없앨 것입니다. 이건 효과적으로 우리가 민감한 방향으로 움직이는 양을 줄일 것입니다. 반면에, 수평 방향으로는 우리의 속도는 계속해서 커질 거고 덜 민감한 차원을 가로질러 우리의 하강을 더 가속화할 것입니다. 여기에 모멘텀을 더하는 것도 역시 이 하이 컨디션 넘버 문제 (high condition number problem)에 도움을 줍니다. 마지막으로, 오른쪽에서 우리는 반복적으로 노이즈가 있는 경사 하강을 시각화하고 있는데요. 여기 까만색이 바닐라 SGD이고, 여기저기 지그재그하고 있죠. 파란색 선은 모멘텀이 있는 SGD입니다. 우리가 그걸 더했기 때문에, 시간에 따라 속도가 증가하고 노이즈는 경사추정에서 평균값으로 없어지는 거죠. 이제 최소를 향해서 훨씬 더 부드러운 경로를 따르게 되고, 비교해 보면, SGD는 노이즈 때문에 왔다갔다 하고 있죠.
SGD + 모멘텀을 할 때, 생각해 볼 수 있는 그림 하나가 있는데요. 여기 빨간점은 우리의 현재 위치죠. 현재 위치에서, 우리는 어떤 빨간 벡터를 가지는데 이건 경사의 방향이죠. 혹은 현재 지점에서 우리의 경사 추정일 수도 있죠. 녹색은 우리의 속도 벡터의 방향입니다. 우리가 모멘텀 업데이트 (update)를 할 때, 이 둘의 가중치 평균에 따라서 움직이는 겁니다. 이것이 우리의 경사 추정에서 약간의 노이즈를 극복하도록 도와줍니다.
'AI' 카테고리의 다른 글