[aaabdaaabac]
위와 같은 문자열이 주어졌다고 가정하면, 단어에 등장한 글자로 사전을 구성하면 (a, b, c, d)가 된다.
이제 가장 많이 등장하는 연속하는 두 글자를 하나로 병합합하면, 여기서는 aa가 동시에 많이 등장하므로 이를 Z로 치환할 수 있다.
[ZabdZabac]
이제 글자 사전은 (a, b, c, d, Z)가 된다.
이 문자열에서 다시 ab를 Y로 압축할 수 있다.
[ZYdZYac]
사전에는 Y가 추가되어서 (a, b, c, d, Z, Y)가 된다.
마지막으로 ZY를 묶어서 X로 치환하면 아래와 같이 압축할 수 있다.
[XdXac]
BPE의 가장 큰 장점은 분석 대상 언어에 대한 사전 지식이 필요 없다. 그저 말뭉치 데이터만 주어지면 자주 나타나는 문자열을 토큰으로 분석하기 때문에 어떤 언어에도 적용이 가능하다.
학습 순서
corpus = [
"이번에는 자연어 처리에 대해 공부했다",
"자연어 처리에는 다양한 기법들이 있다",
"그 중에서도 이번에는 서브워드 토큰화를 해보았다",
"서브워드 토큰화는 어떤 언어에도 적용할 수 있다는 장점이 있다"
]
띄어쓰기를 기준으로 나눠준 후 사전을 만든다.
words = []
for sentence in corpus:
words.extend(sentence.split(" "))
vocabs = set()
for word in words:
for c in word:
vocabs.add(c)
단어 사전을 이용하여 각 단어들을 토큰화 하는 함수를 만든다.
import re
def build_pattern(vocabs):
sorted_vocabs = sorted(vocabs, key=lambda x: len(x), reverse=True)
pattern = re.compile(r"|".join(sorted_vocabs))
return pattern
함께 자주 등장하는 쌍을 찾는다.
from collections import Counter
def count_pairs(words, pattern):
c = Counter()
for word in words:
tokens = pattern.findall(word)
for i in range(len(tokens)-1):
c[f"{tokens[i]}{tokens[i+1]}"] += 1
return c
원하는 단어수를 만족할 때까지 반복한 뒤 만들어진 사전을 이용하여 토큰화 한다.
학습과정에서 사용하지 않은 단어가 들어간 문장을 위의 사전을 이용해 토큰화하면 아래와 같은 결과가 나온다.
new_sentence = "다음에는 어떤 토큰화 기법에 대해서 공부할지 기대된다"
[huggingface 사이트]
https://huggingface.co/docs/tokenizers/index
import re
def preprocess_text(text):
text = text.lower()
text = re.sub(r"[^ㄱ-ㅎㅏ-ㅣ가-힣a-zA-Z!?.,\' ]", "", text)
text = re.sub(r"\s+", " ", text)
text = text.strip()
return text
import tokenizers
from tokenizers.trainers import WordPieceTrainer
trainer = WordPieceTrainer(
vocab_size=10000,
min_frequency=50,
special_tokens=["[PAD]", "[UNK]", "[SOS]", "[EOS]"]
)
--> vocab_size: 훈련된 모델이 포함할 최종 하위 단위 토큰의 개수를 결정한다. 이 값이 클수록 더 세밀한 토큰화가 이루어지지만, 모델의 크기와 처리 시간이 증가할 수 있다. 보통 훈련 데이터에서의 고유한 단어 수를 기준으로 설정한다.
min_frequency: 훈련 데이터에서 최소한으로 등장하는 빈도수를 나타낸다. 이 값보다 빈도수가 낮은 하위 단위 토큰은 무시된다. 이를 통해 희귀한 단어나 노이즈를 줄일 수 있다.
--> [PAD]: padding의 약자로 문장 간에 길이를 맞춰주기 위해 일부러 채워넣은 토큰을 의미
[UNK]: unknown의 약자로 인식하지 못한 토큰을 나타냄
[SOS]: start of sentence의 약자로 문장의 시작을 표시
[EOS]: end of sentence의 약자로 문장의 끝을 표시
def batch_iterator(df, batch_size=1000):
for i in range(0, len(df), batch_size):
batch_df = df.iloc[i:i+batch_size]
yield batch_df["document"]
--> batch_size: 보통 batch_size는 2의 제곱수로 설정하는 것이 일반적이다.
이는 하드웨어 가속기(GPU, TPU)의 연산 최적화 및 메모리 할당과 관련된 이유가 있다.
하지만 데이터의 크기, 하드웨어의 성능, 모델의 복잡도 등에 따라 조정이 필요할 수 있다.
실험을 통해 다양한 batch_size를 시도하여 모델의 성능과 훈련 시간을 평가하며 적절한 값을 찾는 것이 좋다.
--> yield: Python에서 제너레이터를 생성하는데 사용되는 함수이다.
제너레이터는 이터레이터(순회 가능한 객체)를 간편하게 만들어주는 방법 중 하나로, 데이터를 한 번에 모두 메모리에 로드하지 않고 필요한 시점에 데이터를 생성하거나 반환할 수 있도록 도와준다.
from tokenizers import Tokenizer
from tokenizers.models import WordPiece
from tokenizers.pre_tokenizers import Whitespace
tokenizer = Tokenizer(WordPiece())
tokenizer.pre_tokenizer = Whitespace()
tokenizer.train_from_iterator(batch_iterator(train_df), trainer=trainer)
--> Whitespace(): 이것은 pre_tokenizer로 사용되는 것으로, 입력 텍스트를 공백 문자를 기준으로 분리하여 초기 토큰화를 수행한다.
예를 들어, 문장 "Hello, world!"는 "Hello,"와 "world!"로 분리된다.
vocabs = tokenizer.get_vocab()
sorted_vocabs = sorted(vocabs.items(), key=lambda x: x[1])