21년 2월 2주

안녕하세요. 고감자 입니다. 블로그에서는 오랜만이네요. 한창 블로그를 많이 쓰던 때에 비교하면 거의 블로그는 방치 상태였는데, 그럼에도 불구하고 글쓰기의 묘미는 여전히 있다고 생각합니다. 그리고 공개되는 글이 없는 반면에 제가 개인적으로 정리하고 메모하는 글을 상당합니다. 그런데, 그러한 글들은 거의 공유되지 못하고 죽어버린 정보가 되는 경우가 많다는 생각이 들었습니다. 따라서 올해부터는 개인적으로 정리하고 소화하는 정보를 블로그에 정리해보고자 합니다. 뭐 일종의 뉴스레터 같은거라 생각합니다. 아마도 이 뉴스레터는 개인적인 관심사 위주로 흘러갈 거라서 아마도 같은 관심사를 가진 분들에게는 큰 도움이 될수도 있을 것입니다.

정리는 기술단위로 진행하겠습니다만, 이번 주제는 요즘 핫한 큰 모델 학습하기…(GPT-3…)…


ZeRO, Pipe Model Parallel

10억개 정도 파라메터를 가진 모델은 V100 GPU 한장에서 파인튜닝이 불가능하다. 이를 가능하게 하는 여러가지 기술이 소개되고 있는데, 이에 대한 내용을 정리해 본다.

Hugingface Transformers

DeepSpeed는 GPU 메모리를 획기적으로 줄이면서 학습 효율을 높이는 기술(ZeRO, Offload)을 제공하고 있고, FairScale은 DeepSpeed의 ZeRO(stage 1)과 GPipe의 Pipe Parallel 기술을 PyTorch API로 잘 정리해서 제공하고 있다. 이 기술(ZeRO)가 transformers trainer에 포함되었다는 포스팅인데, 역시 Transformers는 기술들을 민주화하는 역량이 상당한것 같음.

위 글의 내용을 포함해서 다양한 논의들이 이곳에서 진행되고 있으니 관심 있으신 분들은 구독!

PyTorch Lightning

PyTorch Lightning은 간편한 사용과 다양한 기능 덕분에 필자도 애용하는 학습 프레임웍. Facebook의 Production에서도 기본 학습 프레임웍으로 사용하고 있다고함.

PyTorch Lightning은 Facebook Research의 FairScale를 채용해서 앞서 설명한 ZeRO와 GPipe의 Pipe Model Parallel을 적용해 학습하는 방법을 소개하고 있다. 필자도 빠르게 사용해 봤는데, ZeRO의 경우 잘 동작하나 Pipe Model Parallel은 몇가지 이슈가 있었음. 특히 필자가 올린 이슈는 해결이 쉽지 않아 보였는데, 빨리 해결되길 바람.

곧 PyTorch에 포함된 ZeRO를 보게될거 같음. 하지만 아직까지는 빅 모델 학습의 끝판왕은 Megatron-LM임. 학습은 잘 하시더라도 학습된 모델 잘들 사용하고 있는지? :\

torch.utils.data.DataLoader 부분을 잘 사용하는것은 생각보다 중요합니다. 특히나 대용량의 데이터를 효과적으로 다룰때는 내부가 어떻게 동작하는지 이해하는건 매우 중요함. 위 git에서는 아주 간단한 dataloader를 직접 만들어 보면서 그 구현 원리를 파악하고 있음. 관련된 블로그 글도 있으니 참고!

QA

GPT같은 모델에서 원하는 방향으로 생성을 잘 하려면 context를 잘 줘서 생성해야 됨. 언어모델 학습때 사용한 위키로 관련 정보를 보강해서 context를 만들어 줄 수 있으면 생성시 좋은 효과를 볼 수 있는데 특히 QA와 같은 knowledge-intensive 테스크에서 좋은 성능을 보여줌.

Model Serving

필자의 경우 주로 cortex 사용해서 모델 API를 제공해왔음. 이를 활용한 이유는 인스턴스 성능에 따라 모델의 출력(throughput) 조절하기가 간단했기 때문임(전통적인 멀티프로세싱으로 인한 스케일링…방식).

BentoML은 MLOps분들이 좋아할만한 플랫폼임. 다양한 모델을 커버하고 있고, 모델 버전관리를 하는 레포지터리의 UI는 가장 맘음에 든다. 그리고 모델 인퍼런스 성능에 영향을 주는 micro batching을 적용해 response time 최적화가 가능하다는 큰 장점이 있음.


Research

학습때 사용한 데이터를 언어생성할 수 있다는 건데, 문제는 민감 정보가 포함될 수 있다는 것임. 이를 위해 학습 데이터에 일종의 노이즈를 가해서 생성하는 차등정보보호(Differential Privacy)와 같은 조치와 더불어 학습 데이터를 문서 이하 레벨로 중복필터을 하는 방식을 제안하고 있다. 중복필터가 중요한 이유는 이러한 공격(attack)이 모델의 과적합과 관련이 되있다고 보기 때문임.

통계청에서 공유하고 있는 차등정보보호 관련 발표자료.

lable semantics + generation 를 이용한 서로 다른 테스크(NLU)들 사이의 knowledge sharing, few shot 성능 좋음, T5 활용(BART도 가능할 것으로 판단). 인코더, 디코더 모델의 실무 활용성을 가늠해볼 수 있는 논문입니다.

generation 모델 학습시 단순한 auxiliary tasks 추가만으로 더 좋은 생성 결과를 보여줬다. auxiliary task 는 인코더 디코더 학습시 인코더에 넣는 방식으로 해서 확장 가능하다. 코퍼스 성격에 따라 다르나, order recovery , content recovery(MLM) 모두 NLG에 중요한 task임.디코더가 단순해 빠른 생성이 가능. 성능 좋은 인코더가 있을 경우 빠르게 NLG 성능을 올릴 수 있는 방법임

인코더 MLM with RoBERTa (autoencoding). 디코더 NS Generation + pointer generator (autoregressive generation). 잘 학습된 인코더만 있을 경우 NLG를 잘 할 수 있는 모델을 찾는데 의미가 있음.. 성능은 그럭저럭

AI 연구/개발자로서 1년을 보내며

AI 연구/개발자로서 첫 1년.. 결과적으로 매우 의미있었습니다.

DT조직에서 AI 조직으로 옮긴지 1년이 넘었다. 왜 옮겼는지 궁금한 분들은 이전 블로그 글에서 확인할 수 있다.

올해는 서비스 적용 가능한 대화 엔진을 만드는데 집중했는데, 생각지도 못하게 서비스에 적용하는 경험도 했으며,엔진의 컴포넌트를 평가하기 위해 참석한 국제 대회에서도 첫 참가에 2등의 성적으로 입상했다. Data Scientist로 오랫동안 일하고 고작 1년만에 이런 결과가 나온데는 같이 일하는 팀원분들의 지원과 노고 덕분이라 생각한다. 여기에서는 이러한 결과를 자세히 정리하고 내년을 계획하는 페이지로 활용하고자 한다.

얻어걸린 훌륭한 동료들…

조직을 옮기고 1년 동안 지내보니 옮기기 전에 전혀 기대하지 못했던….속칭 얻어 걸린게 하나 있다. 그건 바로 훌륭한 동료들이다(관련 글). 획기적인 대화 엔진 개발은 절대 혼자 힘으로 가능하지 않다. 결국 서로의 빈곳을 어떻게 채울것인가인데, 이를 100% 서로 채워준 팀분들의 가치를 올해 많이 느꼈다. 이러한 와중에 다양한 방식으로 개인적인 기회와 위기가 찾아왔지만 결정의 기준이 된건 동료였고, 그 결정은 절대 후회되지 않았다.

BERT는 정말 놀라운 기술입니다.

BERT 논문이 나오는 시점에 팀의 기술 계획이 세워졌는데, BERT를 기반으로 하는 대화엔진 개발이 핵심 주제가 되었다. 그렇게 KoBERT(https://github.com/SKTBrain/KoBERT) 학습을 시작하게 되었다. 일반적인 딥러닝 학습과 BERT와 같은 unsupervised language model의 큰 차이점은 대용량의 데이터가 주어진다는 것인데, 이를 효과적으로 학습하는데 이전의 검색 서비스/엔진 개발 경험은 큰 도움이 되었다.

TPU가 아닌 수개의 V100머신이 있는 상황에서 KoBERT를 어떻게 학습하게 되었는지에 대한 자세한 이야기는 페이스북에 있는 글을 인용하고자 한다.

최근 행사에서 T-Brain 홍보 부스에 와 왜 KoBERT(https://github.com/SKTBrain/KoBERT)를 MXNet으로 학습했느냐 물어보시는 분들이 있다고 들었습니다(이 이야기는 어디서 들으셨는지.. 사실 도구는 중요하지 않아요. 쓸만한걸 만들었냐가 중요하죠). 여기엔 몇가지 이유가 있는데, 말씀 드려보도록 하겠습니다. (참고로 T-Brain은 주로 PyTorch를 연구용으로 씁니다. ^^ 저도 연구용으로 쓰고 있구요. 게다가 PyTorch와 MXNet은 코드 레벨에서 상당히 유사하답니다.)

1. 시기….
팀에서 한국어 버트를 학습하자고 결정한 시점은 작년 12월 말 이었고, 그 당시 TensorFlow를 제외하고 버트의 사전학습을 할 수 있는 공개된 코드가 전혀 없었습니다(Huggingface BERT는 파인튜닝 코드가 공개된 상황이었죠.. 기억으로 올해 1월 말에 사전 학습 코드 비슷한게 나오긴 했는데, 완전한 코드는 아니었습니다). 게다가 TensorFlow의 학습 코드는 TPU에 최적화된 코드여서 보유하고 있던 V100 머신(멀티 노드)에서 학습이 보장되지 않았고, 무엇보다 제가 TensorFlow에 익숙하지 않았습니다. 그렇다면 GCP에서 학습하면 되지 않나 라고 할 수 있는데, 경험상 쓸만한 모델을 만들려면 시행착오를 최소화 한다 하더라도 대략 7천만 ~ 1억 정도 클라우드 비용이 소모될 것으로 예상했습니다. 어찌될지도 모를 작업에 1억을 마련해줄 조직은 없겠죠… 마침 이 시점에 AWS AI팀에서 MXNet으로 버트 학습 코드를 만들고 있었고 제가 버트 사전 학습 데이터 생성 코드를 컨트리뷰트 하면서 함께 작업하게 되었습니다. 드뎌 팀에서 보유한 V100 만으로 멀티 노드 버트 학습을 할 수 있을거 같은 생각이 든거죠(1월 초).

2. 분산 트레이닝 퍼포먼스
MXNet의 분산 학습 퍼포먼스는 초기 개발부터 알려진 바 대로 타 플랫폼에 비해 상당히 좋은 성능을 가지고 있었고, 지금도 그렇습니다. 작업 하면서 같은 하드웨어 환경에서 NVidia에서 공개한 TensorFlow의 throughput보다 10% 이상 좋았으며, Horovod 멀티 노드 학습시 처리 성능의 선형성이 보장되는 모습을 보면서 최적의 학습을 할 수 있었습니다. 역시나 분산 학습 퍼포먼스는 MXNet이었죠.

3. 개인적인 익숙함과 신뢰, 자신감
MXNet 코어 개발자들과의 네트워크가 있어서 어떠한 문제가 발생하더라도 도움을 받을 수 있었습니다. 게다가 엔진 코드 부터 NLP 라이브러리 소스코드 모두에 익숙해 문제에 대한 해결책을 가장 빠르고 정확하게 찾을 자신이 있었죠. 물론 MXNet으로 버트를 학습해 공개하면 많은 사람들이 쓰지 못하겠죠. 따라서 MXNet에서 PyTorch 버트로 컨버터를 만들었고 그렇게 MXNet으로 학습된 모델을 PyTorch 그리고 ONNX로 변환해서 공개한 것입나다.

4. 이해하고 만들면서 쓰자!
버트를 논문 레벨이 아니라 직접 코드레벨로 컨버팅을 하면서 이해하고 싶었습니다. 당시 Masked LM 학습 코드는 좀 복잡하더군요. ㅜㅜ
아마도 이런 시도는 자신감이 아니면 어려웠겠죠. 왜냐면 모델 학습에 실패할 경우 오로지 제가 모든 선택의 책임을 져야 했으니까요. 다행히 2월 한달 동안의 뉴질랜드 가족 여행 중 구동해둔 멀티 노드 학습은 한번도 죽지 않았고(머신은 일하고 개발자는 휴가인….), 돌아와서 정리해 엔진에서 사용하기 시작했죠. 다행히 엔진에서 성능향상이 크게 되고, 이런 저런 케이스에 다양하게 사용되어 성과를 봐 느즈막하게 10월 초에 오픈하게 된 겁니다. 사실 버트 성능은 학습을 할 수록 계속 성능이 오르고 있었는데, ROI 측면에서 중지했습니다. 아마도 여유가 있으면 더 학습을 했을텐데… 연구용 리소스를 무시할 수 없어 정지했네요.

앗 그리고.. 왜 토큰 개수가 8000개냐구요? 여러 토큰 개수로 학습을 시도했을때 초기 학습 성능 향상 속도가 가장 좋았습니다. V100이 수십대 정도 있었다면 토큰을 다양하게 해서 여러 모델을 만들었을 텐데… 그럴 넉넉한 환경은 아니었습니다. 그러나 8000개여서 모델이 조금 가볍다는 것과 그러다 보니 인퍼런스 속도 개선이 있어 엔진 적용시 잇점으로 작용했다는 것입니다. 결과적으로 생각해보면 거의 음절 단위 토큰을 사용한건데, 버트가 학습하면서 음절간의 관계까지 학습한게 아닐까 합니다.

앞으로 MXNet은 계속 사용할 예정입니다. MXNet 개발 엔지니어들과 많은 도움을 주고 받았기 때문입니다. 지금도 제가 무엇이든 요청해도 컨콜까지 하며 도움을 주고 받을 수 있는 네트워크가 있다는건 상당히 큰 잇점입니다. 연말이 되었으니 그동안 쌓인 MXNet 코드를 기여할 때가 된거 같습니다. 작년 크리스마스때 버트에 대한 PR을 처음 보냈는데, 올해도 비슷하게 크리스마스를 보내지 않을까 하네요.

from : https://www.facebook.com/gogamza/posts/10215635213256172

결과적으로 KoBERT를 활용하면서 NLU, QA 등 대화엔진 컴포넌트들의 성능이 크게 향상되었다. 내가 맡은 과거 대화로그를 기반으로 파인튜닝 한 QA의 경우 아래와 같은 답변을 도출했는데, 기대 없이 대충 질문을 던졌던 나로 하여금 큰 충격을 줬던 결과여서 캡쳐해둔 화면이다. 관련 세미나를 하고 데모를 모여주고 난 뒤 몇몇 참석자분들이 이걸로 창업하는거 아니냐는 농담섞인 질문을 하기도 했을 정도였다.

KoBERT 기반의 QA, from ConvAI Prj inteview

T-World Direct라는 판매 채널의 봇에 적용된 모델로 키워드 기반의 QA로는 절대 나올 수 없는 결과를 보여줬으며 수년전에 키워드만으로 존재한 진정한 시멘틱 검색이라 할 수 있을 것이다. KoBERT가 문장의 유사도를 계산하고 이들간의 랭킹을 잘 학습한 결과이다. 이런 경험을 통해 구글 검색의 BERT 활용 기사가 충분히 공감이 갔으며 어떻게 가능했는지 가늠이 가능했다. 내가 지금 검색팀에 있다면 바로 적용할 기술이다.

KoBERT를 오픈하고 이에 대한 많은 관심에 놀랐으며, 지금은 DistilKoBERT와 같은 다양한 파생 프로젝트들이 커뮤니티에서 생성되고 있다. KoBERT의 인기는 이런 모델의 필요성에 대한 대중의 관심과 모델의 성능 덕분이라 생각한다. KoBERT 사례에서 다시 느낀것은 기술 역시 내가 편하고 좋아야 대중도 좋아한다는 것이었다. 앞으로도 내가 만족할 수 있는 기술을 만들 수 있게 노력해야 될 것이다.

딥러닝 대회 2등 내년엔 1등 합시다!

DSTC(Dialog System Technology Challenges)8라는 큰 대회의 여러 트랙중에서 앞서 이야기한 QA 기술과 유관한 Response Selection 트랙에 참석했다. 목적은 아래 세가지 였다.

  1. 엔진 컴포넌트에 대한 객관적인 성능 검증
  2. 대회 결과물의 엔진 적용
  3. 입상(?)

엔진 컴포넌트에 대한 객관적인 성능 검증은 엔진의 서비스 적용시 발생할 수 있는 여러 잡음을 해소할 수 있는 중요한 부분이었는데, 함께 참여한 동료들의 노고 덕분에 결국 위 세가지 모두 달성 할 수 있었다.

이 대회를 거치며 딥러닝 대회에 대한 성격 그리고 노하우를 알 수 있었다. 일단 딥러닝 대회는 데이터와 컴퓨팅 리소스의 제한이 거의 없는 추세이기 때문에 이들에 대한 확보와 효과적 활용이 입상 여부와 큰 관련이 있다. 그렇다 보니 성능 좋은 Language Model 보유 여부가 중요한데, 따라서 내년 대회에 좀 편하기 위해 연말에 이에 대한 준비를 좀 하고 있다.

엔진 컴포넌트 개별의 객관적인 성능을 측정하기 위해 좋은 대회이며, 좀더 확장된 트랙으로 내년에도 참여할 생각이다.

오픈소스에 대한 기여는 곧 나와 대중을 위한 기부….

작년 이맘때 BERT 학습 관련 여러 PR을 GluonNLP에 했으며, 덕분에 2월에 DMLC 맴버가 되었다. DMLC로부터 KoBERT 학습을 위한 여러 도움을 받기도 하고 주기도 했다.

올해 가장 큰 오픈소스에 대한 기부는 KoBERT to GluonNLP 일 것이다. 이미 KoBERT는 MXNet Model zoo에 올라가 있고, 해당 코드 PR은 리뷰중에 있다. API 수정된 부분이 많아 NLP 모델 서빙 API 개선에 대한 논의가 시작되고 있는 상황이다. 이 논의 과정도 너무 재미있고 고민도 재미있다. 게다가 이 사례를 기점으로 운좋게도 조직의 성과와 연결되는 몇가지 협업들이 진행되고 있는 상황이다. 역시 이것도 얻어걸린 거니 묵묵히 잘 도와줄 따름이다.

오픈소스 개발하는 사람은 난 기본적으로 기부자라고 본다. 이는 다른사람이 나를 바라볼때도 마찬가지다. 이는 나를 누군가가 바라볼때 첫인상의 큰 부분을 차지한다. 이런 좋은 첫인상은 결국 좋은 기회로 다가온다.

기업에서 하는 AI연구의 지속 가능성 탐색

DT에서 AI연구로 업무 전환을 하면서 과연 연구와 서비스 개발의 선순환을 만들 수 있을 것인가 스스로 질문을 했다. 왜냐면 기업에서의 연구는 반드시 현실 문제에 발을 딛고 있어야 의미가 있고, 연구가 가질 수 있는 리스크를 줄일 수 있기 때문이었다. 서비스 개발을 하면서 발생한 다양한 도전적인 주제들을 해결하여 서비스에 적용하고 다시 주제를 발굴하는 과정을 통해서 연구와 개발간의 선순환을 만들 수 있을 것이란 가설을 기반으로 올 한해 동안 그러한 선순환이 가능한지 가늠해 보았다. 결국 논문이라는 결과는 도출하지 못했지만, 국제적인 대회참가와 입상 그 결과물을 엔진에 적용하는 과정을 통해 이들간의 선순환이 가능하다는 나름의 결론을 내릴 수 있었다. 아마도 2020년에는 이러한 가설에 대해서 좀더 객관적인 결론을 충분히 도출 할 수 있을 것으로 예상한다.

내년엔…

DT와 AI가 어떻게 다른가? 둘다 경험을 해본 나로서 나름의 정의를 내리자면, DT는 일하는 방식을 변화시키는 방향, AI는 DT를 기반으로 서비스를 변화시키는 기술이라는 차이가 있다고 이야기 하고 싶다. AI는 이를 접하는 사람으로 하여금 Awesome을 느끼게끔 하는게 기본 전제이기 때문에 DT, DevOps, 기획, UI, ML 등을 아우르는 종합 예술이 되어야 된다고 생각한다.

내년엔 이러한 종합 예술의 가시적인 결과를 직접 보고 싶다는 소망이 있다. 이를 위해 대화기술 레벨에서 놀라움에 대한 서포팅을 잘 해야 될 것이다. 물론 대화 기술 자체에 대한 순수가치를 보여주는것도 계속 진행해야 될 부분이라 생각한다.

버트(BERT) 파인튜닝 간단하게 해보자.

작년 말에 GluonNLP 0.6버전 개발에 활발하게 참여하였는데, 그중에서 사용자들이 편리하게 사용할만한 부분에 대해 소개하기 위해 글을 써봤다. 다들 버트, 버트 하는데, 어떻게 사용할지 모를 분들에게 도움이 될 것이라 예상해 본다.

이 글은 MXNet-Gluon 기반으로 설명이 된다. 최근 훌륭한 한글 자료가 인터넷에 나왔으니 관심 있으신 분들은 먼저 참고하시길 바란다.

버트(BERT)

인간은 직접 혹은 간접 경험을 통해 특정 상황이나 문제에 대한 가장 효과적인 해결책을 찾을 수 있는 지혜를 얻을 수 있다. 그러한 이유 때문에 경험을 중요시 여기며 모든 경험을 할수는 간접 경험의 하나인 독서를 강조하곤 한다. NLP 영역에서 인기있는 기술인 언어모델 전이학습은 바로 이러한 컨셉을 적용한 학습전략이다. 언어모델이 어떠한 방식으로 사용될지 확실치 않더라도 미리 양질의 방대한 양의 학습데이터로 미리 학습을 시켜두고 이렇게 학습된 모델을 간단하게 튜닝해 다른 목적으로 활용하는 것이다.

버트(BERT)는 현존하는 가장 강력한 NLP 언어모델로 다양한 NLP테스크에서 가장 좋은 성능을 보여주고 있다.

이 글에서는 버트를 기반으로 대표적인 한글 코퍼스인 네이버 무비리뷰 분류기 성능을 높여보도록 하겠다. 이글에서는 예측 성능에 대한 이야기 보다는 얼마나 간단하게 버트를 한글 코퍼스에 활용할 수 있는지에 대해서 중점적으로 이야기해보겠다. 기존의 버트관련 좋은 글이 많이 나왔기 때문에 버트에 대한 자세한 설명은 해당글을 참고하길 바란다.

데이터 전처리

버트가 기존의 방법론 대비 활용하기 어려운 주요한 이유중에 하나는 아래와 같이 다양한 입력을 받아야되기 때문이다. 대부분 구현체들은 이들에 대한 일반화를 하지 않아 별도의 모형을 구축할때 구현이 까다롭다.

입력 데이터

버트는 입력 문장에 대해서 아래와 같은 작업을 요구한다.

  1. 각 토큰의 Vocabulary 인덱스를 추출해 이를 정해진 길이의 벡터로 생성.
  2. 두 문장 혹은 하나의 문장이 들어올 수 있기 때문에 이들을 구분하기 위한 토큰 타입 벡터 생성
  3. 유효 길이 벡터

토큰 인덱스 : ‘[CLS] is this jack ##son ##ville ? [SEP] no it is not .[SEP]’

토큰 타입: 0 0 0 0 0 0 0 0 1 1 1 1 1 1

유효길이: 14

토큰인덱스는 Token Embedding을 생성하는데 필요하고, 토큰 타입은 Sentence Embedding, 유효길이는 내부적으로 여러 연산을 하는데 필요하다. Positional Embedding은 입력의 길이 정보만 알수 있다면 학습/추론시 자동 생성 가능한 벡터이다.

GluonNLP는 위 작업을 매우 편하게 진행해주는 함수(BERTSentenceTransform)를 제공하고 있다.

먼저 적절한 버트 모델을 로딩한다(구글에서 공개한 multilingual 모델을 사용해야 한글 문제에 적용할 수 있다).

bert_base, vocabulary = nlp.model.get_model('bert_12_768_12',
                                             dataset_name='wiki_multilingual_cased',
                                             pretrained=True, ctx=ctx, use_pooler=True,
                                             use_decoder=False, use_classifier=False)
ds = gluon.data.SimpleDataset([['나 보기가 역겨워', '김소월']])
tok = nlp.data.BERTTokenizer(vocab=vocabulary, lower=False)
trans = nlp.data.BERTSentenceTransform(tok, max_seq_length=10)
list(ds.transform(trans)
[(array([    2,  8982,  9356, 47869,  9566,     3,  8935, 22333, 38851,
             3], dtype=int32),
  array(10, dtype=int32),
  array([0, 0, 0, 0, 0, 0, 1, 1, 1, 1], dtype=int32))]

입력에 대한 전처리 부분을 제외하고 우리가 진행해야 될 부분은 무비리뷰에 대한 긍/부정 레이블을 처리하는 것이다. 모든 학습 데이터는 배치단위로 입/출력이 정의되기 때문에 입력 데이터 처리와 레이블 처리를 동시에 배치 출력하기 위해 Dataset 클래스를 정의하면 아래와 같다. Dataset 클래스는 PyTorch의 Dataset과 동일한 형태를 띈다.

class BERTDataset(Dataset):
    def __init__(self, dataset, sent_idx, label_idx, bert_tokenizer, max_len,
                 pad, pair):
        transform = nlp.data.BERTSentenceTransform(
            bert_tokenizer, max_seq_length=max_len, pad=pad, pair=pair)
        sent_dataset = gluon.data.SimpleDataset([[
            i[sent_idx],
        ] for i in dataset])
        self.sentences = sent_dataset.transform(transform)
        self.labels = gluon.data.SimpleDataset(
            [np.array(np.int32(i[label_idx])) for i in dataset])

    def __getitem__(self, i):
        return (self.sentences[i] + (self.labels[i], ))

    def __len__(self):
        return (len(self.labels))

위에서 로딩한 버트 모델은 인코더 부분이다. 인코더 위에 분류기를 붙여야 긍/부정을 학습할 수 있을 것이다. 버트 논문에서는 하나의 문장이 입력될때 분류기는 아래와 같이 구성하는걸 제안하고 있다.

모든 버트 모델은 첫 클래스 레이블을 pooler 라고 네이밍 하고 있고 이 pooler 결과를 받아 FC(fully connected) 레이어 하나를 추가해 간단하게 구성한다.

class BERTClassifier(nn.Block):
    def __init__(self,
                 bert,
                 num_classes=2,
                 dropout=None,
                 prefix=None,
                 params=None):
        super(BERTClassifier, self).__init__(prefix=prefix, params=params)
        self.bert = bert
        with self.name_scope():
            self.classifier = nn.HybridSequential(prefix=prefix)
            if dropout:
                self.classifier.add(nn.Dropout(rate=dropout))
            self.classifier.add(nn.Dense(units=num_classes))

    def forward(self, inputs, token_types, valid_length=None):
        _, pooler_out = self.bert(inputs, token_types, valid_length)
        return self.classifier(pooler_out)                               

나머지 부분은 일반적인 학습 모듈과 크게 다르지 않다. 아래는 학습 로그를 찍어본 것이다.

[Epoch 1 Batch 50/2344] loss=8.4847, lr=0.0000026681, acc=0.556
[Epoch 1 Batch 100/2344] loss=7.6343, lr=0.0000053362, acc=0.612
….
[Epoch 1 Batch 2250/2344] loss=4.5963, lr=0.0000422197, acc=0.805
[Epoch 1 Batch 2300/2344] loss=4.2460, lr=0.0000419234, acc=0.806
Test Acc : 0.84662
[Epoch 2 Batch 50/2344] loss=4.4179, lr=0.0000413664, acc=0.846
[Epoch 2 Batch 100/2344] loss=4.4742, lr=0.0000410702, acc=0.843
….
[Epoch 2 Batch 2200/2344] loss=3.4934, lr=0.0000286265, acc=0.869
[Epoch 2 Batch 2250/2344] loss=3.5244, lr=0.0000283302, acc=0.869
[Epoch 2 Batch 2300/2344] loss=3.1572, lr=0.0000280339, acc=0.869
Test Acc : 0.86312
[Epoch 3 Batch 50/2344] loss=3.4649, lr=0.0000274769, acc=0.885
[Epoch 3 Batch 100/2344] loss=3.4550, lr=0.0000271806, acc=0.885
….
[Epoch 3 Batch 2250/2344] loss=2.8383, lr=0.0000144406, acc=0.901
[Epoch 3 Batch 2300/2344] loss=2.4659, lr=0.0000141443, acc=0.901
Test Acc : 0.86686
[Epoch 4 Batch 50/2344] loss=2.6485, lr=0.0000135873, acc=0.919
[Epoch 4 Batch 100/2344] loss=2.6904, lr=0.0000132911, acc=0.916
….
[Epoch 4 Batch 2250/2344] loss=2.3454, lr=0.0000005511, acc=0.924
[Epoch 4 Batch 2300/2344] loss=2.0258, lr=0.0000002548, acc=0.925
Test Acc : 0.87136

성능

일반적으로 네이버 무비 리뷰 문제로 모델을 생성시 83~85% 정도의 정확도를 보인다고 알려져 있다. 간단하게 4 에폭 파인튜닝 후 최종 성능은 0.871이다. 물론 더 학습할 경우 성능이 오를 가능성이 있으니 좀더 학습해 보는 것도 괜찮을 것이다.

테스크(한국어) 특화 pre-training의 필요성

위 성능은 테스크 특화의 아무런 튜닝을 하지 않은 상황에서 좋은 성능이나, 버트를 쓰지 않아도 달성 가능한 성능(fasttext + LSTM + Attention)으로 고무적인 성능은 아니다. 이런 성능의 주된 이유는 한국어 특화된 버트 모형을 사용하지 않아서이다. 필자의 경험으로 한국어만 학습한 버트 모형을 해당 문제에 적용했을때 이 문제에서 0.90 이상의 정확도를 보임을 경험했고, 학습(pre-training)을 지속할수록 성능이 올라갈 수 있음을 확인했다. 이는 자신의 테스크에 잘 동작하는 버트 모형의 학습(pre-training)이 필요할 수 있음을 시사하고 있고, 아직 공개적인 한국어 버트모델이 나오지 않은 상황에서 더 필요한 작업이라 볼 수 있다.

마치며

이 글에서는 GluonNLP에서 제공하는 버트 모형을 이용해 간단하게 한국어 관련 모델을 학습해 봤다. GluonNLP는 버트를 간단하게 로딩하는 인터페이스를 제공하고 있고, 이들이 요구하는 형태로 데이터를 전처리 하는 API를 제공하고 있어 활용하기 복잡한 버트 모형을 다양한 문제에 간단하게 활용 가능하게 한다. 이곳에서 활용한 전체 코드는 여기에 공개해 두었다.