Tokenizer 토크나이저는 텍스트를 word 단어나 subword 서브워드로 나눠줄 때 사용한다.
뒤에 보면 알겠지만 토크나이저도 막대한 양의 텍스트를 통해서 학습한 결과기 때문에 업데이트하거나
특정 언어에 맞게 추가 학습을 해야할 경우도 있다.
여기서는 가장 간단한 공백 구분과 자주 쓰이는 BPE, WordPiece, SentencePiece를 살펴본다.
Whitespace 공백
1. Whitespace 공백을 기반으로 하여 아래의 문장을 나눈다면 다음와 같다.
문장 = "Don't you love 🤗 Transformers? We sure do."
["Don't", "you", "love", "🤗", "Transformers?", "We", "sure", "do."]\
2. BPE (Byte Pair Encoding)
BPE는 다시 2가지로 나뉜다.
2.1. Character-level BPE
가장 기본적인 BPE로 가장 자주 나타나는 character pair 문자 쌍을 병합해서 새로운 토큰을 생성하는 방식이다. 구체적인 토크나이징 방법은 아래 예시로 살펴본다.
예시)
corpus = ['low', 'lower', 'lowest']
1. 초기에는 모든 개별 문자를 Vocabulary로 등록한다.
vocab = [ l, o, w, e, r, s, t ]
2. 자주 등장하는, 빈도가 높은 문자 쌍을 병합한다.
lo 빈도 3
ow 빈도 3
we 빈도 2
이다.
lo 부터 합친다.
(lo, w)
(lo, w, e, r)
(lo, w, e, s, t)
그 다음 lo과 w를 합친 low가 빈도 3, we가 빈도 2다.
low를 합치면
(low)
(low, e, r)
(low, e, s, t)
가 나온다.
3) 원하는 Vocab 사이즈까지 반복한다.
GPT-1의 경우 vocabulary size가 40,478인데, 총 478개의 base characters를 가지고 40,000 merges 병합까지 토크나이저를 학습했다.
2.2. Byte-level BPE
GPT-2 이후의 GPT 시리즈에서 사용하는 토크나이저다.
모든 텍스트를 바이트로 변환하여 처리한다. Emoji 이모티콘도 처리가 가능하다.
구체적인 토크나이징 방법은 아래 예시로 살펴본다.
예시)
입력 문장 = "hello🙂"
1. Input string 입력 문자열을 바이트 시퀀스로 변환
바이트로 변환 → [104, 101, 108, 108, 111, 240, 159, 153, 130]
2. BPE 병합 수행
BPE 병합 → "he", "llo", "🙂" 등으로 처리.
3. 목표 Vocab size까지 반복
바이트 변환 설명 추가
h, e, l, o, 🙂의 unicode 코드 포인트는 각각 아래와 같다.
U+0068, U+0065, U+006C, U+006F, U+1F642
h, e, l, o는 영문자라서 ASCII로 표기 가능한데 모두 1바이트 문자다.
UTF-8 (10진수)로 표현하면
104, 101, 108, 111이다.
따라서 hello = [104, 101, 108, 108, 101] 이다.
이때 이모지 (slightly smiling face)는 범위가 U+10000 이상이므로 4 바이트 UTF-8 인코딩이 필요하다.
UTF-8의 인코딩 규칙에 따르면 U+10000–U+10FFFF의 유니코드 범위는 4 bytes로 표기 한다.
이때 포맷은 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 다.
1F642는 16진수로 이진수로 변환하면 21자리 수 1111101101000010가 된다.
21 비트를 6 + 6 + 6 + 3으로 나눠서 4바이트 UTF-8 패턴으로 끼워 넣는다.
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
↓
11110000 10011111 10011010 10000010
이는 10진수로 표기하면 [240, 159, 153, 130]이 된다.
따라서
"hello🙂".encode("utf-8")의 결과는
[104, 101, 108, 108, 111, 240, 159, 153, 130]가 된다.
GPT-2의 경우 256 bytes의 base tokens과 a special end-of-text token을 가지고 50,000 병합을 거쳐서 vocabulary size가 50,257이 되도록 토크나이저를 학습시켰다.
3. WordPiece
BERT에서 소개한 토크나이징 방법으로 희귀한 단어를 subword로 분해하는게 핵심이다.
아래와 같은 방식으로 작동한다.
1. 초기 vocab을 특수 토큰과 문자 단위로 시작
2. 여러가지 subword 조합을 생성
3. 전체 corpus의 likelihood가 가장 많이 증가하는 subword 조합을 선택
4. Vocab size까지 반복
그 결과로 "unhappiness"라는 단어를 토크나이징 한다면 아래와 같이 분해된다.
["un", "##happi", "##ness"]
un은 자주 등장하는 접두사, ##ness는 자주 쓰이는 subword (별도로 접미사로 설정하지는 않음),
그리고 happy, happiness 등에서 발견할 수 있는 ##happi로 분해된다.
Character-level BPE와 유사하면서도 약간 다른데 BPE는 단순히 문자 쌍의 빈도를 우선시하는 단순한 방법이라면,
WordPiece는 전체 likelihood를 고려하는 다소 복잡한 방법이다.
4. SentencePiece
Google에서 만든 라이브러리로 공백이 없는 문자열에서도 작동하도록 설계되었다.
핵심 아이디어는 공백 '_' 역시 하나의 문자열로 취급하는 것이다.
따라서 중국어나 일본어 등 다양한 언어들에 대해서 잘 작동한다고 한다.
SentencePiece에는 BPE 방법과 Unigram 방법 두 가지가 있다.
4.1. BPE 방법
공백이라는 문자열의 추가 빼고는 모두 Character-level BPE와 동일하다.
4.2. Unigram 방법
1. 초기 서브워드 사전 구성
2. 가능한 분할 → E-step
3. 확률 업데이트 → M-step
4. Vocab size까지 반복
원문 논문 Subword Regularization: Improving Neural Network Translation Models with Multiple Subword Candidates (링크)를 보면 알겠지만 EM 알고리즘을 사용한다.
다음은 ChatGPT로 알아본 예시다
입력 문장: "Hello do you love transformers? Sure we do."
초기 subword vocab =
["H", "He", "Hell", "Hello",
"d", "do",
"y", "yo", "you",
"l", "lo", "lov", "love",
"t", "tr", "tra", "trans", "transf", "transfo", "transformers",
"Sure", "S", "we", "w", "e", "do", "."]
각 서브워드의 초기 확률 = 1 / 24 ≈ 0.0417 (균등 가정)
2. E-step (예: "do you")
가능한 분할 예시
문장 "do you"를 대상으로 가능한 분할 조합을 나열하고, 각각의 확률 계산:
가능한 분할:
["d", "o", "y", "o", "u"]
P = 0.0417⁵ ≈ 1.25e-7
["do", "you"]
P = 0.0417 * 0.0417 ≈ 0.0017
["do", "y", "o", "u"]
P = 0.0417 * 0.0417³ ≈ 3.01e-6
["d", "o", "you"]
P = 0.0417³ ≈ 7.25e-5
["d", "o", "yo", "u"]
P = 0.0417⁴ ≈ 3.02e-6
총합 Z = 1.25e-7 + 0.0017 + 3.01e-6 + 7.25e-5 + 3.02e-6 ≈ 0.0018
기대 사용 횟수 계산 (예: “do”):
"do"가 등장하는 분할:
- 2번에서 1회 (0.0017 / Z)
- 3번에서 1회 (3.01e-6 / Z)
→ 기대 사용 횟수(do) ≈ (0.0017 + 3.01e-6) / 0.0018 ≈ 0.945
다른 서브워드도 같은 방식으로 기대 사용 횟수를 계산.
3. M-step
서브워드 | 기대 사용 횟수 | 새 확률 |
"do" | 0.945 | 0.945 / 합 |
"you" | 0.8 | 0.8 / 합 |
"d" | 0.2 | ... |
... | ... | ... |
이를 토대로 다시 E-step을 반복하고 다시 M-step을 반복한다.
이렇게 서브워드의 확률이 보다 정교해진다.
그리고 Unigram의 loss 함수는 아래와 같다.
$ L = - \sum_{i=1}^{N} ( \sum_{x \in S(x_i)} p(x) )$.
E와 M 스텝을 반복하면서 확률이 가장 작은 서브워드를 제거하는데 이는 바로 loss가 변화가 가장 작은 서브워드의 제거와 같은 말이다.
그렇게 Vocab size에 도달할 때 까지 반복한다.
한국어 토크나이저
한국어의 경우 과거에는 KoNLPy(링크)를 많이 썼던거 같은데 요즘은 잘 모르겠다.
그럼에도 불구하고 참고용으로 첨부한다.
- Hannanum Class
- Kkma Class (꼬꼬마)
- Komoran Class
- Mecab Class (은전한닢)
- Okt Class (구 트위터)
References:
https://huggingface.co/docs/transformers/tokenizer_summary
https://process-mining.tistory.com/189
https://devocean.sk.com/blog/techBoardDetail.do?ID=164570&boardType=techBlog
https://konlpy.org/ko/latest/index.html
https://sequencedata.tistory.com/81
ChatGPT의 답변 결과
https://asidefine.tistory.com/281
https://velog.io/@gibonki77/SentencePiece
https://process-mining.tistory.com/190
'NLP' 카테고리의 다른 글
Embedding Models (0) | 2025.05.04 |
---|---|
Longformer (2020) 논문 리뷰 (0) | 2025.04.09 |
GLU variants (2020) 논문 리뷰 (0) | 2025.04.09 |
MQA (Multi-Query Attention) (2019) 논문 리뷰 (0) | 2025.04.09 |
GPT 2 (2019) 논문 리뷰 (0) | 2025.04.09 |