인공신경망 학습 레시피 (번역)

본 글은 번역글입니다. TensorFlow Korea의 회원분들께서 번역에 공동 참석해주셨습니다. 귀중한 시간 할애해주신 nakosung님, Younghan Kim님, dg kim님 및 여러 분들께 감사드립니다!

출처: https://karpathy.github.io/2019/04/25/recipe/
저자: Andrej Karpathy
원제: A Recipe for Training Neural Networks


몇 주 전, “인공신경망을 학습시킬때 가장 흔히 범하는 실수”를 주제로한 트윗을 올렸습니다. 그 트윗은 제가 생각했던 것 보다 많은 호응을 불러일으켰습니다. 이는 분명 컨볼루션 레이어의 작동원리에대한 간단한 이론과 컨볼루션 네트웍에서 최고의 성능을 뽑아내기위한 기법 사이에 있는 큰 간극을 많은 사람들이 겪어왔기 때문이 아닌가 생각합니다.

이는 중요한 논제인만큼, 제 블로그에 먼지도 좀 털겸, 트위터에 올린 내용을 바탕으로 보다 긴 글을 적어보는게 좋겠다고 결심했습니다. 그러나 흔히 범하는 오류를 줄줄이 나열하는것보다는, 이러한 오류를 어떻게 완전히 회피하거나 재빨리 수정할 수 있는지에대해 보다 심도있게 적어볼까 합니다. 비결을 간단하게 말씀드리자면 특정한 프로세스를 따르는 것인데요, 제가 알기로 이런 프로세스는 문서화가 잘 안되어있었습니다. 본론으로 들어가기 이전에 우선 두 가지 중요한 관찰사항에대해 얘기하겠습니다.

1. 인공신경망 학습과정은 라이브러리를 이용해 완벽히 추상화하는게 불가능하다

인공신경망을 학습시키는건 쉽다고들 합니다. 수 많은 라이브러리와 프레임웍들이 서른 줄 남짓한 코드를 보여주며 당신의 데이터 문제를 얼마나 손쉽게 풀수있는지를 뽐내면서, 마치 인공신경망 학습이란게 전원 넣고 버튼만 누르면 될것같은 (잘못된) 인상을 줍니다. 다음과 같은 코드 조각을 흔히 보셨을겁니다:

>>> your_data = # 당신의 멋진 데이터셋
>>> model = SuperCrossValidator(SuperDuper.fit, your_data, ResNet50, SGDOptimizer)
# 세상을 정복하기위한 코드

이 라이브러리와 예제는 마치 인공신경망 학습이 마치 일반적인 소프트웨어와 같이 깔끔한 API와 추상화가 가능하다는 인상을 줍니다. 마치 리퀘스트 라이브러리처럼요.

>>> r = requests.get(‘https://api.github.com/user', auth=(‘user’, ‘pass’))
>>> r.status_code
200

완전 쿨하죠! 어느 용기있는 개발자가 쿼리, 문자열, 주소, GET/POST리퀘스트, HTTP 연결 등등에대해 여러분을 대신해 열심히 이해해서 그 복잡성을 몇 줄의 코드 뒤에 완전히 숨겨둔겁니다. 우리는 이런것에 익숙하고 또 이런것을 기대하죠. 하지만 불행히도 인공신경망은 이런것과는 완전히 다릅니다. 이미지넷 분류기 정도에서 한 발짝만 벗어나도 “포장만 뜯으면 쓸 수 있는” 그런 기술과는 한참 멀어지지요. 제 블로그 글 “당신도 역전파를 이해해야한다”에서도 이러한 논점을 피력한 적이 있는데요, 불행히도 상황은 사실 훨씬 심각합니다. 역전파와 SGD만 적용한다고해서 인공신경망이 마술처럼 동작하지는 않거든요. 배치놈만 쓴다고해서 수렴이 마술처럼 이루어지는것도 아니구요. RNN을 도입한다 해서 텍스트를 마술처럼 그저 이해하게 되는것도 아닙니다. 또한 강화학습으로 문제를 정의할 수 있다 해서 꼭 그렇게 풀어야 하는것도 아니지요. 기술에대해 이해하지 못해도 사용만 할 수 있다는 입장을 고수한다면 당신은 아마 실패할겁니다. 그래서 드릴 말씀이..

2. 학습 실패는 소리없이 찾아온다

코드를 잘못 짜거나 설정을 잘못 넣는다면 종종 예외처리문을 만납니다. 문자열을 넣어야할 곳에 정수를 넣거나, 어떤 함수가 매개변수를 세 개만 받거나, import문이 실패하거나, 키가 존재하지 않는다거나, 두 개의 리스트간에 원소의 숫자가 일치하지 않는다거나 말이죠. 게다가 어떠한 기능에대해서는 단위테스트를 작성할 수도 있습니다.

인공신경망 학습에 있어서 이는 시작에 불과합니다. 모든것이 구문적으로 올바르더라도 뭔가가 제대로 짜여지지 않았을 수가 있고, 이는 탐지하기가 정말 어렵습니다. 오류가 발생할 수 있는 경우의 수는 많고, 오류는 구문적이기보다 논리적이고, 단위테스트를 하기가 매우 까다롭습니다. 예를들어 데이터를 증대(augmentation)시키는 과정에서 이미지의 왼쪽과 오른쪽을 뒤집어놓고 레이블은 뒤집는걸 깜빡했을 수도 있습니다. 이런 실수를 범했음에도 불구하고 뉴럴넷이 좌우반전 속성까지도 학습해버리는 바람에 학습된 모델이 (충격적이게도) 잘 동작할지도 모릅니다. 혹은 자기회기모델 (autoregressive model)을 학습시키면서 off-by-one bug(예를 들자면, 배열이나 루프에서 바운더리 컨디션 체크와 관련한 논리적 에러) 인해 예측하려는 값을 인풋으로 취하는 실수를 할지도 모릅니다. 혹은, 변화도(gradient)를 잘라낸다는게 손실함수(loss)를 잘라내버려 학습과정에서 아웃라이어 데이터들이 무시될수도 있습니다. 또는 미리 학습해둔 체크포인트로부터 웨이트를 초기화했는데 원래의 평균값을 쓰지 않는 실수를 범할 수도 있습니다. 혹은 그냥 정규화 세팅, 학습률, 학습률 감소율, 모델 크기 등에대한 설정을 잘못 할 수도 있지요. 따라서 당신이 잘못 설정한 인공신경망이 예외를 발생시킨다면, 그건 아주 운이 좋은 경우일겁니다. 대부분의 경우에 학습은 아무 문제 없다는듯 진행되고 성능만 살짝 떨어지고 말겁니다.

이 결과에 따라, (아주 중요!!! 아무리 강조해도 지나치지 않은 부분) 신경망을 학습하기 위한 방법으로 “빠르고 강렬한” 방법은 전혀 작동하지 않고 오히려 고통만 가져옵니다. 고통을 겪어야 하는 것은 신경망이 잘 작동되도록 하기 위한 아주 자연스러운 부분이지만, 학습과 관련된 모든 요소들에 아주 조심스럽고, 방어적이고 시각화에 집착하며 접근하면 고통은 크게 줄어들 수 있습니다. 제 경험상에 따르면 이렇게 참을성을 가지고 디테일에 집착하는 태도가 딥러닝을 성공시키는데 가장 중요한 요소입니다.

레시피

위에서 언급한 두 사실에 기반하여 저는 인공신경망을 도입할 때에 늘 참고하는 저만의 구체적인 프로세스를 적립했으며, 이 프로세스를 여러분들께 소개드리려 합니다. 이 프로세스를 보시면 제가 위의 두 원칙을 아주 진지하게 받아들였다는걸 아실겁니다. 각 단계를 거치면서 모델은 단순한 것에서 시작하여 점차 복잡한 것으로 발달되며, 매 단계마다 어떠한 일이 생길지에 대한 구체적인 가설을 세우며, 각 단계가 도입된 후 혹시 발생했을지 모를 문제를 찾아내기위해 실험과 검증을 반복합니다. 이렇게 조심스럽게 접근하는 이유는 검증되지 않은 복잡성이 갑자기 늘어나는걸 최대한 방지하여 발견하는데까지 평생이 걸릴수도, 혹은 영영 발견되지 못할수도 있는 버그나 오류를 최대한 예방하고자 함입니다. 트레이닝을 위한 코드를 짜는 것을 인공지능망 학습 과정에 비유하자면, 아주 작은 학습률로 학습을 시작한뒤 매 반복 (iteration)마다 테스트 데이터셋 전체를 평가하듯이 각 단계를 진행해야 합니다.

1. 데이터와 하나가 되어라

뉴럴넷을 학습하는 첫 단계는 뉴럴넷 코드는 건드리지 않고 당신의 데이터를 철저하게 살피는 것입니다. 이 단계는 굉장히 중요합니다. 저는 수천 개의 데이터를 훑어보며, 그 분포를 이해하고 패턴을 찾는 데 많은 시간을 보내는 것을 좋아합니다. 다행히도 당신의 뇌는 이러한 작업을 잘합니다. 한번은 데이터에 중복이 있다는 것을 발견한 적이 있습니다. 또 손상된 이미지, 라벨을 발견한 적도 있습니다. 데이터의 불균형과 편향을 찾아볼 수도 있습니다. 저는 대체로 저 스스로가 데이터를 분류하는 과정에 주의를 기울이는데, 이것은 우리가 궁극적으로 사용하게 될 아키텍처에 대한 힌트를 줍니다. 예를 들자면 이런거죠. 아주 지역적인 특성들 만으로 충분한지 혹은 전역적인 맥락이 필요한지, 얼마나 많은 변화가 있고 어떤 형태들을 가지고 있는지, 어떠한 비정상적인 변화가 감지되고, 전처리를 통해 이를 제거할 수 있을지, 공간적 위치가 중요한지, 아니면 average 풀링이 좋을지, 세부사항이 얼마나 중요하고 얼마나 많은 이미지들을 샘플링을 통해 줄일 수 있을지, 라벨에 얼마나 노이즈가 많은지, 등을 살펴볼 수 있습니다.

또한, 인공신공망이란 결국 데이터를 압축하고 편찬해주는 도구이기에, 네트워크 예측(오류)을보고 어디에서 발생한 것인지 이해할 수 있습니다. 만약 예측값이 원래 데이터와 일관성이 떨어진다면, 무엇인가 잘못되었다는것을 알 수 있습니다.

데이터의 품질에대한 대략적 감을 잡으셨다면 데이터를 찾고, 걸러내고, 정렬하기위한 간단한 코드를 작성해봅니다. 그 기준은 레이블의 타입, 주석 (annotation)의크기와 숫자 등 생각나는 무엇이건 될 수 있습니다. 그리고 각 기준에따른 데이터의 분포를 시각화해보고 각 기준에따라 시각회 했을때 분포를 벗어나 튀는 녀석들, 즉 outlier들을 찾아봅니다. 아웃라이어는 거의 언제나 데이터의 품질과 관련된 문제이거나 전처리과정에서의 버그로인해 나타납니다.

2. 트레이닝에서 평가까지 전 단계를 아우르는 골격을 먼저 짜고 기준 성능을 측정해보라

이제 데이터도 이해했겠다, 완전 쩌는 멀티스케일 ASPP FPN 레즈넷을 이용해 멋진 모델을 트레이닝 하면 되겠죠? 물론 그러면 안됩니다. 그것은 고통으로 가는 지름길이죠. 바로 다음 단계는 학습과 평가에 이르는 골격을 짜고 몇 번의 실험을 통해 정확성에대한 신뢰를 쌓는 단계입니다. 이 단계에서는 절대 망치기 힘든 단순한 모델을 몇 개 고르는게 좋습니다. 아주 작은 컨볼루션넷이나 선형 분류기 같은걸로요. 이제 이걸 학습시키고, 손실을 시각화하고, 메트릭을 측정하고, 모델의 예측값을 얻고, 몇가지 명시적인 가정을 세운후 이를 확인하기위한 일련의 실험을 수행해봅니다. 몇가지 팁:

  • 랜덤 시드를 고정시켜라. 랜덤 시드로 고정값을 사용하시면 코드를 다시 돌렸을때 항상 같은 결과가 나오도록 할 수 있습니다. 가변 변수를 하나라도 줄이는게 정신건강에 이롭습니다.
  • 단순화. 불필요하게 화려한 요소들을 최소화하세요. 예를들어 이 단계에서 데이터 증대는 꼭 끄셔야 합니다. 일반화를 위한 데이터 증대는 다음 단계에서 소개할것이지만, 지금 단계에서는 어이없는 버그를 칠수있는 또 하나의 기회가 될 뿐입니다.
  • 평가과정에서 중요한 숫자를 확인하세요. 테스트 손실을 그려볼 때에는 전체 테스트셋을 돌려봅니다. 단순히 배치에따른 테스트 손실을 텐서보드로 시각화하면서 혼련을 거쳐 손실을 매끄럽게 바꾸는데에만 초점을 맞추지 마세요. 우리는 정확성을 추구하고 있는만큼 트레이닝 손실에만 안주해서는 안됩니다.
  • 초기의 손실값을 확인하라. 최초의 로스값이 올바른지를 확인하세요. 예를들어 마지막 레이어를 잘 초기화 했다면 초기의 소프트맥스층 결과값에서 -log(1/클래스갯수) 가 측정되어야 합니다. L2 리그레션이나 휴버로스 등의 경우에도 이와같이 기본값을 유도해낼 수 있습니다.
  • 초기화를 잘 하라. 마지막 레이어의 가중치를 잘 초기화시키세요. 예를들어 평균이 50인 값에 근사시킨다면 최종 바이어스를 50으로 초기화하세요. 양수와 음수가 1:10의 비율로 섞인 불균형한 데이터셋이 있다면, 네트웍이 초기에 0.1의 확률을 예측하도록 로짓 값을 초기화 하세요. 이 값들을 잘 설정함으로써 수렴이 더 빨리 이루어지고, 최초 반복(iteration)들이 바이어스 값 조정에 그침으로써 발생하는 하키스틱 모양의 손실 곡선을 피할 수 있습니다.
  • 인간 능력이란 기준값. 손실 이외에도 인간이 해석가능한 메트릭을 모니터링 하세요 (예: 정확도). 가능하다면 인간이 낼수있는 정확도도 측정해서 이와 비교해보세요. 이것이 여의치 않을 경우 매 테스트 데이터마다 주석을 두 번 달아서 하나는 예측값으로, 하나는 참값 (ground truth)으로 간주하세요.
  • 입력과 독립적인 기준값. 입력과 독립적인 기준값을 학습해보세요. 예를들어 가장 쉬운 방법은 모든 입력을 단순히 0으로 주는 겁니다. 이렇게 하면 진짜 데이터를 넣었을 때보다 성능이 떨어져야겠죠. 정말 그런가요? 말하자면 아무 정보가 없는 데이터로부터 모델이 정말로 아무런 정보를 추출해내지 못하는지 확인 해보세요.
  • 한 배치만 과적합 시키기. 아주 작은 예제에 한해서 하나의 배치에만 과적합 시켜보세요 (예를들어 두개). 그렇게 하기 위해서 레이어나 필터를 더해서 모델의 용량을 키우고, 가장 낮은 손실값 (0) 에 도달할 수 있는지 확인하세요. 제가 선호하는 방법은 레이블과 예측값을 하나의 그림에 시각화시켜서, 손실값이 최소일 때 그 둘이 정확히 일치하는지를 확인하는 겁니다. 만약 일치하지 않는다면 어딘가에 문제가 있는 것이고, 다음 단계로 나아갈 수 없습니다.
  • 학습 손실값이 감소하는지 확인하라. 이 단계에서는 지금까지 작은 모델을 사용했기 때문에 모델이 데이터셋에 과소적합 되어있을겁니다. 모델의 용량을 아주 조금만 증가시켜 보세요. 그리고 손실값이 예상되로 감소하는지 확인해보세요.
  • 모델에 넣기 바로 전 단계에서 데이터를 시각화. 모호함의 여지 없이 데이터를 시각화할 수 있는 가장 적절한 장소는 모델에 입력을 집어넣기 바로 전 단계입니다. (y_hat = model(x) 나 텐서플로우의 sess.run 직전 말입니다.) 다시 말하자면, 네트워크에 들어가기 바로 직전 단계에 텐서에 저장되어있는 데이터와 레이블을 해석하는건데요, 이 단계의 데이터만이 유일하게 진실을 드러내줍니다. 저는 이 단계에서 데이터 처리와 증대 과정에서 생긴 문제를 잡은것이 한두번이 아닙니다.
  • 학습중 예측값의 다이나믹을 시각화. 저는 학습이 진행되는 중에 미리 정해둔 테스트 배치에대한 모델의 예측값을 시각화하는걸 좋아합니다. 이 예측값이 어떻게 움직이는지에대한 역학을 보면 학습이 어떻게 진행되고 있는지에대한 강렬한 직관적 이해를 얻을 수 있습니다. 많은 경우, 데이터가 특정하지 않은 방향으로 오락가락만 하고 있다면 네트웍이 말하자면 아둥바둥대고 있다는걸 느낄 수 있고, 모델이 안정적이지 않다 (instable)는걸 알 수 있습니다. 학습률이 너무 낮거나 너무 높은 경우 (오락가락하는) 잡음의 양을통해 이를 쉽게 알아차릴 수 있습니다.
  • 의존성을 알기위해 역전파를 이용하라. 당신의 딥러닝 코드는 종종 복잡하고 벡터화되어있고 널리 전파되는 (broadcasted) 연산들을 포함하게 됩니다. 제가 몇 번 본적있는 비교적 흔한 버그로는, (예를 들어 전치 (transpose) 나 치환 (permute) 대신에 view를 사용해서) 사람들이 이를 잘못 이해해서 무심코 배치 차원간에 정보를 섞어버리는 것입니다. 우울한 사실이지만, 이런 버그가 발생하더라도 학습은 보통 계속되는데, 다른 예제로부터 이 정보를 무시하는법을 학습하게 되기 때문입니다. 이런 버그와 관련된 문제를 잡기위한 한 가지 방법은 특정 예제 i에대한 손실을 1.0으로 설정하고 입력단까지 역전파를 시켜서 이 i번째 입력에 대해서만 0이 아닌 경사도가 나오는지 확인하는 것입니다. 일반화해서 얘기하자면, 경사도를 이용해 신경망이 무엇에 의존하고 있는지에대한 정보를 얻을 수 있고, 이를 디버깅에 이용할 수 있습니다.
  • 특수한 경우를 일반화하기. 이건 일반적인 코딩 팁에 가깝습니다만, 사람들은 종종 너무 일반적인 기능을 구현하는 등 한번에 먹기엔 너무 큰 일을 맨땅에 시도하다 버그를 내곤 합니다. 저는 처음에는 매우 구체적인 함수부터 시작해서 이것이 잘 동작하는지부터 먼저 확인한 다음 보다 일반적으로 작동하는 함수를 다시 짜서 여전히 같은 결과를 얻는지 확인하는 방법을 씁니다. 이 기법은 벡터 연산 코드에 종종 쓰는데요, 저는 거의 항상 루프를 이용한 구현을 먼저 한 다음, 한 번에 하나씩 벡터 연산 코드로 다시 짭니다.

3. 과적합시키기

이 단계에 다다르면 데이터셋에대한 이해도도 충분할 것이고 학습과 평가를 해주는 파이프라인도 잘 동작하고 있을겁니다. 어떠한 모델이 학습되더라도 신뢰할 수 있는 메트릭도 반복적으로 계산할 수 있을겁니다. 입력과 독립적인 기저 성능값, 몇개의 대략적인 성능 기저값 (최소한 이정도는 도달해야겠죠), 그리고 사람이 낼수있는 정확도에대한 대략적인 추정치도 알고있습니다 (이것이 학습으로 달성하고자하는 목표치가 되겠습니다). 이제 본격적으로 반복을 통해 좋은 모델을 학습시킬 준비가 되었습니다.

제가 좋은 모델을 찾기위해 택하는 방법은 두 단계로 나눠집니다. 첫째, 과적합에 용이한 큰 모델을 학습 시키면서, 학습 손실값을 최소화하는데만 집중합니다. 둘째, 정규화를 적당히하여 테스트 (validation) 손실값을 줄입니다. 이 때 학습 손실값은 조금 희생합니다. 제가 이 두 단계를 선호하는 이유는, 어떤 모델을 사용했건 오류값 자체가 줄어들지 않는다면 이를통해 버그나 잘못된 설정 등의 이슈를 잡아낼 수 있기 때문입니다.

이 단계에 적용되는 팁과 트릭들:

  • 모델 선정. 좋은 학습 손실값에 도달하기 위해서는 데이터에 적합한 모델 아키텍처를 선정해야 합니다. 모델 선정에관해 제가 드리고싶은 가장 중요한 조언은 영웅이 되려하지 말라는 겁니다. 레이어를 레고처럼 희한하게 쌓으면서 미친듯이 창의적인 아키텍처를 만들어내려는 사람들을 저는 많이 봤습니다. 프로젝트의 초기 단계에서는 이러한 유혹을 철저히 물리치시길 바랍니다. 저는 단순히 가장 연관있는 논문을 찾아서 그들이 제시한 가장 단순하면서도 성능이 좋은 아키텍처를 채택하라고 항상 조언합니다. 이미지 분류기를 예로들자면 영웅이 되려하지 말고 그냥 ResNet-50을 복붙해서 돌리세요. 특별제작과 최적화는 뒷 단계에서도 얼마든지 할 수 있습니다.
  • Adam이 안전합니다. 베이스라인을 확립하는 초기 단계에서 저는 Adam을 학습률 3e-4 정도로 사용하는걸 선호합니다. 제 경험상으로는 아담이 학습률을 포함한 하이퍼파라메터 설정에 영향을 가장 적게 받는 편이었습니다. 콘볼루션 네트웍의 경우에는 튜닝이 잘 된 SGD가 거의 모든 경우에 아담보다 조금 더 나은 성능을 보여주나, 최적의 학습률 구간이란 정말 좁고 문제에 따라 가변적입니다. (덧: RNN을 포함한 시퀀스 모델의 경우 아담을 사용하는게 더 보편적입니다. 다시 한 번 말씀드리지만, 프로젝트의 초기에는 절대 무리해서 영웅이 되려하지 마시고 가장 연관있는 논문을 따라하시길 추천드립니다.)
  • 복잡도는 한번에 하나씩만 더하세요. 분류기에 들어갈 입력값이 여러개라면 한 번에 하나씩만 연결하시고, 하나씩 입력을 늘릴때마다 성능이 기대반큼 향상되는지를 꼭 확인하시길 조언드립니다. 주방 싱크대같이 복잡한걸 처음부터 통째로 때려넣으시면 안됩니다. 복잡도를 서서히 시키기위한 다른 방법들도 있습니다. 예를들어 처음엔 작은 이미지부터 시작해 점점 크기를 키워가는 방법도 있습니다.
  • 학습률 감쇠율은 호환이 안됩니다. 다른 도메인에서 쓰이던 코드를 가져와서 재사용할 경우 학습률 감쇠 (learning rate decay)를 매우 조심해서 다루셔야 합니다. 서로 다른 문제에 서로 다른 학습률 감쇠함수를 쓰는건 정말 당연한 일입니다. 게다가 학습률 감쇠 함수는 보통 현재 이폭 숫자에 맞춰 계산되도록 구현되어 있는데, 적정 이폭은 데이터셋에따라 크게 달라지기 마련입니다. 예를들어 ImageNet은 이폭30에서 학습률을 1/10로 떨어뜨립니다. 지금 학습시키는 모델이 ImageNet이 아니라면 이 방법이 통용될 가능성은 거의 없습니다. 주의를 기울이지 않는다면 학습률은 너무 빨리 0에 가깝게 떨어져버려 모델이 충분히 수렴하지 못할겁니다. 저는 학습을 시킬때 학습률 감쇠 기능을 완전히 꺼버리고 (저는 constant LR을 사용합니다) 감쇠값을 가장 마지막에 튜닝합니다.

4. 일반화 (REGULARIZE)

모든게 잘 진행되었다면 지금쯤 최소한 학습용 데이터셋에는 확실히 맞춰진 큰 모델을 가지고 있을겁니다. 이제 일반화 (regularization)을 적용하여 학습 정확도는 조금 희생하더라도 테스트 정확도를 올려야할 때입니다. 이 단계에 적용되는 팁은:

  • 더 많은 데이터를 모아라. 어떠한 경우에도 모델을 일반화시킬 수 있는 최선의 방법은 더 많은 (실제) 데이터를 모으는 것입니다. 더 많은 데이터를 모을 수 있는 상황에서 엔지니어링에 많은 노력을 소모하는것은 매우 흔한 실수입니다. 제가 아는한 잘 설계된 네트웍의 성능을 무한하게 향상시킬 수 있는 단 하나의 유일한 방법은 더 많은 데이터를 넣는 것입니다. 차선책으로는 (컴퓨팅 자원이 허용하는 경우) 앙상블이 있지만, 이는 다섯개 정도의 모델 이후로는 성능이 증가하지 않습니다.
  • 데이터 증대 (augmentation). 진짜 데이터를 더 모으는 방법 바로 다음으로 좋은 방법은 반쪽짜리 가짜 데이터를 만들어내는 방법입니다. 더 공격적인 데이터 증대 기법들을 적용해보세요.
  • 창의적인 데이터 증대. 반쪽짜리 데이터로도 부족하다면 완전한 가짜 데이터도 시도해보세요. 데이터를 뻥튀기시켜줄 다양한 창의적인 방법들이 연구되고 있습니다. 예를들어 도메인 랜덤화, 시뮬레이션, 데이터와 배경을 합성시키는 하이브리드 기법들, 혹은 GAN 등이 있습니다.
  • 전학습. 미리 학습시킨 네트워크를 사용하는건 왠만해서는 부작용이 없습니다. 이미 충분한 데이터가 있는 경우라도 마찬가지입니다.
  • 지도학습을 고수하라. 비지도식 전학습에 현혹되지 마세요. 2008년쯤에 올라온 블로그 글이 하는 말과는 다르게도, 최신 컴퓨터 비전 분야에서는 제가 아는한 쓸만한 결과물이 없습니다. (반면 자연어처리 분야에서는 BERT등의 비지도학습 기법들이 꽤나 좋은 결과를 거두고 있다고 하네요. 텍스트가 더 분명해지고 노이즈에비한 시그널의 비율이 높아져서 그런것 아닐까 합니다.)
  • 입력 차원은 낮게. 미심쩍은 시그널을 포함한 피처를 제거하세요. 미심쩍은 입력이 추가될수록 데이터셋의 사이즈가 충분히 크지 않은 경우 과적합의 빌미가 됩니다. 이와 비슷하게, 이미지의 세세한 디테일이 크게 중요치 않다면 더 작은 이미지를 사용해보세요.
  • 모델 크기는 작게. 많은 경우, 도메인에대한 지식을 이용해 신경망의 크기를 줄일 수 있습니다. 예를 들자면, ImageNet의 백본 가장 윗쪽에 완전연결 레이어 (fully-connected layer)를 사용하곤 했지만, 훗날 평균 풀링으로 대체되어 엄청난 수의 파라메터를 줄일 수 있게 되었습니다.
  • 작은 크기의 모델을 사용하세요. 대부분의 경우 도메인 특화된 제약 조건을 활용해서 네트워크 크기를 줄일 수 있습니다. 예를 들어서, 이미지넷의 백본 위에 Fully Connected 레이어를 사용하는 게 트렌드였지만, 이것들은 간단한 average pooling을 대체되었고, 처리 과정에 필요했던 수많은 파라미터를 없앨 수 있었습니다.
  • 배치크기를 줄이세요. 배치정규화 안의 정규화 때문에 더 작은 크기의 배치가 어떤 면에서 더 강한 레귤러라이제이션을 이끌어 냅니다. 이는 배치의 평균/분산 값들은 전체 데이터셋의 평균/분산 값의 추정치이기 때문인데, 따라서 더 작은 크기의 배치를 사용하면 스케일과 옵셋 값이 배치에 따라 더 많이 “뒤흔들리게” 되기 때문입니다.
  • 드롭. 드롭아웃을 추가하세요. ConvNet의 경우에는 Dropout2d (spatial dropout). Dropout은 배치정규화와는 잘 어울리지 못하는 경우가 있으므로, 주의해서/아껴서 사용하세요.
  • 가중치 감쇠. 가중치 감쇠 페널티를 증가시키세요.
  • 조기 종료. 지금까지 측정된 테스트 (validation) 손실값을 이용하여 과적합이 시작되려는 시점에 학습을 종료하세요.
  • 더 큰 모델을 시도해보세요. 이 항목을 가장 마지막에, 그리고 조기종료 바로 다음에 소개하긴 합니다만, 과거 경험에 따르면 큰 모델은 당연히 과적합에 이르게 되지만 조기종료를 사용해 과적합 전에 학습을 미리 멈춘 모델은 더 작은 모델로 학습시킨것보다 훨씬 나은 성능을 보여주기도 합니다.

끝으로, 학습시킨 분류기가 잘 동작한다는 추가적인 확신을 얻기위해서 저는 네트웍의 첫 번째 레이어의 가중치값을 시각화해서 깔끔한 모서리가 나오는지를 확인합니다. 첫번째 레이어의 필터가 마치 노이즈처럼 보인다면 뭔가가 잘못됐을 수 있습니다. 이와 비슷하게, 네트워크 중간쯤의 활성화 값도 이상한 값을 보일 수 있으며, 이것이 문제에대한 힌트를 줄 수도 있습니다.

5. 튜닝

이제쯤 당신은 데이터셋과 한 몸이 되어, 낮은 손실값에 도달하는 수많은 모델 아키텍처를 탐색하는 반복의 굴레에 들어와 계실겁니다. 이 단계에 적용되는 몇 가지 팁을 소개합니다:

  • 그리드 탐색보단 무작위 탐색. 여러개의 하이퍼파라메터를 동시에 튜닝하자니 모든 가능한 경우의 수를 커버할 수 있는 그리드 탐색을 이용하는게 좋을것 같지만, 무작위 탐색을 쓰는게 가장 효율적이라는걸 꼭 기억해두세요. 그 이유를 직관적으로 설명드리자면 인공신경망의 성능이 특정한 소수의 파라미터에 훨씬 민감하게 반응하기 때문입니다. 예를들어 파라미터 a를 변경했을때 손실값이 달라졌지만 b는 변경해도 아무런 영향이 없다면, a를 훨씬 철저히 샘플해보는게 좋겠지요.
  • 하이퍼파라미터 최적화. 찾아보시면 정말 많은 베이지안 하이퍼파라미터 최적화 도구들이 있고, 몇 친구들에 따르면 꽤나 성공적이기도 했다는데요, 제 경험에따르면 다양한 모델과 하이퍼파라메터를 효율적으로 탐색하는 현존 최고의 기술은 인턴에게 시키는겁니다 :) 농담입니다.

6. 마지막 한 방울까지

최적의 아키텍처와 하이퍼파라미터를 찾아낸 후에도 마지막 한방울의 성능까지 짜낼 수 있는 몇 가지 트릭이 있습니다.

  • 앙상블. 모델 앙상블은 어떠한 경우에서라도 2%정도는 정확도를 올려주는 확실한 방법입니다. 런타임에서의 계산량을 부담할 수 없는 경우라면 어둠의 지식 (dark knowledge)을 이용해 앙상블을 증류 (distil)하는 기법을 시도해보세요.
  • 계속 학습 시키기. 테스트 손실값 (validation loss)가 줄어들지 않으면 학습을 중단시키는 사람들을 종종 봅니다. 제 경험에따르면 아무리 오랜시간을 학습시켜도 학습은 계속됩니다. 한 번은 깜빡하고 겨울 휴가기간 내도록 학습을 시켜둔 적이 있었는데요, 1월에 휴가에서 복귀했더니 역대급 모델이 탄생해 있었습니다.

결론

이까지 오셨다면 성공을위해 필요한 모든 재료들을 갖추신겁니다. 기술과 데이터셋, 그리고 해결하고자 하는 문제에대해 깊은 이해를 갖추었고, 학습과 평가를 위한 총체적인 인프라도 갖추었으며, 점점 복잡한 모델들도 탐색했으며, 각 단계에서 예측가능한 만큼의 성능 향상도 이루었을 겁니다. 이제 당신은 많은 논문을 읽고, 실험도 많이 돌리고, 역대급 결과를 만들어낼 준비를 모두 마쳤습니다. 행운을 빕니다!

원글 출처: https://karpathy.github.io/2019/04/25/recipe/

This translation was inspired by Hwalsuk Lee who shared this article on TensorFlow Korea