4장. LLM 을 완성하는 3단계와 Hugging Face
실습 3 — 우리 토크나이저 학습하기
가상 쇼핑몰의 글 shop_corpus.txt(공지, 상품 설명, 이용 안내, 후기, 상담)와 3장의 cs_dialogs.txt 로, 어휘를 3,000개까지 만들도록 BPE 토크나이저를 직접 학습합니다. 만든 토크나이저와 Qwen 의 토크나이저가 같은 문장을 몇 토큰으로 자르는지 견줍니다. 두 글 모두 연습용으로 지어낸 것입니다.
1단계. 글 살펴보기
with open("shop_corpus.txt", encoding="utf-8") as f:
corpus = f.read()
lines = corpus.splitlines()
print("줄 수:", len(lines), "/ 글자 수:", len(corpus))
for line in lines[:5]: # 앞의 다섯 줄
print(line)줄 수: 2500 / 글자 수: 132492
[상담] 고객: 청바지 관련해서 궁금한 게 있어요. 상담원: 안녕하세요, 고객님. 배송은 결제 후 1~3일 안에 도착합니다. 감사합니다.
[공지] 3월 28일부터 봄맞이 할인 행사를 엽니다.
[공지] 4월 6일부터 봄맞이 할인 행사를 엽니다.
[상품] 텀블러는 이중 스테인리스로 만들어 보온이 오래갑니다. 가격은 10,000원입니다.
[안내] 제주와 섬 지역은 배송이 2일 더 걸립니다. 3만원보다 적게 주문하면 배송비는 3,000원입니다.2단계. BPE 토크나이저 학습하기
import os
from tokenizers import ByteLevelBPETokenizer
# ByteLevelBPE: 글을 바이트로 쪼갠 뒤, 자주 붙어 나오는 쌍을 하나로 합쳐 어휘를 키워 간다
bpe = ByteLevelBPETokenizer()
bpe.train(
["shop_corpus.txt", "cs_dialogs.txt"], # 학습할 글 파일들
vocab_size=3000, # 어휘를 3,000개까지 만든다
min_frequency=2, # 두 번 이상 나온 쌍만 합친다
special_tokens=["<pad>", "<eos>"], # 채움 표시와 글 끝 표시를 미리 넣어 둔다
)
os.makedirs("out_tok", exist_ok=True)
bpe.save("out_tok/tokenizer.json") # 실습 4~6 에서 이 파일을 다시 불러온다
print("어휘 크기:", bpe.get_vocab_size())어휘 크기: 11623단계. transformers 용으로 감싸고 써 보기
from transformers import PreTrainedTokenizerFast
# 학습한 토크나이저를 transformers 의 토크나이저로 감싼다. 채움·끝 표시가 무엇인지 알려 준다
tok = PreTrainedTokenizerFast(tokenizer_file="out_tok/tokenizer.json", pad_token="<pad>", eos_token="<eos>")
text = "배송비는 3만원 이상 주문하면 무료입니다."
ids = tok(text)["input_ids"]
print("토큰 수:", len(ids))
print("조각:", [tok.decode([i]) for i in ids]) # 토큰 하나씩 글로 되돌려 본다
print("되돌리기:", tok.decode(ids))토큰 수: 10
조각: ['�', '�송', '비', '는', ' 3', '만원', ' 이상', ' 주문하면', ' 무료입니다', '.']
되돌리기: 배송비는 3만원 이상 주문하면 무료입니다.4단계. Qwen 토크나이저와 견주기
from transformers import AutoTokenizer
qwen = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-0.5B")
for s in ["배송비는 3만원 이상 주문하면 무료입니다.", "교환은 상품을 받은 날부터 7일 안에 신청할 수 있습니다.",
"오늘 날씨가 정말 좋네요."]:
print(f"우리 {len(tok(s)['input_ids']):2d}개 / Qwen {len(qwen(s)['input_ids']):2d}개 : {s}")우리 10개 / Qwen 15개 : 배송비는 3만원 이상 주문하면 무료입니다.
우리 14개 / Qwen 22개 : 교환은 상품을 받은 날부터 7일 안에 신청할 수 있습니다.
우리 20개 / Qwen 10개 : 오늘 날씨가 정말 좋네요.이 실습이 뜻하는 것
이 실습은 우리 가게 전용 줄임말 사전을 만드는 것과 같습니다. 가게의 글을 훑어보고(1단계), 자주 붙어 다니는 글자 쌍을 하나로 묶어 줄임말 목록을 만들고(2단계), 그 목록으로 문장을 잘라 보고 되돌리며(3단계), 큰 회사의 범용 사전과 몇 조각이 되는지 견줍니다(4단계). 여기서 줄임말 목록이 BPE(Byte Pair Encoding, 바이트 쌍 부호화) 토크나이저의 어휘, 범용 사전이 Qwen 의 토크나이저입니다. 비유와 다른 점은, 사람의 줄임말은 뜻을 보고 만들지만 BPE 는 뜻과 상관없이 함께 나오는 횟수만 본다는 것입니다.
| 단계 | 출력 | 뜻 |
|---|---|---|
| 1. 글 읽기 | 줄 수 2500, 글자 수 132492 | 쇼핑몰 글이 2,500줄, 13만 글자쯤입니다. 줄마다 공지, 상품, 안내, 상담 같은 말머리가 붙어 있습니다 |
| 2. 어휘 크기 | 1162 | 3,000개를 목표로 했지만 두 번 이상 나오는 쌍이 바닥나 1,162개에서 멈췄습니다. 같은 문장 틀로 지어낸 글이라 그렇습니다 |
| 3. 토큰 수 | 10 | 쇼핑몰 문장 하나가 10조각이 되었습니다 |
| 3. 조각 | ' 주문하면', ' 무료입니다' | 쇼핑몰 글에 자주 나온 말이 통째로 한 조각이 되었습니다. 앞의 깨진 글자 둘은 "배" 한 글자가 두 조각에 나뉜 것입니다 |
| 3. 되돌리기 | 원래 문장 그대로 | 조각을 이어 붙이면 잃는 글자 없이 돌아옵니다 |
| 4. 쇼핑몰 문장 | 우리 10·14개, Qwen 15·22개 | 우리 분야의 글은 우리 토크나이저가 더 짧게 자릅니다. 토큰이 적으면 모델이 읽을 양도 줄어듭니다 |
| 4. 일상 문장 | 우리 20개, Qwen 10개 | 쇼핑몰과 상관없는 문장은 거꾸로 우리 것이 두 배로 잘게 자릅니다 |
한 줄로 줄이면, 우리 가게 글로 만든 토크나이저는 그 가게의 글을 짧게 자르는 대신 다른 글에는 약하다는 것을 보인 실습입니다. 여기서 저장한 out_tok/tokenizer.json 을 실습 4부터 실습 6까지 다시 불러 씁니다. 어휘가 1,162개에서 멈춘 것은 지어낸 글의 성질이라, 실제 글에서도 이 크기가 나온다는 뜻은 아닙니다.
코드 설명
ByteLevelBPETokenizer는 글을 먼저 바이트로 나눕니다. 그래서 처음 보는 글자도 바이트 조각으로는 늘 나타낼 수 있습니다. 그 위에서 자주 나오는 쌍을 합쳐 "배송", "무료" 같은 덩어리를 만듭니다.- 글은 2,500줄, 132,492글자입니다. 어휘를 3,000개까지 만들라고 했지만 1,162개에서 멈췄습니다. 글이 같은 문장 틀로 지어져 두 번 이상 나오는 쌍(
min_frequency=2)이 더 없었기 때문입니다. 실제 글로 학습하면 목표한 크기를 채웁니다. - "배송비는 3만원 이상 주문하면 무료입니다."가 10토큰이 되었고,
' 주문하면',' 무료입니다'처럼 쇼핑몰 글에 자주 나온 말이 한 덩어리가 되었습니다. 첫 두 조각'�','�송'은 "배" 한 글자의 바이트가 두 토큰에 걸쳐 갈려, 하나만 되돌리면 깨진 글자로 보이는 것입니다. 이어 붙여 되돌리면 원래 문장이 그대로 나옵니다. - 쇼핑몰 문장 둘은 우리 토크나이저가 10개와 14개로, Qwen(15개, 22개)보다 적게 자릅니다. 반대로 쇼핑몰과 상관없는 "오늘 날씨가 정말 좋네요."는 우리 것이 20개, Qwen 이 10개입니다. 토크나이저는 학습한 글에 맞춰지므로, 우리 분야의 글로 만들면 그 분야의 글을 짧게 자르는 대신 다른 글에는 약해집니다.
- 3단계에서 토큰 하나씩
decode한 까닭은, 바이트 단위 토큰을 그대로 찍으면ë°°같은 알아보기 힘든 글자로 보이기 때문입니다.