본문 바로가기

AI/자연어처리(NLP)

서브워드 분절하기(sentencepiece, bpe, sub-word, bpe-droupout)

자연어처리를 오랫동안 하다보니 나만의 한글 데이터셋에 Bert, Transformer 등 새로운 모델의 입력을 만들어야 할 일이 많다.

 

한국어 자연어처리를 하는사람들이라면 매번 데이터셋에 맞는 sub-word 모델을 학습하고 vocab 사전을 만들어야한다

 

본 글에서는 내 데이터를 서브워드 분절하는 방법을 다루고자 한다

 

서브워드를 만드는 알고리즘은 사실 너무 많다.

1) 구글의 sentencepiece

2) opennmt

3) rust로 짜여 속도가 빠른 huggingface의 tokenizers

4) bpe 기법을 고안한 rsennrich의 코드

 

본 글에서는 구글의 sentencepiece 사용법을 다루고 다음글에서 huggingface의 tokenizers와 비교해 보려 한다

 

 

 

자연어 처리 시 모든 단어들을 벡터화 하는데 한계가 있다.

 

우리가 사용하는 글에는 이름, 숫자, 희귀단어나 단어장에 없는 신종어, 변형어, 약어가 섞여 있다. 

 

이런 Rare word problem에서 Out-of-vocabulary(OOV) 문제가 발생하는데 이를 해결하기 위하여 Sub-word 전략을 사용한다.

 

BPE(Byte Pair Encoding) 알고리즘은 NMT, BERT 등 최근 자연어처리 알고리즘에서 전처리로 이용되는 서브워드(sub-word) 단위 분절 방법이다.

 

단어는 의미를 가진 더 작은 서브워드들의 조합으로 이루어진다. 빈도수에 따라 문자를 병합하여 subword를 구성한다.

 

예를 들면 "conference" -> 'con', 'f', 'er', 'ence' 로 분절 할 수 있다.

 

한국어, 일본어, 영어 등 언어 형식에 구애받지 않고, 별도의 문법 입력 없이 조사 구분이 가능하다.

 

어휘에서 자유롭게 학습이 가능하고, Rare word를 위한 back-off model이 필요 없다. 그리고 성능 향상 효과도 있다.

 

서브워드 단위로 쪼개면 차원과 sparsiry도 줄일 수 있다. 그리고 처음본 단어(unknown word) 처리에 효과적이다.

 

 

BPE-dropout

- BPE의 성능 개선하기 위한 방법으로 Subword regularization method 이다. 이 방법은 robustness와 accuracy를 향상시킨다. 학습데이터로 BPE를 학습 시 merge 부분에 랜덤으로(주로 10%) 확률로 dropout을 가하여 모델이 다른 segmantation을 보도록 학습하고, 이를 이용한 분절 시 dropout을 끄는 방식이다

이를 통해 같은 입력 단어가 다양한 분절 결과를 가질 수 있다. 아래 오른쪽 표는 BPE-dropout이 일반 BPE에 비해 다양한 NMT 문제에서 BLEU 수치가 높아짐을 모인다.

- 희귀 단어(자주 등장하지 않는 단어)에 강하다

왼쪽 그림은 BPE로 source data를 임베딩한 결과이다(SVD). 파란 점이 rare words(희귀단어) 인데, BPE는 희귀 단어들이 자기들끼리 모여있는 경향이 있다. 그에 반해 BPE-dropout은 희귀단어들도 일반 단어들과 잘 섞여 있다

이는 희귀 단어라 할지라도 일반단어와 함께 잘 어우러져 있고 그러므로 희귀단어가 입력 문장에 나왔을 때도 잘 임베딩 될 수 있다는걸 보여준다

- 오타에 강하다

오타가 섞인 번역에서 특히 더 좋은 성능을 보인다. 오른쪽 표를 보면 misspelled 상황에서 BPE에 비해 성능차이가 많이 난다. 즉 오타에 훨씬 강건하다.

- 코드

BPE를 고안한 R. sennirich는 하루만에 이를 반영한 코드를 공개했다. https://github.com/rsennrich/subword-nmt/blob/master/subword_nmt/apply_bpe.py 267line, 아래 코드는 그 구현부분이다. Symbol pairs의 list를 구축할 때, dropout(=0.1) 확률로 해당 segmantation을 누락시킨다.

pairs = [(bpe_codes[pair],i,pair) for (i,pair) in enumerate(zip(word, word[1:])) if (not dropout or random.random() > dropout) and pair in bpe_codes]

 

1. 구글 sentencepiece 설치하기

command line에 pip를 이용하여 간편하게 설치할 수 있다.

pip install sentencepiece

 

2. 사용법

1) 모델 만들기

  - 가지고 있는 text로 빈도수 기반 BPE 모델을 만들 수 있다.

  - 파이썬을 이용하여 sentencepiece 를 호출하고, spm.SentencePieceTrainer.Train 함수를 실행한다.

    --input : 학습시킬 text의 위치

    --model_name : 만들어질 모델 이름, 학습 후 <model_name>.model, <model_name>.vocab 두 파일 생성

    --model_type : 어느 방식으로 학습할것인가, (unigram(default), bpe, char, word)

    --vocal_size : Subword 갯수 설정, 보통 3,200개

    --character_coverage : 몇%의 데이터를 커버할것인가, default=0.9995, 데이터셋이 적은 경우 1.0으로 설정

  - 아래 코드를 수행하면 m.model, m.vocab이 생성된다.

 

 

15만 문장 학습 시 48s 소요

%%time
import sentencepiece as spm


input_file = 'data/train_tokenizer.txt'
vocab_size = 32000
model_name = 'subword_tokenizer_kor'
model_type = 'bpe'
user_defined_symbols = '[PAD],[UNK],[CLS],[SEP],[MASK],[UNK1],[UNK2],[UNK3],[UNK4],[UNK5]'

input_argument = '--input=%s --model_prefix=%s --vocab_size=%s --user_defined_symbols=%s --model_type=%s'
cmd = input_argument%(input_file, model_name, vocab_size,user_defined_symbols, model_type)

spm.SentencePieceTrainer.Train(cmd)

 

2) BPE 분절하기

  - 1) 과정 에서 가지고 있는 text로 BPE 모델을 만들었다.

  - 이 모델(m.model)을 이용하여 분절을 수행한다.

 

%python
>>> import sentencepiece as spm
>>> sp = spm.SentencePieceProcessor()
>>> sp.Load("m.model")

 

2-1) text -> subword

sp.EncodeAsPieces("This is a test")

['▁This', '▁is', '▁a', '▁', 't', 'est']

 

2-2) text -> subword id

sp.EncodeAsIds("This is a test")

[497, 100, 49, 6, 47, 514]

 

2-3) subword id -> text

sp.DecodeIds([497, 100, 49, 6, 47, 514])

'This is a test'

 

2-4) subword -> text

sp.DecodePieces(['▁This', '▁is', '▁a', '▁', 't', 'est'])

'This is a test'

 

2-5) subword size

sp.GetPieceSize()

1000

 

2-6) id -> piece

sp.IdToPiece(2)

sp.IdToPiece(2)

'</s>'

 

2-7) piece -> id

sp.PieceToId('')

sp.PieceToId('')

 

2-8) Drop-out 사용하기

enable_sampling=True 일 때 Drop-out이 적용되며 alpha=0.1은 10% 확률로 dropout 한다는 의미이다.

for _ in range(5):
    print(sp.encode('This is a test', out_type=str, enable_sampling=True, alpha=0.1, nbest_size=-1))

 

2-9) encode의 인자로 encoding 하기

print(sp.encode('나는 오늘 아침밥을 먹었다.', out_type=str))
print(sp.encode('나는 오늘 아침밥을 먹었다.', out_type=int))

 

 

 

이와같이 구글 sentencepiece 파이썬 버전을 이용하여 손쉽기 BPE를 수행할 수 있다.

 

reference

https://github.com/google/sentencepiece

https://kh-kim.gitbook.io/natural-language-processing-with-pytorch/00-cover-3/06-bpe

https://github.com/rsennrich/subword-nmt

BPE-dropout : arxiv.org/abs/1910.13267