Tokenisation Overview
The simplest tokeniser:
- Splits text into chunks in some rule-based way, e.g. a word-based tokeniser would split on spaces and punctuation.
text = "Hello, world!"
tokens = text.split()
print(tokens)['Hello,', 'world!']Python's split() method only splits on spaces by default. We can use regular expressions to cover spaces and punctuation that also delimit words.
How does the re.findall(r'\w+|[^\w\s]') regex work?
It has two parts separated by | (which means "or"):
\w+matches one or more word characters (letters, digits, underscores).[^\w\s]matches anything that's not a word character and not whitespace. So it matches punctuation.
The ^ inside the brackets [^...] means "not", so [^\w\s] literally means "not a word character, not whitespace".
Both split() and findall() break text into pieces, but they work differently:
split(): You tell it what to remove (the separators), and it gives you what's left.findall(): You tell it what to keep (the pattern), and it gives you all matches.
import re
tokens = re.findall(r'\w+|[^\w\s]', text)
print(tokens)['Hello', ',', 'world', '!']- Maps those chunks to numeric values:
vocab = {token: idx for idx, token in enumerate(set(tokens))}
print(vocab){'Hello': 0, ',': 1, '!': 2, 'world': 3}token_ids = [vocab[token] for token in tokens]
print(token_ids)[0, 1, 3, 2]If we try to tokenise text with a word not in our vocabulary, we'd get a KeyError. So we need our first example of a special token, <UNK>.
vocab['<UNK>'] = len(vocab)Now we can safely encode any text by using vocab.get(token, vocab['<UNK>']) instead of vocab[token].
new_text = "Goodbye, world!"
new_tokens = re.findall(r'\w+|[^\w\s]', new_text)
new_ids = [vocab.get(token, vocab['<UNK>']) for token in new_tokens]
print(new_tokens)
print(new_ids)['Goodbye', ',', 'world', '!']
[5, 1, 3, 2]Vocabulary size
A word-level vocabulary has two problems. It needs an entry for every word form it will ever see ("run", "runs", "running"), and anything outside it collapses into <UNK>. A character-level vocabulary has neither problem, but sequences become very long and each token carries little meaning. Subword tokenisers sit in between: common words get a single token, rare words are built from pieces, and the vocabulary size is a knob we choose.
Subword tokenisers
- Byte Pair Encoding: start from bytes and repeatedly merge the most frequent adjacent pair. Built from scratch, including GPT-style pre-tokenisation and special tokens.
- SentencePiece: treats whitespace as an ordinary symbol so no language-specific pre-tokeniser is needed, and adds byte fallback, normalisation and the Unigram model.
Linked from
Revision log
- Split byte pair encoding and SentencePiece into their own notes, merging in the ai-notes notebooks
Tags: AI