Werkplaats / De werkplaats
In jouw eigen tempo
← Taalmodellen
MODULE 01 · LES 01

Een teken, een byte en een token zijn verschillende dingen

Behoud Unicode en witruimte exact, inspecteer UTF-8 en maak normalisatie een expliciete keuze.

60 min3 checkpoints
DIT GA JE ONTDEKKEN
  • Je onderscheidt Unicode-codepunten, UTF-8-bytes en tokens.
  • Je decodeert zonder stil gegevensverlies en legt normalisatie als keuze vast.

Je typt “café 🙂” en verwacht na opslaan precies dezelfde tekst terug. Toch zijn zichtbare tekens, Unicode-codepunten, bytes en modeltokens verschillende eenheden. Een tokenizer maakt die omzettingen expliciet voordat een taalmodel met token-ID's kan rekenen.

Denk aan tekst die in een envelop met genummerde bouwsteentjes wordt verstuurd. UTF-8 bepaalt hoe codepunten als bytes worden vastgelegd; een byte-BPE kan later meerdere bytes bundelen tot één token. Een token is dus niet automatisch een letter of een woord.

1. Inspecteer codepunten en bytes afzonderlijk

Python-len op een str telt Unicode-codepunten. De letter é is één codepunt, maar gebruikt de UTF-8-bytes 195 en 169; 🙂 is één codepunt met vier bytes. Een zichtbaar letterteken kan bovendien meerdere codepunten bevatten, zoals e met een apart accent.

text = 'é🙂'
ids = list(text.encode('utf-8'))
print(len(text),len(ids))  # 2 6
print(ids)
# [195,169,240,159,153,130]

Een codepuntentelling is daarom geen telling van zichtbare lettertekens, grapheme clusters genoemd. Deze les implementeert geen grapheemteller. We onderzoeken de exacte gegevens waarop onze tokenizer gaat werken.

TekstCodepuntenUTF-8-bytes
é12
e + los accent23
🙂14

Maak utf8_ids(text) als een lijst exacte gehele bytewaarden, met de normale str.encode-validatie. Behoud alle hoofdletters, spaties en nieuwe regels. ID 0 tot en met 255 vertegenwoordigen in onze eerste woordenschat rechtstreeks de mogelijke bytes; grotere BPE-ID's komen pas na geleerde samenvoegingen.

VAN CODEPUNT NAAR BYTESInspecteer wat je tokenizer werkelijk krijgt

Een zichtbaar teken kan meerdere codepunten bevatten. Bekijk codepunten en hun UTF-8-bytes; dit is nog geen geleerde BPE-tokenisatie.

Codepunten6UTF-8-bytes10Na NFC: codepunten6
cU+006399
aU+006197
fU+0066102
éU+00E9195 · 169
␠U+002032
🙂U+1F642240 · 159 · 153 · 130

NFC verandert deze tekst niet. Onze BPE past geen normalisatie, trimmen of lowercasing toe.

01
CHECKPOINTUTF-8 codeert Unicode met een variabel aantal bytes

2. Herstel de hele byteketen strikt

Een los deel van een meerbytecode hoeft geen geldige UTF-8 te zijn. Byte 195 begint bijvoorbeeld de codering van é, maar mist zonder 169 zijn vervolg. Verzamel daarom eerst alle bytes en decodeer daarna één keer.

Noem UTF-8-coderen E en strikt teruglezen D. Voor geldige Unicode-tekst s hoort deze roundtrip exact dezelfde string op te leveren. Een fout melden bij beschadigde data is iets anders dan stil een andere tekst teruggeven.

D(E(s))=s
restored = bytes(ids).decode('utf-8')
print(restored == text)  # True
try:
    bytes([195]).decode('utf-8')
except UnicodeDecodeError:
    print('Onvolledige UTF-8')

Maak from_utf8(ids) met alleen exacte integers van 0 tot 255: bool en float horen hier niet bij; behoud de invoerlijst. Geef ValueError bij ongeldige waarden en laat ongeldige UTF-8 een UnicodeDecodeError geven; die is ook een ValueError. Gebruik geen errors='ignore' of 'replace', want daarmee verlies of verander je gegevens.

02
CHECKPOINTDecodeer pas nadat alle bytes bij elkaar staan

3. Laat normalisatie een zichtbare keuze zijn

De strings é en e plus los accent kunnen hetzelfde ogen, terwijl hun codepunten verschillen. NFC-normalisatie kiest een canonieke vorm; voor dit voorbeeld voegt zij e en het accent samen. Dat is nuttig bij sommige toepassingen, maar past niet ongemerkt bij een belofte van exact tekstherstel.

import unicodedata

original = 'e\u0301'
nfc = unicodedata.normalize('NFC',
                            original)
print(len(original),len(nfc))  # 2 1
print(list(original.encode('utf-8')))
# [101,204,129]
print(list(nfc.encode('utf-8')))
# [195,169]
angstrom = '\u212b'
same_len = unicodedata.normalize('NFC',
                              angstrom)
print(len(angstrom),len(same_len),
      angstrom != same_len)  # 1 1 True

Verwar “changed” niet met een lengtetoename of -afname. Een normalisatie kan ook andere codepunten met dezelfde lengte geven. Vergelijk daarom de strings zelf; lowercasing en strip zijn bovendien weer andere bewerkingen dan NFC.

Maak normalization_report(text) met original,nfc,changed,original_codepoints,nfc_codepoints. Bewaar original exact en zet changed op text!=nfc. Onze BPE verandert de tekst niet; dit rapport laat alleen zien wat een expliciete NFC-keuze zou doen.

03
CHECKPOINTGelijk lijken betekent niet dezelfde bytes

4. Controleer ook onopvallende witruimte

Een spatie aan het begin of een nieuwe regel kan in code en prompts betekenis hebben. De voorbeeldtekst hieronder heeft vier codepunten en zeven bytes: spatie, A, newline en een vierbyte-emoji. Na de roundtrip blijven alle vier posities staan.

sample = ' A\n🙂'
encoded = sample.encode('utf-8')
decoded = encoded.decode('utf-8')
print(len(sample),len(encoded))  # 4 7
print(decoded == sample)        # True
print(repr(decoded))
# ' A\n🙂'

Probeer in de visualisatie eerst é en daarna e met los accent. Voorspel vóór je klikt wat verandert aan codepunten, bytes en NFC. De inspecteur toont die drie afzonderlijk; hij heeft nog geen BPE-merges geleerd.

Wat neem je mee?

  • Codepunten, bytes en tokens tellen verschillende dingen.
  • Strikt decoderen bewaart de volledige byteketen of meldt een fout.
  • Normalisatie en witruimtebewerking vragen een expliciete productkeuze.

Hierna leer je terugkerende aangrenzende byteparen samenvoegen.

Verder lezen & bronnen

Inhoud gecontroleerd op 2026-10-28.
DE VOLGENDE STAPLeer herhaalde byteparen samenvoegen
JOUW EXPERIMENT

Probeer het zelf

Python
main.py

Python start zodra jij code uitvoert.

UITVOER

Je uitvoer verschijnt hier.
Alles rekent in je eigen browser.

Jouw checkpoints0 / 3
01
UTF-8 codeert Unicode met een variabel aantal bytes

Schrijf utf8_ids(text): een lijst gehele bytewaarden van de UTF-8-codering. Behoud spaties en nieuwe regels. Gebruik de normale str.encode-validatie.

Een hint

list(text.encode("utf-8")) laat iedere byte als integer zien.

02
Decodeer pas nadat alle bytes bij elkaar staan

Schrijf from_utf8(ids). Accepteer alleen exacte integers van 0 t/m 255 (geen bool of float); raise ValueError bij ongeldige waarden. Decodeer de volledige bytes-reeks strikt. Behoud de aangeleverde ID-lijst.

Een hint

Valideer met type(n) is int. Gebruik bytes(ids).decode("utf-8") na de controle.

03
Gelijk lijken betekent niet dezelfde bytes

Schrijf normalization_report(text) met original, nfc, changed, original_codepoints en nfc_codepoints. Verander original niet; gebruik unicodedata.normalize("NFC",text).

Een hint

changed is text!=nfc. Gebruik len op de twee strings voor codepunten, niet op de bytes.

Vastgelopen? Bekijk de voorbeeldoplossing
def utf8_ids(text):
    return list(text.encode("utf-8"))

def from_utf8(ids):
    if any(type(n) is not int or not 0<=n<=255 for n in ids):
        raise ValueError("Ongeldige bytewaarde")
    return bytes(ids).decode("utf-8")

import unicodedata

def normalization_report(text):
    nfc=unicodedata.normalize("NFC",text)
    return {"original":text,"nfc":nfc,"changed":text!=nfc,"original_codepoints":len(text),"nfc_codepoints":len(nfc)}

Voer de code uit en verklaar voor jezelf waarom elke stap werkt.