deeqnlp로 BERT 만들기

deeqnlp 토크나이저를 이용한 BERT 학습

Posted by hunbl on August 20, 2021 · 9 mins read

BERT

BERT(Bidirectional Encoder Representations from Transformers)를 만들어야 하는 첫번째 이유는 얘가 자연어처리를 아주 잘하기 때문입니다. BERT로 사전학습모델(Pre-trained model)을 한 번 만들어 놓으면, 이것 하나만으로 쉽게 다양한 자연어처리 문제를 풀 수 있습니다. 이전까지의 자연어처리는 문제 종류마다 각기 다른 복잡한 모델을 만들어야했고 따로따로 학습을 시켜야 했거든요. 지금은 BERT보다 낫다는 여러 후계자들에게 1등 자리를 내준것 같기도 하지만, 여전히 BERT는 현역이고 NLP의 기본이기때문에 우리는 일단 BERT로 사전학습모델을 만들어 보기로 합니다. 그런데말입니다 그렇게 널리 사용되는 모델이라면 이미 많은 사전학습모델이 공개되어 있어서 그냥 갖다 쓰기만 하면 되지는 않을까요? 이 오픈소스의 시대에 말입니다… 네 물론 많이 있긴 합니다. 하지만 우리에겐 아직도 BERT를 새로 학습시켜야하는 몇가지 이유가 있습니다. 우선 우리의 목적은 원래의 BERT가 사용하는 토크나이저를 우리 deeqnlp 형태소 분석기로 바꾸는 것이었습니다. BERT에 기본 탑재된 토크나이저는 인접된 문자들의 출현 빈도만 고려해서 토큰을 만들기 때문에 단어들이 기본으로 분리되어 있는(공백문자로) 영어같은 언어에는 잘 맞지만, 의미단위 어절이 붙어있는 경우가 많은 우리나라말에 적용하면 말이 안되는(의미를 반영하지 않는) 토큰들이 잔뜩 나오게 됩니다. 그래서 우리는 토크나이저에 형태소분석을 사용해서 한글을 잘 이해하는 BERT를 만들고자 했던 것입니다. 또 하나의 이유는 BERT 모델을 0에서부터 만들 수 있는 경험과 기술이 다양한 NLP 모델을 만들거나 다른 사전학습모델도 만들 수 있는 바탕이 될거라 생각했기 때문입니다.

CORPUS - 말뭉치

위에서 사전학습모델만 만들어두면 만사 OK인것처럼 넘어갔지만 사실 여긴 엄청난 함정이 숨어 있습니다. ‘사전학습’의 목적이 이 세상 모든 문서를 다 읽혀서 마치 언어를 이해하는 것같은 모델을 만드는 것에 있다는 겁니다. 즉 사전학습모델을 만들기 위해서는 엄청나게 많은 양의 문서(말뭉치)가 필요합니다. 원조 BERT가 총 30억어절의 문서 전체를 3~4번 학습했다고 하죠. 우리도 보통 수백만에서 수천만 줄의 문서를 준비해서 무지 많은 학습을 시켜야 합니다. 게다가 그 많은 문서를 학습에 맞게 수정하고 가공까지 해야 합니다. 그럼 이런 학습을 위한 원본 문서는 어때야 하고, 어떤 가공처리공정(이쪽 업계에선 정제라고 합니다)을 거쳐야 하는지 알아봅시다.

  • 우선 문법, 어법에 맞고 보통 많이 쓰이는 내용을 담은, 완결된 문장이어야 합니다. 책, 뉴스, 사전 등이 좋겠죠. 양은 최소 수백만 줄은 넘어야 하고 다양한 내용이 섞여 있을수록 좋은것 같습니다.
  • 이런 원본 말뭉치들이 준비되었으면 학습데이터를 만들 수 있는 기본 말뭉치로 만듭니다. 기본 말뭉치는 ‘text’파일이고 한 줄에 한 문장, 문단과 문단사이는 빈 문장 의 법칙만 지키면 됩니다! corpus_images
  • 준비된 원본 문서의 포맷에 따라 text, csv, json, xml 파일을 읽고 변환할 수 있는 툴을 만들어야 하는 경우가 많습니다.
  • 문장분리, 특수문자 제거 등의 전처리를 해야 합니다.
  • 불용어, 의미 없는 문장 삭제 등의 처리를 해야 합니다. 의미 없는 내용이 포함되면 학습에 방해가 되기 때문에 중요한 과정인데, 이것은 원본의 내용에 따라 완전 케바케이므로 사용하려는 원본을 잘 들여다보고 적절한 전처리를 만들어야 합니다.
  • 예를들어 문장이 한 단어(또는 두 단어 이하라던가)면 제목이거나 의미를 알 수 없는 내용일 것이므로 삭제. 이메일, 전화번호, url 등이 빈번하게 등장하는 문서라면 모두 불용어 처리해야 합니다. 이모지, 한자 등의 삭제. 반복되는 문자 삭제(또는 축소). 반복되는 불용어 문장(이건 뉴스, 게시판 스크래핑 데이터에 포함될 수 있는 광고, 명령 라인 등 여러 경우가 있을 수 있습니다)에 대한 패턴검색 삭제 등 해야 할 일은 엄청 많습니다. corpus_images

vocab & Tokenizer

문서를 학습데이터로 만든다는 얘기는 자연어를 숫자로 바꾼다는 얘깁니다. 그 방법을 ‘토큰화’라하고, 토큰화하는 코드를 Tokenizer, 그 결과 만들어지는 사전을 vocab이라고 합니다. 최초에 vocab을 만들고, 이것을 사용해서 모델을 만들면, 그 모델을 사용한 결과는 vocab이 있어야만 내용을 우리가 아는 언어로 읽을 수 있게 됩니다. 문자에 숫자를 대응한 사전이라고 하면 생각나는 것들이 이미 있죠. 아스키코드라던지 유니코드 같은 것이 있을 텐데 그것을 그대로 사용할 수 있는 문제는 아닙니다. 문자 하나하나를 토큰으로 사용한다면 vocab의 크기는 작아지겠지만 문장을 데이터로 변환할 때 전체 데이터 크기가 너무 커집니다. 그럼 단어를 토큰으로 사용하면 어떨까요? 변환된 데이터 크기는 작아지겠지만 반대로 vocab 크기가 너무 커지겠죠. vocab이 국어사전(또는 영어사전) 크기가 될 테니까요. 이 크기의 균형을 맞추기 위해 여러가지 Tokenizer가 사용되는 겁니다. BERT에서는 Wordpiece라는 방법을 사용하는데 이것은 문자 단위에서 시작해서 문자를 붙일 수 있는데로 붙여봐서 적당한 빈도수와 vocab 크기를 결정하는 모델입니다. 우리는 deeqnlp 형태소 분석기로 Tokenizer를 만들 겁니다. 빈도수를 볼모로 글자 조합을 바꿀 수 없으니까 vocab 크기가 좀 커지지만 모든 토큰이 의미를 가지는 말조각이 됩니다.

실제로 토크나이저가 어떻게 동작하는지 봅시다. deeqnlp의 경우

t="한글로된 문장을 잘나눌수있을까요?"
>>> dq.tokenize(t)
['한글', '로', '되', 'ㄴ', '문장', '을', '잘', '나누', 'ㄹ', '수', '있', '을까', '요', '?']

BERT의 경우는

>>> en=tk.encode(t)
>>> en.tokens
['[CLS]', '한글', '##로', '##된', '문장을', '잘', '##나', '##눌', '##수', '##있', '##을까', '##요', '?', '[SEP]']

결과가 비슷한 듯 다른 것을 알 수 있습니다. (입력 문장은 일부러 띄어쓰기를 틀린 것입니다. 예제는 예제일뿐 학습데이터에는 틀린 문장을 사용하지 맙시다.)

우리는 두 토크나이저로 각각 vocab을 만들어 두가지 모델을 만들 겁니다. BERT에는 기본적으로 Wordpiece vocab을 만드는 기능이 빠져 있지만 huggingface의 transformers 라이브러리를 사용해서 vocab을 만들 수 있습니다. 우리는 학습 말뭉치로부터 35000 크기의 vocab을 만들었습니다. deeqnlp로는 50000 크기의 vocab을 만들어서 사용합니다.

학습데이터 생성

이제야 BERT 코드가 등장합니다. 원래 google에서 제공한 코드를 텐서플로우 2.x 버전으로 업그레이드했고 deeqnlp 토크나이저를 추가했습니다.

python bert/create_pretraining_data.py \
  --input_file= "/data/corpus/train-00001.txt" \
  --vocab_file="vocab.txt" \
  --tokenizer="deeq" \
  --max_seq_length=128 \
  --do_lower_case=False \
  --dupe_factor=1 \
  --output_file="/data/bert/train-00001.tf"
  • tokenizer: deeqnlp 토크나이저를 사용하기 위해 추가한 옵션입니다.
  • max_seq_length: 원래의 BERT 에서는 512 이지만 그 크기로 학습을 하면 GPU 메모리를 너무 많이 사용하기 때문에 일단 128 크기로 설정합니다. 문장의 토큰이 최대 128개라는 의미라서 웬만한 문장은 다 커버됩니다. 그리고 우선 128로 학습시킨 다음에 512 크기의 데이터로 추가학습을 시키면 512 크기의 모델을 만들 수 있다고 합니다.
  • do_lower_case: 우리는 영어를 사용할 게 아니라서 대소문자 변환 하지 않습니다. ‘lowercase=False’ 이 설정은 vocab 만들때, 토크나이저 여기저기, finetune에서의 설정 등 각종 config에 많이 등장하므로 앞으로도 나올 때마다 잘 설정해 줘야 합니다.
  • dupe_factor: 학습데이터 수가 부족하면 ‘dupe_factor=9’ 처럼 하여 데이터 수를 늘일 수 있습니다. 이것은 입력문장 하나로 몇 개의 학습데이터를 만들지 정하는 옵션입니다. 데이터를 늘이면 학습 정확도가 높아 보이긴 하는데, 실제 모델 성능이 높아지는 것 같지는 않습니다. 비슷한 문제를 여러 번 푸는 셈이니까 점수는 잘 나온다는 거 겠죠. 경험상 무조건 많고(양이)! 다양한(내용이)! 학습데이터가 좋은 모델을 만듭니다.

이렇게 위에서 정제한 모든 문서를 텐서플로우의 학습파일로 만들어 주면 학습할 준비가 끝납니다.

학습

이제는 돈($)과 시간($$)이 사용될 차례입니다.

python bert/run_pretraining.py \
  --bert_config_file="bert_config.json" \
  --input_file="/data/bert/train*.tf" \
  --output_dir="/data/out/bert/" \
  --do_train=True \
  --do_eval=True \
  --max_seq_length=128 \
  --train_batch_size=124 \
  --num_train_steps=1000000 \
  --gpus="1" \
  • max_seq_length: 학습데이터 만들 때와 같습니다.
  • train_batch_size: GPU(혹은 TPU)가 혀용하는 최대값을 해주는 게 좋습니다. 학습시간의 문제만이 아니고 배치크기가 클 수록 학습이 더 잘 된다고 합니다. 32G GPU 1개를 쓴다면 128 정도 크기가 가능합니다.
  • num_train_steps: 학습데이터 크기에 따라 다르겠죠. 보통은 30억 어절을 512 배치크기로 1백만 스텝 돌려서 3바퀴(epoch) 정도 학습 했다고 하는 오리지널 BERT를 기준으로 학습 횟수를 정합니다. 모든 학습데이터의 갯수와 배치크기로 1 epoch에 필요한 step 수를 계산할 수 있고 이것을 바탕으로 최종 학습 횟수를 정하면 될 것 같습니다.

일반인(?)이 사용할 수 있는 고오급 GPU(24G나 32G 메모리)를 사용한 경험에 의하면 대략 백만 스텝 학습에 10일 정도 걸리는 것 같습니다. 돈을 들여서 상용 모델을 만든다면 적절한 크기와 갯수의 TPU를 사용하는 것이 제일 좋습니다. 최신 TPU는 BERT 전체 학습을 분단위 시간에 끝낸다는 전설같은 얘기를 어디서 본 것 같기도 하구요…;

잘 만들고 있나요?

중간중간 여러가지 NLU 작업을 시켜 보고 학습이 잘 되고 있는지 봅니다.

model / task question-pair hate-speech korsts nsmc kornli 참고
bert-multi 92.7 56.6 77.6 86.4 75.8 구글 다국어 BERT, 비교군
wordpiece 550k 93.4 64.8 78.6 86.6 75.5 BERT vocab, 550000 steps 학습
wordpiece 1M 94.3 61.8 79.5 86.7 76.1 BERT vocab, 1000000 steps 학습
wordpiece 1.5M 93.8 63.2 80.4 86.6 76.8 BERT vocab, 1500000 steps 학습
deeqnlp 660k 90.4 64.4 43.2 87.8 71.1 deeqnlp, 600000 steps 학습
deeqnlp 1M 90.5 60.8 49.0 87.6 70.4 deeqnlp, 1000000 steps 학습
deeqnlp 1.5M 90.6 63.7 50.6 87.7 70.4 deeqnlp, 1500000 steps 학습
  • bert-multi 말고는 아직 학습 중인 상태입니다.
  • deeqnlp는 vocab 사이즈가 커서 성능향상이 더딘 것 같습니다. 공부를 더 많이 시킵시다.
  • 모델마다 잘 하는 태스크가 조금씩 다른 것을 알 수 있습니다. 또 점수는 점수일 뿐 성능의 절대적인 지표가 아닙니다.
  • 아무튼 학습은 잘 되고 있는 것 같습니다.

여기까지 실제 BERT를 처음부터 모델까지 학습시키는 과정에 대한 경험과 실전적인 내용을 빠르게 살펴 보았습니다. 누군가에게는 도움이 되는 내용이 1g이라도 들어 있었다면 좋겠네요.