딥 러닝이 유행하기 시작할 무렵 딥 러닝의 장점으로 나왔던 것이 특징을 추출하는 알고리즘(Feature extractor)을 데이터를 통해 학습한다는 것이었다. 즉 이전 머신 러닝 모델들이 데이터에서 특징을 추출한 다음 그 위에 모델을 얹은 식이었다면 딥 러닝 모델은 데이터를 넣어주면 결과까지 이어지는 모델을 만들 수 있다는 것이다. 딥 러닝은 많은 문제들에서 실제로 그런 식으로 적용된다. 가장 기본적인 이미지 분류를 생각하면, 이미지의 픽셀값 자체를 큰 전처리 없이 모델에 입력하면 그 이미지의 클래스가 바로 추출되고, 또한 그렇게 되도록 바로 학습할 수 있다. 더 복잡한 객체 탐지(Object detection) 문제 또한 딥 러닝 모델들이 이미지의 픽셀값을 입력으로 하여 각 객체를 포함하는 박스의 크기를 예측하는 방식으로 구성할 수 있다.

그렇지만 이런 식으로 모델 하나만으로 풀 수 없는 - 최소한 지금까지는 - 문제들이 많다. 예를 들어 객체를 탐지했다고 하면 그 객체들이 이미지에서 어떻게 배치되어 있는지, 이왕이면 자연어로 출력해주기를 바라게 되는 것이 아니겠는가? 언젠가는 이런 복잡한 작업들의 조합도 한 방에 처리할 수 있는 모델도 나오겠지만 일단 검증된 모델도 적고 데이터도 부족한 상황에서는 다른 방법을 찾게 된다. 자연스럽게 생각할 수 있는 방법은 이런 것이다:

문제를 두 단계로 나누고, 1단계에서 제법 쓸만한 모델은 있으니 (예를 들어, 객체 탐지 모델) 그 위에 2단계 모델을 만들어 붙이면 되지 않을까? (예를 들어, 자연어 생성)

그리고 여러분의 문제는 이제 두 개가 된다.

이번에 다룬 문제가 이런 식이었고 이런 문제를 풀면서 꽤 흥미로운 경험이었다고 생각했기 때문에 그에 대한 이야기를 써보려고 한다. 여러 모델을 조합하는 문제 뿐만 아니라 데이터를 만들고 검수하고 모델을 학습하고 평가하는 과정들에 대해서도 다루려 한다. 좀 뻔한 이야기이기는 하겠지만.

데이터

모델을 위한 데이터

데이터를 준비하는 과정에서 중요한 문제 중 하나는 모델이 처리하기에 적절한 데이터를 만들어야 한다는 것이다. 모델에 입력하기 적절한 형태여야 하고, (추후 다른 모델이 제공하게될) 모델에 제공해야할 특징feature들이 빠져서는 안 되고, 모델이 결과로 내보내기 용이한 레이블을 사용해야 하며, 그 레이블들을 우리가 최종적으로 원하는 결과로 변환할 수 있을지를 고려해야 한다. 그런데 데이터를 만드는 시점에서는 데이터가 어떠한지도 모르고 어떤 모델이 잘 작동할지도 모른다. 레이블이 없는 데이터를 직접 많이 보고, 문제와 모델에 대한 도메인 지식, 그리고 아래에 나올 여러 문제들을 충분히 고려하는 수밖에 없다.

모델 둘을 위한 데이터를 동시에 만든다는 것

일단 데이터가 없으면 데이터부터 만들어야 한다. 2단계 모델을 학습하기 위한 데이터는 당연히 필요하고, 위의 사례에서는 1단계 모델이 제법 쓸만하다고 가정했지만 1단계 모델도 특정한 도메인 영역에 대해서는 충분히 좋지 않을 가능성이 있기 때문에 1단계 모델을 파인튜닝하기 위한 데이터도 필요하다. 이상적으로는 순서대로 1단계 모델을 위한 데이터를 만들고 1단계 모델의 결과를 기반으로 2단계 모델을 위한 데이터를 만드는 식으로 할 수 있겠다. 그렇지만 그건 비효율적이니 결국 이 두 모델을 위한 데이터를 병렬적으로 만들게 된다. 그리고 여러분들은 순차적으로 수행해야 하는 문제를 병렬적으로 처리했을 때 어떤 문제가 발생할 수 있는지 모두 알고 있을 것이다.

모델이 완벽할 수 없고, 또한 실질적인 성능에서 큰 문제는 없더라도 결과가 모델 2를 위해 만든 데이터와는 미묘하게 다를 수 있다. 또한 모델 1과 모델 2는 별개로 학습되기 때문에 각 모델들이 최적화되는 목표 또한 다르다. 따라서 모델 2는 사람이 만든 데이터에 대해서 학습하면서도 모델 1에서 발생할 수 있는 오류에 대해서 강인robust해야 한다. 그런데 모델 1에서 발생할 수 있는 오류들을 어떻게 알 수 있겠는가? 결과와 데이터를 열심히 보고 (물리적인 의미다.) 신뢰롭게 추출할 수 없는 특징에 지나치게 의존하지 않도록 만드는 수밖에 없다.

사람의 오류

일단 머신 러닝 모델로 문제를 풀어야겠다는 생각이 들었다는 건 문제가 모호할 가능성이 높다는 것이다. 문제가 모호하다는 것은 데이터를 레이블링하는 사람이 봐도 헷갈리거나 혹은 사람에 따라 생각이 다를 수 있다는 것을 의미한다. 사실 이런 비교적 근본적인 문제 말고도 사람이 하는 일이다보니 하다보면 피곤하고 귀찮아져서 문제가 발생할 가능성도 높다. (직접 해보면 알 수 있다.) 데이터가 모호하고 오류가 많아지면 그 데이터로 학습한 모델 또한 문제가 발생할 가능성이 높다. 더 나아가 평가하는 데이터셋에도 모호성과 오류가 있을 가능성이 높아지니 평가 지표 자체의 신뢰성이 낮아질 가능성도 있다.

사실 이 문제에 대한 뾰족한 해결 방법은 잘 모르겠다. 데이터 구축 과정의 베스트 프랙티스를 공부해야겠다는 생각을 많이 했다. 이 문제에 대한 연구는 꽤 많다. (사회학에서 유명한 보상을 많이 제공한다고 품질이 올라가는 것은 아니라는 연구 같은 것들.) 검수 과정을 철저하게 하고 여러 명이 레이블링하게 하는 방식 같은 것도 쓸 수 있겠지만 가뜩이나 비싼 데이터 구축이 더 비싸진다. 일단 문제를 파악하는 것 자체가 중요하니 사람이 직접 다시 레이블링하고 두 레이블 셋을 비교해서 신뢰성reliability이 얼마나 되는지를 검토하는 것이 필요한 것 같다.

모델

데이터 전처리

레이블링 작업이 끝나고 나면 그 데이터를 모델을 학습시키는데 사용할 수 있도록 가공하는 작업이 필요하다. 이 과정에서 위생sanity 점검은 정말 정말 정말 중요하다! 레이블링 결과가 제대로 되어있는지, 전처리 결과가 모델을 학습하기 적절한 형태가 되었는지, 그리고 모델이 예측해야할 정보가 혹시 모델 입력으로 들어가지는 않았는지를 고려해야 한다. 마지막과 같은 경우는 이상한 실수라고 생각할 수도 있겠지만 전처리 과정이 복잡해지면 이런 정보가 아주 간접적이 방식으로나마 들어가는 경우가 생긴다. 그러면 머신 러닝 모델은 이걸 절대로 놓치지 않는다. 그러니 전처리된 데이터를 철저히 점검하고 레이블들이 정상적인지 직접 봐야 한다.

탐색적 데이터 분석

데이터 과학 서적이나 강의에서 가장 강조하는 것 중 하나가 데이터를 직접 보라는 것일 것이다. 데이터를 보지 않고 모델부터 돌려보지 말라는 것은 흔한 충고다. 그러나 데이터를 어떻게봐야 잘 보는 것인가? 데이터를 보는 것이 말처럼 쉬운 일은 아니다. 그래도 표 형태의 데이터라면 기본적으로 뽑아봐야할 기술 통계들과 도표들이 존재하지만, 딥 러닝을 적용하는 종류의 데이터들(이미지, 텍스트 등)은 이런 기술 통계적 도구가 (없진 않지만) 아무래도 부족하다.

첫 번째 방법은 정말로 데이터를 직접 보는 것이다. 이미지라면 이미지를 실제로 보고 텍스트라면 텍스트를 실제로 읽어보라. 아주 많이 보지 않더라도 데이터에 문제는 없는지, 지금 풀려고 하는 문제의 난이도가 어떠한지, 어떤 특징feature들이 모델 입력으로 필요할지, 그리고 어떤 예외적 사례들이 존재하는지를 많이 파악할 수 있다. 레이블 정보를 같이 보는 것이 좋으니 데이터와 레이블을 결합해서 보기 위한 도구나 스크립트를 만들 필요가 있다.

두 번째 방법은 역설적이지만 일단 모델을 만들어서 학습시키고 평가해보는 것이다. 대충 어느 정도의 성능을 기대할 수 있을지 파악할 수 있고, 모델의 성능 특성, 즉 모델이 어려워하는 케이스, 레이블, 그리고 더 나아가면 오차 행렬confusin matrix 등은 데이터에 대한 많은 정보를 제공한다.

모델 점검

두 가지 사항을 체크해야 한다.

  1. 모델 구현에 문제가 없는가? 이것은 작은 크기의 데이터를 가지고 loss를 0으로 떨어뜨릴 수 있는지를 통해 어느 정도 검증할 수 있다.

  2. 모델이 100% 완벽하게 동작한다면 목표를 달성할 수 있는가? 이는 모델을 학습시키는 레이블을 가지고 평가했을 때 평가 지표가 충분히 좋게 나오는지를 가지고 판단할 수 있다. 모델이 완벽하게 동작한다면 평가 지표에서도 완벽하면 좋겠지만 그럴 수 없는 경우도 있다. 이러한 경우에도 상한이 어느 정도인지를 파악하는 것이 도움이 된다.

데이터 구축 과정에서도 언급했지만 레이블은 모델이 예측하기 쉬운 형태여야 하는 동시에 그 레이블이 우리가 최종적으로 원하는 결과로 쉽게 변환될 수 있어야 한다. 이 둘 사이에는 트레이드오프가 있을 수 있다. 어쨌든 목표는 최종 결과의 퀄리티이기 때문에 데이터와 모델 둘 다 최종적으로 좋은 결과를 생성하기 용이하도록 구성하는 것이 중요하다. 여기서 발생하는 문제가 커지면 이걸 메꾸기 위해 필요한 휴리스틱(혹은 룰 기반 처리, 혹은 삽질)이 증가할 가능성이 높다.

실험

모델은 빠르게 구현하고 이것저것 실험해보자. 실험 결과를 남기고, 단순히 남기는 것 뿐만 아니라 비교할 수 있도록 정리하는 것이 좋다. 너무 당연한 이야기인가? 그러나 실험과 실험 결과들을 어떻게 관리해야 하는지에 대한 의견들이 많이 갈리는 것(link 1, link 2)을 보면 당연함에 비해 쉬운 일은 아닌 것 같다. 하다보면 구현과 실험 결과들이 엉망진창이 되기 쉽다.

모델을 어떻게 만들고 학습시켜야 하는가

모델을 어떻게 만들고 학습시켜야 성능이 잘 나올까? 여기에 가볍게 답을 낼 수 있다면 좋겠지만 (또한 그렇게 될 수 있기를 희망하지만) 쉽지 않은 것 같다. 통찰이나 아니면 많은 데이터를 직접 다뤄본 경험이 필요한 문제인 것 같다.

그렇지만 기본적인 팁들은 있다.

  1. 모델이 풀기 쉬운 데이터와 레이블을 준비해야 한다. 예를 들어 텍스트라면 당연히 순서대로 정렬되어 있는 것이, 그리고 더 많은 맥락을 활용할 수 있는 것이 효과적이다. 레이블 또한 입력 데이터를 갖고 추론하기 쉬운 형태인 것이 좋다. 예를 들어 객체 검출에서는 객체가 포함된 박스 전체에서 박스를 예측하는 것보다는 박스의 중심 근처에서 박스를 예측하는 것이 더 용이하다고 알려져 있다. 왜냐면 박스는 대상 객체 외의 배경 영역 같은 것들도 포함하고 있을 가능성이 높기 때문이다. 이것이 모델이 예측하기 쉬운 레이블의 한 예가 될 수 있을 것 같다. 표현력이 충분히 강력한 머신 러닝 모델은 임의적으로 연결된 데이터에도 완벽하게 피팅할 수 있다. 그렇지만 이래서는 일반화에 문제가 생길 것이다. 일반화를 위해서는 어떤 특징을 입력으로 주는가와 어떤 레이블을 예측하는가가 중요하다.

  2. 프리트레이닝된 모델을 사용할 수 있다면 사용하자. 성능이 높아지진 않더라도 학습 속도가 빨라질 가능성이 높다. 또한 만약 레이블이 없는 데이터를 충분히 많이 사용할 수 있다면 그 데이터를 활용해 프리트레이닝을 해보는 것도 좋다.

  3. 각 문제 도메인에서 잘 작동하는 기본 모델을 충분히 활용하는 것이 좋다. 복잡한 부품들을 붙이기 전에 하이퍼 파라미터를 적절히 설정하고 언더핏이면 모델 크기를 늘리기, 오버핏이면 데이터를 늘리기 같은 기본적인 전략이 꽤 효과적이다.

  4. 최상의 성능을 끌어내자면 학습 하이퍼파라미터를 탐색할 필요가 있지만 쉽게 준수한 결과를 낼 수 있는 베스트 프랙티스들은 꽤 있다. 기존 논문에서 쓸만한 하이퍼파라미터를 가져오는 것도 한 가지 방법이고, fast.ai에서 밀어서 유명해진 learning rate finder, 1 cycle learning rate policy 같은 전략들도 도움이 된다.

정리

머신 러닝 모델과 관련된 문제는 결국 수많은 점검과 점검으로 대처하는 수밖에 없는 것 같다. 위의 문제들을 점검할 수 있는 테스트를 만들어도 좋고, 테스트를 만들기 까다롭다면 점검하고 데이터를 분석해 결과를 볼 수 있는 스크립트를 만드는 것도 좋은 것 같다.

평가

모든 모델은 틀렸다. 따라서 과학자는 어떤 부분에서 결정적으로 틀렸는지에 주의를 기울여야 한다. 호랑이가 돌아다니는데 쥐에 신경을 빼앗기는 것은 적절하지 않다.

  • George Box

train/valid/test 스플릿

데이터 과학 서적, 강의에서 가장 강조하는 것 중 다른 한 가지가 train/valid/test 스플릿의 중요성 아닐까? 그런데 데이터를 단순히 섞어서 쪼개는 것으로는 충분하지 않은 경우가 있다. 데이터에 중복이 많거나 유사도가 높은 경우가 있다. 예를 들어 한 사진에서 나온 서로 다른 부분이 각 샘플로 분리되어 있는 경우가 있겠다. 이 경우에는 섞어서 쪼개는 것만으로는 충분하지 않다. 실제 모델이 투입되었을 때에 비해 성능이 과대 평가될 가능성이 높기 때문이다. 이걸 고려해서 쪼개는 것이 좋다.

또한 valid 셋과 test 셋이 동떨어져 있어서 valid 셋에서의 성능과 test 셋에서의 성능이 따로 논다고 하면 곤란하다.

그리고 효과적인 평가를 위해서는 valid와 test 셋의 크기가 충분히 클 필요가 있다. 보통 사람 욕심이 train 셋을 크게 잡고 싶기 마련이지만 평가 데이터가 충분하지 않으면 평가 지표의 신뢰성이 낮아지고, 평가 지표를 통해 수행한 의사결정이 왜곡될 가능성이 높다. 딥 러닝에서 다루는 비정형 데이터들은 다양성이 높기 때문에 적은 수의 평가 데이터로는 이 분산을 커버하기가 어렵다.

데이터가 적다면 기존의 교차 검증cross validation의 도구들을 적용할 수 있겠다. 그러나 대신 평가 과정에서 더 많은 시간을 투자해야할 가능성이 높다.

평가 지표 선정

평가 지표를 설정하는 것 자체가 어려운 경우가 있다. 보통 목표는 사람이 봐서 좋은 것, 쓸만한 것인 경우가 많기 마련인데 이 사람이 봐서 좋은 정도를 알고리즘화하기가 어려운 것이다. 결국엔 사람이 보고 품질을 평가하는 것 밖에는 뾰족한 답이 없는 경우도 많다.

아주 엄격한 평가 지표를 사용하면 실제 사용에는 문제가 적은데도 수치가 낮게 나올 가능성이 높아진다. 보수적인 기준이 안전한 것이 아니냐고 할 수도 있겠지만 수치가 낮으면 기분이 좋지 않기 때문에 마냥 좋은 것만은 아니다. 이 기분, 특히 다른 사람이 수치를 보고 느끼는 기분이 어떠한가는 아주 중요한 문제이다.

수치가 얼마나 좋아야 하는가

모델이 완벽한 성능을 내준다면 좋겠지만 현실적으로 그러기는 어렵다. MNIST에서도 0.25% 정도의 에러는 발생한다는 걸 기억하자. 머신 러닝 모델이 푸는 문제는 보통 모호하고 확률적이며, 따라서 더 감소시킬 수 없는 에러irreducible error가 존재하기 마련이다. 이 에러 이하의 에러를 달성하려고 노력하는 것은 의미 없는 일이다. 따라서 정확한 의사결정을 위해선 기대할 수 있는 최고 성능이 어느 정도인지를 파악하고 모델의 성능이 그와는 얼마나 차이가 있는지를 파악하는 것이 중요하다. 이걸 파악해야 모델과 데이터에 얼마나 더 자원을 투입할 것인지를 결정할 수 있다.

이 문제에 대해서는 사람에게서 기대할 수 있는 결과가 어느 정도인지와 서비스에 적합한 수준이 되려면 어느 정도의 성능이 나와야 하는지를 파악하는 것이 필요할 것이다. 이 두 지표는 데이터를 준비하는 과정에서도 평가할 수 있다.

정리

데이터를 잘 준비하고, 파이프라인의 각 단계에서 발생할 수 있는 에러들에 대처하고, 검증된 모델들로 실험하면서 모델과 데이터의 특성을 파악하고, 적절한 평가 지표를 사용하여 좋은 의사 결정을 내리며, 이 모든 과정을 철저하게 검증하는 것. 지루할 정도로 당연하면서도 실제로 따르자면 만만찮게 까다롭고 번거로운 과정이다. 그렇지만 효율적으로 머신 러닝 파이프라인을 구축하려면 이런 자주 일어나는 문제들에 대해 잘 대처하는 것이 중요하다. (아마 다른 개발 프로세스도 그렇긴 하겠지만.) 그리고 이 평이한 과정들만으로도 흥미로운 결과를 기대할 수 있다는 것이 머신 러닝의 좋은 점이 아닐까 싶다.