토크나이저 정리(BPE,WordPiece,SentencePiece)

Donghae Suh·2022년 10월 9일
2

NLP

목록 보기
1/3

https://huggingface.co/docs/transformers/main/tokenizer_summary

이번 글에서는 BPE(Byte-Pair Encoding), WordPiece,SentencePiece에 대해 다룰것이다.

text를 분할하여 조각을 내는 것(Tokenizing)은 생각보다 어렵다.

예를들어

"Don't you love 🤗 Transformers? We sure do."

위와 같은 문장을 공백기준으로 분할한다 하자.

그럼 다음과 같을 것이다.

["Don't", "you", "love", "🤗", "Transformers?", "We", "sure", "do."]

하지만 이때

"Transformers?" , "do." 를 보면

puntuation(구두점) 들이 같이 포함돼있음을 볼 수 있다.

이렇게 된다면 같은 단어에 대해 서로 다른 구두점을 가지는 단어들을

서로 다른 것으로 간주해야 하므로, 구두점에 대해서도 분리해야 한다.

["Don", "'", "t", "you", "love", "🤗", "Transformers", "?", "We", "sure", "do", "."]

좋아졌지만 아직 개선해야 할 부분이 있다.

"Don't""do not" 을 의미하는데 " ' " (apostrophe) 때문에 서로 3개로 분리되었다.

이것은

["Do", "n't"]

으로 분리된다면 좋을 것이다.

바로 위와 같은 작업이 어려운 것이다.

이 때문에 언어모델마다 자신만의 tokenizer가 있는 이유이기도 하다.

위처럼 복잡하다면 그냥 character 단위로 분해한다면 어떨까?

매우 간단하고 메모리를 줄일 수 있지만, 이럴 경우 각 token에 의미있는 정보를 담을 수가 없다.

today라는 의미를 단순히 t라는 character가 표현할 수 없는 것과 동일한 맥락이다.

이때문에 transformer 모델들에서는

위의 둘을 모두 사용한 ( word-level(단어 수준) , character-level(문자 수준) ) Tokenizer를 사용한다.

이를 subword tokenization이라고 부른다.

Subword tokenization 알고리즘은 아래와 같은 규칙을 따른다.

' 자주 쓰이는 문자조합은 더 작은 subword로 분리하면 안된다 '

' 하지만 희귀한 문자조합은 의미있는 더 작은 subword로 분리되어야 한다 '

예를들어

"annoyingly" 가 희귀한 단어로 분류되면

"annoying" 과 "ly" 으로 분리되어야 한다는 것이다.

이러면 "annoying" 과 "ly" 두개의 token에 각기 다른 의미를 부여할 수 있게 되고

"annoyingly" 를 위 두개의 의미있는 token의 조합으로 생각 할 수 있게 된다.

Subword tokenization을 사용하면 적당한 vocabulary size(사전 크기)로 모델을 사용할 수 있다.

또한 Subword tokenization은 처음보는 단어에도 모델이 처리할 수 있게 해준다.

이것이 가능한 이유는 처음 보는 단어도 우리가 이미 학습한 subword로 만들면 되기 때문이다.

예를 들어 BertTokenizer로 "I have a new GPU!" 를 tokenize하면 다음과 같다.

>>> from transformers import BertTokenizer
>>> tokenizer = BertTokenizer.from_pretrained("bert-base-uncased")
>>> tokenizer.tokenize("I have a new GPU!")

["i", "have", "a", "new", "gp", "##u", "!"]

GPU라는 단어를 학습하지 않았어도,

'gp'라는 subword와 '##u'라는 뒤에붙여지는 subword로 표현하였다.

또 다른 예로 XLNetTokenizer를 사용하면 다음과 같다.

>>> from transformers import XLNetTokenizer

>>> tokenizer = XLNetTokenizer.from_pretrained("xlnet-base-cased")
>>> tokenizer.tokenize("Don't you love 🤗 Transformers? We sure do.")

["▁Don", "'", "t", "▁you", "▁love", "▁", "🤗", "▁", "Transform", "ers", "?", "▁We", "▁sure", "▁do", "."]

"▁" 가 뜻하는 의미는 아래에 설명할 SentencePiece에서 볼 수 있을 것이다.

여기에서 Transformers라는 희귀한 단어가

"Transform"과 "ers"로 분리된 것을 볼 수 있다.

이제 3개의 서로다른 subword tokenization을 자세히 알아보자.


Byte-Pair Encoding (BPE)

Byte-Pair Encoding (BPE)는 아래 논문을 통해 등장하였다.

https://arxiv.org/abs/1508.07909

BPE는 pre-tokenizer(공백기준분리)에 기반한다.

pre-tokenization 이후에

각 분리된 token과 그 token의 training data속 등장 빈도를 담은 집합을 만든다.

("hug", 10), ("pug", 5), ("pun", 12), ("bun", 4), ("hugs", 5)

이제 base vocabulary을 만드는데

이것은 token을 문자단위(character-level)로 쪼갠 것들의 집합이다.

["b", "g", "h", "n", "p", "s", "u"]


맨 처음으로, base vocabulary를 가지고 표현한다.

("h" "u" "g", 10), ("p" "u" "g", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "u" "g" "s", 5)

BPE는 symbol(가장 작은 단위) 쌍이 포함된 token의 등장빈도에 따라

가장 많이 등장하는 symbol 쌍을 vocabulary에 추가한다.

위에서는 'hu' 쌍이 hug와 hugs에서 등장하였으므로, 앞서 구한 token들의 등장빈도를 가지고

hug:10, hugs:5이므로 => 10+5 =15 라는 값을 부여받는다.

그러나

'ug' 쌍이 hug,pug,hugs에서 등장했으므로,

10+5+5=20이므로

'ug' 쌍이 가장 많은 등장 빈도를 가진 symbol 쌍이 된다.

=> 이 'ug'를 vocabulary에 추가한다.

["b", "g", "h", "n", "p", "s", "u","ug"]

다시 이 사전으로 앞선 집합을 표현하면 다음과 같다.

("h" "ug", 10), ("p" "ug", 5), ("p" "u" "n", 12), ("b" "u" "n", 4), ("h" "ug" "s", 5)

여기에서는 "un" 쌍이 가장 등장 빈도가 많으므로 (12+4=16)

( "h"와"ug"쌍도 10+5=15로 많지만 16보다 작다.)

"un"을 vocabulary에 추가한다.

이후 "hug"를 추가하면 된다.

그러면 vocabulary는 다음과 같다.

["b", "g", "h", "n", "p", "s", "u", "ug", "un", "hug"]

이 사전으로 위의 집합을 다시 표현하면

("hug", 10), ("p" "ug", 5), ("p" "un", 12), ("b" "un", 4), ("hug" "s", 5)

다음과 같아진다.

Byte-Pair Encoding은 앞서 설명한 사전 추가 법칙에 의해

쌍을 만들 수 있을 때까지 만들어서 끝낼 수 있지만,

실제 사용할 때에는

우리가 설정하는 hyperparameter로 vocabulary size(사전크기)를 정해서

그 사전크기까지만 쌍을 사전에 추가하고 끝낸다.

예를들어 GPT같은 경우

vocabulary size가 40478인데

478이 base character이고

40000개의 symbol 쌍을 사전에 추가할것이라고 정해주어서

사전의 크기가 40478개가 된 것이다.

+ Byte-leve BPE에 대하여

base vocabulary에 가능한 모든 character들을 포함하는 것은 꽤 클 수 있다.

만약 모든 unicode character를 base character로 한다면

그 크기는 138000(138K)가 되기 때문이다.

더 나은 base vocabulary를 만드려는 노력으로 GPT-2에서는 byte-level BPE를 사용한다.

이것은 text를 character의 sequence로 보지 않고

Byte들의 sequence로 보는 것이다.

( 우리가 흔이 쓰는 UTF-8 encoding에서는 Unicode character를 1~4byte로 인코딩한다.)

그렇다면 어떤 문자가 나오든 Byte로 표현해서 해석하면 되기 때문에

만약 학습하지 못한 character가 나오더라도 Byte로 해석해서 등장빈도를 계산하면 된다.

=> token이 필요없어지고 어떤 문자에도 적용 가능하게 된다.

​​

출처 : https://arxiv.org/pdf/1909.03341.pdf

이때 256 Byte정도만 있으면 적절한 base vocabulary가 마련된다고 한다.


WordPiece

WordPiece는 BERT,DistilBERT,Electra에서 쓰이는 subword tokenization 알고리즘이다.

이 알고리즘은 다음 논문에서 소개된다.

[ JAPANESE AND KOREAN VOICE SEARCH ]

https://static.googleusercontent.com/media/research.google.com/ja//pubs/archive/37842.pdf

  1. 유니코드 캐릭터수준으로 초기화 ( 캐릭터로 쪼개진 word unit 저장소 만들기)

  2. 위 저장소(vocabulary)에서 language model(단어 sequence에 확률을 부여하는 것)을 만든다.

  3. 현재 저장소에서 두개 유닛을 뽑아서 합치고 새로운 유닛을 만든다.

이후, 모델에 추가할 때(vocabulary에 추가로 저장할 때), 모든 가능한 경우(합쳐서 새로운 만든 것들)중에 training data의 확률을 가장 많이 증가시키는 새 유닛(병합되어 만들어진 subword)을 선택한다

  1. 정해놓은 vocabulary size를 넘거나, 확률이 특정 값(threshold) 아래로 떨어지기 전까지 2번으로 돌아간다.

예를 들어보면

2개의 unit을 뽑아 합치는 모든 경우에 수에 대해
"u" 다음에 "g"가 붙여서 생기는 "ug"를 vocab에 추가할 때가 Language model의 확률이 가장 높일 경우,
이 "ug"를 새롭게 vocabulary에 저장한다는 뜻이다.


Unigram

Unigram은 다음 논문에서 소개된 subword tokenization 알고리즘이다.

[Subword Regularization: Improving Neural Network Translation Models with Multiple Subword Candidates]

https://arxiv.org/pdf/1804.10959.pdf

BPE와 WordPiece와 달리

Unigram은 처음에 base vocabulary를 아주 크게 잡고

점차 줄여나가는 방식이다.

base vocabulary는

pre-tokenized words(공백기준분리단어)나 흔히 등장하는 문자들이 될 수 있을 것이다.

Unigram은 단독으로 쓰여서 transformer 모델에 바로 쓰이지 않고

아래 소개할 SentencePiece와 함께 쓰인다.

매 학습 단계(training step)에서

Unigram 알고리즘은

현재 vocabulary로 표현한 학습 데이터와 unigram 언어 모델간의

loss(주로 log-likelihood)를 정의한다.

그런 다음,

vocabulary의 각 symbol에 대해

이 symbol이 없어졌을 경우 전체 loss가 얼마나 증가하는지를 계산한다.

이 계산 결과에 따라

Unigram은 loss가 가장 적게 증가하는 symbol들을 우리가 정한 비율만큼 제거한다.

이 과정은 우리가 설정한 vocabulary size가 될 때까지 반복된다.

( Unigram 알고리즘은 어떤 단어든지 tokenized될 가능성이 있어 base characters를 계속 기억한다.)

Unigram이 BPE와 WordPiece와 달리 symbol들을 합치지 않기 때문에

training의 정도에 따라 tokenizing 방식의 차이가 존재 할 수 있다.


SentencePiece

앞서 설명한 모든 tokenization 알고리즘은 같은 문제를 가지고 있다.

바로 처음에 공백 기준으로 단어를 분리한다는 것이다.

하지만 모든 언어가 단어를 분리하기 위해 공백을 사용하지 않는다.(중국어,일본어)

한가지 해결방법은

각 언어마다 그 언어에 적절한 pre-tokenizer를 사용하는 것이다.

(XLM은 specific Chinese, Japanese, Thai pre-tokenizer를 사용한다)

이것보다 일반적으로 문제를 해결하기 위해 SetencePiece는

input을 그대로 사용한다.

그래서 사용할 character들의 집합에 공백이 포함되어있다.

XLNetTokenizer 는 SentencePiece를 사용한다.

여기서 앞서 언급한 "_"가 vocabulary에 붙어있는 것이다.

즉, "_" 가 "공백"을 의미하는 것이다.

이 방법의 이점은

Decoding(token들을 우리가 읽을 수 있는 단어로 변환해 출력)을 할때

모든 token을 그냥 이어붙이고(concatenate)

"_" 부분을 공백으로 치환해주기만 하면 된다는 점이다.

SentencePiece를 사용하는 모든 Transformer 모델에서 ( ALBERT,XLNet,Marian,T5 )

SentencePiece는 Unigram과 함께 사용된다.

  • SentencePiece 논문

[ SentencePiece: A simple and language independent subword tokenizer and detokenizer for Neural Text Processing ]

https://arxiv.org/pdf/1808.06226.pdf

1개의 댓글

comment-user-thumbnail
2023년 4월 22일

글 잘 봤습니다. 감사합니다!

답글 달기