- Je telt paren per document en voert merges zonder overlap uit.
- Je leert een vaste rangorde van merges met een reproduceerbare tie-break.
In tekst komen kleine stukjes vaak terug. Je kunt “ab” telkens met twee byte-ID's bewaren, of een nieuw ID afspreken voor dat terugkerende paar. Byte pair encoding (BPE) leert zulke afspraken stap voor stap uit een trainingscorpus.
Zie een merge als twee aangrenzende kaartjes vervangen door één nieuw kaartje. De oorspronkelijke 256 bytekaartjes blijven beschikbaar. Het subword-paper werkt met tekens; deze onderwijsvariant begint met bytes en gebruikt geen speciale tokens of regex-voorsegmentatie.
1. Tel posities, zonder documenten aan elkaar te plakken
Een paar bestaat uit twee aangrenzende token-ID's in hetzelfde document. In aaaaa staan vier aa-posities: tussen positie0–1,1–2,2–3 en3–4. Die telling mag overlappen; zij zegt nog niet hoeveel vervangingen één merge kan uitvoeren.
def pair_counts(sequences):
counts = {}
for tokens in sequences:
for p in zip(tokens,tokens[1:]):
counts[p]=counts.get(p,0)+1
return counts
counts = pair_counts([[97]*5,[98,97]])
print(sorted(counts.items()))
# [((97,97),4),((98,97),1)]
De tweede documentlijst draagt hier één ba-paar bij. Zij begint geen nieuw paar met het laatste token uit het eerste document. Let ook op dezelfde paren in meerdere documenten: hun aantallen moeten worden opgeteld, niet door een latere dictionary overschreven.
Maak pair_counts(sequences), met tupleparen als keys en frequenties als waarden. Verander de aangeleverde lijsten niet. Deze kleine bouwsteen wordt straks na iedere merge opnieuw gebruikt, omdat ook de aangrenzende paren veranderen.
012. Vervang zonder dubbel gebruik van een token
Scan van links naar rechts. Bij een match verbruik je twee oude posities; anders neem je één oud token over. Zo wordt aaaaa bij aa→256 de reeks [256,256,97]: twee vervangingen, ondanks vier getelde posities.
def merge_pair(tokens,pair,new_id):
result = []; i = 0
while i < len(tokens):
match = (i+1 < len(tokens) and
(tokens[i],tokens[i+1])
== pair)
if match:
result.append(new_id)
i += 2
else:
result.append(tokens[i])
i += 1
return result
print(merge_pair([97]*5,(97,97),256))
# [256,256,97]
Noem de lengte vóór de stap L_voor en het aantal echte vervangingen m. Iedere vervanging maakt twee tokens tot één, dus de nieuwe lengte is precies m kleiner. De frequentietelling f is geen veilige vervanger voor m wanneer paren overlappen.
Maak merge_pair(tokens,pair,new_id) met een nieuwe resultaatlijst. Ook wanneer geen paar past, mag je geen alias teruggeven die later de oorspronkelijke invoer verandert. Gebruik de visualisatie met aaaaa en vergelijk getelde posities met echte vervangingen.
Deze korte ASCII-teksten houden ieder bytefragment leesbaar. De paren worden na iedere stap opnieuw geteld; documentgrenzen blijven staan.
Laatste paar: a + n. Getelde posities: 6; echte vervangingen: 6.
Volgende paar: a + an, 3 keer. Gelijke tellingen worden beslist met het kleinste numerieke ID-paar.
Een vaak geteld paar kan overlappen. In aaaaa zijn er vier aa-posities, maar één merge-stap vervangt er slechts twee.
3. Leer een vaste rangorde
Train op de twee documenten “abab” en “ba”. Ab en ba hebben ieder frequentie 2; onze tie-break kiest het numeriek kleinste paar, dus (97,98). ID256 staat daarna voor ab, en de reeksen worden [256,256] en [98,97].
Na opnieuw tellen staan (256,256) en (98,97) ieder één keer in het corpus. Nu wint (98,97), dat ID257 krijgt; daarna kan (256,256) ID258 worden. De totale lengtes zijn 6,4,3,2, telkens zonder de documentgrens te overschrijden.
seqs = [list(b'abab'),list(b'ba')]
merges = []; lengths = [6]
for rank in range(3):
counts = pair_counts(seqs)
key = lambda p:(-counts[p],p)
pair = min(counts,key=key)
new_id = 256+rank
seqs = [merge_pair(s,pair,new_id)
for s in seqs]
merges.append((*pair,new_id))
lengths.append(sum(map(len,seqs)))
print(merges)
# [(97,98,256),(98,97,257),
# (256,256,258)]
print(lengths) # [6,4,3,2]
Maak train_bpe(documents,budget=20), met merges en lengths. Kies het hoogste huidige aantal en bij gelijkstand het kleinste numerieke ID-paar; bewaar regels in trainingsvolgorde. Begin lengths vóór training en stop bij het budget of zodra geen paar meer bestaat.
4. Houd dit lesmodel klein en reproduceerbaar
Het budget is een maximum: twee losse documenten “a” en “b” hebben helemaal geen paar. Voor “é” telt de trainingsgrens bytes, niet codepunten. Onderstaande tekst heeft 2048 codepunten maar 4096 UTF-8-bytes.
print(pair_counts([[97],[98]])) # {}
text = 'é'*2048
nbytes = len(text.encode('utf-8'))
print(len(text),nbytes)
# 2048 4096
Accepteer maximaal 64 documenten van elk 4096 bytes en een exact integerbudget van 0 tot 128; geef anders ValueError. Bewaar de invoervolgorde en tekst. Meer merges voorspellen geen betere taalmodelkwaliteit: we leren hier alleen terugkerende bytepatronen.
03Wat neem je mee?
- Paartellingen mogen overlappen, vervangingen gebruiken ieder token hoogstens één keer.
- Tel na iedere merge opnieuw en respecteer documentgrenzen.
- Frequentie plus een vaste tie-break maakt de trainingsrang reproduceerbaar.
Hierna gebruik je de geleerde rangorde op nieuwe tekst, zonder opnieuw te trainen.