BERT에서 Mecab tokenization 코드

BERT에서 mecab tokenizer을 사용하여 pre-training와 fine-tuning을 실험했었다.
- 물론 제대로 하려면 코퍼스와 비용이 많이 필요하기 때문에 간단히 샘플위주로 학습했었다.
- 기존의 BERT은 BPE을 쓰기 때문에 sentence piece을 이용하여 한국어 코퍼스에 맞게 vocabulary와 tokenizer을 학습 및 구축해야한다.
- 근데 mecab은 이미 구축된 tokenizer이고 mecab을 이용한 사전이 구축되어 있었기 때문에 그냥 mecab을 쓴 것도 있다ㅎㅎ

아무튼 이때 mecab을 이용해서 입력을 tokenization하는 코드를 짜야한다.
- BERT의 tokenization.py을 다음과 같이 수정하는게 제일 쉽다. (물론 여러 방법이 있을 것이다. 이 부분은 고정하고 다른 부분을 수정하는 식 등)

class mecabTokenizer(object):
  def __init__(selfvocab_file):
    self.vocab = load_vocab(vocab_file)
    self.inv_vocab = {v: k for k, v in self.vocab.items()}
    self.joo_tokenizer=MeCab.Tagger('-d /usr/lib/mecab/dic/mecab-ko-dic/').parse 

  def tokenize(selftext):
    text = text.strip()
    split_tokens = []
    text_split = self.joo_tokenizer(text).split('\n')
    words = text.split(' ')
    word = ''
    word_position = 0
    pre_exist = 0
    for k in range(len(text_split)):
      token = text_split[k].split('\t')[0]
      if word_position == len(words):
        # print('ok')
        break
      while words[word_position] == '':
        word_position += 1

      word += token
      if pre_exist == 0:
        split_tokens.append(token)        
        if word == words[word_position]:
          pre_exist = 0
          word_position += 1
          word = ''
        else:
          pre_exist += 1
      else:
        split_tokens.append("##"+token)
        if word == words[word_position]:
          pre_exist = 0
          word_position += 1
          word = ''
        else:
          pre_exist += 1
          
    return split_tokens

  def convert_tokens_to_ids(selftokens):
    return convert_by_vocab2(self.vocab, tokens)

  def convert_ids_to_tokens(selfids):
    return convert_by_vocab(self.inv_vocab, ids)    

  • 구축한 모델은 mecab tokenizer을 사용하고 pos tagging은 사용하지 않은 모델
    • vocab은 128000개이다.
  • 처음에 코드를 분석하면서 vocab_generation을 할 때, ##부분을 깜빡함을 인지하였다.
    • 단순히 run_pretraining을 할 때는 ##을 고려안해도 상관없다.
    • 실제로 이것을 fine_tunning할 때도 코드를 커스터마이징해서 쓸 수도 있다.
    • 하지만 기본적으로 BERT의 vocab 구성은 ##을 이용한다. (## 이용하는게 일반적)
    • 즉 "사과나무" → "사과" + "##나무" 와 같게 쪼개지는 단어는 ##이 붙어버린다.
    • 따라서 BERT 모델을 사용할 때 코드에 이런 것을 처리하는 코딩들이 들어있는데 한글어 버전에서 ##을 안쓰면, 이 부분을 수정해야한다.
  • 모델을 학습하기 위해서 데이터 전처리를 하는 과정이 필요하다.
    • 즉 데이터로 미리 학습데이터를 tf_record로 만들어 둔다.
    • 이것을 어떻게 만드냐에 따라서 방법이 여러가지가 있을 것이다.
  • 위와 같이 tokenization.py을 수정하고 코드를 돌리면 된다.
    • 돌리다보면, 가끔식 error가 발생을 하는데 발생이유를 찾아보면 데이터 문제다.
    • 찾은 error로는 text에 다음과 같이 띄어쓰기가 2번 있는 경우다.
    • "이 텍스트는  샘플입니다."에서 "텍스트"와 "샘플"사이의 띄어기가 2번 있는 경우
    • 혹은 양끝에 공백이 있는 경우다.
      • "이 텍스트는 샘플입니다. "에서 "샘플입니다." 뒤에 공백이 있는 경우
    • 이런 자잘한 error을 찾아줘서 수정해주면 된다.
  • 시행착오
    • 맨처음에는 read_squad_examples 함수를 수정하는 방향으로 갔다.
    • 왜냐하면, read_squad_examples을 영어버전으로 돌려보면, 띄어쓰기 단위로 token을 쪼개서 answer_start와 answer_end을 찾는다.
    • 즉, 데이터에서 주어진 MRC에서 주어진 정답 answer은 character 단위의 위치이다.
      • 예)"정답은 6번째 입니다."에서 answer_position 6라는 것은 "번"의 위치를 말다. (띄어쓰기 포함)
    • 이것을 띄어쓰기 단위로 token을 쪼개서 이것과 align을 시키는 것이다.
    • 하지만 우리가 모델을 학습할 때는 사용한 tokenizer의 token 단위의 position을 찾아야 한다.
      • 따라서 meacb tokenizer에 맞게 수정코드를 짜면, 이 뒤에 사용되는 함수들을 수정해야한다.
    • 하지만 뒷 부분을 수정하다보면  convert_examples_to_features 함수를 수정해줘야 한다.
      • 이 함수를 분석해보면 띄어쓰기로 쪼개진 token을 실제 사용하는 tokenizer의 subtoken으로 쪼개서 align을 맞춘다.
      • 하지만 위에서 이미 mecab_tokenizer로 align을 시켰으니 이 부분을 적절히 삭제, 수정하면 된다.
      • 하지만 굳이 이렇게 전체적으로 코드를 수정안해도 학습이 되니 시행착오로 남겨둔다..
  • 단, 만약에 BERT모델을 huggingFace와 같은 라이브러리를 이용하여 사용한다면
    • fine_tunning을 실제로 코드를 밑바닥부터 짜야하는데
    • 이럴 때는 시행착오에서 겪었던 tokenizer에 맞는 answer position align을 직접 짜줘야한다.
  • 만약 tensorflow-BERT로 다른 task의 fine-tunning을 할 때, 구글의 텐서플로우식 코드를 짜거나 huggingface로 변화하여 사용해야한다.
    • Google식 코드를 짠다면 def create_model 부분을 task 별로 수정하면 된다.
    • 불가능한 것은 아니지만, 텐서플로우와 구글이 짜둔 BERT 코드들에 익숙해져야하므로 huggingface로 변환하여 사용하는 걸 추천.

댓글