BPE (Byte-Pear Encoding) Tokenizasyon Algoritması

Oğuzhan Yenen
9 min readJan 12, 2024

--

Bölüm 1 : Tokenizasyon (Belirteçleme) Üzerine Derinlemesine Bir Analiz
Bölüm 2 : BPE (Byte-Pear Encoding) Tokenizasyon Algoritması
Bölüm 3 : WPC (WordPiece) Tokenizasyon Algoritması
Bölüm 4 : Unigram Tokenizasyon Algoritması
Bölüm 5 : Adım Adım Özel Bir Tokenizer Oluşturma (Uygulama)

Tekrar merhaba arkadaşlar. Bir önce ki yazımızda tokenizasyon kavramı üzerine derinlemesine bir analiz yapmıştık. Tokenizasyonun ne olduğundan, hangi türlerinin bulunduğundan ve zorluklarından bahsetmiştik.

Bu devam niteleiğindeki yazı ile günümüzde popüler olan, yaygın şekilde kullanılan üç tokenizasyon algoritmasını inceleyeceğiz. Üç algoritma üzerine yoğunlaşacağız çünkü günümüzde ChatGPT den BERT modellerine kadar güncel modellerin çoğu bu algoritmaları kullanırlar.

O halde BPE (Byte-Pair Encoding) algoritması ile başlayalım.

https://www.freecodecamp.org/news/train-algorithms-from-scratch-with-hugging-face/

BPE (Byte-Pear Encoding) Algoritması

Önceden tanımlanmış kelime dağarcığına, boşluğa veya noktalama işaretlerine dayanan geleneksel tokenizasyon yöntemlerinden farklı olarak BPE, veri odaklı bir yaklaşım kullanır. Bitişik karakterleri veya karakter dizilerini (bigramlar) yeni sembollerle yinelemeli olarak birleştirir; böylece alt kelimelerden oluşan yeni bir kelime dağarcığı oluşturur.

BPE veri sıkıştırma algoritması olarak ortaya çıkmıştır ve ilk olarak 1994 yılında yayınlanan “ Veri Sıkıştırma İçin Yeni Bir Algoritma” makalesinde açıklanmıştır.

Alt kelime (sub-word) tabanlıdır.

OpenAI’ın potansiyelini fark etmesiyle daha da geliştirilmiş ve ilk olarak GPT modelinin ön eğitimi sırasında tokenizer olarak kullanılmıştır. Sonrasında GPT-2, RoBERTa, BART ve DeBERTa dahil olmak üzere birçok Transformer modeli tarafından tokenizer olarak kullanılmıştır.

BPE, önceden tanımlanmış bir kelime boyutuna ulaşılana kadar bir metin derlemindeki en sık bayt veya karakter çiftlerini yinelemeli olarak birleştirerek çalışır. Ortaya çıkan alt kelime birimleri, orijinal metni daha kompakt ve etkili bir şekilde temsil etmek için kullanılır.

BPE’yi öne çıkaran etkenler nelerdir?

  • Geleneksel tokenizasyon teknikleri, eğitim setinde bulunmayan kelimelerle karşılaştıklarında bocalayabilir. BPE eğitim sırasında gördüğü alt kelimeleri kullanarak bu sorunu büyük oranda çözer. (GENELLEME YAPABİLME KAPASİTESİ)
  • İnsan dili tabiatı uyarınca karmaşıktır ve bu karmaşıklık kelimenin yanı sıra alt kelime düzeyinde de olur. BPE, bir kelimenin farklı biçimlerini anlamayı mümkün kılar. (MORFOLOJİK ÖZELLİKLERİ YAKALAMA)
  • BPE dilden bağımsızdır ve herhangi bir metne uygulanabilir; bu da onu çok dilli modeller (multilingual) veya sınırlı hesaplama kaynaklarına sahip daha az bilinen diller için önemli bir araç haline getirir. (ESNEKLİK)
  • BPE, nadir kelimeleri daha küçük birimlere bölerek sözcük boyutunu etkili bir şekilde azaltır ve modellerin hafıza açısından daha verimli olmasını sağlar. (SIKIŞTIRILMIŞ TEMSİL)

BPE’in çalışma mekanizması nasıldır?

Aşağıda BPE’nin çalışma mekanizmasını gösteren bir şekil çizmeye çalıştım.

BPE çalışma prensibi

Bu görseli açıklayalım:

  • İlk adımda eğitim verisinden her kelimenin sıklığını çıkararak işleme başlıyoruz.
    örnek külliyat
    (“bayat”, 5), (“bayrak”, 3), (“kayak”, 4), (“kıyak”, 2)
    her kelimeyi simge haline dönüştürelim
    (“b”,”a,”y”,”a”,”t”, 5), (“b”,”a”,”y”,”r”,”a”,”k”, 3), (“k”,”a”,”y”,”a”,”k”, 4), (“k”,”ı”,”y”,”a”,”k”, 2)
  • İkinci adımda en sık tekrarlanan bitişik sembol çiftlerini buluyoruz.
    yukarıda görüldüğü gibi en sık tekrarlanan karakter (a ve k) olmasına rağmen ikili sembol olarak baktığımızda en sık ikili (ay =12) olduğunu görürüz. Bu da bize (ay) ikilisinin birleşmesi gerektiğini gösterir.
  • Üçüncü aşamada en sık görülen sembol çiftinin her oluşumunu yeni bir sembolle değiştiriyoruz.
    Son durumda :
    (“b”,”ay”,”a”,”t”, 5), (“b”,”ay”,”r”,”a”,”k”, 3), (“k”,”ay”,”a”,”k”, 4), (“k”,”ı”,”y”,”a”,”k”, 2)
  • Dördüncü aşamada bu oluşan yeni sembolü korpusumuza, kelimelerin bulunduğu dizimize ekliyoruz.
    Şimdi en sık kullanılan çift , yani bize ilk üç harfli belirtecimizi veren ("b", "ay")birleştirme kuralını öğreniyoruz .
    (“bay”,”a”,”t”, 5), (“bay”,”r”,”a”,”k”, 3), (“k”,”ay”,”a”,”k”, 4), (“k”,”ı”,”y”,”a”,”k”, 2)
  • Son adımda ise bu işlemi belirtilen K yineleme sayısına ulaşana veya durmaya karar verene kadar tekrar ediyoruz.
    Bir sonra ki birleştirme kuralı (ak) ile devam eder ve belirli bir yineleme sayısına kadar işlem yapar

Örnek Uygulama

Normal şartlarda bu yazı dizisini kaleme alırken örnekleri Transformers kütüphanesinin “AutoTokenizer” sınıfını kullanarak vermeyi planlıyordum. Fakat şu durumda bu biraz basit kaçacak sanırım. Bu yüzden daha önceleri geliştirdiğim ve kütüphanemde bulunan aşağıdaki kodu sizlerle paylaşmak konuyu anlama açısından etkili olacaktır.

from collections import Counter, defaultdict
class BPE:
def __init__(self, metin, yineleme):
self.metin = metin
self.yineleme = yineleme

def al_kulliyat(self): #her kelimenin metin içerisinde ki sıklığını tespit et
kulliyat = Counter(self.metin.split())
return {k: f for k, f in kulliyat.items()}

def al_istatistikler(self, kulliyat): #birleşik halde bulunan (bigram) sembol çiftlerinin sıklığını tespit et
cifts = defaultdict(int)
for w, f in kulliyat.items():
sembols = w.split()
for i in range(len(sembols) - 1):
cifts[sembols[i], sembols[i+1]] += f
return cifts

def birlestir(self, cift, kulliyat): #tüm kelimelerde en sık kullanılan sembol çiftlerini birleştir ve külliyatı güncelle
yeni_corpus = {}
bigram = ' '.join(cift)
degisim = ''.join(cift)
for w in kulliyat:
new_word = w.replace(bigram, degisim)
yeni_corpus[new_word] = kulliyat[w]
return yeni_corpus

def isle(self): #işlemi başlat
kulliyat = self.al_kulliyat()
kulliyat = {' '.join(word): freq for word, freq in kulliyat.items()}
print("başlangıç kelimeleri: \n", kulliyat)

for i in range(self.yineleme):
cifts = self.al_istatistikler(kulliyat)

if not cifts:
break

en_iyi_cift = max(cifts, key=cifts.get)
kulliyat = self.birlestir(en_iyi_cift, kulliyat)
print(f"{i + 1}. yinelemeden sonra, en iyi çift: {en_iyi_cift}")
print("külliyatı güncelle: ", kulliyat)


metin = "bu bugün sen ve senin için gelen gelecektir"
BPE(metin= metin, yineleme=10).isle()

Yukarıda ki örnek bir BPE algoritmasının uygulanmasının basit bir prototibidir. Sizde kendi örnek metnizi kullanarak sonuçları gözlemleyebilirsiniz. Yukarıda ki sınıfı incelediğinizde orada bulunan yineleme (iteration) parametresin birleşecek sembollerin sayısını kontrol etttiğini görebilirsiniz. Peki bu yineleme sayısını neye göre belirleyeceğiz?

Yineleme (iteration) Sayısının Belirlenmesi

BPE algoritmasından en iyi performansı alabilmek için uygun bir yineleme sayısı belirlemek oldukça önemlidir. Bunun kesin bir tanımı, tarifi yoktur. Bunu belirlerken aşağıda ki yöntemlerden faydalanabiliriz :

  • Bir doğrulama kümesindeki en iyi modeli bulmak için çeviride BLEU, metin sınıflandırmasında F1 puanı gibi ölçümleri kulanabiliriz.
  • Sistemin işlem gücü ve hafızasını dikkate alarak optimal bir parametre belirleyebiliriz.
  • Frekans platosunu kullanabiliriz. En yaygın alt kelime çiftinin frekansındaki bir plato, daha sonraki birleştirmelerin o kadar faydalı olmayacağını gösterebilir.

BPE, elimizdeki külliyatı en verimli şekilde temsil edebilmek için, her yinelemede sıklığa bakarak birleşmeye yönelik tüm potansiyel seçenekleri gözden geçirir. Yani sistem kaynaklarını dikkat almak yerine mümkün olan en iyi çözümü optimize edecek şekilde tasarlanmıştır.

Buraya kadar BPE algoritması ile alakalı önemli gördüğüm tüm noktalara değinmeye çalıştım. Şimdi de transformers kütüphanesini kullanarak bir BPE algoritmasını nasıl örnekleyeceğimizi görelim.

Burada kullandığım kodları buradan alarak düzenledim ve yorumladım. Her adımı tek tek açıklayarak devam edelim.

Transformers kütüphanesi ile BPE tokenizer oluşturma

1- Adım: Kelime dağarcığını (külliyat) tanımla
Bu aşamada işlemi basit tutmak adına üç dört cümleden oluşan bir corpus tanımlayalım.

corpus= [
"Bu bir bpe uygulamasidir." ,
"Bu bolum tokenizasyonla ilgilidir." ,
"Bu bolumde bpe tokenizer algoritmasi gosterilmektedir." ,
"Umarim onlarin nasil egitildigini anlayabilir ve token uretebilirsiniz." ,
]

2- Adım: Tokenizer modelini yükle ve kelimelerin frekanslarını hesapla
Bu aşamada öncelikle AutoTokenizer sınıfı ile “GPT2” modelini yüklüyoruz ve frekansları hesaplaması için bir metod hazırlıyoruz.

from transformers import AutoTokenizer
tokenizer = AutoTokenizer.from_pretrained("gpt2")
from collections import defaultdict

word_freqs = defaultdict(int)

for text in corpus:
words_with_offsets = tokenizer.backend_tokenizer.pre_tokenizer.pre_tokenize_str(text)
new_words = [word for word, offset in words_with_offsets]
for word in new_words:
word_freqs[word] += 1

print(word_freqs)
defaultdict(<class 'int'>, {
'Bu': 3, 'Ġbir': 1, 'Ġbpe': 2, 'Ġuygulamasidir': 1, '.': 4,
'Ġbolum': 1, 'Ġtokenizasyonla': 1, 'Ġilgilidir': 1, 'Ġbolumde': 1,
'Ġtokenizer': 1, 'Ġalgoritmasi': 1, 'Ġgosterilmektedir': 1, 'Umarim': 1,
'Ġonlarin': 1, 'Ġnasil': 1, 'Ġegitildigini': 1, 'Ġanlayabilir': 1,
'Ġve': 1, 'Ġtoken': 1, 'Ġuretebilirsiniz': 1
})

3- Adım: Corpus içinde ki karakterlerden kelime dağarcığını hesapla

alphabet = []

for word in word_freqs.keys():
for letter in word:
if letter not in alphabet:
alphabet.append(letter)
alphabet.sort()

print(alphabet)
['.', 'B', 'U', 'a', 'b', 'd', 'e', 'g', 'i', 'k', 'l', 'm', 'n', 'o', 
'p', 'r', 's', 't', 'u', 'v', 'y', 'z', 'Ġ']

Bu arada modelin kullandığı özel belirteçleri sözlüğün başına eklemeyi unutmamalıyız. GPT-2 modeli için kullanılan tek özel belirteç “<|endoftext|>” dir.

vocab = ["<|endoftext|>"] + alphabet.copy()

Eğitime başlamadan önce her kelimeyi ayrı ayrı karakterlere ayırmamız gerekiyor.

splits = {word: [c for c in word] for word in word_freqs.keys()}

4- Adım: Çift semboller için frekansı hesapla
Artık eğitime hazır olduğumuza göre her çift sembol için frekansı hesaplayan bir fonksiyon yazalım. Bunu eğitimin her adımında kullanmamız gerekecek:

def compute_pair_freqs(splits):
pair_freqs = defaultdict(int)
for word, freq in word_freqs.items():
split = splits[word]
if len(split) == 1:
continue
for i in range(len(split) - 1):
pair = (split[i], split[i + 1])
pair_freqs[pair] += freq
return pair_freqs

Artık en sık görülen sembol çiftini bulabiliriz. Bunun için basit bir döngü yazalım ve sonucuna bakalım:

best_pair = ""
max_freq = None

for pair, freq in pair_freqs.items():
if max_freq is None or max_freq < freq:
best_pair = pair
max_freq = freq

print(best_pair, max_freq)
('i', 'l') 7

Buradan öğrenilecek ilk birleşmenin (“i”, “l”) olduğunu tespit ettik. Bunu sözlüğe ekleyebiliriz.

merges = {("i", "l"): "il"}
vocab.append("il")

Devam etmeden önce bu birleşmeyi sözlüğümüze uygulamamız gerekiyor. Bunun içinde kısa bir kod yazalım:

def merge_pair(a, b, splits):
for word in word_freqs:
split = splits[word]
if len(split) == 1:
continue

i = 0
while i < len(split) - 1:
if split[i] == a and split[i + 1] == b:
split = split[:i] + [a + b] + split[i + 2 :]
else:
i += 1
splits[word] = split
return splits
splits = merge_pair("i", "l", splits)
print(splits["Ġilgilidir"]

"""
['Ġ', 'il', 'g', 'il', 'i', 'd', 'i', 'r']
"""

Artık genel yapımız ana hatları ile hazır. Bundan sonra ki son aşamada tüm birleştirmeleri öğrenene kadar işlemleri belirli oranda yineleyeceğiz.

5- Adım: Yineleme
Bu aşamada artık istediğimiz tüm birleştirmeleri öğrenene kadar işlemleri döngüye almamız gerekiyor. Ben bu adımda yineleme (iteration) sayısını 50 olarak verdim. Kodumuz aşağıda:

vocab_size = 50

while len(vocab) < vocab_size:
pair_freqs = compute_pair_freqs(splits)
best_pair = ""
max_freq = None
for pair, freq in pair_freqs.items():
if max_freq is None or max_freq < freq:
best_pair = pair
max_freq = freq
splits = merge_pair(*best_pair, splits)
merges[best_pair] = best_pair[0] + best_pair[1]
vocab.append(best_pair[0] + best_pair[1])
MERGES
{('i', 'l'): 'il', ('i', 'r'): 'ir', ('Ġ', 'b'): 'Ġb', ('l', 'a'): 'la', ('a', 's'): 'as', ('n', 'i'): 'ni', ('B', 'u'): 'Bu', ('d', 'ir'): 'dir', ('Ġ', 't'): 'Ġt', ('Ġt', 'o'): 'Ġto', ('Ġto', 'k'): 'Ġtok', ('Ġtok', 'e'): 'Ġtoke', ('ni', 'z'): 'niz', ('n', 'la'): 'nla', ('r', 'i'): 'ri', ('t', 'e'): 'te', ('Ġb', 'p'): 'Ġbp', ('Ġbp', 'e'): 'Ġbpe', ('Ġ', 'u'): 'Ġu', ('m', 'as'): 'mas', ('mas', 'i'): 'masi', ('Ġb', 'o'): 'Ġbo', ('Ġbo', 'l'): 'Ġbol', ('Ġbol', 'u'): 'Ġbolu', ('Ġbolu', 'm'): 'Ġbolum', ('Ġtoke', 'niz'): 'Ġtokeniz'}

26 Adet

VACAB
{('i', 'l'): 'il', ('i', 'r'): 'ir', ('Ġ', 'b'): 'Ġb', ('l', 'a'): 'la', ('a', 's'): 'as', ('n', 'i'): 'ni', ('B', 'u'): 'Bu', ('d', 'ir'): 'dir', ('Ġ', 't'): 'Ġt', ('Ġt', 'o'): 'Ġto', ('Ġto', 'k'): 'Ġtok', ('Ġtok', 'e'): 'Ġtoke', ('ni', 'z'): 'niz', ('n', 'la'): 'nla', ('r', 'i'): 'ri', ('t', 'e'): 'te', ('Ġb', 'p'): 'Ġbp', ('Ġbp', 'e'): 'Ġbpe', ('Ġ', 'u'): 'Ġu', ('m', 'as'): 'mas', ('mas', 'i'): 'masi', ('Ġb', 'o'): 'Ġbo', ('Ġbo', 'l'): 'Ġbol', ('Ġbol', 'u'): 'Ġbolu', ('Ġbolu', 'm'): 'Ġbolum', ('Ġtoke', 'niz'): 'Ġtokeniz'}

50 Adet

Sonuç olarak başlangıçta 26 adet tokenimiz varken 24 adet yeni birleştirme kuralı öğrendik. Böylece 50 kelimeye duyarlı bir tokenizer elde ettik.

Bu alanda çalışmalar yapıyorsanız sizde fark etmişsinizdir ki Dil Modelleri (LM) veya Büyük Dil Modelleri (LLM) açısından Türkçe üzerine geliştirilmiş bir tokenizer maalesef yok. Bu büyük bir eksikliktir. Gerçek anlamda bağımsız doğal dil işleme modelleri geliştirmek açısından Türkçe ile büyük bir külliyat üzerinde eğitilmiş tokenizer modeline ihtiyacımız var.

Son olarak bir de tokenize metodu yazalım ve sonuçları gözlemleyelim.

def tokenize(text):
pre_tokenize_result = tokenizer._tokenizer.pre_tokenizer.pre_tokenize_str(text)
pre_tokenized_text = [word for word, offset in pre_tokenize_result]
splits = [[l for l in word] for word in pre_tokenized_text]
for pair, merge in merges.items():
for idx, split in enumerate(splits):
i = 0
while i < len(split) - 1:
if split[i] == pair[0] and split[i + 1] == pair[1]:
split = split[:i] + [merge] + split[i + 2 :]
else:
i += 1
splits[idx] = split

return sum(splits, [])
print(tokenize("Bu bir token degildir."))

"""
Tokenize Edilmiş Hal:
['Bu', 'Ġb', 'ir', 'Ġtoke', 'n', 'Ġ', 'd', 'e', 'g', 'il', 'dir', '.']
"""

Geliştirdiğimiz uygulama bilinmeyen bir karakter varsa hata verecektir. Çünkü bunlarla başa çıkmak için hiçbir şey yapmadık. GPT-2'nin bilinmeyen bir belirteci yoktur (bayt düzeyinde BPE kullanırken bilinmeyen bir karakter elde etmek imkansızdır), ancak bu burada gerçekleşebilir çünkü tüm olası baytları ilk sözcük dağarcığına dahil etmedik.

Düşünelim? Değerlendirelim?

Aslında kısa kısa bahsetmek istediğim algoritmalar, bunu da ekleyeyim, şunu da paylaşayım derken iyice uzadı başlı başına bir yazı oldu. Bu sebepten ötürü her bir algoritmayı ayrı ayrı işlemeye ve örneklendirmeye karar verdim. Biraz uzun bir yazı dizisi olacak fakat değeceğini düşünüyorum. Şimdi yazıyı özetlemeye çalışırsak:

BPE, alt kelime tokenizasyonu ile başa çıkmak için sağlam ve esnek bir yol sunar. Birçok modern doğal dil işleme mimarisinin önemli bir bileşenidir.

Birleştirme sayısına karar vermek için mekanizmalarını anlamak, doğal dil işleme alanında faliyet gösteren herkes için oldukça önemlidir.

BPE, derlemi en verimli şekilde temsil etmek için, her yinelemede sıklığına bakarak birleşmeye yönelik tüm potansiyel seçenekleri gözden geçirir. Yani mümkün olan en iyi çözümü optimize etmek iyi bir yaklaşım izler.

Genel olarak külliyat büyük olur fakat yine de bilinmeyen bir kelimenin çıkma ihtimali her zaman vardır. Hele ki günümüz Türkçe’nin kullanımını düşündüğümüz zaman. Tokenizasyon işlemine başlamadan önce bir normalizasyon işlemi yapmak faydalı olacaktır.

Bilinmeyen (yeni) kelimeleri simgeleştirmek ve ileride kullanmak üzere sözlüğümüze eklemek için bazı yöntemler geliştirmemiz zorunludur. GPT-2 ve RoBERTa belirteçleri (oldukça benzer) bununla baş etmek için akıllıca bir yönteme sahip: Kelimelerin Unicode karakterleriyle değil, baytlarla yazılmış olduğuna bakıyorlar. Bu şekilde temel sözcük dağarcığı küçük bir boyuta (256) sahip olur, ancak aklınıza gelebilecek her karakter yine de dahil edilecek ve sonunda bilinmeyen simgeye dönüştürülmeyecektir.

Umarım konuyu biraz olsa anlaşılır hale getirebilmişimdir. Uygulama kodlarını (bundan sonra ki süreç içinde geçerli) aşağıda ki github adresi üzerinden paylaşıyorum.

Buraya kadar okuma zahmeti gösterdiğiniz için teşekkür ederim. Bir sonra ki bölümde WPC (WordPiece) algoritmasında buluşmak üzere sizlere esenlikler diliyorum.

Görüşmek üzere..

GITHUB: https://github.com/tr-brain-com/BPE

--

--

Oğuzhan Yenen
Oğuzhan Yenen

Written by Oğuzhan Yenen

Adalet Bakanlığı Büyük Veri ve Yapay Zeka Takım Lideri