BERT(Bidirectional Encoder Representations from Transformers)를 만들어야 하는 첫번째 이유는 얘가 자연어처리를 아주 잘하기 때문입니다. BERT로 사전학습모델(Pre-trained model)을 한 번 만들어 놓으면, 이것 하나만으로 쉽게 다양한 자연어처리 문제를 풀 수 있습니다. 이전까지의 자연어처리는 문제 종류마다 각기 다른 복잡한 모델을 만들어야했고 따로따로 학습을 시켜야 했거든요. 지금은 BERT보다 낫다는 여러 후계자들에게 1등 자리를 내준것 같기도 하지만, 여전히 BERT는 현역이고 NLP의 기본이기때문에 우리는 일단 BERT로 사전학습모델을 만들어 보기로 합니다. 그런데말입니다 그렇게 널리 사용되는 모델이라면 이미 많은 사전학습모델이 공개되어 있어서 그냥 갖다 쓰기만 하면 되지는 않을까요? 이 오픈소스의 시대에 말입니다… 네 물론 많이 있긴 합니다. 하지만 우리에겐 아직도 BERT를 새로 학습시켜야하는 몇가지 이유가 있습니다. 우선 우리의 목적은 원래의 BERT가 사용하는 토크나이저를 우리 deeqnlp 형태소 분석기로 바꾸는 것이었습니다. BERT에 기본 탑재된 토크나이저는 인접된 문자들의 출현 빈도만 고려해서 토큰을 만들기 때문에 단어들이 기본으로 분리되어 있는(공백문자로) 영어같은 언어에는 잘 맞지만, 의미단위 어절이 붙어있는 경우가 많은 우리나라말에 적용하면 말이 안되는(의미를 반영하지 않는) 토큰들이 잔뜩 나오게 됩니다. 그래서 우리는 토크나이저에 형태소분석을 사용해서 한글을 잘 이해하는 BERT를 만들고자 했던 것입니다. 또 하나의 이유는 BERT 모델을 0에서부터 만들 수 있는 경험과 기술이 다양한 NLP 모델을 만들거나 다른 사전학습모델도 만들 수 있는 바탕이 될거라 생각했기 때문입니다.
위에서 사전학습모델만 만들어두면 만사 OK인것처럼 넘어갔지만 사실 여긴 엄청난 함정이 숨어 있습니다. ‘사전학습’의 목적이 이 세상 모든 문서를 다 읽혀서 마치 언어를 이해하는 것같은 모델을 만드는 것에 있다는 겁니다. 즉 사전학습모델을 만들기 위해서는 엄청나게 많은 양의 문서(말뭉치)가 필요합니다. 원조 BERT가 총 30억어절의 문서 전체를 3~4번 학습했다고 하죠. 우리도 보통 수백만에서 수천만 줄의 문서를 준비해서 무지 많은 학습을 시켜야 합니다. 게다가 그 많은 문서를 학습에 맞게 수정하고 가공까지 해야 합니다. 그럼 이런 학습을 위한 원본 문서는 어때야 하고, 어떤 가공처리공정(이쪽 업계에선 정제라고 합니다)을 거쳐야 하는지 알아봅시다.
문서를 학습데이터로 만든다는 얘기는 자연어를 숫자로 바꾼다는 얘깁니다. 그 방법을 ‘토큰화’라하고, 토큰화하는 코드를 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"
이렇게 위에서 정제한 모든 문서를 텐서플로우의 학습파일로 만들어 주면 학습할 준비가 끝납니다.
이제는 돈($)과 시간($$)이 사용될 차례입니다.
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" \
일반인(?)이 사용할 수 있는 고오급 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를 처음부터 모델까지 학습시키는 과정에 대한 경험과 실전적인 내용을 빠르게 살펴 보았습니다. 누군가에게는 도움이 되는 내용이 1g이라도 들어 있었다면 좋겠네요.