ICS 582Lecture 02Full guide
Words and tokens
The whole lecture on one page, taught concept by concept. Work through the parts in order, mark each concept once you understand it, and open the slide chips when you want the original slides.
- Parts
- 11
- Concepts
- 46
- Slides
- 94
- Reading
- 276 min
Part 01: What counts as a word
Why a word is not a universal unit, the difference between tokens and types, how corpus vocabularies compare, and the preprocessing decisions every tokenizer must make.
4 concepts, slides 1-10
Why this part matters
Every count, probability, embedding table and cost estimate in NLP starts from a decision about what a unit is. Before a language model can predict the next token, before a retrieval system can index a document, and before Zipf or Heaps can be plotted, someone has to say where one unit ends and the next begins.
This part makes that decision visible. It starts from a sentence that has no single word count, gives you the two definitions every later formula depends on (token and type), reads one famous table of corpus sizes, and finishes with the checklist of preprocessing choices a tokenizer must make. Your research pipeline will fix a tokenization policy once, and every N, |V| and sequence length downstream inherits it. Exams ask for token and type counts under a stated policy and for the consequences of each preprocessing choice.
By the end you can
- State the token and type definitions and compute N and |V| for a given string under a stated policy for case and punctuation.
- Explain why the same sentence has 16 or 18 tokens and 14 or 16 types depending on the policy, and why speech transcripts may keep uh and main-.
- Read the SLP3 corpus table, compute the type to token ratio, and explain both the falling ratio and the Switchboard genre effect.
- Name the four preprocessing decisions and the four ambiguous boundaries, with one downstream consequence each, and state the Penn Treebank convention.
The whole lecture answers one question: what unit does a model operate on, and how do we get from raw bytes to that unit? Everything else is a stop on the way. The roadmap has eight stops, and it is worth holding the shape in mind because the lecture overview page mirrors it exactly.
- Words, with Zipf and Heaps. How to count units, and the two laws that govern how frequency and vocabulary behave as a corpus grows.
- Morphemes. The meaningful parts inside a word, which explain why walk, walks and walked are one lemma but three types.
- Corpora. Where the text comes from, and why genre, domain and time shift every count in this part.
- Unicode. How characters become bytes, so that a tokenizer can promise to represent any script without an unknown symbol.
- Tokenization with BPE. The algorithm that learns its own units from data instead of trusting spaces.
- Sentence segmentation. The period that ends a sentence versus the period inside Dr. or U.S.
- Regular expressions. The pattern language behind pre-tokenizers, cleaners and feature extractors.
- Minimum edit distance. A distance between strings that scores spelling correction and speech recognizers alike.
The first stop carries Zipf's law and Heaps' law. The second is the Morpheme. The third is the Corpus. The fourth turns a Code point into UTF-8 bytes. The fifth is Tokenization and byte-pair encoding. Then come Sentence segmentation, the Regular expression and, finally, Minimum edit distance. This part covers only the opening of the first stop, but it sets the vocabulary every later part reuses.
The seven learning goals on slide 3 map onto the same stops: know why a word is not a universal unit, distinguish types from tokens and explain why vocabulary keeps growing, know what morphemes are, explain Unicode and byte-level processing, describe BPE and tokenizer design choices, use regular expressions, and compute minimum edit distance with dynamic programming. The first two goals are this part.
Recall
Name the eight stops of the lecture roadmap in order.
Count the words in this sentence before reading on: They picnicked by the pool, then lay back on the grass and looked at the stars. Most people say sixteen. Some say eighteen. Both are right, and the reason they can both be right is the subject of this concept.
Worked example
Counting the picnic sentence three ways
Split on spaces
Sixteen pieces: They, picnicked, by, the, pool,, then, lay, back, on, the, grass, and, looked, at, the, stars. The comma and the period ride along inside the pieces they touch.Give punctuation its own place
Cut the comma off pool and the period off stars. Now there are eighteen units. Jurafsky and Martin note that large language models generally count punctuation as separate words, so eighteen is the modern default rather than a quirk.Collapse repeats
the occurs three times. Counting each distinct item once gives fourteen distinct words without punctuation, or sixteen if the comma and the period are counted as distinct items too.Lowercase
Folding They to they changes nothing here, because no other they appears. In another sentence it would merge two distinct items into one.Four answers, one sentence
The sentence never changed. The counting policy did.Policy Tokens N Types |V| Whitespace only 16 14 Punctuation split 18 16 The picnic sentence under two counting policies
In many writing systems spaces suggest boundaries, but a space is only a suggestion. The moment you want a number, you have to decide what to do with everything that a space does not settle, and each decision is tied to what the text is for. Splitting text into units is Tokenization, and the resulting units are tokens. Jurafsky and Martin put it plainly: how we define words depends on the task. The table below pairs each question from the slide with a task that answers yes and a task that answers no.
| Question | A task that says yes | A task that says no |
|---|---|---|
| Is punctuation its own token? | Language models and parsers, where a period or a question mark carries meaning | Bag-of-words retrieval, where punctuation only adds noise |
| Do we lowercase? | Information retrieval, where a query rarely matches the case of the document | Named entity recognition and part-of-speech tagging, where a capital is evidence |
| How do we treat numbers, URLs, emojis, hashtags? | Social media analysis keeps #NLP and emojis whole because they carry sentiment | A grammar checker may replace every number by one placeholder |
| Do we keep disfluencies? | Speech recognition, where uh predicts a restart and identifies the speaker | Parsing and translation, which want the fluent sentence underneath |
Speech: where the units fall apart
Written text at least offers spaces. A transcript of spontaneous speech offers something stranger. Consider the utterance I do uh main- mainly business data processing. There is a broken-off word, main-, which Jurafsky and Martin call a fragment, and a filler, uh, which they call a filled pause. Together these are a Disfluency. Should a tokenizer keep them?
For speech recognition the answer is yes, and for a reason that surprises people: fillers help predict the upcoming word, because they signal that the speaker is restarting the clause or idea, and they are a cue to speaker identification. Clark and Fox Tree went further and argued that uh and um are conventional English words: uh announces an expected minor delay, um a major one. A parser or a translation system, on the other hand, wants the fluent sentence underneath, so it drops them. The Switchboard portion of the Penn Treebank release is explicitly annotated for disfluencies, so this decision is built into a real corpus, not a thought experiment.
Languages with no spaces at all
The space is a convention of some scripts, not of language. Chinese, Japanese and Thai are written without spaces between words, so there is no orthographic word to count. Jurafsky and Martin give a Chinese sentence about Yao Ming reaching the finals and show that its units depend on which standard you adopt. Chinese words average only 1.5 to 1.9 characters, so the character is often the practical unit, and the question "how many words?" simply has no answer until a segmentation standard is chosen.
One Chinese sentence, three counts (SLP3 section 2.1)
- Chinese Treebank standard
- 3 words
- Peking University standard
- 5 words
- Characters
- 7 units
Recall
How many tokens and types does the picnic sentence have without punctuation, and with it?
Recall
Give two reasons Jurafsky and Martin give for keeping uh and um in speech recognition.
Quick check
Which of these is a reason a speech recognizer keeps the filler uh while a parser drops it?
Once a policy is fixed, two numbers describe any text. Take Hamlet's to be or not to be. Six units are written down in order. Only four different ones appear, because to and be each occur twice. Those two numbers have names, and the whole of corpus statistics is built on them.
A Token is one occurrence of a unit in running text, and the total count of tokens is written N. A Type is a distinct vocabulary item, and the number of types is the size of the Vocabulary, written |V|. So to be or not to be has N = 6 tokens and |V| = 4 types: to, be, or, not. The two are tied together by a bookkeeping identity: every token belongs to exactly one type, so the type counts add up to the token count.
Try it yourself. The counter below applies a tokenization policy to a sentence and recomputes both numbers live. Start with the picnic sentence, switch on punctuation splitting and watch N go from 16 to 18. Then load the Apple sentence and flip lowercase: N stays put while |V| drops, because Apple and apple have become one type. That asymmetry is the whole lesson of the next concept in miniature.
16 tokens, 14 types
- They×1
- picnicked×1
- by×1
- the×3
- pool,×1
- then×1
- lay×1
- back×1
- on×1
- grass×1
- and×1
- looked×1
- at×1
- stars.×1
Punctuation and clitic tokens are teal, the <NUM> placeholder is in the accent color. Watch both counts change as you flip each policy.
Five corpora, one table
Jurafsky and Martin collect rough counts for several English corpora, and the lecture reproduces their table. Two columns are on the slide. The two on the right are computed here: types per thousand tokens, and its reciprocal, tokens per type. Read those computed columns first, because they tell a story the raw counts hide.
| Corpus | Genre | Types |V| | Tokens N | Types per 1,000 tokens | Tokens per type |
|---|---|---|---|---|---|
| Shakespeare | Plays and poems | 31 thousand | 884 thousand | 35.1 | 28.5 |
| Brown corpus | 15 written genres, 1961 | 38 thousand | 1 million | 38.0 | 26.3 |
| Switchboard | Telephone conversation | 20 thousand | 2.4 million | 8.3 | 120 |
| COCA | 8 balanced genres | 2 million | 440 million | 4.5 | 220 |
| Google n-grams | Web text | 13 million | 1 trillion | 0.013 | 76,923 |
Down the table, N grows by six orders of magnitude while |V| grows by less than three. The ratio of types to tokens falls from about 35 per thousand for Shakespeare to about 0.013 per thousand for the Google corpus. This is the first preview of Heaps' law: vocabulary grows without bound but sublinearly, |V| = k N^β with β between 0 and 1, and Jurafsky and Martin report values from 0.44 to 0.56 or higher, so vocabulary goes up a little faster than the square root of the text length. Part 02 fits that law properly; here it is enough to see the bend.
Now read across rows instead of down. Brown has one million tokens and 38 thousand types. Switchboard has more than twice as many tokens and only 20 thousand types, fewer even than Shakespeare with a third of Switchboard's tokens. Size alone cannot explain that. Switchboard is about 2,400 telephone conversations among 543 speakers on about 70 everyday topics, and casual conversation reuses a small everyday vocabulary. Brown samples 500 texts across 15 written genres, and Shakespeare's plays reach for coinages and archaic forms. This is the point of slide 6: even within one language, genre and domain shift the vocabulary, so |V| depends on the Corpus, not only on N.
Why the vocabulary never stops growing
The falling ratio and the endless growth have the same cause. Function words like the and of are seen early and then only repeat. Content words keep arriving. Slide 8 lists where they come from, and each source is a reason your model will meet a word it has never seen.
- Proper names and new products: every person, place, company and gadget is a type, and new ones appear every day.
- Creative spellings and typos: cooool, gr8 and every misspelling is a fresh type under a wordform policy.
- Code-switching: speakers mix languages within one utterance, so an English corpus carries Spanish, Hindi or Arabic types as well.
- Morphology: walk, walks, walked and walking are four wordform types but one Lemma. Inflection multiplies surface forms, and morphologically rich languages multiply them far more.
- Compounding and multiword expressions: New York and ice cream behave as single lexical units but span a space. Sag and colleagues called multiword expressions a pain in the neck for NLP, and Jurafsky and Martin note that tokenizing them needs a dictionary and is tied up with named entity recognition.
The consequence is that out-of-vocabulary words are unavoidable under any word-level policy. However large the training corpus, tomorrow's text contains a name or a typo that is not in V. That is the problem subword tokenization solves later in this lecture, by building rare words out of pieces that are in the vocabulary.
Recall
Define token and type, then give N and |V| for 'to be or not to be'.
Recall
Why does Switchboard have fewer types than Shakespeare despite almost three times as many tokens?
Quick check
Counting punctuation as tokens and ignoring case, what are N and |V| for 'to be, or not to be.'?
Quick check
Switchboard has almost three times Shakespeare's tokens but fewer types. What explains this?
Quick check
The vocabulary size |V| of a model is best described as a property of which of these?
Every policy in this part has been described in words. Here is one written down as rules and run on a real sentence. The Penn Treebank convention is the standard for parsed corpora of English, and Jurafsky and Martin use this example to show its output.
Worked example
Penn Treebank tokenization of one sentence
Input
"The San Francisco-based restaurant," they said, "doesn't charge $10".Separate all punctuation
Each quotation mark, comma and the final period becomes its own token, so restaurant," turns into restaurant, , and ".Keep hyphenated words together
Francisco-based stays one token. The convention treats a hyphenated compound as a unit.Split clitics
doesn't becomes does and n't. Marcus and colleagues describe this in the original Treebank paper: contractions and the Anglo-Saxon genitive of nouns are automatically split into their component morphemes, so children's becomes children and 's, and won't becomes the odd pair wo and n't.Separate the currency sign
$10 becomes $ and 10.Output
" The San Francisco-based restaurant , " they said , " does n't charge $ 10 " . That is N = 18 tokens and |V| = 14 types, because the four quotation marks are one type and the two commas are one type.
The four decisions
The worked example silently made four decisions, and slide 9 names them. Each has options, each helps some task and each hurts another. The table is the answer to the exam question "list four preprocessing decisions and one downstream consequence of each".
| Decision | Options | Helps | Hurts |
|---|---|---|---|
| Case folding | Keep case, lowercase everything, or truecase | Retrieval recall: a query for apple matches Apple | Named entity recognition: Apple, Bush, the Fed and General Motors lose their capital evidence |
| Punctuation | Attach to the word, separate it, or drop it | Parsers and sentence segmenters, which read commas and periods as structure | Dropping loses question marks and quotes; naive splitting breaks m.p.h., Ph.D., $45.55 and 01/02/06 |
| Normalization | Accents, precomposed versus decomposed letters, curly versus straight quotes, hyphen versus dash | Matching and a compact vocabulary: one quote type, not four | Meaningful distinctions vanish, such as accented pairs in French or a minus sign versus a hyphen |
| Numbers | Keep raw, map to <NUM>, or segment into digit chunks | <NUM> shrinks |V|; digit chunks let a model generalize arithmetic | Raw numbers make every price a new type; <NUM> throws away magnitude |
Case folding is the most tempting shortcut. Manning, Raghavan and Schütze observe that for information retrieval, lowercasing everything often remains the most practical solution, and in the same breath list what it destroys: proper nouns distinguished only by case, such as General Motors, the Fed against fed, and Bush against bush. Their alternative is truecasing, restoring the likely original case with a classifier. Jurafsky and Martin add that for other tasks capitalization is a useful feature and is retained, so some teams keep a cased and an uncased model side by side.
Normalization is the quiet one. A Unicode normalization form decides whether the precomposed letter Ç at U+00C7 and the sequence C plus combining cedilla are the same type. Unicode Standard Annex 15 defines four forms, NFC, NFD, NFKC and NFKD, and the same logic applies to four kinds of quotation mark and three kinds of dash that a keyboard and a word processor scatter through any corpus. Skip it and your vocabulary silently holds several copies of the same word.
Numbers show the trade-off most sharply. Kept raw, every price and every year is a new type and |V| explodes. Mapped to a single <NUM> placeholder, the vocabulary shrinks but the model can no longer tell 12 from 12,000. Modern tokenizers pick a middle path: Jurafsky and Martin describe the GPT-4o tokenizer chunking numbers into groups of up to three digits, so magnitude survives as a sequence of small pieces.
The four ambiguous boundaries
Even after the four decisions are made, some characters refuse to settle. Slide 10 collects them. In each case the same character is a boundary in one context and part of a unit in another, and a tokenizer has to pick a default and accept the errors it causes.
| Boundary | Example | Treebank default | Alternative |
|---|---|---|---|
| Hyphens | state-of-the-art | Keep the hyphenated word together as one token | Split into state, of, the, art so each piece is a known type |
| Abbreviations | U.S., Dr., e.g. | Keep the internal periods; the trailing period is ambiguous with a sentence end | Strip periods entirely, at the cost of merging US the country with us the pronoun |
| Clitics | we'll, I'm, possessive 's | Split off the clitic: we 'll, I 'm, children 's, does n't, wo n't | Keep the contraction whole, so doesn't and does not are unrelated types |
| Social media | #NLP, @user, URLs | Splits # and @ off their word and breaks a URL at its colon, slashes and question mark | A social-media tokenizer keeps hashtags, handles and URLs whole with dedicated patterns |
A Clitic is the trickiest of the four because the apostrophe is three-way ambiguous. Jurafsky and Martin point out that it marks the genitive (the book's cover), quotation ('The other class', she said) and the contracted clitic (we'll, I'm). Hyphens run from co-education, where the pieces are meaningless alone, to Hewlett-Packard, where each is a name, and Manning and colleagues show there is no rule that suits both. Abbreviation periods will return as the central problem of Sentence segmentation later in the lecture. Social media adds hashtags, handles and URLs that should be kept whole, since #NLP or a web address split at its punctuation is worthless. A Treebank-style tokenizer will not do this by itself: it pads # and @ with spaces and breaks a URL at its colon, so a social-media pipeline adds hashtag, handle and URL patterns in front of it.
Recall
Under the Penn Treebank convention, tokenize 'doesn't' and 'children's'.
Quick check
Which preprocessing decision helps information retrieval but hurts named entity recognition?
Recap
If you remember nothing else
- A word is not a natural unit: the picnic sentence has 16 or 18 tokens and 14 or 16 types depending on the punctuation policy.
- Token = one occurrence (N); type = one distinct item (|V|); 'to be or not to be' is N = 6, |V| = 4.
- Speech adds fragments (main-) and filled pauses (uh, um); speech recognizers keep them, parsers drop them.
- Chinese, Japanese and Thai have no orthographic words; the same Chinese sentence can be 3, 5 or 7 units.
- Types grow with tokens but sublinearly (Heaps: |V| = k N^beta, beta about 0.5); the type to token ratio falls from 3.5% (Shakespeare) to 0.0013% (Google n-grams).
- Genre matters as much as size: Switchboard has 2.4 million tokens but only 20 thousand types.
- Vocabulary is hard because of names, new products, typos, code-switching, morphology and multiword expressions; OOV is unavoidable.
- Four preprocessing decisions: case folding, punctuation, normalization, numbers. Four ambiguous boundaries: hyphens, abbreviations, clitics, social media.
- Penn Treebank convention: separate punctuation, keep hyphenated words, split clitics and possessives (doesn't -> does n't, children's -> children 's).
- |V| is a property of the corpus plus the tokenization policy, never of the language alone.
Sources
- Speech and Language Processing, 3rd edition draft, chapter 2: Words and TokensBookJurafsky and Martin, StanfordDraft of August 19, 2026. Sections 2.1 (words, types, Fig. 2.1, Heaps), 2.5 (corpora), 2.8 (Penn Treebank tokenization)(opens in a new tab)
- Words and Tokens lecture slidesDocsJurafsky and Martin, StanfordSource of the 31 thousand Shakespeare figure reproduced on slide 7(opens in a new tab)
- Building a Large Annotated Corpus of English: The Penn TreebankPaperComputational Linguistics 19(2), 1993, Marcus, Santorini and MarcinkiewiczFootnote 8: contractions and the genitive split into component morphemes(opens in a new tab)
- nltk.tokenize.treebank: TreebankWordTokenizerDocsNLTK Projectdon't becomes do n't, punctuation separated(opens in a new tab)
- Introduction to Information Retrieval, section 2.2.1: TokenizationBookManning, Raghavan and Schütze, Cambridge University PressToken and type definitions, O'Neill, hyphens from co-education to Hewlett-Packard(opens in a new tab)
- Introduction to Information Retrieval, section 2.2.3: Capitalization and case-foldingBookManning, Raghavan and Schütze, Cambridge University PressGeneral Motors, the Fed, Bush; truecasing; lowercasing remains the most practical solution for IR(opens in a new tab)
- Using uh and um in spontaneous speakingPaperCognition 84(1), 2002, Clark and Fox Treeuh signals a minor delay, um a major one; both are conventional English words(opens in a new tab)
- Multiword Expressions: A Pain in the Neck for NLPPaperCICLing 2002, Sag, Baldwin, Bond, Copestake and FlickingerWords with spaces such as San Francisco as one analysis of multiword expressions(opens in a new tab)
- Switchboard-1 Release 2DocsLinguistic Data Consortium, LDC97S62About 2,400 two-sided telephone conversations, 543 speakers, about 70 topics(opens in a new tab)
- Treebank-3DocsLinguistic Data Consortium, LDC99T42The Switchboard portion is dysfluency-annotated(opens in a new tab)
- Web 1T 5-gram Version 1, readmeDocsLinguistic Data Consortium, LDC2006T131,024,908,267,229 tokens, 13,588,391 unigrams; unigram cutoff 200, n-gram cutoff 40(opens in a new tab)
- Natural Language Processing with Python, chapter 2: Accessing Text CorporaBookBird, Klein and Loper, NLTK ProjectBrown corpus: 1961, 500 sources, 15 genres, the first million-word electronic corpus(opens in a new tab)
- Unicode Standard Annex #15: Unicode Normalization FormsDocsUnicode ConsortiumNFC, NFD, NFKC, NFKD; canonical equivalence of U+00C7 and C plus combining cedilla(opens in a new tab)
- Corpus of Contemporary American English (COCA)DocsEnglish-Corpora.orgThe 440 million figure is the older size SLP3 uses; the current corpus is about 1 billion words, 1990 to 2019(opens in a new tab)
Part 02: Zipf and Heaps: the shape of vocabulary
Two empirical laws that explain why a few words are everywhere, most words are rare, and vocabulary never stops growing, and why that pushes NLP toward subword units.
4 concepts, slides 11-16
Why this part matters
Every tokenizer, every n-gram smoother and every embedding table you will build for Arabic NLP is a response to two curves. One says that a handful of words carry most of any text while almost everything else is rare. The other says that no matter how much text you read, new words keep arriving.
Part 01 defined types and tokens and showed that corpora vary wildly in size. This part asks what the relationship between the two looks like, and the answer turns out to be the same for English, Arabic and undotted Arabic Wikipedia, with only the constants changing. Those constants are numbers you can quote in your own research on Arabic text, and the two formulas behind them are the kind of thing an exam asks you to compute with. By the end you should be able to look at a vocabulary size and say whether it is surprising, and to explain in one sentence why subword units exist.
By the end you can
- State Zipf's law and compute f(R) from f(1) and α.
- Explain why a power law is a straight line on log-log axes, and diagnose the head and tail deviations on a real corpus.
- State Heaps' law, compute |V| for given k, β and N, and compare the English and Arabic Wikipedia fits.
- Argue from Heaps' law why OOV words are unavoidable and why about half of all types are hapax legomena.
- Connect sparsity and OOV to smoothing, backoff and subword tokenization.
Take a novel, count every word, and sort the counts from largest to smallest. Manning and Schütze did exactly this for Mark Twain's Tom Sawyer: 71,370 tokens and 8,018 distinct types. The most frequent type is "the" with 3,332 occurrences, then "and" with 2,972, then "a" with 1,775. At the other end sit thousands of words like "applausive" that appear exactly once. The hundred most common types alone cover 50.9% of every token in the book.
Give each type a rank R, with R = 1 for the most frequent word. Now multiply frequency by rank. For most of the list the product hovers around a constant, roughly 8,000 to 10,000, a relationship Estoup noticed in 1916 and George Kingsley Zipf made famous in the 1930s and 1940s, so it carries Zipf's name. Zipf's law says that frequency falls off as a power of rank.
| Word | Frequency f | Rank R | f × R |
|---|---|---|---|
| the | 3,332 | 1 | 3,332 |
| and | 2,972 | 2 | 5,944 |
| a | 1,775 | 3 | 5,325 |
| he | 877 | 10 | 8,770 |
| turned | 51 | 200 | 10,200 |
| applausive | 1 | 8,000 | 8,000 |
Because the law is a proportionality, the useful working form anchors it to the top word. If you know how often the most frequent word occurs, everything else follows from the rank alone.
With α = 1 the arithmetic is nothing more than division. The word at rank 10 occurs about a tenth as often as the top word, the word at rank 1,000 about a thousandth as often. Slide 12 will show that natural language has α slightly below one, so the fall is a little gentler and the rare words a little less rare than the pure 1 / R rule predicts.
Worked example
From f(1) to f(R)
Fix the anchor
Suppose the top word occurs f(1) = 1,000,000 times in a large Corpus.Apply α = 1
f(10) = 1,000,000 / 10 = 100,000 and f(1000) = 1,000,000 / 1000 = 1,000.Apply the slide's α = 0.93
1000^0.93 = 10^(3 × 0.93) = 10^2.79 ≈ 617, so f(1000) ≈ 1,000,000 / 617 ≈ 1,622. At rank 10, 10^0.93 ≈ 8.51 gives f(10) ≈ 117,490.Three exponents side by side
α f(10) f(100) f(1000) f(100,000) 1.00 100,000 10,000 1,000 10 0.93 117,490 13,804 1,622 22 1.10 79,433 6,310 501 3
Read the table across a row and you see the fall; read it down a column and you see what α does. A smaller exponent leaves more mass in the tail, which for a language model means more probability spread across rare words. A larger exponent concentrates the text in fewer types. The exponent is a property of the corpus, so it differs slightly between languages, genres and preprocessing choices such as case folding.
Recall
The most frequent word in a corpus occurs 2,000,000 times. Under Zipf with α = 1, roughly how often does the rank 1,000 word occur? And with α = 0.9?
Quick check
A corpus follows Zipf's law with α = 1 and its top word occurs 500,000 times. About how often does the word at rank 50 occur?
Slide 12 plots rank against frequency for three corpora, English Wikipedia, Arabic Wikipedia and an undotted Arabic Wikipedia in which the dots that distinguish letters such as ب, ت and ث have been removed. Both axes are logarithmic, labelled from 10^0 to 10^6 in rank (the Arabic curve runs on to about 2 × 10^6) and to 10^7 in frequency, and two dashed straight lines, each pinned to the top word of its corpus, are drawn with α = 0.9252 for Arabic and α = 0.9290 for English. The first thing to understand is why anyone would expect a straight line at all.
Write Zipf's law as an equation with a constant, f(R) = C · R^−α, and take the logarithm of both sides. A product becomes a sum and a power becomes a multiplier, which is the whole trick.
On axes that show log R and log f, this is the equation of a straight line with slope −α and intercept log C, which is log f(1) since log 1 = 0. The Stanford IR book states the special case directly: log cf_i = log c + k log i with k = −1. So the two fitted slopes on the slide, −0.9252 and −0.9290, are the exponents of the two Zipf fits, and the fact that they are nearly parallel says Arabic and English share the same kind of head-and-tail shape.
Two places where the real curve leaves the line
Look at the slide again and the solid curves do not follow the dashed lines everywhere. They deviate in two characteristic ways, and both are diagnostic rather than noise. At the head, the curve is flatter than the power law: the top few words are common, but the drop from rank 1 to rank 10 is smaller than R^−α demands. This is the same shortfall the Tom Sawyer table showed: f × R was 3,332 for "the" against 8,000 to 10,000 further down, and it is why Mandelbrot added the shift term (R + ρ).
How that flat head looks on a plot depends on where the line is anchored. On slide 12 both fits pass through f(1). The blue English curve bulges slightly above the purple line only for ranks 2 to about 15, then runs just under it. The Arabic curves behave differently: they dip below the red line for ranks 3 to about 50 and cross back above it. At Wikipedia scale the head deviation is small, and the head is not uniformly flatter. The flat head is clearest in small texts such as Tom Sawyer and the explorer's sample; on slide 12 the dominant departure is the tail.
At the tail, beyond about rank 10^4, the curves steepen sharply below both lines and then turn into a staircase with horizontal steps at frequencies 3, 2 and 1, stretching out to about rank 2 × 10^6 for Arabic. The reason is that a frequency is a count, and counts are integers. There is no word that occurs 1.5 times. If 400,000 types each occur exactly once, they occupy 400,000 consecutive ranks at height f = 1, which draws as one long flat step. The step at f = 2 is shorter because fewer types occur exactly twice, and so on upward until the counts are large enough that adjacent ranks rarely share a value and the curve looks smooth. The bottom step is the longest because types seen once are the largest single group, the hapax legomena that concept four returns to.
What to read off a log-log rank-frequency plot
- Slope of the fitted line
- −α, the Zipf exponent
- Intercept at log R = 0
- log f(1), the frequency of the top word
- Rightmost rank on the curve
- |V|, the number of types in the corpus
- Length of the bottom step
- The number of hapax legomena
- Bend at the head relative to the line
- How much flatter the top few words are than the law; above a line pinned to f(1), below a line fitted through the bulk
One more thing the slide shows without saying it. In the tail, the Arabic curves sit above the English one: at any low count, Arabic has more types occurring at least that often. Templatic morphology and attached clitics mean that a single Arabic lexeme surfaces as many distinct spelled forms, each of which is a separate type for a whitespace tokenizer. That is a preview of the next concept, where the same fact shows up as a larger k in Heaps' law.
Rank against frequency
The teal curve is the real count of every type in Pride and Prejudice, chapters 1 to 3 (3,438 tokens, 845 types). The dashed line is f(R) = C / R^α for the α you choose. With the best fit, C is the least-squares intercept over the top 200 ranks, currently 3.5× the top count f(1) = 125, so the head falls short of the line and the tail steps run under it.
| rank | predicted | observed |
|---|---|---|
| 10 | 51.8 | 48 |
| 100 | 6.1 | 6 |
| 500 | 1.4 | 1 |
The tail is a staircase because counts are integers: 475 types share the step at f = 1, 129 share f = 2, and 63 share f = 3.
Recall
Why is a power law a straight line on log-log axes, and what does the slope tell you?
Recall
Name the two visible departures from a Zipf line on a real corpus and give the cause of each.
Quick check
On a log-log rank-frequency plot, what does the slope of the fitted straight line equal?
Zipf describes a Corpus frozen at one size. Now let the corpus grow. Read the first thousand words of Pride and Prejudice and you meet 361 distinct types. After ten thousand words the count is 1,698; after fifty thousand, 4,304; at the end of the novel, 123,627 tokens have produced 6,298 types. Each new stretch of text adds fewer new words than the last, but it never adds none. The same pattern holds across corpora of every size.
| Corpus | Instances N | Types |V| |
|---|---|---|
| Shakespeare | 884 thousand | 29 thousand |
| Brown corpus | 1 million | 38 thousand |
| Switchboard telephone speech | 2.4 million | 20 thousand |
| COCA | 440 million | 2 million |
| Google n-grams | 1 trillion | 13 million |
The rule behind these numbers is Heaps' law, also called Herdan's law after the linguist who described it in 1960, nearly two decades before Heaps (1978). The Vocabulary size is a power of the token count, with an exponent strictly between zero and one.
The two halves of the constraint on β say two different things. Because β < 1, growth is sublinear: doubling the corpus does not double the vocabulary. Because β > 0, growth is unbounded: the derivative d|V| / dN = k β N^(β − 1) is positive for every N, so the curve keeps rising however far you extend it. SLP3 phrases the second half bluntly: no matter how big our vocabulary, we will never have a vocabulary that captures all the possible words that might occur.
The slide's three fits, and what the constants mean
Slide 14 fits the law to the same three corpora as before and reports k and β for each. The constants are worth reading as a pair. k is a multiplicative scale (the predicted |V| at N = 1) that shifts the whole curve up, and β sets the slope on log-log axes: the ratio between two corpus sizes, (N2 / N1)^β, depends on β alone. Plugging in three corpus sizes makes the comparison concrete.
| Corpus | k | β | |V| at 10⁴ | |V| at 10⁶ | |V| at 10⁸ | New types per 1,000 tokens |
|---|---|---|---|---|---|---|
| English Wikipedia | 14.947 | 0.583 | 3,210 | 47,049 | 689,531 | 187 at N = 10⁴ |
| Arabic Wikipedia | 73.321 | 0.533 | 9,936 | 115,672 | 1,346,568 | 49 at N = 2.5 × 10⁵ |
| Undotted Arabic Wikipedia | 90.491 | 0.506 | 9,563 | 98,312 | 1,010,661 | not reported |
Worked example
English fit at one million tokens
Exponent
N^β = (10^6)^0.583 = 10^(6 × 0.583) = 10^3.498.Evaluate the power
10^3.498 ≈ 3,147.8.Multiply by k
|V| ≈ 14.947 × 3,147.8 ≈ 47,049.Result
About 47 thousand types. For comparison, the Brown corpus has 38 thousand types at one million tokens (SLP3 Figure 2.1), and the IR book's Reuters fit, 44 × 1,000,020^0.49, gives 38,323 against an actual 38,365. Same ballpark, different corpus.
Vocabulary against corpus size
| N | |V| | new types per 1,000 |
|---|---|---|
| 10⁴ | 3,210 | 187.2 |
| 10⁶ | 47,049 | 27.4 |
| 10⁸ | 689,531 | 4 |
Now compare languages. At a million tokens Arabic Wikipedia reaches 115,672 types, about two and a half times the English figure, and undotted Arabic reaches 98,312. Arabic gets there because of its k, 73 against 15: templatic morphology and attached clitics such as wa-, bi- and -hu multiply the surface forms of every lexeme, so the vocabulary is large from the very first pages. Its β is lower, though, so the curve bends sooner: a hundredfold larger corpus multiplies the English vocabulary by 100^0.583 ≈ 14.7 but the Arabic vocabulary by only 100^0.533 ≈ 11.6. Removing the dots collapses distinct letters into one shape, and the fit agrees that this shrinks the vocabulary: undotted Arabic is below dotted Arabic at every size in the table. Its larger k (90 against 73) is offset by its smaller β, and the two curves cross below about 2,400 tokens, so k alone never tells you which vocabulary is bigger; evaluate k N^β at the N you care about. Parts 03 and 04 explain the morphology.
Typical values, and how the slide compares
The constants reported across five reference points are easiest to read side by side.
| Source | k | β |
|---|---|---|
| SLP3, current draft | not given | 0.44 to 0.56 |
| IR book, typical | 30 to 100 | ≈ 0.5 |
| Reuters-RCV1 (IR book) | 44 | 0.49 |
| Earlier SLP3 drafts, larger corpora | not given | 0.67 to 0.75 |
| Slide 14 fits | 14.9 to 90.5 | 0.506 to 0.583 |
SLP3 summarizes the law as vocabulary growing a little faster than the square root of the corpus length in words, with β depending on corpus size and genre. The Arabic exponents, 0.533 and 0.506, fall inside SLP3's 0.44 to 0.56 band; the English 0.583 is just above it, which SLP3's "or even higher" allows. The English k of 14.9 is on the low side, which is what you get when a fit trades a smaller k for a larger β; the two constants are not independent estimates.
Recall
Using the English Wikipedia fit (k = 14.947, β = 0.583), how many types would you expect at 10^4 tokens and at 10^6 tokens?
Quick check
With k = 73.321 and β = 0.533, roughly how many types does a one-million-token Arabic corpus have under the slide's Heaps fit?
Quick check
Which statement about vocabulary growth under Heaps' law is correct?
Put the two laws together and a design problem falls out. In Tom Sawyer, 3,993 of the 8,018 types occur exactly once, which is 49.8%. In the three chapters of Pride and Prejudice inside the explorer, 475 of 845 types occur once, or 56%. A type seen exactly once is a Hapax legomenon, from the Greek for "read only once", and in corpus after corpus these are about half of everything a model must store.
That one half is not a coincidence; it falls out of Zipf's law. With α = 1, the type at rank R has count C / R, so the number of types with count at least m is the rank at which the count drops to m, namely C / m. The types with count exactly m are then C / m − C / (m + 1) = C / (m (m + 1)), and dividing by the total C gives a share of 1 / (m (m + 1)): one half seen once, one sixth seen twice, one twelfth seen three times.
| Count | Zipf prediction | Tom Sawyer | Sample corpus |
|---|---|---|---|
| Exactly once | 50% | 49.8% | 56% |
| Exactly twice | 16.7% | 16.1% | 15% |
Baayen (2001) develops this connection between the frequency spectrum and vocabulary growth in full, and it is why the bottom step of the staircase on slide 12 is the longest.
Slides 15 and 16 list the consequences, and they form a chain rather than a list. Start with size. Heaps' law says a word-level Vocabulary on a large corpus runs to hundreds of thousands or millions of types, and every one of them needs a row in an embedding matrix and a column in the output softmax. Memory grows with |V| × d, where d is the embedding width. Then comes sparsity, which is Zipf's contribution: since half of those types belong to hapax legomena, half of the parameters in that matrix have been trained on a single example. Whatever the model learned about "applausive" it learned from one sentence.
Next comes the guarantee. Because Heaps' law has a positive derivative at every N, any finite training corpus stops somewhere on a curve that is still rising. New text therefore always contains types that were never seen, and those types are out-of-vocabulary. This is not a defect of a small corpus that a bigger one would cure; a bigger corpus only pushes the OOV rate down along the same curve, never to zero. Finally, evaluation inherits the problem. Earlier SLP3 drafts warned that mapping unseen words to an <UNK> special token lets a model buy a lower perplexity simply by choosing a smaller vocabulary, since more of the test set collapses onto one easy symbol. The current chapter 3 sidesteps the issue by making the vocabulary subword tokens, so that, in its words, the test set can never contain unseen tokens.
- Huge word-level vocabularies: memory in the embedding and output layers scales with |V|.
- Sparsity: about half of all types are hapax legomena, so about half of the rows have one training example.
- Guaranteed OOV: Heaps' law keeps rising, so new text always brings unseen types.
- Evaluation sensitivity: mapping unseen words to <UNK> changes what a perplexity number means.
Two answers to the same problem
The two families of language model respond to the chain at different points. N-gram models keep the word as the unit and repair the probability estimate: smoothing moves a little probability mass from seen events to unseen ones, and backoff falls back to a shorter context when the longer one was never observed. Lecture 3 and SLP3 chapter 3 develop both. Neural models change the unit instead. With Subword tokenization, learned by byte-pair encoding as Sennrich, Haddow and Birch showed for machine translation in 2016, a word never seen in training is spelled out of pieces that were, and with byte-level fallback there is nothing left that cannot be represented. The vocabulary becomes a design choice of tens of thousands of units rather than a fact about the corpus, which is exactly the trade part 6 examines.
| N-gram, word units | Neural, subword units | |
|---|---|---|
| Unit of the vocabulary | Whole words | Subword pieces, or bytes as the floor |
| What happens to an unseen word | Mapped to <UNK> or given smoothed mass | Composed from known pieces, so never unknown |
| Where the fix lives | In the probability estimate: smoothing, backoff, interpolation | In the tokenizer: BPE, WordPiece, byte fallback |
| Cost | Probability mass spent on events never seen | Longer token sequences, less interpretable units |
| Covered in | Lecture 3 and SLP3 chapter 3 | Part 6 of this lecture |
Recall
In one sentence, why does Heaps' law guarantee OOV words?
Recall
Define hapax legomenon and give the typical share of types it accounts for.
Quick check
A model trained on 10 million tokens meets a word absent from its training data. Which mechanism lets a modern neural model represent it anyway?
Recap
If you remember nothing else
- Rank types by count. Zipf: f(R) is proportional to 1 / R^α with α near 1 (0.9252 for Arabic and 0.9290 for English Wikipedia on slide 12).
- log f = log C − α log R is a line with slope −α. Real corpora flatten at the head (above a line pinned to f(1), below a line fitted through the bulk) and form integer steps at the tail.
- Heaps: |V| = k N^β with 0 < β < 1, sublinear but unbounded. The English fit 14.947 N^0.583 gives 47,049 types at one million tokens; Arabic 115,672; undotted Arabic 98,312.
- The larger k for Arabic reflects templatic morphology and clitics. Typical β is 0.44 to 0.56 in the current SLP3 draft, and b ≈ 0.5 with 30 ≤ k ≤ 100 in the IR book.
- Slide 14 plots the fitted formula out to 10^19 tokens. That is an extrapolation, not data.
- About half of all types are hapax legomena (49.8% in Tom Sawyer, 56% in the sample corpus). Word vocabularies are huge and sparse, and OOV words are guaranteed.
- N-gram models respond with smoothing and backoff. Neural models respond with subwords (BPE, part 6). Evaluation depends on how unseen items are treated.
- Heaps' law is a consequence of Zipf's law (Lü, Zhang and Zhou 2010), not an independent law.
Sources
- Speech and Language Processing, 3rd edition draft, chapter 2: Words and TokensBookJurafsky and Martin, Stanford UniversityCorpus size table (Figure 2.1), Heaps' law (Equation 2.5), β range 0.44 to 0.56, two-regime growth (Figure 2.2)(opens in a new tab)
- Speech and Language Processing, 3rd edition draft, chapter 3: N-gram Language ModelsBookJurafsky and Martin, Stanford UniversitySmoothing, interpolation and backoff; the current draft notes that subword vocabularies mean the test set can never contain unseen tokens (the <UNK> discussion is from earlier drafts)(opens in a new tab)
- Foundations of Statistical Natural Language Processing, chapter 1 excerptBookManning and Schütze, MIT PressTom Sawyer counts, 49.8% hapax legomena, Table 1.3 f × R products, Zipf and Mandelbrot(opens in a new tab)
- Introduction to Information Retrieval: Heaps' law, estimating the number of termsBookManning, Raghavan and Schütze, Cambridge University Press30 ≤ k ≤ 100 and b ≈ 0.5; Reuters-RCV1 fit k = 44, b = 0.49(opens in a new tab)
- Introduction to Information Retrieval: Zipf's law, modeling the distribution of termsBookManning, Raghavan and Schütze, Cambridge University Presslog cf_i = log c + k log i with k = −1; the fit is 'not particularly good, but good enough'(opens in a new tab)
- Human Behavior and the Principle of Least EffortBookZipf, Addison-Wesley, 1949Zipf's own account of the rank-frequency law and its proposed cause(opens in a new tab)
- Information Retrieval: Computational and Theoretical AspectsBookHeaps, Academic Press, 1978The original statement of the vocabulary growth law(opens in a new tab)
- Word Frequency DistributionsBookBaayen, Kluwer, 2001LNRE models and the derivation of vocabulary growth from frequency distributions(opens in a new tab)
- Zipf's Law Leads to Heaps' Law: Analyzing Their Relation in Finite-Size SystemsPaperLü, Zhang and Zhou, PLoS ONE, 2010Heaps as a derivative of Zipf; asymptotic β = 1 / α and the finite-size caveat(opens in a new tab)
- Zipf's word frequency law in natural language: A critical review and future directionsPaperPiantadosi, Psychonomic Bulletin and Review, 2014Zipf-Mandelbrot form and competing explanations(opens in a new tab)
- Zipf's, Heaps' and Taylor's Laws are Determined by the Expansion into the Adjacent PossiblePaperTria, Loreto and Servedio, Entropy, 2018Two-regime Heaps' law figure cited by SLP3(opens in a new tab)
- Neural Machine Translation of Rare Words with Subword UnitsPaperSennrich, Haddow and Birch, ACL 2016Byte-pair encoding as the subword answer to rare and unseen words(opens in a new tab)
- Wikipedia: Size of WikipediaArticleWikipediaEnglish Wikipedia at about 5.27 billion words, used to scale the slide 14 axes(opens in a new tab)
- Pride and Prejudice, plain text (eBook 1342)DocsProject GutenbergPublic domain text behind the explorer's built-in corpus and the growth figures(opens in a new tab)
Part 03: Morphemes: the parts of words
The smallest meaning-bearing units, roots and affixes, inflection versus derivation, and how English concatenation differs from Arabic root-and-pattern morphology.
3 concepts, slides 17-21
Why this part matters
Everything a tokenizer, a lemmatizer or a morphological analyzer does rests on the vocabulary in this part. The words you meet in Arabic and English text are not atoms. They are built from smaller meaning-bearing pieces, and the way those pieces combine differs sharply between the two languages you work with.
This part gives you three tools. First, the morpheme itself, with its split into roots and affixes, free and bound. Second, a function test: does a piece keep the dictionary word or make a new one? Third, a form test: are the pieces strung in a line, or is the change inside the word? The Arabic root-and-pattern system is the textbook example of what a contiguous subword tokenizer cannot capture, which is why it matters for your research and not only for the exam. Exam questions ask for segmentation with labels, the inflection versus derivation judgement with reasons, and a morpheme-by-morpheme gloss.
By the end you can
- Segment a word into morphemes and label each one as root or affix, free or bound.
- Tell a root from a stem, and name the affix positions (prefix, suffix, infix, circumfix) plus compounding.
- Decide whether a process is inflection or derivation using lexeme identity, part of speech and productivity, including conversion.
- Tell concatenative from non-concatenative form, with English, Turkish and Arabic examples.
- Read and write a Leipzig-style morpheme gloss such as house-PL-1SG.POSS-ABL.
The word cats has four letters but only two units of meaning: cat, the animal, and -s, the signal that there is more than one. The word unhappiness has three: un- for negation, happy for the core idea, and -ness to turn the adjective into a noun. Each of those units is a Morpheme: the smallest unit that carries meaning or a grammatical function. Jurafsky and Martin put it in one line: a morpheme is a minimal meaning-bearing unit in a language, so fox is one morpheme while cats is two (SLP3, section 2.2).
Try the segmentation before the theory. In SLP3 the sentence Doc worked carefully washing the glasses is cut as Doc work-ed care-ful-ly wash-ing the glass-es. Every hyphen marks a boundary where one meaning stops and another begins. Doc and the are single morphemes; carefully is three. Notice what the cuts are not: they are not syllables, and they are not places where the spelling changes. They are places where meaning changes.
The three morphemes of unhappiness
- un-
- Prefix, bound. Adds negation and keeps the word an adjective.
- happy
- Root, free. Carries the core meaning. Spelled happi before -ness, which is orthography, not a new morpheme.
- -ness
- Suffix, bound. Turns an adjective into a noun.
Free and bound
The first sorting of morphemes is by independence. A free morpheme can occur by itself as a word: cat, walk, happy. A bound morpheme never occurs alone and is always attached to some other morpheme, the way -s in dogs is (SIL Glossary of Linguistic Terms). Bound morphemes are the affixes: every un-, -ed, -ing and -ness you will ever segment. The test is simple. Could you say it on its own and be understood? happy, yes. ness, no.
| Kind | Definition | Examples | Stands alone? |
|---|---|---|---|
| Free morpheme | Can occur by itself as a word | cat, walk, happy, snow, man | Yes |
| Bound morpheme | Never occurs alone, always attached to another morpheme | un-, re-, -s, -ed, -ing, -ness | No |
Roots, stems and affixes
The second sorting is by job. SLP3 divides the morphemes of a word into roots, the central morpheme supplying the main meaning, and affixes, which add meanings of various kinds: in worked, work is the root and -ed the affix; in glasses, glass is the root and -es the affix (SLP3, section 2.2). The root is the core lexical material, and in English it is usually a free morpheme.
The lecture adds a third word that students often blur into root: the Stem. A stem is the inflectable base, the thing the grammatical endings attach to. In a simple word like walk the root and the stem are the same string, which is why one slide writes them as root/stem. They come apart once a derived word is inflected. In unhappinesses the root is happy, the core of the meaning, but the plural -es attaches to the whole stem unhappiness. The root is what the word is about; the stem is what the grammar sees.
Affixes are then named by where they sit. A prefix goes before the stem (un-). A suffix goes after it (-ed, -ing). An infix is inserted inside the stem, and a circumfix wraps around it in two parts at once. Both are rare or absent in English. The standard textbook circumfix is the German past participle, as in ge-spiel-t, played, where ge- and -t together express one meaning (Haspelmath and Sims, chapter 2), and the usual English illustration of infixation is the jokey abso-bloody-lutely. Keep these two as illustrations; the slide only says they exist and are rare. Compounding is different again: it joins two roots without any affix, so snowman is snow plus man and laptop is lap plus top, two free morphemes in one word.
| Process | Position | Example | In English |
|---|---|---|---|
| Prefix | Before the stem | un-happy, re-new | Common |
| Suffix | After the stem | walk-ed, walk-ing, cat-s | Very common |
| Infix | Inside the stem | abso-bloody-lutely (expletive infixation) | Rare, marginal |
| Circumfix | Around the stem, two parts at once | German ge-spiel-t, played | Rare; en-light-en is sometimes analysed as one |
| Compounding | Two roots joined, no affix at all | snow-man, lap-top | Common |
Worked example
Segment carefully and label every piece
Find the root
care is a free morpheme that carries the meaning. It can be a word on its own.Peel the first affix
-ful is a bound suffix that turns the noun care into the adjective careful.Peel the second affix
-ly is a bound suffix that turns the adjective into the adverb carefully.Result
care-ful-ly: root (free) + suffix (bound) + suffix (bound). Three morphemes, each with its own job, exactly as SLP3 segments it.
Click the gap between two letters to place a morpheme boundary. Click again to remove it. Then check your cuts against the answer key.
After checking, a solid accent bar is a correct cut, a hollow teal bar is a boundary you missed, and a filled teal bar is a cut that falls inside a morpheme.
Arabic roots: a skeleton, not a string
Everything above assumed the root is a contiguous piece of the word. Arabic breaks that assumption, and it is the reason this part matters so much for your work. The Arabic root for writing is K-T-B, the three consonants ك ت ب. Those three letters never appear next to each other as a word. Instead they are slots, and a pattern of vowels and sometimes extra consonants is threaded between them. The same root with three patterns gives kataba كَتَبَ he wrote, kitāb كِتَاب book, and kātib كَاتِب writer. Look at the Arabic script, where the radicals are highlighted: ك, ت, ب keep their order in every form, and only the short-vowel marks and the long vowel ا between them change.
This is Templatic morphology, also called root-and-pattern morphology. The root is an abstract consonantal skeleton, and actual words are formed by adding the vowels and non-root consonants that go with a particular morphological category around the root consonants (Wikipedia, Semitic root). Ryding's reference grammar treats the root and pattern as the two fundamental building blocks of an Arabic word: the root supplies the semantic field, the pattern supplies the grammatical and derivational shape (Ryding, chapter 3). You will see the mechanism itself in the third concept of this part; for now the point is that K-T-B is a root in exactly the sense defined above, core lexical material, but it is discontinuous.
| Form | Transliteration | Meaning | Pattern |
|---|---|---|---|
| كَتَبَ | kataba | he wrote | CaCaCa |
| كِتَاب | kitāb | book | CiCāC |
| كَاتِب | kātib | writer | CāCiC |
Why NLP cares
SLP3 names three candidates for what a Token should be: words, morphemes and characters (SLP3, section 2.4). Earlier it observes that because morphemes are hard to define consistently across languages, it is very difficult to use morphemes as a standard for Tokenization cross-lingually (SLP3, section 2.2). That difficulty is the whole reason subword tokenizers exist, and you will meet them later in this lecture. Arabic root-and-pattern words are the hard case: there is no contiguous substring that means writing shared between kataba, kitāb and kātib, so a tokenizer that only cuts strings cannot see that they are related.
Quick check
How many morphemes and how many syllables does the word cats contain?
Recall
Segment unhappiness into morphemes and label each as root or affix, free or bound.
Recall
What is the difference between a root and a stem?
Open a dictionary. walk, walked and walks share one entry; the endings only tell you when the walking happened or who did it. happy and happiness get two entries: an adjective and a noun that names its quality. That single observation is the whole distinction between the two functions an Affix can have. Inflection marks grammar (tense, number, case, agreement) and usually keeps the same Lexeme (the slide's hedge, consistent with SLP3's continuum). Derivation creates a new lexeme, and often changes the part of speech.
SLP3 describes the two ends of the scale. Inflectional morphemes are grammatical morphemes that tend to play a syntactic role, such as marking agreement, the plural -s on nouns or the past tense -ed on verbs; they tend to be productive, often obligatory, and their meanings tend to be predictable. Derivational morphemes are more idiosyncratic in their application and meaning, usually apply only to a specific subclass of words, and result in a word of a different grammatical class than the Root, often with a meaning hard to predict exactly (SLP3, section 2.2). SLP3's own example is care, a noun, plus -ful giving an adjective, plus -ly giving an adverb: two derivations in a row, each moving the word to a new category.
The English slide examples line up with that. walk to walk-ed and walk-s is inflection: every regular verb takes these endings, the meaning is exactly past tense (or past participle) or third person singular, and the word stays a verb. happy to happi-ness is derivation: -ness attaches mainly to adjectives, not every adjective takes it comfortably, and the result is a noun.
Derivation with nothing added
Here is the case that catches people. The noun an email became the verb to email without any affix at all. Nothing was attached, the spelling did not change, and yet a new lexeme with a new part of speech now exists. This is Conversion (zero derivation), also called zero derivation: the creation of a word of a new part of speech from an existing word of a different part of speech without any change in form (Wikipedia, Conversion). It is derivation by the test that matters, a new dictionary entry, even though there is no morpheme to point at.
The same two functions in Arabic
Arabic uses both functions too, and the slide picks an example of each. Inflection: katab-tu كَتَبْتُ I wrote is the Stem katab plus the suffix -tu, which marks first person singular past. The lexeme is still the verb to write; only who and when changed. Wikipedia's overview of Arabic verbs confirms katabtu as I wrote and notes that person, gender, number, tense, mood and voice are specified by changes to the vowels between the consonants along with prefixes or suffixes (Wikipedia, Arabic verbs). Derivation: kitāb كِتَاب book and kātib كَاتِب writer share the root K-T-B but are two lexemes, a noun for the object and an agent noun for the person, made by choosing two different patterns. Do not read a division of labour into that pair. The slide's inflection example happens to use a suffix and its derivation example a pattern, but Arabic uses both mechanisms for both jobs: the broken plural kutub كُتُب books inflects kitāb by changing the pattern alone, and maktab مَكْتَب office derives a place noun with the maCCaC pattern, whose ma- sits in front of the root like a prefix.
| Process | What changes | Lexeme identity | Part of speech | English example | Arabic example |
|---|---|---|---|---|---|
| Inflection | Grammar: tense, number, case, agreement | Same lexeme (usually) | Unchanged | walk to walk-ed, walk-s | katab-tu كَتَبْتُ, I wrote |
| Derivation | Meaning or category | New lexeme | Often changes | happy to happi-ness | kitāb كِتَاب book versus kātib كَاتِب writer |
| Conversion | Category only, no change in form | New lexeme | Changes | an email to to email | Not covered on the slide |
Four tests to decide
- Lexeme: would a dictionary list the two forms under one entry? One entry means inflection.
- Category: did the part of speech change? A change points to derivation, though derivation can also keep it, as un-happy does.
- Productivity: does the affix apply to essentially every word of its class with a predictable meaning? Every regular verb takes -ed; not every adjective takes -ness.
- Position: inflection closes the word. In un-happi-ness-es the derivational pieces sit inside and the plural sits outside. Haspelmath and Sims describe this ordering as a strong cross-linguistic tendency (chapter 5).
Worked example
Is -ed in walked inflection or derivation?
Lexeme test
walk and walked are one dictionary entry. Same lexeme.Category test
Both are verbs. The part of speech did not change.Productivity test
Every regular English verb takes -ed, and its meaning is always grammatical, past tense or past participle. Obligatory and predictable.Position test
-ed is the last thing on walked, and in a verb form nothing follows it. That fits the tendency for inflection to sit outside derivation, though participial adjectives such as preparedness show it is a tendency, not a law.Result
Inflection, on all four tests.
Worked example
Label the affixes of un-happi-ness
un-
Negation. The word stays an adjective, but unhappy is a separate dictionary entry from happy, and un- attaches selectively (unhappy, but not unsad). Derivational.-ness
Adjective to noun, a new lexeme with a new category. Derivational.Result
Two derivational affixes and no inflection. Add a plural and you get unhappiness-es, with the inflection on the outside.
Quick check
Which process turns the noun email into the verb to email?
Quick check
Which pair shows the same lexeme with different grammatical marking?
Recall
Is the -ed of walked inflection or derivation? Give two reasons.
Take scissors to walked and you can cut it into walk and ed. Take scissors to sang and there is nowhere to cut: the past tense lives in the vowel, in the middle of the root, and no substring means past. Both words are past-tense Inflection. They differ not in function but in form. That is the second axis of this part, independent of the first: Concatenative morphology attaches segments in a line, and Non-concatenative morphology changes the inside of the word instead.
Concatenative morphology covers everything you have segmented so far: a prefix before, a suffix after, or two roots side by side. cat-s, walk-ed, un-happy and snow-man are all built by placing one contiguous piece next to another. The pieces keep their shape, and you can recover them by cutting. Non-concatenative morphology is defined by what it lacks: word formation and inflection in which the Root is modified and which does not involve stringing morphemes together sequentially (Wikipedia, Nonconcatenative morphology). The slide groups it into internal change, templatic patterns and Suppletion.
Concatenation taken to its limit: Turkish
Turkish shows how far the concatenative strategy can go. The word evlerimden means from my houses, and it is four pieces in a row: ev-ler-im-den. Turkish is a highly agglutinative language, in that much of the grammar is expressed by means of suffixes added to nouns and verbs, and evlerden from the houses is analysed as ev house, -ler plural, -den ablative (Wikipedia, Turkish grammar). Adding the first person possessive -im between them gives the slide's form. Each suffix has a clean boundary and stays recognisable, which is what makes the word so easy to cut (one suffix, -im, still bundles person, number and possession, hence the periods in its gloss).
ev-ler-im-den, from my houses
- ev
- house. Root, free.
- -ler
- PL, plural. Suffix, bound.
- -im
- 1SG.POSS, first person singular possessor, my. Suffix, bound.
- -den
- ABL, ablative case, from. Suffix, bound.
Three ways to change the inside
Internal change is the English strong-verb pattern. sing, sang, sung (and the noun song) differ only in the vowel of the root. This vowel alternation is called Ablaut, a system of regular vowel variations inherited from Proto-Indo-European (Wikipedia, Indo-European ablaut). The root and the tense are fused in one syllable; there is no -ed to remove.
Templatic patterns are the Arabic case from the first concept, now seen as a mechanism. The root ك ت ب stays fixed in order, and a template of vowels and extra consonants is woven through it: kataba كَتَبَ he wrote, kitāb كِتَاب book, kātib كَاتِب writer. The pieces of the pattern are not contiguous with each other and neither are the pieces of the root, so there is no cut that separates the two morphemes. Watch how the radicals never move while the material between them changes.
Look closely at yaktubu يَكْتُبُ he writes, and you will see both mechanisms at once. The prefix ya- and the suffix -u are concatenative; the Stem shape -ktub-, with no vowel after k and u after t, the pattern CCuC woven through the radicals, is templatic. Arabic words are typically built by wrapping affixes around a root-and-pattern core, which is why an Arabic morphological analyzer has to do two different kinds of work.
Suppletion is the extreme case. The past tense of go is went, and no rule relates the two strings. Suppletion is the use of one word as the inflected form of another word when the two words are not cognates; went was originally the past tense of the verb wend (Wikipedia, Suppletion). Haspelmath and Sims treat it as the extreme case of allomorphy, where the two forms share no phonological material, and the slide lists it under non-concatenative because the outcome is the same: a grammatical change with no segment you can cut off. Suppletion also covers good to better.
| Mechanism | How the form changes | English | Other languages | Cut with scissors? |
|---|---|---|---|---|
| Concatenative | A contiguous affix or root is added in sequence | cat-s, walk-ed, un-happy, snow-man | Turkish ev-ler-im-den, house-PL-1SG.POSS-ABL, from my houses | Yes |
| Non-concatenative: internal change | A vowel inside the root changes | sing, sang, sung | German umlaut plural Vater, Väter (illustration) | No |
| Non-concatenative: templatic | A vowel pattern is interleaved with a consonantal root | None in English | Arabic كَتَبَ kataba, يَكْتُبُ yaktubu, كِتَاب kitāb from K-T-B | No |
| Non-concatenative: suppletion | The whole form is replaced by an unrelated one | go, went; good, better | Not on the slide | No |
Two axes, not one
Function and form are independent. walked and sang are both inflection; one is concatenative and one is not. happiness and kitāb are both Derivation; one is concatenative and one is templatic. When you analyse a form, answer both questions separately: what does the change do, and how is the change realised?
The form axis is the one that decides what a tokenizer can see. Subword tokenization methods such as Byte-pair encoding (BPE) work on contiguous substrings, so they approximate concatenative morphology well: walk can be shared between walked and walking because it is a substring of both. A discontinuous root like K-T-B is never a substring of anything, so the same tokenizer cannot share the writing meaning across kataba, kitāb and kātib. That is SLP3's cross-lingual difficulty made concrete, and it is the hook for most Arabic tokenization research.
Quick check
Which form is built by non-concatenative morphology?
Recall
Give one example of non-concatenative morphology in English and one in Arabic.
Recall
Gloss ev-ler-im-den morpheme by morpheme.
Recap
If you remember nothing else
- A morpheme is the smallest unit that carries meaning or grammatical function: cats = cat + -s, unhappiness = un- + happy + -ness.
- Free morphemes stand alone; bound morphemes (affixes) must attach. The root carries the core meaning; the stem is the base that takes inflection.
- Affixes are prefixes, suffixes and the rare infixes and circumfixes; compounding joins two free roots (snowman, laptop).
- Arabic roots such as K-T-B are discontinuous consonant skeletons; patterns interleave vowels to give kataba, kitab and katib.
- Inflection marks grammar and usually keeps the lexeme (walk-ed, katab-tu); derivation makes a new lexeme and often a new category (happi-ness, kitab versus katib); conversion derives with no added form (an email, to email).
- Concatenative morphology attaches segments in a line (cat-s, ev-ler-im-den); non-concatenative morphology changes the inside (sing, sang, sung; kataba, yaktubu, kitab) or replaces the form (go, went).
- Function and form are independent axes: walked and sang are both inflection, one concatenative and one not. Arabic uses both mechanisms at once.
- Syllables are sound units and morphemes are meaning units. The two do not line up.
Sources
- Speech and Language Processing, 3rd edition draft, chapter 2: Words and TokensBookJurafsky and Martin, StanfordSections 2.2 and 2.4: morphemes, roots and affixes, inflectional versus derivational; tokens as words, morphemes or characters(opens in a new tab)
- Understanding Morphology, 2nd editionBookRoutledge, Haspelmath and SimsBasic concepts (affix positions, circumfix), inflection and derivation (chapter 5), suppletion(opens in a new tab)
- A Reference Grammar of Modern Standard ArabicBookCambridge University Press, Karin C. RydingChapter 3, Arabic word structure: root and pattern(opens in a new tab)
- The Leipzig Glossing RulesDocsMax Planck Institute for Evolutionary AnthropologyRule 2 on hyphens; abbreviations PL, 1, SG, POSS, ABL(opens in a new tab)
- Glossary of Linguistic Terms: bound morphemeDocsSIL InternationalDefinitions of free and bound morphemes(opens in a new tab)
- Semitic rootArticleWikipediaConsonantal roots and transfixes; k-t-b forms kataba, yaktubu, kātib(opens in a new tab)
- Arabic verbsArticleWikipediakatabtu, I wrote; affixes plus vowel changes mark person, number, tense(opens in a new tab)
- Turkish grammarArticleWikipediaAgglutination, evlerden analysed as ev, -ler, -den(opens in a new tab)
- Nonconcatenative morphologyArticleWikipediaDefinition; apophony, transfixation, reduplication, truncation(opens in a new tab)
- Indo-European ablautArticleWikipediasing, sang, sung, song as a regular vowel alternation(opens in a new tab)
- SuppletionArticleWikipediaDefinition; went from the verb wend(opens in a new tab)
- Conversion (word formation)ArticleWikipediaZero derivation; email as a verbified noun(opens in a new tab)
Part 04: Clitics and morphological typology
Why some attached pieces are words in disguise, how to tell a clitic from an affix, and where languages sit between isolating and polysynthetic, with English and Arabic placed on the map.
3 concepts, slides 22-28
Why this part matters
Every Arabic NLP model you will build starts with a decision you may not notice making: what is a token? Arabic writes conjunctions, prepositions, the article and pronouns onto the same string as the noun or verb, so وَبِالْبَيْت is one whitespace token that a linguist would call four words. Alotaiby, Foda and Alkharashi (2010) measured what that costs: splitting clitics off a 600,000,000-word corpus removed 24.54% of its vocabulary. That number is why this part exists.
Part 03 gave you morphemes, roots, affixes and the difference between inflection and derivation. This part adds the two remaining pieces of the morphology story. First, a test that decides whether an attached element is an affix at all, or a word that only leans on its neighbor for pronunciation. Second, a vocabulary for comparing whole languages by how much they pack into a word, which ends in a number line that predicts how hard a language will be to tokenize. Exams ask for the diagnostics and the classification with forms; your research needs the same ideas to choose a preprocessing pipeline and to read why a shared subword vocabulary fragments Arabic more than English (part 07).
By the end you can
- Apply two diagnostics (selectivity, paradigm membership) to decide clitic versus affix, including katab-tu, kitab-u-hu and the borderline al-.
- Classify a language form as isolating, agglutinative, fusional or polysynthetic and explain why these are tendencies per subsystem, not boxes.
- Place English and Arabic on the typology map and name one fusional pocket in English.
- Define Greenberg's index of synthesis, order the eight languages on it, and compute it on a short text.
- Explain, with numbers, why rich morphology motivates subword tokenization, morphological segmentation and explicit clitic policies.
Start with an English sentence that Zwicky and Pullum (1983) use in section 2 of their paper: The person I was talking to's going to be angry. The 's is a reduced is, and it is hanging off the preposition to. Now vary the host: The ball you hit's just broken my window puts it on a verb, Any answer not entirely right's going to be marked on an adjective, The drive home tonight's been really easy on an adverb. One element, four host categories. It is a reduced auxiliary throughout, is in the first and third sentence and has in the second and fourth, and in every case it does exactly what the separate word is or has would do.
Compare the plural -s of cats. It attaches to nouns and to nothing else, and it never does the job of a word: there is no sentence in which plural -s is the verb or the subject. That difference in behavior is what separates a Clitic from an Affix. Jurafsky and Martin define a clitic as "a morpheme that acts syntactically like a word but is reduced in form and attached (phonologically and sometimes orthographically) to another word" (SLP3, chapter 2). The two halves of that definition pull in opposite directions, which is why clitics are confusing: in the syntax they are words, in the sound stream they are part of a word.
Why spelling is not the test
English happens to mark most clitics with an apostrophe (I'm, we've, possessive 's), and French does the same with l' in l'opéra. That is a writing convention, not a grammatical fact. Arabic writes every clitic solidly attached with no separator at all, so if attachment in writing were the criterion, Arabic would have no clitics and English would have almost none outside contractions. The slide's key sentence is worth memorizing verbatim: attachment in writing is not enough, use diagnostics.
The diagnostics come from Zwicky and Pullum. Their first criterion is the one this course uses as the primary test, and they state it as: "Clitics can exhibit a low degree of selection with respect to their hosts, while affixes exhibit a high degree of selection with respect to their stems." This is Selectivity. In a footnote they give it as a ladder: an element that attaches to any word of a major class is least selective, then one that attaches only to verbs, then only to a subclass of verbs, then only to a few specified verbs. The further down the ladder, the more affix-like the element. The second test the slide names is paradigm membership: an affix fills one cell of a table of forms, a clitic does not. All six of their criteria are in the table below alongside the slide's two tests, and you should be able to name at least selectivity, arbitrary gaps and idiosyncrasy.
Clitic versus affix diagnostics (Zwicky and Pullum 1983)
- Syntactic role
- A clitic is a function word or pronoun that the syntax treats as a word of its own. An affix marks a feature on its host and has no independent slot in the sentence.
- Selectivity (Zwicky and Pullum A)
- Clitics show low selectivity: they attach to almost any host category. Affixes show high selectivity: one stem class, sometimes one subclass.
- Paradigm membership
- An affix fills a cell in a paradigm (singular, plural; katab-tu, katab-ta, katab-a). A clitic belongs to no paradigm of its host.
- Arbitrary gaps (B)
- Affixed forms have unexplained holes (no English amn't). Clitic combinations do not.
- Idiosyncrasy (C, D)
- Affixed forms show unpredictable sound or meaning (won't, shan't). Clitic groups are regular.
- Syntactic rules (E)
- Syntactic operations such as agreement and inversion treat an affixed word as one unit; a host-plus-clitic group is not treated as a unit.
- Stacking (F)
- Clitics can attach to material that already contains a clitic; affixes cannot attach outside a clitic.
Three Arabic cases and a fourth that refuses to settle
Arabic is where the test earns its keep, because the writing system gives you no hint. Take وَبِالْبَيْت, wa-bi-al-bayt, roughly "and in the house". The wa- is the conjunction "and" and the bi- is the preposition "with, by, in". Both are function words in the syntax, and both attach to whatever word begins the phrase: a noun here, a verb in wa-kataba, a pronoun, a particle. Lowest rung of the selectivity ladder, no paradigm. They are proclitics, clitics that lean forward onto the following word.
Now كِتَابُهُ, kitāb-u-hu, "his book". The -u is the nominative case vowel, an inflectional ending. The -hu is the pronoun "he" in possessive position, and it is the possessor of the noun in the syntax. It attaches to nouns (kitāb-u-hu), to verbs as an object (kataba-hu, "he wrote it") and to prepositions (بِهِ, bi-hi, "with it"). Three host categories, a syntactic role of its own: an Enclitic, leaning backward onto the preceding word.
Finally كَتَبْتُ, katab-tu, "I wrote". The -tu also means something pronoun-like (first person singular), which is exactly what tempts students to call it a clitic. Apply the diagnostics. Selectivity: -tu attaches to verb stems and to nothing else. Paradigm: it is one cell of the perfective conjugation, next to -ta (you, masculine), -ti (you, feminine), -a (he) and -at (she). Syntactic role: it is subject agreement on the verb, and a separate subject pronoun can still appear alongside it. Every test says Inflection, so -tu is an inflectional affix on the Stem katab.
| Form | Transliteration | Meaning | Syntactic role | Selectivity | Paradigm | Verdict |
|---|---|---|---|---|---|---|
| وَبِالْبَيْت | wa-, bi- | and; with, by, in | Conjunction and preposition, function words | Low: nouns, verbs, pronouns, particles | None | Proclitic |
| كِتَابُهُ | -hu | his, him | Pronoun, possessor or object | Low: nouns, verbs, prepositions | None in the host (a pronoun set, not a host paradigm) | Enclitic |
| كَتَبْتُ | -tu | I (subject agreement) | Agreement marker on the verb | High: verb stems only | One cell of the perfective conjugation | Inflectional affix |
| الْبَيْت | al- | the | Definite article, function element | Moderate: nouns and adjectives | Definiteness system (definite, indefinite) | Borderline |
The fourth row is the one the slide flags as borderline, and it is worth understanding why rather than memorizing the label. The article al- is a function element like French l', which argues clitic. But it attaches only to nouns and adjectives, and it alternates with the indefinite ending -n (bayt-un versus al-bayt-u) inside a definiteness system, which argues prefix. Arabic NLP tools split the difference. Pasha et al. (2014) list two segmentation schemes for MADAMIRA: ATB, which "tokenizes all clitics except for the definite article", and D3, in which "basically all clitics are tokenized". ATB also leaves the future particle sa- attached to the verb, where D3 splits it off. Alotaiby et al. (2010) note that their parse-it-as-a-separate-word test "covers most of the clitics except the definite article". When the tools disagree, borderline is the honest verdict.
For each attached element decide: is it a word in the syntax that merely leans on a host (clitic), a member of a paradigm that attaches to one stem class (affix), or a case where analyses disagree? Keys 1, 2 and 3 pick a verdict, arrows move between cards.
Clitic status and tokenization policy are different questions
The slide ends on a practical fork: keep the clitic attached, or split it? Linguistics does not answer that Tokenization question for you, and the cleanest proof is English n't. Zwicky and Pullum devote their paper to showing that n't passes all six criteria as an inflectional affix: it attaches only to auxiliaries, it has arbitrary gaps (there is no amn't), it has idiosyncratic forms (won't, shan't), and it cannot attach to a host that already carries a clitic (I'd've is fine, I'dn't is not). Yet the Penn Treebank tokenization standard splits it, producing does n't (SLP3, section 2.8). The tokenizer made a modeling decision (a negation token is useful) that contradicts the linguistic analysis, and it was right to.
Modern subword tokenizers make the same kind of decision by frequency rather than by rule. Jurafsky and Martin observe that the GPT-4o tokenizer segments 's off proper nouns such as Jane but keeps it inside frequent words such as she's (SLP3, section 2.4.3). For Arabic the choice is explicit and named: whitespace, ATB or D3. Try the three on the same two strings.
Accent chips are clitics, the teal chip is the article, plain chips are the base word. The second word has no article, but ATB still keeps the future particle sa- on the verb.
Worked example
Whitespace, ATB or D3 on one string
Whitespace
وَبِالْبَيْت is one token. Every combination of conjunction, preposition, article and noun becomes its own vocabulary Type.ATB
wa+ bi+ albayt: three tokens. The conjunction and preposition are split, the article stays on the noun.D3
wa+ bi+ al+ bayt: four tokens. Every clitic is separated and the bare noun bayt is recovered.Same string, 1, 3 or 4 tokens
The count of tokens is a property of the policy, not of the text. Any statistic you compute on Arabic (vocabulary size, sequence length, Zipf curve) must state which policy produced it.
Recall
State two diagnostics that separate a clitic from an affix and apply them to katab-tu and kitab-u-hu.
Recall
Why is 'it is written attached' not a valid test, and what does al- show?
Quick check
Which diagnostic shows that Arabic -tu in katab-tu is an affix rather than a clitic?
Take four languages and watch how each packages grammar. English and Turkish can share one meaning, "from my houses". English spends three words: from, my, house-s, four morphemes spread over three words. Turkish spends one: ev-ler-im-den, house-PL-1SG.POSS-ABL, four morphemes in one word, each suffix doing exactly one job in a fixed order. Spanish habl-o, "I speak", is a different meaning chosen to show a different trick: the single vowel -o carries person, number, tense and mood all at once, two morphemes carrying four features, and you cannot cut it into a piece for each. Mandarin wǒmen míngtiān qù Běijīng, "we tomorrow go Beijing", has almost no Inflection (the plural -men of wǒmen is about as far as it goes); word order and free words carry the grammar.
Turkish, Spanish and Mandarin illustrate three of the four labels on slide 24 (the fourth, polysynthetic, comes below), and English is the analytic baseline. The labels line up on two independent axes rather than one scale. The first axis is how many morphemes a word carries, from isolating (about one) to polysynthetic (a word can hold what English needs a clause for). The second axis is how cleanly the morphemes can be cut apart, from agglutinative (one morpheme, one feature, clean seams) to fusional (one segment bundles several features). This two-axis picture goes back to Sapir's 1921 classification, which separated degree of synthesis from technique of combination (Arkadiev 2019, and SLP3 section 2.2 uses the same two ideas).
| Type | Definition | Example language | Example form | Gloss |
|---|---|---|---|---|
| Isolating | Little morphology; grammar carried by word order and function words | Mandarin Chinese | wǒmen míngtiān qù Běijīng | we tomorrow go Beijing |
| Agglutinative | Many affixes, each with one clear function and a clean boundary | Turkish | ev-ler-im-den | house-PL-1SG.POSS-ABL, from my houses |
| Fusional | One affix bundles several grammatical features | Spanish | habl-o | speak-1SG.PRS.IND, I speak |
| Polysynthetic | Very complex words encode sentence-like content, many morphemes per word | Inuktitut on the slide; Koryak in SLP3 | t-ə-nk'e-mejŋ-ə-jetemə-nni-k | one word: I sewed a lot of yurt covers in the middle of a night |
The Turkish and Spanish rows deserve a closer look, because the difference between them is the second axis. Bickel and Nichols (WALS chapter 21) call Turkish case and number monoexponential: -ler means plural and nothing else, and the case suffixes -in, -i, -e each mean one case. In a fusional language such as Russian, they note, there is "no element that exclusively expresses case or number": the genitive plural ending says both at once. English has the same fusion in miniature. Jurafsky and Martin point out that the -s of she reads encodes third person, singular and present tense in one segment, exactly as Spanish -o does.
The polysynthetic row borrows its form from a neighboring language on purpose. The slide names Inuktitut but prints no word, warning that examples vary by dialect and orthography and that the key property is morpheme density. Jurafsky and Martin's example is Koryak: t-ə-nk'e-mejŋ-ə-jetemə-nni-k, one word meaning "I sewed a lot of yurt covers in the middle of a night" (SLP3, example 2.9), and that is the form to quote for the polysynthetic type. Gupta and Boulianne (2020) describe Inuktitut as showing "a much higher degree of polysynthesis than other agglutinative languages usually considered in ASR, such as Finnish or Turkish", which is a useful reminder that polysynthetic languages are usually agglutinative too: the axes are independent.
Tendencies, not boxes
The slide's mental model is that a language can be "more X" in one subsystem and "more Y" in another, and this is not a hedge. Jurafsky and Martin write that "languages can make use of different morphological systems so it would be more accurate to talk about these as general tendencies." Bickel and Nichols (WALS chapter 22) give an example inside one language: "the English past, for example, is more synthetic than the future", because walked packs tense into the verb while will walk spends a separate auxiliary. So when you classify, name the subsystem: English verbs are mostly analytic with a fusional pocket, English Derivation is productive and concatenative, and Arabic verbs are fusional while Arabic attached function elements are clitic-like.
Where English and Arabic sit
Slide 26 places the two languages this course cares about. English is largely analytic: auxiliaries and word order do most of the grammatical work, and inflection is limited to a handful of endings, -s, -ed, -ing. But it has fusional pockets. am, is and are each bundle person, number and tense in a form with no separable ending at all, and went replaces the expected go-ed wholesale, a case of Suppletion. Where English is genuinely productive is derivation and Compounding: un-happi-ness, laptop, and every new noun coined this year.
Modern Standard Arabic sits elsewhere on both axes. Word formation follows templatic morphology, so a consonantal Root such as K-T-B is interleaved with vowel patterns, a non-concatenative mechanism that no amount of prefix and suffix cutting will recover. The verb system is largely fusional, and it is large: Obeid et al. (2020) note that this richness "leads to MSA verbs with upwards of 5,400 forms". On top of the templatic core sit the concatenative pieces from the previous concept, proclitics wa-, bi-, li- and the pronominal enclitics, which behave as clitics in NLP, plus the borderline al-. Sapir's own table, as reproduced by Arkadiev, files English as fusional and analytic and Arabic as fusional, symbolic and synthetic, which is the same placement in older words.
| Subsystem | English | Arabic (MSA) |
|---|---|---|
| Word formation | Productive derivation and compounding, concatenative | Templatic root-and-pattern, non-concatenative |
| Verb inflection | Limited: -s, -ed, -ing; fusional pockets am/is/are, go/went | Largely fusional; upwards of 5,400 forms per verb |
| Attached function elements | 's, 'm, 've, marked by apostrophe | wa-, bi-, li-, pronominal enclitics, written solid |
| Borderline element | n't | al- |
Recall
Classify Turkish, Spanish, Mandarin and Inuktitut by typology and give one form each.
Quick check
Turkish ev-ler-im-den gives each suffix exactly one function. Which typological tendency is that?
Quick check
Which set of English forms is a fusional pocket, where one form bundles several grammatical features?
The typology labels can be turned into a single number. Take a long text, count its morphemes, count its words, and divide. Joseph Greenberg proposed this ratio in 1960 as the Index of synthesis, "calculated as the ratio of morphemes to words in a sufficiently long text" (Arkadiev 2019). Try it on four English words: The cats walked home is the (1), cat-s (2), walk-ed (2), home (1), six morphemes over four words, an index of 1.5. Turkish ev-ler-im-den alone is 4 / 1 = 4.0, and the English equivalent from my houses is 4 / 3 ≈ 1.33.
Greenberg computed the index for eight languages, and the result is the number line on slide 27, reproduced from the same figure in Jurafsky and Martin (SLP3, figure 2.3). Vietnamese sits at the bottom with almost exactly one morpheme per word. English is close behind, which confirms the analytic label of the previous concept. Old English, Yakut, Swahili and Sanskrit cluster in the middle band, and Greenlandic (Eskimo in Greenberg's paper) stands far to the right at almost four morphemes per word. The bands drawn through this line are the ones you already know: analytic below about 2, synthetic between 2 and 3, polysynthetic above 3.
| Language | Greenberg | Slide | Band |
|---|---|---|---|
| Vietnamese | 1.06 | 1.1 | Analytic |
| Persian (Farsi) | 1.52 | 1.5 | Analytic |
| English | 1.68 | 1.7 | Analytic |
| Anglo-Saxon (Old English) | 2.12 | 2.1 | Synthetic |
| Yakut | 2.17 | 2.2 | Synthetic |
| Swahili | 2.55 | 2.5 | Synthetic |
| Sanskrit | 2.59 | 2.6 | Synthetic |
| Eskimo (Greenlandic) | 3.72 | 3.7 | Polysynthetic |
Greenberg's paper contains several more indices, and two of them explain what the synthesis number hides. Eskimo scores 2.72 on suffixation and 1.75 on inflection against English's 0.64 and 0.53; Swahili scores 1.16 on prefixation against English's 0.04. Swahili sits mid-synthetic not because it stacks suffixes but because its noun-class and agreement system lives in prefixes. Same band, different subsystem, which is the tendencies-not-boxes lesson in numbers.
Why the index decides your tokenizer
Slide 28 closes the morphology chapter with one sentence: "word" is not universal, because morphology and clitics change what counts as a Token. The index of synthesis makes that quantitative. Every extra morpheme per word multiplies the number of distinct surface forms a Lemma can take, and every clitic that attaches multiplies it again. Since a model's Vocabulary is a set of surface forms, a language with a high index and heavy cliticization will have a higher Type-token ratio (TTR), far more types for the same number of tokens, and far more of them will be Out-of-vocabulary (OOV) at test time. Recall Heaps' law from part 02: rich morphology tends to raise the exponent, so the vocabulary keeps growing quickly as the corpus grows.
Worked example
What clitic splitting does to an Arabic vocabulary (Alotaiby et al. 2010)
Whitespace tokens
A 600,000,000-word Arabic corpus has 2,207,637 unique whitespace tokens, against about 1.26 million for an equivalent English corpus, a ratio of about 1.76x.Split the clitics
After clitic tokenization with AMIRA the corpus grows to 848,000,000 tokens (about 1.41x more), because each clitic is now a token of its own.Count the types again
Unique tokens fall to 1,665,899, a reduction of 24.54%. The article al- alone accounts for 57.02% of clitic occurrences, wa- for 17.17%, li- for 6.35%, -hu for 5.55%, bi- for 4.9% and -hā for 4.41%.A quarter of the vocabulary was clitic combinations
Nothing about the language changed. A tokenization policy removed 541,738 types, which is why clitic splitting is the first preprocessing decision in Arabic NLP.
At the polysynthetic end the effect is far larger. Schwartz et al. (2020) compare the parallel Inuktitut and English sides of the Nunavut Hansard. The numbers below come from their table 3.2.
| Side | Tokens N | Types |V| | TTR |
|---|---|---|---|
| Inuktitut | 10,869,995 | 1,563,883 | 0.144 |
| English | 20,367,595 | 59,234 | 0.003 |
Inuktitut produces about 26 times as many types from about half as many tokens. Gupta and Boulianne (2020) report the practical consequence for speech recognition: even with a 1.3 million word vocabulary, held-out Inuktitut stories still have "more than 60% of words out-of-vocabulary". A word-level vocabulary cannot be made large enough. That is the open-vocabulary problem, and slide 28 lists three responses to it.
- Subword tokenization with BPE or WordPiece. Sennrich, Haddow and Birch (2016) motivated BPE for translation precisely because vocabularies are "typically limited to 30 000 to 50 000 words, but translation is an open-vocabulary problem", "especially for languages with productive word formation processes such as agglutination and compounding". WordPiece, from Wu et al. (2016), divides words "into a limited set of common sub-word units". Part 06 builds BPE from scratch.
- Morphological segmentation when linguistic structure matters. A frequency-driven tokenizer will not reliably cut وَبِالْبَيْت at the clitic boundaries. MADAMIRA and CAMeL Tools do, using an analyzer and an explicit scheme (ATB or D3), which is what you want when a downstream task needs the bare lemma.
- Careful preprocessing: case, punctuation and a stated clitic splitting policy. Jurafsky and Martin describe the pre-tokenization stage as "breaking the input at spaces and punctuation, stripping off clitics, and breaking numbers into sets of digits" (SLP3), which is the tokenization pipeline's first pass.
The last thread to pull runs into part 07. A multilingual subword vocabulary is trained mostly on English, and Jurafsky and Martin note that such tokenizers "use most of the tokens for English" while "the other languages tend to get their words split up into shorter tokens" (SLP3, section 2.4.3). Arabic suffers twice: it has less training data in the mix, and its clitics and templatic verbs (upwards of 5,400 forms per verb) spread that data across far more surface forms, so fewer of them earn a merge. The same sentence costs more tokens in Arabic than in English, which means more compute per sentence and a shorter effective context.
Recall
Define Greenberg's index of synthesis and place English on it.
Recall
Name the three NLP responses on slide 28 and one tool or paper for each.
Quick check
Which statement about Greenberg's 1960 morphemes-per-word index is correct?
Quick check
Why does a shared multilingual BPE vocabulary usually split Arabic words into more pieces than English words?
Recap
If you remember nothing else
- A clitic is a word in syntax and part of a word in sound; an affix is a paradigm member. Test selectivity and paradigm membership, not spelling.
- wa-, bi- and -hu attach to almost any host (clitics); -tu attaches only to verbs inside the conjugation paradigm (affix); al- is borderline and treated differently by ATB and D3.
- English n't is an affix by Zwicky and Pullum's six criteria, yet the Penn Treebank splits it: linguistic status and tokenization policy are separate choices.
- Typology has two independent axes: morphemes per word (analytic to polysynthetic) and segmentability (agglutinative to fusional). Languages show tendencies per subsystem, not one box.
- Mandarin isolating, Turkish agglutinative (ev-ler-im-den), Spanish fusional (habl-o), Inuktitut polysynthetic (one word, one clause).
- English is largely analytic with fusional pockets (am/is/are, went, reads); Arabic is templatic, fusional in verbs and clitic-heavy.
- Greenberg 1960: Vietnamese 1.06, Persian 1.52, English 1.68, Old English 2.12, Yakut 2.17, Swahili 2.55, Sanskrit 2.59, Eskimo 3.72. Arabic is not in his sample.
- Rich morphology multiplies surface forms (Arabic 1.76x the English vocabulary at 600M words; Inuktitut Hansard 26x the English types), which is why BPE and WordPiece, morphological segmentation and clitic policies exist.
Sources
- Speech and Language Processing, 3rd edition draft, chapter 2: Words and TokensBookJurafsky and Martin, StanfordClitic definition, figure 2.3, tendencies quote, Koryak example, Penn Treebank splitting, GPT-4o clitic behavior, multilingual BPE bias(opens in a new tab)
- Cliticization vs. Inflection: English n'tPaperLanguage 59(3), 1983, Zwicky and PullumSix diagnostics; selectivity criterion A; n't as an inflectional affix(opens in a new tab)
- A Quantitative Approach to the Morphological Typology of LanguagePaperInternational Journal of American Linguistics 26(3), 1960, GreenbergIndex of synthesis and the eight-language table, p. 193(opens in a new tab)
- Morphology in Typology: Historical Retrospect, State of the Art and ProspectsArticleOxford Research Encyclopedia of Morphology, 2019 preprint, ArkadievSapir's two axes; table 3 reproduces Greenberg's indices with two decimals(opens in a new tab)
- WALS chapter 21: Exponence of Selected Inflectional FormativesDocsWorld Atlas of Language Structures, Bickel and NicholsTurkish monoexponential versus Russian polyexponential case and number(opens in a new tab)
- WALS chapter 22: Inflectional Synthesis of the VerbDocsWorld Atlas of Language Structures, Bickel and NicholsSynthesis varies within one language: English past versus future(opens in a new tab)
- Introduction to Arabic Natural Language ProcessingBookSpringer, 2010, HabashArabic clitics, tokenization schemes and morphological analysis(opens in a new tab)
- MADAMIRA: A Fast, Comprehensive Tool for Morphological Analysis and Disambiguation of ArabicPaperLREC 2014, Pasha et al.Table 1: ATB tokenizes all clitics except the definite article, D3 tokenizes all clitics(opens in a new tab)
- CAMeL Tools: An Open Source Python Toolkit for Arabic Natural Language ProcessingPaperLREC 2020, Obeid et al.Upwards of 5,400 forms per MSA verb; D3 tokenization example(opens in a new tab)
- Clitics in Arabic Language: A Statistical StudyPaperPACLIC 24, 2010, Alotaiby, Foda and Alkharashi600M-word corpus, 2,207,637 to 1,665,899 unique tokens (24.54%), clitic frequency table(opens in a new tab)
- Neural Machine Translation of Rare Words with Subword UnitsPaperACL 2016, Sennrich, Haddow and BirchBPE for open-vocabulary translation, agglutination and compounding(opens in a new tab)
- Google's Neural Machine Translation System: Bridging the Gap between Human and Machine TranslationPaperarXiv, 2016, Wu et al.WordPiece sub-word units(opens in a new tab)
- Neural Polysynthetic Language ModellingPaperJSALT 2019 final report, arXiv 2020, Schwartz et al.Table 3.2: Nunavut Hansard token and type counts for Inuktitut and English(opens in a new tab)
- Automatic Transcription Challenges for Inuktitut, a Low-Resource Polysynthetic LanguagePaperLREC 2020, Gupta and BoulianneMore than 60% OOV with a 1.3 million word vocabulary; Inuktitut versus Finnish and Turkish(opens in a new tab)
Part 05: Corpora, Unicode and UTF-8
What a corpus is and why it must be documented, how Unicode names every character with a code point, and how UTF-8 packs code points into bytes that any model can consume.
4 concepts, slides 29-37
Why this part matters
Your research project processes Arabic, and Arabic is where text representation stops being a detail. One letter has four visual shapes, one sound has several hamza spellings, legacy keyboards emit presentation forms, and every letter costs two bytes. A tokenizer that does not know this splits words in places no linguist would recognize.
This part builds the foundation under every tokenizer count in the rest of the course. First it asks what a corpus actually is and what you owe a reader who will trust a model trained on it. Then it descends from characters to numbers (code points), from numbers to bytes (UTF-8), and finally explains why bytes are the floor that byte-level BPE in part 7 stands on. Every corpus you use or build for the project must carry a data statement to be publishable, and every byte count you will ever see in a tokenizer log traces back to the four-row mapping table taught here.
By the end you can
- Name the variation dimensions of a corpus and list the fields of a data statement or datasheet.
- Distinguish code point, glyph and encoding, and explain why saying Unicode is an encoding is wrong.
- Apply NFC, NFD and NFKC to precomposed and decomposed Latin and Arabic examples and say which tokenization bug each prevents.
- Encode any code point into UTF-8 bytes by hand using the four-row table, and decode bytes back.
- Explain why bytes remove the unknown-character problem and what they cost.
Two datasets both labelled "Arabic corpus" can be almost unrelated. One is Modern Standard Arabic newswire from 2005, written by trained journalists in a formal register. The other is Gulf-dialect tweets from 2024, full of Arabizi, emoji and code switching into English. A model trained on the first will stumble on the second, and a vocabulary learned from either will miss most of the other. Same language label, different domain, genre, time, demographics and variety.
A Corpus is a structured collection of texts, often annotated, drawn from sources such as books, news, web crawls, social media and speech transcripts. The word "structured" matters: a corpus is a deliberate sample with metadata, not a pile of files. Jurafsky and Martin describe the dimensions along which such samples vary as language and variety (including code switching), genre, the demographics of the writer or speaker, and time. The table below adds domain, which the slides list separately, and gives an Arabic example for each.
| Dimension | Question it answers | Arabic example |
|---|---|---|
| Language and variety | Which language, dialect or register, and is there code switching? | Modern Standard Arabic newswire versus Najdi tweets, or Arabizi in Latin letters |
| Genre | What kind of text is it? | Quranic commentary, a parliamentary transcript, a product review |
| Time | When was it written or spoken? | Pre-2011 news versus post-2020 social media, where vocabulary shifted |
| Demographics | Who wrote or spoke it, by age, gender, region, education? | Teenage gamers in Riyadh versus retired teachers in Cairo |
| Domain | What is it about? | Medical records, legal contracts, football commentary |
Models inherit their corpus
A language model is a compressed description of its training corpus, so it inherits the corpus's biases and blind spots. Bender and Friedman argue that systems perform best for the populations represented in their training data, so bias and exclusion arise when that data does not match the people the system is deployed for, and the fix begins with knowing what the data contains. Without that knowledge, a result on a benchmark cannot be interpreted: a 95% accuracy on 2005 newswire says nothing about 2024 tweets. To interpret any result you need the metadata below.
Metadata a corpus must carry before its results can be interpreted
- Collection procedure
- How texts were sampled or crawled, and what was filtered out
- Time period
- When the texts were produced, not only when they were collected
- Languages and varieties
- Which languages, dialects and registers, and how much code switching
- Speaker or author demographics
- Age, gender, region, education and any other known attributes
- Licensing and privacy
- Who may use the data, for what, and whether consent was obtained
Data statements and datasheets
Two papers turned that metadata list into a standard document. Bender and Friedman (2018) proposed the data statement for NLP datasets, a short structured write-up with lettered sections from curation rationale through language variety, speaker and annotator demographics, speech situation and text characteristics. Gebru and colleagues (2021) proposed the datasheet for any machine learning dataset, borrowing the idea from electronics: a component datasheet lists operating ranges and test conditions, so a dataset should too. The slides merge the two into six fields.
The six datasheet fields, with the matching sections in the two papers
- Motivation and intended use
- Why the corpus was built, by whom, funded how, and what tasks it is meant for (Gebru: motivation and uses; Bender and Friedman: A, curation rationale)
- Composition
- What the instances are, how many, which languages, varieties and demographics they cover (Gebru: composition; Bender and Friedman: B to F)
- Collection process
- How and when the texts were gathered, by crawl, purchase or recording, and who did it (Gebru: collection process; Bender and Friedman: I, provenance)
- Preprocessing and annotation
- Cleaning, filtering, tokenization and labelling steps, and the annotators' guidelines and demographics (Gebru: preprocessing, cleaning, labeling; Bender and Friedman: D)
- Ethical considerations
- Consent, privacy, sensitive content and the populations who could be harmed by a model trained on it (Gebru: uses and maintenance)
- Distribution constraints
- License, access terms, versioning and who maintains the release (Gebru: distribution and maintenance)
The payoff is transparency, reproducibility and responsible use. Transparency because a reader can judge whether your corpus matches their population. Reproducibility because someone else can rebuild or extend the sample. Responsible use because a datasheet states what the data must not be used for, which is the only place such a limit can live once the files are downloaded.
Recall
Name four fields of a datasheet, and state the one-sentence reason a corpus needs one.
Quick check
Which datasheet field explains why the corpus was created?
Type the word café on two computers. On a Mac the é arrives as one unit, U+00E9. On a system with a decomposed input method it arrives as two, the plain letter U+0065 followed by a combining acute accent U+0301. On screen the two strings are pixel for pixel identical. In a program they are unequal, they have different lengths, and a tokenizer trained on one will split the other into pieces it never saw during training. Arabic has the same trap: a legacy keyboard emits the lam-alef ligature U+FEFB as one character, while a modern one emits U+0644 then U+0627.
To reason about this you need three separate words. Unicode assigns every character a Code point, an integer between 0 and 0x10FFFF written with a U+ prefix in hexadecimal, such as U+0061 for the letter a. That gives 1,114,112 possible positions, and the standard covers Latin, Cyrillic, Arabic, Han, emoji and over 170 other scripts within them. A Glyph is the rendered shape a font draws for a code point. The Unicode core specification is explicit that an abstract character should not be confused with a glyph: the code point is the identity, the glyph is one of many possible pictures of it.
The mapping runs many to many in both directions. One glyph can stand for several code points: the é above is one shape and two encodings, and Latin A (U+0041), Cyrillic А (U+0410) and Greek Α (U+0391) look identical in most fonts. One code point can also have several glyphs: the Arabic letter beh U+0628 is drawn as isolated, initial, medial or final form depending on its neighbours, yet the text stores a single code point and the font chooses the shape.
ASCII: the first 128 code points
The oldest slice of Unicode is ASCII, a 7-bit code with 128 positions defined in RFC 20, which occupies code points U+0000 to U+007F unchanged. The slide shows a fragment of the table in hexadecimal and decimal. The version below adds the binary column, because the binary reveals a fact you will use every time you write a case-folding rule.
| Character | Hex | Decimal | Binary |
|---|---|---|---|
| < | 3C | 60 | 0011 1100 |
| = | 3D | 61 | 0011 1101 |
| > | 3E | 62 | 0011 1110 |
| ? | 3F | 63 | 0011 1111 |
| @ | 40 | 64 | 0100 0000 |
| A | 41 | 65 | 0100 0001 |
| B | 42 | 66 | 0100 0010 |
| C | 43 | 67 | 0100 0011 |
| \ | 5C | 92 | 0101 1100 |
| ] | 5D | 93 | 0101 1101 |
| ^ | 5E | 94 | 0101 1110 |
| _ | 5F | 95 | 0101 1111 |
| ` | 60 | 96 | 0110 0000 |
| a | 61 | 97 | 0110 0001 |
| b | 62 | 98 | 0110 0010 |
| c | 63 | 99 | 0110 0011 |
Compare A at 0100 0001 with a at 0110 0001. They differ in exactly one bit, bit 5, which is worth 0x20 or 32. Every uppercase letter sits 32 positions before its lowercase partner, so Case folding in ASCII is a single OR with 0x20, which is why it is so fast and why it breaks the moment text leaves ASCII. Arabic has no case, and Greek final sigma has no simple bit rule, so Unicode defines case folding as a table lookup instead.
Normalization: one canonical spelling for each character
Back to café. Unicode calls U+00E9 the precomposed form and U+0065 U+0301 the decomposed form, and declares them canonically equivalent: they must be treated as the same character. Arabic has the same pair. Alef with hamza above, U+0623, decomposes canonically to bare alef U+0627 plus the combining hamza above U+0654, as the Unicode code chart records. A different, weaker relation is compatibility equivalence: the lam-alef ligature U+FEFB is compatible with U+0644 U+0627, and the ligature fi (U+FB01) with the two letters f and i. Compatibility variants look different or carry formatting, so folding them loses information.
Unicode normalization converts text to one of four standard forms defined in Unicode Standard Annex 15. The D forms decompose, the C forms decompose and then recompose, and the K forms also apply the lossy compatibility mappings. The table gives the result for é, for the lam-alef ligature and for the fi ligature, with the UTF-8 byte count of each result so you can see that normalization changes lengths, not only identities.
| Form | Operation | é (either spelling) | ﻻ U+FEFB | fi U+FB01 |
|---|---|---|---|---|
| NFD | Canonical decomposition | U+0065 U+0301 (3 bytes) | U+FEFB (3 bytes) | U+FB01 (3 bytes) |
| NFC | Canonical decomposition, then canonical composition | U+00E9 (2 bytes) | U+FEFB (3 bytes) | U+FB01 (3 bytes) |
| NFKD | Compatibility decomposition | U+0065 U+0301 (3 bytes) | U+0644 U+0627 (4 bytes) | f i (2 bytes) |
| NFKC | Compatibility decomposition, then canonical composition | U+00E9 (2 bytes) | U+0644 U+0627 (4 bytes) | f i (2 bytes) |
Worked example
NFD and NFC on a Latin and an Arabic letter
Decompose é
NFD(U+00E9) looks up the canonical decomposition and returns U+0065 U+0301, base letter then combining mark. Two code points, three UTF-8 bytes.Recompose é
NFC(U+0065 U+0301) first decomposes (no change) and then composes the base with its mark, returning U+00E9. One code point, two bytes. Both input spellings end in the same place.Decompose alef with hamza
NFD(U+0623) returns U+0627 U+0654, and NFC folds that pair back to U+0623. Bare alef U+0627 stays bare under every form: it is a different letter, not a variant spelling.Fold the ligature
NFC(U+FEFB) is unchanged, three bytes. NFKC(U+FEFB) returns U+0644 U+0627, two letters and four bytes, because the ligature is only compatibility-equivalent.Result
NFC is the safe default for storage, because it is lossless and shortest. NFKC is the matching form, because it also folds ligatures, fullwidth digits and superscripts, at the price of erasing them.
Here is the tokenizer bug normalization prevents. Suppose a BPE vocabulary is learned on NFC text, so "café" is one frequent token. Now NFD input arrives. The tokenizer sees c a f e U+0301, finds no merge that includes a lone combining mark, and emits "cafe" plus a separate accent token that no training word ever contained. Equality tests, dictionary lookups and Tokenization all fail in the same way, and all three are fixed by normalizing once, at ingestion, before anything else runs. Note that alef normalization in Arabic NLP toolkits, which folds U+0623, U+0625 and U+0622 into bare alef, is an application rule on top of Unicode, not one of the four forms.
Recall
Define code point and glyph, and give one example of each direction of the many-to-many mapping.
Recall
Name two normalization forms and one tokenization bug that normalization prevents.
Quick check
Which statement correctly separates Unicode from UTF-8?
Quick check
NFC applied to the sequence U+0065 U+0301 produces what?
The Arabic word كتاب has four letters, and Python's len agrees: four. Encode it and count the bytes: eight. The English word "Hi" is two and two. The difference is the whole content of this concept. Every Code point above U+007F needs more than one byte, and the rule that decides how many, and which bits go where, is small enough to learn by heart.
UTF-8 is a variable-length encoding form that maps each Unicode scalar value (a code point that is not a surrogate) to a sequence of one to four bytes. It is defined in the Unicode core specification and, for the Internet, in RFC 3629. Code points below 0x80 take one byte, and most other characters take two, three or four. The slide's mapping table is the exact rule, reproduced here with the payload capacity of each row.
| Range | Code point bits | Payload bits | Byte 1 | Byte 2 | Byte 3 | Byte 4 |
|---|---|---|---|---|---|---|
| U+0000 to U+007F | 0xxxxxxx | 7 | 0xxxxxxx | |||
| U+0080 to U+07FF | 00000yyy yyxxxxxx | 11 | 110yyyyy | 10xxxxxx | ||
| U+0800 to U+FFFF | zzzzyyyy yyxxxxxx | 16 | 1110zzzz | 10yyyyyy | 10xxxxxx | |
| U+010000 to U+10FFFF | 000uuuuu zzzzyyyy yyxxxxxx | 21 | 11110uuu | 10uuzzzz | 10yyyyyy | 10xxxxxx |
Read the templates as two kinds of byte. The first byte of every sequence is the lead byte, and its run of leading ones announces the length: 0 means one byte, 110 two, 1110 three and 11110 four. Every later byte is a continuation byte and starts 10, leaving six payload bits. The payload capacities are 7, 11, 16 and 21 bits, and the last is no accident: the largest code point U+10FFFF is exactly a 21-bit number, so four bytes suffice for the whole codespace.
Worked example
Encode the Arabic letter beh, U+0628
Write the code point in binary
0x0628 is 0000 0110 0010 1000. Its value 1576 lies between 0x80 and 0x7FF, so it takes the two-byte row with 11 payload bits.Take the low 11 bits
Drop the five leading zeros: 11000 101000. The template is 110yyyyy 10xxxxxx, so yyyyy = 11000 and xxxxxx = 101000.Fill the templates
Byte 1 is 110 + 11000 = 11011000 = D8. Byte 2 is 10 + 101000 = 10101000 = A8.Result
U+0628 encodes as D8 A8, two bytes.
Worked example
Encode the grinning face emoji, U+1F600
Pick the row
0x1F600 is above 0xFFFF, so it takes the four-byte row with 21 payload bits: 0 0001 1111 0110 0000 0000.Split into u, z, y, x groups
uuuuu = 00001, zzzz = 1111, yyyyyy = 011000, xxxxxx = 000000. The five u bits straddle two bytes: the top three go into byte 1 and the low two into byte 2, exactly as the template 11110uuu 10uuzzzz shows.Fill the templates
Byte 1: 11110 + 000 = F0. Byte 2: 10 + 01 + 1111 = 10011111 = 9F. Byte 3: 10 + 011000 = 98. Byte 4: 10 + 000000 = 80.Result
U+1F600 encodes as F0 9F 98 80, four bytes.
Worked example
Encode the letter a, U+0061
Pick the row and copy
0x61 is below 0x80, so the template is 0xxxxxxx and the seven payload bits 1100001 sit under a leading zero: 01100001.Result
U+0061 encodes as 61, the same number as its ASCII code. Nothing changed.
| Character | Code point | Range | Bytes | UTF-8 (hex) |
|---|---|---|---|---|
| a | U+0061 | U+0000 to U+007F | 1 | 61 |
| é | U+00E9 | U+0080 to U+07FF | 2 | C3 A9 |
| ب | U+0628 | U+0080 to U+07FF | 2 | D8 A8 |
| € | U+20AC | U+0800 to U+FFFF | 3 | E2 82 AC |
| 中 | U+4E2D | U+0800 to U+FFFF | 3 | E4 B8 AD |
| ﻻ | U+FEFB | U+0800 to U+FFFF | 3 | EF BB BB |
| 😀 | U+1F600 | U+010000 to U+10FFFF | 4 | F0 9F 98 80 |
Now do it live. The simulator below encodes whatever you type, one character at a time, and shows the row, the payload bits, the templates and the resulting bytes. It preloads Hi كتاب 😀: nine code points, ten UTF-16 units and sixteen bytes. The € 中 preset exercises the three-byte row, the only one with a 1110 lead byte. Switch the normalization control to NFD with the café preset, or to NFKC with the ligature preset, and watch the byte count change before a single byte is encoded.
- HU+0048U+0000 to U+007F
1 byte, 7 payload bitsbits 100100048010010000xxxxxxx - iU+0069U+0000 to U+007F
1 byte, 7 payload bitsbits 110100169011010010xxxxxxx - ␠U+0020U+0000 to U+007F
1 byte, 7 payload bitsbits 010000020001000000xxxxxxx - كU+0643U+0080 to U+07FF
2 bytes, 11 payload bitsbits 11001000011D9 8311011001110yyyyy1000001110xxxxxx - تU+062AU+0080 to U+07FF
2 bytes, 11 payload bitsbits 11000101010D8 AA11011000110yyyyy1010101010xxxxxx - اU+0627U+0080 to U+07FF
2 bytes, 11 payload bitsbits 11000100111D8 A711011000110yyyyy1010011110xxxxxx - بU+0628U+0080 to U+07FF
2 bytes, 11 payload bitsbits 11000101000D8 A811011000110yyyyy1010100010xxxxxx - ␠U+0020U+0000 to U+007F
1 byte, 7 payload bitsbits 010000020001000000xxxxxxx - 😀U+1F600U+010000 to U+10FFFF
4 bytes, 21 payload bitsbits 000011111011000000000F0 9F 98 801111000011110uuu1001111110uuzzzz1001100010yyyyyy1000000010xxxxxx
Dim cells are the fixed prefix bits of each byte template. Accent cells are the payload bits copied from the code point, left to right. Switch to NFD or NFKC to watch a precomposed letter or a ligature change its byte count before encoding.
Decoding, and why the prefixes matter
The prefixes are what make UTF-8 safe to read from any position. A decoder that sees a byte starting 0 knows it is a complete ASCII character. A byte starting 110, 1110 or 11110 announces one, two or three continuation bytes to follow. A byte starting 10 can never begin a character, so a decoder dropped into the middle of a stream steps backwards at most three bytes to find a lead byte. RFC 3629 lists this as a design property (character boundaries are easily found from anywhere in an octet stream), Jurafsky and Martin call it self-synchronizing, and it is why a corrupted byte damages one character rather than the rest of the file.
Worked example
Decode D8 A8 back to a code point
Read the lead byte
D8 is 11011000. It starts 110, so expect exactly one continuation byte and keep the five bits after the prefix: 11000.Read the continuation byte
A8 is 10101000. It starts 10 as required, so keep the six bits after the prefix: 101000.Concatenate
11000 + 101000 = 11000101000 = 0x628.Result
U+0628, the letter beh. Had the second byte started with anything other than 10, the decoder would report an error at that exact position.
Three consequences follow from the table and are worth stating explicitly. First, backward compatibility with ASCII: every code point below 0x80 becomes the single byte with the same value, so an ASCII file is already a valid UTF-8 file and a UTF-8 file that happens to contain only ASCII is byte for byte an ASCII file. Second, bytes below 0x80 never appear inside a multibyte sequence, so a program that searches for a space or a newline byte will never find one in the middle of an Arabic letter. Third, some byte values are simply impossible: C0, C1 and F5 to FF never occur in valid UTF-8, because they would encode overlong sequences or code points past U+10FFFF (RFC 3629, section 1, with the syntax that rules them out in section 4).
Recall
How many UTF-8 bytes do U+0628, U+00E9, U+4E2D and U+1F600 need, and why?
Recall
Encode ñ, U+00F1, by hand.
Recall
Why is UTF-8 backward compatible with ASCII, and how does a decoder find where a character starts?
Quick check
How many bytes does UTF-8 use for the Arabic letter beh, U+0628?
Quick check
A decoder reads a byte whose top bits are 10. What does it conclude?
A tokenizer trained with a character vocabulary in 2020 meets the melting face emoji 🫠, added as U+1FAE0 in Unicode 14 the following year. It has never seen the character, so it emits the unknown token. The same happens with an Arabizi spelling like "ktaab" typed into a word-level vocabulary, or any typo, any new script, any rare Han character. Now imagine the base symbols were bytes instead. Every UTF-8 byte is one of exactly 256 values, all of them already in the vocabulary, so there is nothing left to be unknown.
That is Byte-level tokenization: treat the UTF-8 bytes of the text, not its characters, as the atoms. Radford and colleagues motivated it in the GPT-2 paper with the numbers. A Code point base vocabulary would start at over 130,000 symbols before a single merge (about 160,000 in Unicode 17), while a byte-level base needs only 256. Jurafsky and Martin draw the consequence: because there are only 256 possible byte values, there will be no unknown tokens, and the <UNK> symbol becomes unnecessary. Arbitrary scripts, emoji and typos all decompose into bytes the model has seen.
| Property | Character vocabulary | Byte vocabulary |
|---|---|---|
| Base symbols | About 160,000 assigned characters (over 130,000 when GPT-2 was written) | Exactly 256 byte values |
| Unknown input possible | Yes, any unseen character | No, every byte is in the base |
| Base units for كتاب | 4 characters | 8 bytes |
| Interpretability of a base unit | A letter you can read | Half of an Arabic letter |
| Structure | Given by the script | Recovered by learned merges |
The price is structure. Bytes do not know where a character ends. The word كتاب becomes eight opaque symbols, and the letter ب is D8 A8, two units that mean nothing on their own. A sentence of Arabic is roughly twice as many base units as Latin text of the same length, which the simulator's كتاب preset shows as 4 characters against 8 bytes. Left alone, a byte model would spend its capacity relearning the UTF-8 table. So bytes are never used alone: they are combined with learned merges that glue frequent byte sequences back into characters, then into subwords. Jurafsky and Martin note that BPE over bytes rediscovers the two- and three-byte UTF-8 sequences early, and that it can also learn invalid sequences straddling a character boundary, which implementations filter out. GPT-2 adds a rule that prevents merges across character categories, because naive byte merges were sub-optimal.
Recall
What does Python return for len of the string كتاب, and for len of its UTF-8 encoding? Which number does a byte-level tokenizer start from?
Quick check
How large is the base vocabulary of a byte-level tokenizer before any merges?
Recap
If you remember nothing else
- A corpus is a situated sample that varies by domain, genre, time, demographics and variety. Models inherit its limits, so document it with a data statement or datasheet.
- Datasheet fields: motivation and intended use, composition, collection process, preprocessing and annotation, ethical considerations, distribution constraints.
- A code point is an integer from U+0000 to U+10FFFF. A glyph is the drawn shape. The two map many to many.
- ASCII is the first 128 code points, and upper and lower case letters differ by exactly 0x20.
- NFC and NFD make é and e plus U+0301 compare and tokenize alike. Only NFKC and NFKD also fold the ligature ﻻ into ل plus ا.
- UTF-8 uses 1, 2, 3 or 4 bytes with lead patterns 0, 110, 1110, 11110 and continuation bytes 10xxxxxx. Payloads hold 7, 11, 16 and 21 bits.
- U+0628 is D8 A8, U+00E9 is C3 A9, U+4E2D is E4 B8 AD, U+1F600 is F0 9F 98 80, and a is 61.
- String length counts code points, not bytes: كتاب is 4 code points and 8 bytes.
- Bytes give a 256-symbol base with no unknown character. Learned merges restore the structure that bytes lose.
Sources
- Speech and Language Processing, chapter 2: Words and TokensBookJurafsky and Martin, 3rd edition draftSections 2.3 on code points, UTF-8 and its self-synchronizing property, 2.4.3 on byte-level BPE, 2.5 on corpora and datasheets(opens in a new tab)
- RFC 3629: UTF-8, a transformation format of ISO 10646RFCIETFByte templates (section 3); section 1 lists ASCII compatibility, character boundaries findable from any position, and the bytes C0, C1, F5 to FF never appearing(opens in a new tab)
- The Unicode Standard, Version 17.0, chapter 3: ConformanceDocsUnicode ConsortiumAbstract character versus glyph (D7), codespace and code point (D9, D10), UTF-8 encoding form (D92)(opens in a new tab)
- Unicode Standard Annex 15: Unicode Normalization FormsDocsUnicode ConsortiumTable 1 defines NFD, NFC, NFKD and NFKC(opens in a new tab)
- FAQ: UTF-8, UTF-16, UTF-32 and BOMDocsUnicode ConsortiumUnicode versus encoding forms, the 21-bit codespace, UTF-16 in Java and Windows(opens in a new tab)
- Arabic code chart, U+0600 to U+06FFDocsUnicode ConsortiumU+0623 decomposes canonically to U+0627 U+0654; U+0628 ARABIC LETTER BEH(opens in a new tab)
- Arabic Presentation Forms-B code chart, U+FE70 to U+FEFFDocsUnicode ConsortiumU+FEFB is compatibility-equivalent to U+0644 U+0627(opens in a new tab)
- RFC 20: ASCII format for network interchangeRFCIETF7-bit code, 0x5B is [, 0x5D is ], A is 0x41 and a is 0x61(opens in a new tab)
- Unicode HOWTODocsPython documentationlen counts code points; encode returns bytes; unicodedata.normalize(opens in a new tab)
- Data Statements for Natural Language Processing: Toward Mitigating System Bias and Enabling Better SciencePaperTransactions of the ACL, Bender and Friedman, 2018Schema sections A to I; systems work best for the populations represented in their training data(opens in a new tab)
- Datasheets for DatasetsPaperCommunications of the ACM, Gebru et al., December 2021Motivation, composition, collection, preprocessing, uses, distribution, maintenance; the electronics datasheet analogy(opens in a new tab)
- Language Models are Unsupervised Multitask LearnersPaperOpenAI, Radford et al., 2019Section 2.2: over 130,000 code point symbols versus a 256-symbol byte base; merges blocked across character categories(opens in a new tab)
- Usage statistics of UTF-8 for websitesArticleW3TechsUTF-8 used by 99.1% of websites with a known encoding, September 2026(opens in a new tab)
Part 06: Tokenization units and byte-pair encoding
Words, characters or subwords as the unit of text, the tradeoffs among them, and the BPE algorithm that learns a subword vocabulary by merging frequent pairs, traced by hand on a toy corpus.
5 concepts, slides 38-48
Why this part matters
Every model you train or evaluate in this course fixes a tokenizer before it sees a single example. That choice decides the vocabulary size, the sequence length, the cost per token, and whether an Arabic clitic such as wa- or the article al- ends up as a clean unit or as a fragment glued to whatever follows it.
This part first asks what a good tokenization is trying to achieve, then compares the three obvious units (words, characters, subwords) against those goals. The second half is the byte-pair encoding algorithm that modern language models use to learn subwords from data: how it trains, a full hand trace on the toy corpus from the slides with every pair count shown, and how the learned merges are replayed on new text. Tracing BPE by hand is a standard exam exercise, and reading a merge list is what you will do when you debug a Hugging Face tokenizer on Arabic text in your research.
By the end you can
- Name the four properties a tokenization balances and place words, characters and subwords on the coverage versus compactness spectrum.
- Write the BPE training pseudocode from memory and explain the role of k.
- Run BPE by hand for k merges on a small corpus, showing pair counts, stating the tie rule, and listing the vocabulary and final corpus.
- Encode an unseen word by replaying the merge list in order and explain why order matters.
- Explain how a leading space marker versus an end-of-word marker changes the learned units.
Take the six-word string that the rest of this part will keep returning to: set new new renew reset renew. Split it at spaces and you have one token per word. Split it into characters, keeping a marker for each space, and you have one token per character. Split it with the subword vocabulary this part will learn and you get something in between.
| Split | Tokens N | Distinct types | Vocabulary |V| |
|---|---|---|---|
| Words | 6 | 4 | 4 |
| Characters with marker | 29 | 7 | 7 |
| BPE, k = 4 | 15 | 6 | 11 |
Same text, three very different values of N and |V|. That is the whole subject of Tokenization in one example. Tokenization converts raw text into a sequence of tokens, the units that a model or a pipeline operates on, and the choice of unit fixes three things at once: the Vocabulary the model must learn embeddings for, the length of every input, and what the model can represent at all. Earlier parts of this lecture treated the unit as given and asked what a word is. This part reverses the question: given that we may pick the unit, what should we optimise for?
Four properties a tokenization balances
- Coverage
- How rarely new text contains a token the vocabulary cannot represent. Characters and bytes win outright; words lose.
- Compactness
- How short the token sequence is for a given text. Words win; characters lose by a factor of four to six.
- Meaningfulness
- How well one token lines up with one unit of meaning. Words win; characters carry almost none on their own.
- Cross-lingual applicability
- Whether the same scheme works for Arabic, Chinese and English alike. Data-driven subwords and bytes win; whitespace words fail for scripts without spaces.
The four criteria pull in different directions, which is why no single unit wins. Coverage asks how often new text contains something the vocabulary cannot represent, the out-of-vocabulary problem from the Zipf and Heaps part: with word tokens the answer is always, because Heaps' law says the word vocabulary never closes. Compactness asks how many tokens the model must process; every token costs attention, memory and, in a hosted API, money. Meaningfulness asks whether one token corresponds to one unit of meaning, which makes downstream tasks such as tagging and parsing easier. Cross-lingual applicability asks whether the same scheme works for Chinese, which has no spaces, and for Arabic, whose clitics glue several words into one whitespace token.
There is also a quieter reason to care. Jurafsky and Martin note that a standardised tokenization is essential for replicability: a perplexity or an F1 score only means something if the units are the same, so the tokenizer is part of the experimental protocol, not a preprocessing detail (SLP3, chapter 2).
Recall
Name the four properties a good tokenization balances, and say which one word tokens fail on.
Take the word renewed. As a word token it is one unit, but only if it appeared in training; if it did not, it is an unknown. As characters it is 7 letters, eight tokens with the leading space marker, none of which means anything alone. With the subword vocabulary learned later in this part it is four tokens, _re new e d, and the model has seen every one of them before even though it has never seen the word. Those three outcomes are the three granularities, and each buys something by paying for something else.
| Words | Characters | Subwords | |
|---|---|---|---|
| Vocabulary size | Very large and open-ended; grows with the corpus under Heaps' law | Tiny: the letters of a script, or 256 byte values | Fixed by design, typically 32k to 200k units |
| Sequence length | Shortest: one token per word | Longest: one token per character | In between: frequent words stay whole, rare words split |
| Out-of-vocabulary | Yes: rare words, misspellings, new names | None: every string is spelled from the base symbols | Almost none: unseen words are composed from pieces |
| Meaningfulness | Highest; aligns with lexicons and syntax | Spread across many tokens; the model must learn to reassemble it | Often morpheme-like, but only by accident of frequency |
| Best use | POS tagging, parsing, lexicon-driven systems that need explicit word boundaries | Noisy OCR or typo-heavy text, very low-resource languages, specialised tasks | Modern language models and machine translation |
Words: meaningful, but the vocabulary never closes
Word tokens are the natural first choice. They are meaningful, they line up with dictionaries and with the lexicons that rule-based systems consult, and tasks that need explicit word boundaries, such as part-of-speech tagging, syntactic parsing and lexicon-driven pipelines, are built around them. The problems are the ones the first four parts of this lecture already exposed. "Word" is ambiguous across languages: punctuation, contractions, compounds, clitics and scripts without spaces all give different answers. And the vocabulary is enormous and open-ended. Heaps' law says |V| keeps growing with N, so however large the training corpus, test text brings rare and unseen words, and every one of them is an OOV token the model must lump into a single UNK symbol.
Characters: nothing is unknown, but everything is long
Go to the other extreme and tokenize into characters. The vocabulary is tiny, a few dozen letters for Latin script or, at byte level, exactly 256 values. There is no OOV at all, because any string is spelled from the base symbols, and the tokenizer is robust to spelling variants, typos and OCR noise since a damaged word still shares most of its characters with the clean one. The price is paid in sequence length and in meaning. The 6-word string became 29 tokens, roughly five times longer, and attention cost grows with the square of that length. Two words that sit a sentence apart become dozens of positions apart, so a model with a fixed context window sees less of the surrounding text and must learn dependencies across far longer distances. Meaning is spread across many tokens, so the model must spend capacity learning to reassemble words before it can learn anything about them, which makes training slower. Characters are the right call for noisy text, for extremely low-resource languages where no subword vocabulary can be trusted, and for specialised tasks such as transliteration where the character really is the unit.
Subwords: the middle ground modern models use
Subword tokenization keeps a vocabulary smaller than a word vocabulary and produces sequences shorter than a character sequence, and its decisive property is the third row of the table: an unseen word is composed from pieces that were seen. The GPT-2 paper describes byte-pair encoding as a practical middle ground that interpolates between word-level inputs for frequent symbol sequences and character-level inputs for infrequent ones (Radford et al., 2019). Frequent words stay whole, rare words shatter into recognisable parts, and nothing is ever unknown.
There are two ways to decide what the pieces are. The linguistically natural way is to cut at morphemes, since morphemes carry meaning by definition. But the morphology parts of this lecture showed why that is hard: allomorphy changes the shape of a morpheme by context, non-concatenative morphology interleaves an Arabic root with a pattern rather than concatenating anything, and clitics blur the boundary between word and affix. Morpheme segmentation needs a linguist, or a model, per language, and it remains ambiguous even then. It is useful in some applications but it is not the common approach in modern language models. The language-agnostic way is to learn frequent substrings from data with no linguistic knowledge at all. That is what byte-pair encoding does, and Jurafsky and Martin name BPE and unigram language modelling as the two families widely used in modern language models (SLP3, section 2.4). They work across languages, reduce OOV and stay efficient, which is why the rest of this part is about one of them.
Quick check
Which statement about tokenization granularity is correct?
Recall
Give one reason to prefer characters over words, and one reason to prefer subwords over both.
Here is the smallest corpus that shows the whole idea, borrowed from Jurafsky and Martin: the ten-character string A B D C A B E C A B with the vocabulary {A, B, C, D, E}. Look for the pair of adjacent symbols that occurs most often. It is A B, three times. Replace every A B with a new symbol AB and add it to the vocabulary. Now look again.
Worked example
Two merges on a ten-character corpus
Merge (A, B)
The corpus becomes AB D C AB E C AB, seven symbols instead of ten, and the vocabulary grows to six: {A, B, C, D, E, AB}.Merge (C, AB)
The most frequent adjacent pair is now C AB, twice. The corpus becomes AB D CAB E CAB, five symbols, and the vocabulary grows to seven with CAB.Two merges
Vocabulary 5 to 7, corpus 10 to 5 symbols. Each merge adds exactly one vocabulary entry and shortens the corpus by the number of non-overlapping occurrences of the pair it replaces (for pairs of two different symbols, simply the pair count).
That loop is the entire training algorithm of byte-pair encoding. The slide's pseudocode, which is Figure 2.6 of SLP3 adapted from Bostrom and Durrett (2020), reads as follows. Start with V equal to the set of unique characters (or bytes) in the Corpus C. Then repeat k times: find the most frequent pair of adjacent tokens tL, tR in C, form the new token tNEW = tL + tR by concatenation, add it to V, and replace every occurrence of tL tR in C with tNEW. Return V. One iteration of the loop is a Merge.
Sennrich, Haddow and Birch, who introduced BPE for neural machine translation, state the consequence plainly: the final symbol vocabulary size is equal to the size of the initial vocabulary plus the number of merge operations, and the number of merges is the only hyperparameter of the algorithm (Sennrich et al., 2016). In the toy example k is 2 or 4. In practice Jurafsky and Martin describe tens of thousands of merges on a very large corpus to produce vocabularies of 50,000, 100,000 or even 200,000 tokens, and GPT-2's vocabulary is 50,257 entries (Radford et al., 2019).
Two practical details shape the counting. First, merges never cross word boundaries. Sennrich's implementation says it does not consider pairs that cross word boundaries for efficiency, so the corpus is stored as a table of distinct words with their counts, and a pair inside a word that occurs twice in the corpus is counted twice. That is exactly why the slides list the toy corpus as four rows with counts rather than as running text. Second, the base symbols can be characters or bytes. GPT-2 works on UTF-8 bytes because a Unicode Code point vocabulary would start at over 130,000 entries, whereas byte-level BPE starts at exactly 256 and, since every byte is in the vocabulary, there can never be an unknown token (Radford et al., 2019; SLP3, section 2.4).
Where the algorithm came from, and its two halves
The name is older than NLP. Philip Gage published byte pair encoding in 1994 as a data compression method: find the most frequently occurring pair of adjacent bytes in the data, replace all instances with a byte that was not in the original data, and repeat until no further compression is possible (Gage, 1994). Sennrich and colleagues adapted it for word segmentation, and two things changed. The units became characters rather than bytes, and the stopping rule became a fixed k rather than exhaustion, so the merges are no longer a throwaway table for one file but a reusable vocabulary shared by every text the model will ever see.
That reuse is why BPE has two parts. The trainer runs once, over the training corpus, and produces the vocabulary together with the ordered list of merges it performed. The encoder runs every time new text arrives and applies that list. The two must never be confused, and the last concept of this part is devoted to the encoder. The simulator below is the trainer: step through the merges and watch the pair table decide each one, then encode a word of your own in its lower panel; the encoder concept returns to that panel on its own.
- 2_new
- 2_renew
- 1set
- 1_reset
Grey chips are the initial symbols, teal chips are learned merges in the order they were learned, and the accent chip is the newest.
| Rank | Pair | Count | First seen at | Note |
|---|---|---|---|---|
| 1 | (n, e) | 4 | position 1 | winner, tie broken by rule |
| 2 | (e, w) | 4 | position 2 | tie at the maximum |
| 3 | (_, r) | 3 | position 4 | |
| 4 | (r, e) | 3 | position 5 | |
| 5 | (_, n) | 2 | position 0 | |
| 6 | (e, n) | 2 | position 6 | |
| 7 | (s, e) | 2 | position 10 | |
| 8 | (e, t) | 2 | position 11 | |
| 9 | (e, s) | 1 | position 15 |
- characters_renewed
- output_ r e n e w e d (8 tokens)
The encoder never counts anything. It replays the merge list top to bottom; a merge whose pair does not occur in the word is skipped.
Quick check
In one BPE training step, what exactly does the trainer merge?
Recall
State the difference between the BPE trainer and the BPE encoder in one sentence each.
Now run the BPE trainer by hand on the slide Corpus, set new new renew reset renew, for k = 4. This is the same example as the current SLP3 draft (example 2.11 in section 2.4), so the tables here match both the slides and the book symbol for symbol. The slides show the corpus and the Vocabulary after each Merge but never the pair counts that decided it, so those are worked out in full below.
First the setup. Every word except the first carries a leading space marker, drawn as an open box on the slides and written _ here. The first word, set, starts the string, so it has none. Grouping equal words gives four rows with counts: 2 × _ n e w, 2 × _ r e n e w, 1 × s e t and 1 × _ r e s e t. The initial vocabulary is the seven symbols _, e, n, r, s, t, w, and the corpus holds 2 × 4 + 2 × 6 + 3 + 6 = 29 symbols. Pair counts are weighted by row count: a pair that occurs once inside _ n e w counts 2.
Worked example
Four merges on set new new renew reset renew
Merge 1: count every adjacent pair
Pair Count Note (n, e) 4 winner: tied with (e, w), seen first in the corpus (e, w) 4 tie at the maximum (_, r) 3 (r, e) 3 (_, n) 2 (e, n) 2 (s, e) 2 (e, t) 2 (e, s) 1 (n, e) occurs once in _ n e w (count 2) and once in _ r e n e w (count 2), so it scores 4. So does (e, w). The pseudocode says nothing about ties; the slide merges n e, which is the pair encountered first when scanning the corpus. The corpus becomes 2 × _ ne w, 2 × _ r e ne w, 1 × s e t, 1 × _ r e s e t and ne joins the vocabulary.
Merge 2: recount on the new corpus
Pair Count Note (ne, w) 4 winner: the only pair at 4 once ne exists (_, r) 3 (r, e) 3 (_, ne) 2 (e, ne) 2 (s, e) 2 (e, t) 2 (e, s) 1 The old pair (e, w) no longer exists because the e in front of every w is now inside ne. (ne, w) scores 4 alone, so new is merged with no tie. The corpus becomes 2 × _ new, 2 × _ r e new, 1 × s e t, 1 × _ r e s e t.
Merge 3: a second tie
Pair Count Note (_, r) 3 winner: tied with (r, e), seen first (r, e) 3 tie at the maximum (_, new) 2 (e, new) 2 (s, e) 2 (e, t) 2 (e, s) 1 (_, r) and (r, e) both score 3: twice from _ r e new and once from _ r e s e t. The slide merges _ r, again the pair seen first. The corpus becomes 2 × _ new, 2 × _r e new, 1 × s e t, 1 × _r e s e t.
Merge 4: the tie resolves itself
Pair Count Note (_r, e) 3 winner: the only pair at 3 once _r exists (_, new) 2 (e, new) 2 (s, e) 2 (e, t) 2 (e, s) 1 With _r in place, (r, e) has disappeared and (_r, e) inherits its count of 3, the unique maximum. Merging it gives _re. Slide 47 shows merges 3 and 4 together for this reason: the second is forced by the first.
After k = 4
Vocabulary _, e, n, r, s, t, w, ne, new, _r, _re (11 entries, 7 + 4). Corpus 2 × _ new, 2 × _re new, 1 × s e t, 1 × _re s e t, which is 15 symbols instead of 29. Merge list, in order: ne, new, _r, _re.
| Merge | New token (count) | Corpus symbols | |V| | Corpus |
|---|---|---|---|---|
| 0 | start | 29 | 7 | 2 _ n e w, 2 _ r e n e w, 1 s e t, 1 _ r e s e t |
| 1 | ne (4, tie) | 25 | 8 | 2 _ ne w, 2 _ r e ne w, 1 s e t, 1 _ r e s e t |
| 2 | new (4) | 21 | 9 | 2 _ new, 2 _ r e new, 1 s e t, 1 _ r e s e t |
| 3 | _r (3, tie) | 18 | 10 | 2 _ new, 2 _r e new, 1 s e t, 1 _r e s e t |
| 4 | _re (3) | 15 | 11 | 2 _ new, 2 _re new, 1 s e t, 1 _re s e t |
Ties: the pseudocode is silent, so state your rule
Two of the four steps were decided by a tie, and the algorithm as written never says how to break one. Real implementations do have a rule, and it is worth knowing which. The minimal listing in the Sennrich paper (Algorithm 1) picks the best pair with Python's max(pairs, key=pairs.get), which in modern Python (3.7 and later, where dictionaries keep insertion order) returns the first pair encountered while scanning. The released subword-nmt implementation instead selects with max(stats, key=lambda x: (stats[x], x)), which prefers the lexicographically largest pair among the tied ones. On this corpus that rule agrees with the slides at merge 1, choosing (n, e) because n sorts after e, but at merge 3 it chooses (r, e) over (_, r). The Hugging Face course states the first-encountered convention explicitly: when there is a choice of the most frequent pair, the first one encountered is selected. The 🤗 Tokenizers library breaks ties by internal token ids, and the simulator's alphabetical option takes the smallest pair.
The simulator lets you switch between first-seen and alphabetical order, and on this corpus the very first merge already differs: alphabetical order picks (e, w) over (n, e), so the vocabulary ends with ew where the slides have ne, even though the corpus rows after four merges are identical. That is exactly why an exam answer must state its rule.
| First pair seen (slides) | Alphabetical (smallest first) | |
|---|---|---|
| Merge 1 | ne (tied with ew, seen first) | ew (tied with ne, ew < ne) |
| Merge 2 | new from (ne, w) | new from (n, ew) |
| Merge 3 | _r (tied with re, seen first) | _r (tied with re, _ sorts first) |
| Merge 4 | _re | _re |
| Learned tokens | ne, new, _r, _re | ew, new, _r, _re |
| Corpus after k = 4 | 2 × _ new, 2 × _re new, s e t, _re s e t | 2 × _ new, 2 × _re new, s e t, _re s e t |
The order of the rows matters too, because "first encountered" depends on it. The slides list the four rows by count, most frequent first, and so does SLP3. Jurafsky and Martin continue the same trace past the slides: merges five to seven are _new, _renew and se, each decided by a tie at count 2, and merge eight, set, is then forced because (se, t) is the only pair left at 2. The simulator reproduces that order exactly when its rows are listed the same way.
What the space marker does to the learned units
The marker is not cosmetic. Because this deck attaches it to the start of a word, the trainer learned _re, a word-initial piece, and that token is different from a re that occurs inside a word, as in _ c a re, which never merges into _re. Jurafsky and Martin put it this way: the system has essentially induced that there is a word-initial prefix re- (SLP3, section 2.4). This leading-marker convention is the one used by GPT-2, whose byte-level tokenizer shows the space as Ġ, and by SentencePiece, which shows it as ▁ (U+2581); in both, the space rides on the following word (Hugging Face tokenizers documentation). It also explains why set in the trace never carries a marker: it is the first word of the string, so no space precedes it, and s e t and _ s e t are different strings to the trainer.
The original paper did it the other way round. Sennrich, Haddow and Birch appended a special end-of-word symbol to every word so that the original tokenization could be restored after translation, and older SLP3 drafts followed them with the corpus low lowest newer wider new (Sennrich's own listing uses low lower newest widest). Under that convention every word gets a marker, the marker sits at the end, and the prefix would be learned as plain re.
| Leading marker (this deck) | End-of-word suffix (Sennrich 2016) | |
|---|---|---|
| Where the marker sits | Before the word: _new, _re | After the word: new·, set· |
| First word of the string | No marker: set stays s e t | Marker like every word: s e t · |
| Prefix re- is learned as | _re, distinct from word-internal re | re, identical to word-internal re |
| Used by | This deck, current SLP3, GPT-2 (Ġ), SentencePiece (▁) | Sennrich et al. 2016, older SLP3 drafts |
Quick check
After merging ne and new on 'set new new renew reset renew', which pair wins merge 3 under first-seen tie-breaking?
Recall
After merges ne, new, _r and _re on 'set new new renew reset renew', list the vocabulary and the corpus.
Recall
Step 1 counts (n, e) = 4 and (e, w) = 4. What does the pseudocode say about the tie, and what do real implementations do?
Recall
How does the leading space marker change what is learned?
Training is over. The merge list ne, new, _r, _re is frozen, and a word arrives that the trainer never saw: renewed, with its leading space. The encoder does not count anything. It writes the word as characters and replays the four merges, first to last.
Worked example
Encoding three words with the four learned merges
_renewed, an unseen word
Start from _ r e n e w e d, eight symbols. Merge 1 (n e) applies: _ r e ne w e d. Merge 2 (ne w) applies: _ r e new e d. Merge 3 (_ r) applies: _r e new e d. Merge 4 (_r e) applies: _re new e d. Four tokens, all in the vocabulary, no unknown._newest, where two merges do not apply
_ n e w e s t becomes _ ne w e s t, then _ new e s t. Merges 3 and 4 look for _ r and _r e, find neither, and are skipped. Output _ new e s t. The marker stays separate because (_, new) was merge 5 in the book's longer run and is not in this list.set at the start of a sentence
No leading marker, so the input is s e t. None of the four merges mentions s or t, so the output is s e t, three tokens. With k = 8 it would be the single token set.The encoder is a replay
Same list, same order, every input. _renewed is a word the model has never seen, tokenized entirely from pieces it has.
Try the same replay on words of your own. The panel below starts from the four slide merges; type newest, reset or an Arabic transliteration, tick sentence-initial to drop the marker, and raise k to watch _new and set collapse into single tokens once merges five to eight exist.
- 1.ne
- 2.new
- 3._r
- 4._re
- characters_renewed
- 1. n+e_renewed
- 2. ne+w_renewed
- 3. _+r_renewed
- 4. _r+e_renewed
- output_re new e d (4 tokens)
The encoder never counts anything. It replays the merge list top to bottom; a merge whose pair does not occur in the word is skipped.
The rule behind the trace is short. To tokenize a new string, start from characters or bytes, apply the learned merges in the order they were learned, and output the resulting subword sequence. The slide calls this "greedy": each merge fires on every place it matches as soon as its turn comes, the encoder never goes back, and it never searches for the longest vocabulary entry. Jurafsky and Martin are exact about what the encoder does not do: it just runs on the test data the merges learned from the training data, in the order they were learned, and the frequencies in the test data play no role, only the frequencies in the training data (SLP3, section 2.4). Sennrich describes the same procedure: split words into sequences of characters, then apply the learned operations to merge the characters into larger known symbols. Because the list is fixed, the output is deterministic: the same input always yields the same tokens, which is what lets a model trained on one tokenization be served with it later.
Order is not a convention but a dependency. Merge 2 joins ne and w, and ne only exists if merge 1 has already run; merge 4 needs _r from merge 3 in the same way. Replaying the list out of order would produce segmentations that never occurred during training, so the model would receive token ids it has no experience of, even though every id is technically in the vocabulary. With tens of thousands of merges, most words in ordinary text are covered by a single token and only rare words break into pieces, which is the interpolation between word and character level that the GPT-2 paper describes (Radford et al., 2019).
| Trainer | Encoder | |
|---|---|---|
| Input | A training corpus with word counts | One new string |
| Counts pair frequencies | Yes, at every step | Never |
| Output | A vocabulary plus an ordered merge list | A token sequence |
| Runs | Once, before any model is trained | On every input, forever after |
| Hyperparameter | k, the number of merges | None; the merge list is fixed |
One last coverage detail depends on the base symbols. A character-level BPE trained on English letters still has a hole: the Hugging Face course shows mug encoding as [UNK] ug because m never appeared in its toy corpus, so it is not in the vocabulary at all. A byte-level tokenizer starts from all 256 byte values, so any UTF-8 string, including Arabic, emoji and typos, is encodable and the UNK special token is never needed (Radford et al., 2019; Hugging Face tokenizers documentation). The next part looks at what this does to non-Latin scripts.
Quick check
When BPE encodes new text, what decides which merges are applied?
Recall
With merges ne, new, _r, _re learned in that order, how is the unseen word '_renewal' encoded, and why can no other segmentation occur?
Recall
Why must the encoder apply merges in the order they were learned?
Recap
If you remember nothing else
- Tokenization converts text into the units a model operates on; the unit choice trades coverage, compactness, meaningfulness and cross-lingual reach.
- Words: meaningful but ambiguous, huge vocabularies, OOV. Characters: tiny vocabulary, no OOV, very long sequences. Subwords: the middle ground modern LMs use.
- BPE has a trainer and an encoder. The trainer starts from characters or bytes and merges the most frequent adjacent pair k times, so |V| = initial symbols + k.
- On 'set new new renew reset renew' the first four merges are ne (4), new (4), _r (3), _re (3), giving 11 vocabulary items and a corpus of 15 symbols instead of 29.
- Two of those four steps were ties. The pseudocode does not break ties; first-seen order gives ne, alphabetical order gives ew, so always state your rule.
- The leading space marker (GPT-2's Ġ, SentencePiece's ▁) makes _re a word-initial token. Sennrich's original code used an end-of-word suffix instead.
- The encoder replays merges in learned order and never counts frequencies. '_renewed' becomes _re new e d with no unknown token.
- BPE units are frequent substrings, morphemes only by accident.
Sources
- Speech and Language Processing, chapter 2: Words and Tokens (section 2.4, Byte-Pair Encoding)BookJurafsky and Martin, draft of August 19, 2026Same toy corpus as the slides, Figure 2.6 pseudocode, the encoder quote, 50k to 200k vocabularies(opens in a new tab)
- Neural Machine Translation of Rare Words with Subword UnitsPaperSennrich, Haddow and Birch, ACL 2016BPE for segmentation, end-of-word symbol, |V| = initial + merges, Algorithm 1 minimal listing(opens in a new tab)
- subword-nmt: learn_bpe.pyDocsRico SennrichPicks the best pair with max(stats, key=lambda x: (stats[x], x)), so ties go to the lexicographically largest pair(opens in a new tab)
- A New Algorithm for Data CompressionArticlePhilip Gage, The C Users Journal 12(2), 1994The original byte pair compression, which stops when no pair repeats(opens in a new tab)
- Language Models are Unsupervised Multitask Learners (section 2.2)PaperRadford et al., OpenAI, 2019Byte-level BPE: 256 base symbols versus 130,000 code points, vocabulary of 50,257, interpolation between word and character level(opens in a new tab)
- Byte-Pair Encoding tokenization (LLM course, chapter 6)DocsHugging FaceFirst-encountered tie rule, encoding by replaying merges, [UNK] for an unseen character(opens in a new tab)
- Tokenizers: ComponentsDocsHugging FaceByteLevel pre-tokenizer with the Ġ prefix, Metaspace with ▁ (U+2581), no unknown token needed(opens in a new tab)
- Byte Pair Encoding is Suboptimal for Language Model PretrainingPaperBostrom and Durrett, Findings of EMNLP 2020Origin of the pseudocode figure; BPE units versus morphology(opens in a new tab)
Part 07: Tokenizers in practice and sentence segmentation
Pre-tokenizers, byte-level BPE, multilingual fairness, SuperBPE, the design checklist for a real tokenizer, and the related problem of finding sentence boundaries.
4 concepts, slides 49-55
Why this part matters
Part 06 gave you the BPE algorithm on a toy corpus. This part shows what happens between that algorithm and the tokenizer inside GPT-4o or Llama 3, and why the answer matters for an Arabic NLP researcher more than for most people. An English-centric tokenizer can double or triple the cost of Arabic input and shortens the context window a model can spend on it, and every choice that causes that was made by a designer before training started.
We start from one real sentence and account for every split. That takes us through the pre-tokenizer, byte-level BPE and the reason a token can cut an Arabic letter in half. We then measure who pays for a shared vocabulary, look at SuperBPE, which lets merges cross spaces, and compress the whole design space into a checklist. The part closes with sentence segmentation, the first step of any parsing or translation pipeline, where one glyph, the period, can end a sentence, close an abbreviation or sit inside a number.
By the end you can
- Read a real tokenization such as the slide sentence and explain every split by a pre-tokenizer rule or a BPE merge.
- State the pros and the con of byte-level BPE with a concrete byte split of an Arabic letter.
- Compute a tokenization premium and name three costs of over-segmenting a low-resource language.
- Explain what SuperBPE changes in the BPE curriculum and what it trades away.
- Walk the tokenizer design checklist and give one consequence for each choice.
- Disambiguate a period with rule-based, statistical and Punkt-style evidence.
Type the sentence Anyhow, she's seen Jane's 224123 flowers anyhow! into the tiktokenizer web app and pick GPT-4o. You get thirteen tokens, and Jurafsky and Martin use exactly this figure in SLP3 to introduce tokenizers in practice. Three of those tokens are worth staring at before any theory: Anyhow at the start is two tokens, Any and how, while the same word at the end is one token, ·anyhow. Jane's becomes ·Jane plus 's, but she's stays whole. And 224123 becomes 224 and 123, with a lone space token in front of it.
| Tokenizer | Vocabulary | Tokens | Pieces |
|---|---|---|---|
| o200k (GPT-4o) | 200K | 13 | Any | how | , | ·she's | ·seen | ·Jane | 's | · | 224 | 123 | ·flowers | ·anyhow | ! |
| cl100k (GPT-4) | ~100K | 14 | Any | how | , | ·she | 's | ·seen | ·Jane | 's | · | 224 | 123 | ·flowers | ·anyhow | ! |
| gpt2 | 50,257 | 14 | Any | how | , | ·she | 's | ·seen | ·Jane | 's | ·224 | 123 | ·flowers | ·any | how | ! |
Every one of these splits has a mechanical explanation, and none of it is in the BPE trainer from part 06. A shipping BPE tokenizer is a pipeline. Hugging Face documents it as four stages: normalization, pre-tokenization, the model (BPE, unigram or WordPiece), and post-processing that adds special tokens. The slide lists the families: most modern tokenizers are BPE-like, with unigram (Kudo 2018) and WordPiece (Schuster and Nakajima 2012) as the variants, and almost all of them sit behind a regex-based Pre-tokenizer.
The pre-tokenizer sets the fences
The pre-tokenizer is a Regular expression that cuts the text into pieces before any merge is counted. GPT-2 uses one pattern, and cl100k and o200k are tightened variants of it. Its alternatives are tried left to right at every position, which is why the order in the table below matters.
The six alternatives of the GPT-2 pre-tokenizer regex, in order
- 's|'t|'re|'ve|'m|'ll|'d
- A clitic contraction becomes its own piece, so 's is split off Jane
- ?\p{L}+
- An optional space followed by letters: a word, with its leading space glued on
- ?\p{N}+
- An optional space followed by any run of digits, unbounded in GPT-2; cl100k and o200k replace it with \p{N}{1,3}, three digits and no space
- ?[^\s\p{L}\p{N}]+
- An optional space followed by punctuation or symbols, so ! and , are pieces
- \s+(?!\S)
- Runs of whitespace that are not followed by a non-space, which keeps one space for the next word
- \s+
- Any remaining whitespace, such as newlines
On We're 350 dogs! Um, lunch? this regex yields We, 're, ·350, ·dogs, !, ·Um, ,, ·lunch, ?, the example SLP3 prints asFigure 2.15. The consequence for BPE is the single most useful rule of this part. Pair counts during training and merges during encoding are computed inside each piece. A pair whose two halves sit in different pieces is never a candidate, so the pre-tokenizer sets an upper bound: the final tokens are always parts of its pieces, never unions of them.
Now the three observations explain themselves. The leading space is part of the word piece, so ·anyhow and Anyhow are different byte strings. The lowercase, space-prefixed form is common in running text and earned a merge up to a whole token; the capitalized, sentence-initial form is rarer and stops at Any plus how. Position matters because the space marker is inside the piece. Second, the Clitic 's: under GPT-2 and cl100k the first regex alternative cuts every contraction into its own piece, so ·she and 's can never merge. o200k attaches the contraction to the word alternative, so the piece is ·she's, and since she's is a very frequent English word it has a token of its own, while ·Jane's does not and falls back to ·Jane plus 's. SLP3 states the same asymmetry: clitics are segmented off proper nouns like Jane but counted as part of frequent words like she's. Third, the digits: cl100k and o200k replace ·?\p{N}+ with \p{N}{1,3}, which caps any digit run at three per piece and, because the new alternative has no optional space, leaves the space before 224123 as a piece of its own (token id 220 in both vocabularies).
Recall
What does a pre-tokenizer do, and why do BPE merges normally not cross its pieces?
Vocabulary size is a parameter, not a result
The Vocabulary of a BPE tokenizer is its base symbols plus one entry per merge, and the trainer stops after k merges. Nothing in the algorithm chooses k. The slide gives a typical range of 30k to 100k; real systems now go further, and SLP3 quotes 50,000, 100,000 or even 200,000.
Vocabulary sizes shipped with well-known models
- GPT-2 (byte-level BPE)
- 50,257
- BERT base (WordPiece)
- 30,522
- Llama 3 (BPE)
- 128K
- GPT-4o, o200k (byte-level BPE)
- 200K
A larger vocabulary buys shorter sequences and more whole-word tokens, at the cost of a bigger embedding table and rarer training examples per token. The number is chosen, and the next concept shows that who benefits from that budget is also chosen.
Byte-level BPE: 256 symbols and no unknown character
The base symbols of the toy BPE in part 06 were characters. Radford et al. (2019) explain why GPT-2 did not do that: a base vocabulary of Unicode code points would exceed 130,000 entries before a single merge, while a byte-level version needs only 256. So GPT-2 runs merges over the UTF-8 bytes of the text. That is Byte-level tokenization, and its three advantages on the slide follow from one fact: every string in every script is a sequence of bytes from a set of 256, so there is no OOV character, any script works, and noisy text with typos or stray symbols still tokenizes. GPT-2 also adds a rule that prevents merges across character categories, with an exception for spaces, so that dog., dog! and dog? do not each become a token.
The con on the slide, that tokens may be less interpretable, is not abstract for Arabic. Arabic letters live in U+0600 to U+06FF, inside the two-byte range of UTF-8, so every letter is two bytes and a merge boundary can fall between them.
Worked example
One Arabic word through three byte-level tokenizers
Code points
كتاب is four code points: U+0643 kaf, U+062A ta, U+0627 alif, U+0628 ba.UTF-8 bytes
Each is two bytes: D9 83, D8 AA, D8 A7, D8 A8. Eight bytes, so the base encoding is eight symbols.Apply each vocabulary's merges
Tokenizer Tokens Byte groups What a human sees gpt2 5 D9 | 83 | D8 AA | D8 A7 D8 | A8 Kaf split in two, fourth token straddles alif and the lead byte of ba cl100k 3 D9 83 | D8 AA | D8 A7 D8 A8 Every token is whole letters o200k 1 D9 83 D8 AA D8 A7 D8 A8 The whole word is one token Interpretability is repaired by merges, not by the base
Under gpt2 the letter kaf is D9 in one token and 83 in another, and the fourth token holds alif plus half of ba. The larger vocabularies had enough Arabic in training to merge whole letters and then the whole word. The base alphabet never changed; the merge budget did.
Try both ideas yourself. The playground below runs the GPT-2 regex on any sentence, replays a small merge list inside each piece and reports which merges the fences blocked, and shows the UTF-8 bytes with the token brackets measured above.
GPT-2 rules: digits unbounded, with the leading space attached, contractions split into their own piece. Vocabulary about 50,257 tokens.
The regex is the GPT-2 pattern with the digit and contraction rules switched per preset. Non-ASCII characters enter the merge stage as their UTF-8 bytes, exactly as in byte-level BPE. The merge list is a short illustrative one, not the real vocabulary, so token counts in the Pieces and Merges views can differ from tiktoken by a token or two. The byte brackets and the token count shown with them are real measurements.
Quick check
Why do BPE merges normally stop at the boundaries a pre-tokenizer produces?
Quick check
In byte-level BPE, what can happen to a two-byte character such as Arabic kaf?
Recall
Give two pros and one con of byte-level BPE, with a concrete example of the con.
Take one meaning and write it twice. The book is on the table. is six words and a period, and its Arabic translation الكتاب على الطاولة. is three words and a period. Run both through cl100k and the English costs seven tokens while the Arabic costs twelve. Same meaning, half the words, nearly twice the tokens.
| Text | Words | cl100k tokens | o200k tokens | Tokens per word (cl100k) |
|---|---|---|---|---|
| The book is on the table. | 6 | 7 | 7 | 1.17 |
| الكتاب على الطاولة. | 3 | 12 | 6 | 4.0 |
The o200k column shows the fix: a 200K vocabulary trained on more multilingual text spent merges on Arabic, so كتاب became one token and the Arabic sentence dropped to 6 tokens. The premium comes from how the budget was spent, not from the script.
The cause is the merge budget of the previous concept. BPE spends its k merges where the frequencies are, and English dominates the training text of most large models. English words therefore become single tokens while Arabic words fragment into letter-sized byte tokens. SLP3 puts it plainly: multilingual tokenizers tend to use most of their tokens for English, and other languages get poorer representations. The shared Vocabulary is a budget, and a shared budget favors whoever was most frequent when it was allocated. That is the slide's first two bullets in one sentence: shared vocabularies favor high-resource languages, so low-resource languages are over-segmented into longer sequences.
Petrov et al. (2023) turn this into a number. Take parallel sentences, tokenize both, and divide the length for language A by the length for language B. They call the ratio the tokenization premium of A relative to B.
| Language | GPT-2 / RoBERTa tokenizer | ChatGPT / GPT-4 (cl100k) | ByT5 (bytes) |
|---|---|---|---|
| Portuguese | 1.94 | 1.48 | n/a |
| German | 2.14 | 1.58 | n/a |
| Chinese (Simplified) | 3.21 | 1.91 | 0.93 |
| Standard Arabic | 4.40 | 3.04 | 1.60 |
| Burmese | 16.89 | 11.70 | 3.51 |
| Shan | 18.76 | 15.05 | 3.94 |
Arabic pays 4.40 times the English token count under the GPT-2 vocabulary and 3.04 under cl100k. Shan and Burmese pay over fifteen and eleven times. The ByT5 column shows that even byte and character models, which have no merges at all, are unequal because scripts differ in bytes per character: Petrov et al. report up to fifteen times difference for subword models and over four times between the byte encodings of Burmese or Tibetan and Chinese. Ahia et al. (2023) measured the same effect on ChatGPT across 22 languages, with some needing five times as many tokens as others, and name two causes: how much of the language was in the pretraining data, and inherent properties of the language and its script. In their analysis of the BLOOMZ tokenizer they flag Arabic as an outlier with more tokens than some other mid-resourced languages.
Three bills for one over-segmented sentence
- Compute. Every layer processes every token, so three times the tokens is at least three times the work, and self-attention, whose cost grows with the square of sequence length, makes long inputs worse still.
- Context. A window of fixed token length holds a third as many Arabic words as English words. Ahia et al. note that fewer in-context examples fit, which directly lowers few-shot quality.
- Money and quality. APIs bill per token, so Ahia et al. conclude that speakers of many languages are overcharged while obtaining poorer results, and SLP3 adds that fragmented tokens give poorer representations of meaning.
Premium under ArabicBERT, relative to Arabic (Petrov et al. 2023, Table 2)
- Kanuri (Arabic script)
- 1.27
- Acehnese (Arabic script)
- 1.73
- English
- 1.82
The slide's third bullet, that tokenizer design affects fairness and performance, is an engineering decision you can make differently.
Quick check
Under an English-heavy vocabulary, why does Arabic text become more tokens than its English translation?
Recall
Why does a low-resource language get longer token sequences under a shared vocabulary, and what are two consequences?
SuperBPE: letting merges cross the space
If fences limit how much a vocabulary can compress, one way to compress more is to remove a fence late in training. Liu et al. (2025) call this SuperBPE: a pretokenization curriculum for BPE that first learns subwords under the ordinary whitespace fences, then continues with merges that are allowed to bridge whitespace and produce superwords. The figure on the slide is their example. Ordinary BPE gives By | the | way | , | I | am | a | fan | of | the | Milky | Way | ., thirteen tokens. SuperBPE gives By the way | , I am | a | fan | of the | Milky Way | ., seven.
The gain is the slide's stated goal, efficiency. At a 200K vocabulary SuperBPE beats ordinary BPE on every measure the authors report, so the extra training stage pays for itself.
SuperBPE at 200K vs BPE (Liu et al. 2025)
- Token reduction vs BPE
- 33%
- Average over 30 downstream tasks
- +4.0 points
- MMLU
- +8.2 points
- Inference compute
- -27%
SLP3 mentions BoundlessBPE (Schmidt et al. 2025) as a sibling method with the same idea.
The tradeoff is the slide's third bullet, Compositionality. Under ordinary BPE, Way in Milky Way and way in By the way share visible structure with every other use of the word. Once Milky Way is one token, the model must learn its relation to way from scratch, and a rare phrase that almost matches a superword is split in an unfamiliar way. Fewer tokens means less shared structure across tokens.
Every surprise in this part came from a switch someone set before training: whether the space belongs to the next word, whether digits are capped, whether contractions split, how many merges to make, which languages got the budget. The slide collects those switches into a checklist. Treat each line as a question with a consequence, because that is how an exam or a design review will ask it.
The tokenizer design checklist, each choice with its consequence
- Pre-tokenization
- Split on whitespace, and what happens to the space? Drop it (BERT), glue it onto the next piece (·world in GPT-2) or keep it as an explicit symbol (▁ in SentencePiece). The answer decides whether ·anyhow and anyhow can ever share a token, fixes the fences that merges cannot cross, and decides whether detokenization can restore the original text exactly. SuperBPE removes the fence in its second stage.
- Normalization
- NFC or NFD, lowercasing, accent stripping? BERT uncased runs NFD, lowercase and strip-accents, so Héllò becomes hello and case and diacritics are gone for good. GPT-style tokenizers keep them, at the cost of separate entries for Apple and apple.
- Special tokens
- <BOS>, <EOS>, <UNK>, <PAD>. A byte-level tokenizer never needs <UNK>. GPT-2 has one special token, <|endoftext|> at id 50256; BERT adds [CLS] and [SEP] in post-processing.
- Numbers
- Cap digit runs (\p{N}{1,3}) or split every digit? Decides whether 224123 is two tokens or six, and shapes how well the model does arithmetic on numbers it has never seen whole.
- URLs and emoji
- Byte fallback keeps them representable: gpt2 encodes 😀 (F0 9F 98 80) as F0 9F 98 plus 80. A URL becomes a long run of punctuation and word pieces unless a rule keeps it whole.
- Multilingual coverage and bias
- How much of the vocabulary budget each language gets. An English-heavy budget gives Arabic a premium of 3.04 under cl100k and Shan 15.05, which is the fairness cost of the previous concept.
Three answers to the spaces question
The slide's pre-tokenization line asks one question with three real answers. Once the text is split on whitespace, what happens to the space? The choice fixes the Space marker (end-of-word marker) convention the model will live with, and it decides whether detokenization can recover the original string.
| Tokenizer | Pieces | Where the space goes | Detokenization |
|---|---|---|---|
| BERT | Hello | world | Space discarded | Not reversible: detokenization cannot say where the spaces were |
| GPT-2 | Hello | ·world | Space glued onto the next piece | Reversible: every piece carries its leading space |
| SentencePiece | ▁Hello | ▁world | Space kept as an explicit ▁ symbol | Losslessly reversible: replace ▁ by a space and the original text is restored |
BERT throws the space away, which keeps the alphabet small but makes the token stream lossy: don't and the three tokens don, ', t cannot be told apart from a hyphenless spelling once the spaces are gone. GPT-2 glues the space onto the following piece, so ·world and world are different entries and the original spacing is recoverable. SentencePiece keeps the space as an explicit ▁ symbol, which is exactly reversible: swapping ▁ back to a space restores the input byte for byte. It also means the regex fences from the first concept apply to a text where spaces are ordinary symbols, so SentencePiece can in principle learn a merge that crosses what used to be a word boundary.
Quick check
Which checklist choice makes Héllò and hello identical?
The rule that ties the list together is that all of it is frozen with the model. The Unicode normalization form, the case folding decision, the Special tokens and the Pre-tokenizer regex are recorded in the tokenizer file, and Hugging Face's pipeline documentation warns that changing the normalizer or the pre-tokenizer requires retraining the tokenizer, which in turn means the embeddings of the language model no longer match. You cannot fix an English-centric tokenizer after the fact; you choose it before the first gradient step.
Recall
Name four items of the tokenizer design checklist and one consequence for any one of them.
Here is a passage with six periods, one question mark and one exclamation mark: Dr. Ahmad arrived at 5 p.m. on Monday. He paid 3.50 riyals. Was it enough? Yes! It contains four sentences. A splitter that cuts at every period would produce seven fragments and would glue the last two sentences together. Decide for yourself which marks end a sentence before reading the rules.
Click a punctuation mark to cycle it through sentence boundary, not a boundary, and undecided. Then check. Each verdict names the rule and the feature that decided it.
Dr Ahmad arrived at 5 pm on Monday He paid 350 riyals Was it enough Yes
A double bar marks a sentence boundary, a middle dot marks a period that belongs to its word or number. After checking, an accent ring means correct and a teal ring with strikethrough means wrong or undecided.
Sentence segmentation is the task of finding sentence boundaries, and the slide names its usual customers: parsing and machine translation, both of which take one sentence at a time. SLP3 states the difficulty exactly. Question marks and exclamation points are relatively unambiguous markers of sentence boundaries. The period is ambiguous between a sentence boundary and an abbreviation marker such as Dr. or Inc., and it can even do both at once: when a sentence ends in Inc., one period marks the abbreviation and the boundary. Kiss and Strunk (2006) list the period's other jobs too: initials, ordinal numbers and ellipses, and in their corpora abbreviations account for up to 30% of the candidate boundaries.
Rules, lexicons and the joint solution
The slide's rule-based heuristics combine punctuation with capitalization patterns, an abbreviation list and quote or bracket balancing. Each rule is a feature that votes on one candidate mark.
Rule-based features and what they decide
- Punctuation plus capitalization
- A period followed by whitespace and a capitalized token suggests a boundary; a lowercase next token argues against one
- Abbreviation list
- If the token before the period is Dr, Inc, p.m. or another listed abbreviation, the period belongs to the word
- Quote and bracket balancing
- A closing quote or bracket after the mark still belongs to the sentence that is ending
- Digit on both sides
- 3.50 and 3.14 are numbers, so a word tokenizer keeps them whole and the segmenter never sees the period
Notice the conflict in the exercise. After Dr. comes Ahmad, capitalized, so the capitalization rule votes for a boundary and only the abbreviation lexicon overrules it. After p.m. comes on, lowercase, so both rules agree. This is why the slide says segmentation is often done jointly with Tokenization and abbreviation lexicons: if the tokenizer has already kept Dr., p.m. and 3.50 as single tokens, the segmenter never has to look at those periods. Stanford CoreNLP states its rule in exactly that form: a sentence ends when a sentence-ending punctuation mark is not already grouped with other characters into a token, optionally followed by closing quotes or brackets. SLP3 notes the abbreviation dictionary can be hand-built or machine-learned.
Learning the boundary
The statistical approach on the slide replaces hand-tuned votes with a classifier that learns the probability of a boundary from annotated data. Its features are the same evidence the rules used: the token before the mark, whether it is in an abbreviation list, its length, whether the next token is capitalized, whether the next token is a frequent sentence starter, and, in neural models, the surrounding context as a whole. Kiss and Strunk (2006) went one step further and removed the annotation. Their Punkt system, the default sentence tokenizer in NLTK, is unsupervised and language independent. It treats an abbreviation as a very tight collocation of a truncated word and a final period, which can be detected from raw text by asking whether the word occurs with a period far more often than chance, helped by the facts that abbreviations are usually short and sometimes contain internal periods. It also learns frequent sentence starters and collocations across a period. Its measured results on newspaper text are in the table.
Punkt boundary detection results (Kiss and Strunk 2006)
- Mean accuracy, 11 languages
- 98.74%
- Boundary error, English
- 1.65%
- Boundary error, German
- 0.35%
| Approach | How it decides | Needs | Strengths and limits |
|---|---|---|---|
| Rule-based | Hand-written heuristics and a hand-built abbreviation list | None | Fast and transparent; brittle on new domains and languages |
| Statistical (supervised) | P(boundary | context) | Annotated sentence boundaries | Adapts to a domain; needs labeled data per language |
| Punkt (unsupervised) | Abbreviations as tight collocations of a truncated word and a period, plus sentence starters | Raw text only | Language independent; 98.74% mean accuracy over eleven languages |
Quick check
Which punctuation mark is hardest for sentence segmentation, and why?
Recall
Why is the period harder than the question mark for sentence segmentation, and what are two features a classifier would use?
Recall
What does Punkt learn without any labeled data?
Recap
If you remember nothing else
- A production tokenizer is a pipeline: normalization, regex pre-tokenization, a BPE, unigram or WordPiece model, then special-token post-processing.
- Merges live inside pre-tokenizer pieces. The leading space belongs to the piece, so Any|how at the start and ·anyhow later are different byte strings.
- cl100k and o200k cap digit runs at three per piece, and vocabulary size is a design parameter: 50,257 for GPT-2, 30,522 for BERT, 128K for Llama 3, 200K for GPT-4o.
- Byte-level BPE starts from 256 symbols, so nothing is out of vocabulary, but a token may cut a multi-byte character: gpt2 splits kaf into D9 and 83.
- Shared vocabularies favor English. Under cl100k Arabic pays a premium of 3.04 and Shan 15.05 (Petrov et al. 2023); longer sequences cost compute, context and money.
- SuperBPE adds a second stage whose merges cross spaces: up to 33% fewer tokens at 200K, at the price of compositionality.
- Every checklist switch is frozen with the model: pre-tokenization, normalization, special tokens, numbers and URLs, multilingual coverage.
- The period is ambiguous and the question mark and exclamation mark mostly are not. Abbreviation lexicons, capitalization, decimal checks or Punkt's collocation test decide.
Sources
- Speech and Language Processing, 3rd edition draft, chapter 2: Words and TokensBookJurafsky and Martin, StanfordGPT-4o tokenization of the slide sentence (2.4.3), the GPT-2 regex output (2.6.9), sentence segmentation (2.8.1)(opens in a new tab)
- Language Models are Unsupervised Multitask LearnersPaperRadford et al., OpenAI, 2019Section 2.2: byte-level BPE, 130,000 code points versus 256 bytes, the character-category merge rule(opens in a new tab)
- tiktoken encoding definitionsDocsOpenAI, GitHubPre-tokenizer regexes and vocabulary sizes of gpt2, cl100k_base and o200k_base(opens in a new tab)
- TiktokenizerDocstiktokenizer.vercel.appVisualizes tokenizations, the tool named on slide 49(opens in a new tab)
- Neural Machine Translation of Rare Words with Subword UnitsPaperSennrich, Haddow and Birch, ACL 2016BPE for subword tokenization(opens in a new tab)
- Subword Regularization: Improving Neural Network Translation Models with Multiple Subword CandidatesPaperKudo, ACL 2018The unigram language model tokenizer(opens in a new tab)
- Japanese and Korean Voice SearchPaperSchuster and Nakajima, ICASSP 2012WordPiece(opens in a new tab)
- Language Model Tokenizers Introduce Unfairness Between LanguagesPaperPetrov, La Malfa, Torr and Bibi, NeurIPS 2023Tokenization premium definition and Table 1 numbers; ByT5 and ArabicBERT premiums(opens in a new tab)
- Do All Languages Cost the Same? Tokenization in the Era of Commercial Language ModelsPaperAhia et al., EMNLP 202322 languages on ChatGPT and BLOOMZ; overcharged while obtaining poorer results; Arabic as an outlier under the BLOOMZ tokenizer(opens in a new tab)
- SuperBPE: Space Travel for Language ModelsPaperLiu et al., 2025Two-stage pretokenization curriculum; 33% fewer tokens at 200K; +4.0% average, +8.2% MMLU, 27% less inference compute(opens in a new tab)
- The tokenization pipelineDocsHugging Face tokenizers documentationNormalization, pre-tokenization, model, post-processing; BERT vocabulary of 30,522; retraining after changing components(opens in a new tab)
- Introducing Meta Llama 3DocsMeta AI128K token vocabulary(opens in a new tab)
- Unsupervised Multilingual Sentence Boundary DetectionPaperKiss and Strunk, Computational Linguistics 32(4), 2006Punkt: abbreviations as collocations; 98.74% mean accuracy over eleven languages(opens in a new tab)
- nltk.tokenize.punktDocsNLTK documentationThe unsupervised Punkt sentence tokenizer shipped with NLTK(opens in a new tab)
Part 08: Regular expressions: the core syntax
From ELIZA's pattern rules to the building blocks of regular expressions: literals, bracket classes, ranges, negation, quantifiers, wildcards, anchors and the backslash aliases.
4 concepts, slides 56-65
Why this part matters
Every tokenizer you will build or inspect, every corpus cleaning script for Arabic or English, every date or number normalizer, and every exam question on this chapter runs through the same ten or so symbols. The GPT-2 pre-tokenizer later in this lecture is one regular expression. Edit distance in the next parts starts from tokens that a regex produced.
This part builds the core syntax from the ground up, starting with the program that made pattern matching famous. ELIZA held conversations in 1966 using nothing but keyword rules and text substitution, and people believed it understood them. From there we take the symbols one family at a time: literals and disjunction, bracket classes, quantifiers and the wildcard, anchors and the backslash aliases. The next part adds precedence, groups, substitution and lookahead on top of exactly this vocabulary, so every table here is worth being able to reproduce from memory. A live tester at the end of the part loads every example row from the slides so you can check each first match yourself.
By the end you can
- Explain how ELIZA produced replies from keyword substitution rules and name the Eliza effect.
- Read and write literals, concatenation, disjunction and bracket classes, including ranges and negation.
- Apply * + ? {n} {m,n} and the wildcard, and predict greedy versus lazy matches on a given string.
- Use ^ $ \b to match whole words and line-based formats, and explain why anchors consume no characters.
- Expand each alias \d \D \w \W \s \S and state where Python and JavaScript flavors differ.
In 1966 Joseph Weizenbaum at MIT published a program that imitated a Rogerian psychotherapist, the kind of therapist who reflects the patient's own words back as questions. He called it ELIZA. Read the exchange below as a patient would have, one line at a time.
| Speaker | Line |
|---|---|
| User | I need some help, that much seems certain. |
| ELIZA | WHAT WOULD IT MEAN TO YOU IF YOU GOT SOME HELP |
| User | Perhaps I could learn to get along with my mother. |
| ELIZA | TELL ME MORE ABOUT YOUR FAMILY |
| User | My mother takes care of me. |
| ELIZA | WHO ELSE IN YOUR FAMILY TAKES CARE OF YOU |
| User | My father. |
| ELIZA | YOUR FATHER |
| User | You are like my father in some ways. |
It reads like attention. It is not. The whole program is a list of keywords, and for each keyword a small set of decomposition rules that split the input around the keyword, paired with reassembly rules that build a reply out of the pieces. Weizenbaum wrote the rules in his own notation. A decomposition rule such as (0 YOU 0 ME) means "an indefinite number of words, then YOU, then an indefinite number of words, then ME", and its reassembly rule (WHAT MAKES YOU THINK I 3 YOU) inserts the third component, whatever words stood between YOU and ME, into a fixed frame (Weizenbaum, 1966, p. 38). The dictionary he had built "so far" contained about 50 keywords. Nothing is parsed, nothing is understood, and the program has no memory of what "mother" means beyond the fact that it is filed under FAMILY.
Jurafsky and Martin rewrite the same idea in modern regular expression syntax, and this is the version to remember. After the input is uppercased and pronouns are swapped (I'M becomes YOU ARE, MY becomes YOUR), rules like these run in order until one fires (SLP3, section 2.6.7):
- re.sub(r".* YOU ARE (DEPRESSED|SAD) .*", r"I AM SORRY TO HEAR YOU ARE \1", input)
- re.sub(r".* ALWAYS .*", r"CAN YOU THINK OF A SPECIFIC EXAMPLE", input)
Worked example
One ELIZA reply, step by step
User types
He says I'm depressed much of the time.Uppercase
HE SAYS I'M DEPRESSED MUCH OF THE TIME.Swap pronouns
I'M becomes YOU ARE: HE SAYS YOU ARE DEPRESSED MUCH OF THE TIME.Try the rules in order
The pattern .* YOU ARE (DEPRESSED|SAD) .* matches: .* swallows HE SAYS, the literal YOU ARE matches, the parenthesized alternative picks DEPRESSED, and the trailing .* swallows the rest.Reassemble
The replacement I AM SORRY TO HEAR YOU ARE \1 copies whatever the parentheses captured into the slot marked \1.Result
I AM SORRY TO HEAR YOU ARE DEPRESSED, which is the reply SLP3 derives in section 2.6.7 and the fourth reply in Weizenbaum's opening transcript. Swap DEPRESSED for SAD in the input and the same rule prints SAD.
| Weizenbaum (1966) | Meaning | Modern regex |
|---|---|---|
| 0 in a decomposition rule | An indefinite number of words | .* |
| A positive integer n in a decomposition rule | Exactly n words | ((?:\S+ ){n}), one group holding exactly n words |
| 3 in a reassembly rule | Insert the third component the decomposition found | \1 style backreference to a capture group |
| Keyword dictionary, about 50 entries | Which rule set to try, ranked by keyword | A cascade of re.sub calls tried in order |
The parentheses and the \1 are a capture group and a Backreference, which the next part treats properly. For now the point is the shape of the machine: a keyword selects a rule, a pattern with wildcards splits the sentence, a frame reassembles the pieces. Weizenbaum's paper opens with the exchange "Men are all alike." answered by "IN WHAT WAY", and the reply is not about men at all: a rule keyed on the wording of the input prints a fixed question with no slot to fill.
Why people believed it
Crude as the rules were, SLP3 notes that many people who talked to ELIZA came to believe that it really understood them, and that this led researchers to think for the first time about the impact of chatbots on their users (Weizenbaum, 1976). The effect now carries the program's name. The Eliza effect is the tendency to unconsciously assume that computer behaviors are analogous to human behaviors. Douglas Hofstadter defines its specific form as our susceptibility to read far more understanding than is warranted into strings of symbols strung together by computers (Hofstadter, 1995). The standard illustration is not a chatbot at all but a cash machine that prints THANK YOU at the end of a transaction, which a naive observer might take for real gratitude. ELIZA exploited the same reflex at conversational scale: because it echoed your own words, every reply seemed relevant, and relevance felt like understanding.
What regex is for in an NLP pipeline
A regular expression is fast, flexible pattern matching for text. It needs no training data, and it is precise in a way a learned model cannot be: a pattern either matches or it does not, and you can read off why. The slide lists four jobs, and it is worth having a concrete example ready for each.
Four jobs regex does in NLP (slide 57), with an example each
- Tokenization and pre-tokenization
- Split text into words, numbers and punctuation before a BPE tokenizer merges inside each piece. The GPT-2 pre-tokenizer in the next part is one regex.
- Normalization
- Recognize dates and numbers in any of their surface forms and rewrite them to one canonical form, such as dd-mm-yyyy.
- Filtering and cleaning
- Drop lines that are all punctuation, strip HTML tags, remove boilerplate, detect a script you do not want in a training corpus.
- Feature extraction
- Does the token contain a digit, start with a capital, end in -ing? Each answer is a binary feature a classifier can use.
The slide's last line matters more than it looks: regex is language- and task-dependent. A class like [a-z] says nothing about Arabic letters. A word boundary \b assumes words are made of letters, digits and underscores separated by something else, which fails outright for Chinese, where there are no spaces, and quietly misbehaves for Arabic under an ASCII-only definition of "letter". Recall the typology in part 04: an isolating language and a templatic one need different patterns for the same task, and a pattern tuned for tweets will not fit legal text. Regex gives you precision; it does not give you generality for free.
Quick check
Which mechanism produced ELIZA's reply YOUR FATHER from the input My father?
Recall
Why did ELIZA convince people, in two sentences: one about the program and one about the people?
Start with the simplest pattern there is. The regex Buttercup matches the substring Buttercup in "I'm called little Buttercup" and nowhere else (SLP3, section 2.6.1). Each letter is a literal that matches itself, and writing letters one after another means "this, then this, then this". That is concatenation, and abc matches exactly the three characters abc in that order.
Two more operators complete the algebra on slide 58. The vertical bar is disjunction: a|b matches either a or b. Parentheses group and define scope, so gupp(y|ies) matches guppy or guppies, while guppy|ies would match guppy or the bare string ies, because the bar has the lowest precedence of all and splits the whole pattern into two halves (SLP3, section 2.6.4). Regex is case sensitive throughout: s does not match S. Concatenation, disjunction and the Kleene star, which the next concept introduces, are the three operations that define regular languages in theory of computation. Parentheses only set scope, and classes, ranges, +, ? and counted repetition are all shorthand built from those three.
The square-bracket class
Disjunction between single characters is so common that it has its own notation. A Character class in square brackets matches exactly one character from the set it lists. This is the fix for case sensitivity: [mM]ary matches Mary or mary. Read every row of the next three tables as the slides present them, pattern, meaning, and the first match marked in the example string.
| Pattern | Matches | First match in the example |
|---|---|---|
| [mM]ary | Mary or mary | Mary Ann stopped by Mona's |
| [abc] | a, b or c | In uomini, in soldati |
| [1234567890] | Any digit | plenty of 7 to 5 |
Look closely at the second row. "In uomini" contains no a, b or c, so the first match is the a in soldati, eighteen characters in. A class matches one character, so a bracket never matches "abc" as a word; SLP3 asks why [catdog] does not mean cat or dog, and the answer is the same: it is one character drawn from c, a, t, d, o, g. Listing every digit is tedious, so a dash inside brackets gives a range in code point order: [0-9], [a-z], [A-Z], but also [2-5] or [b-g].
| Pattern | Matches | First match in the example |
|---|---|---|
| [A-Z] | An upper case letter | we should call it 'Drenched Blossoms' |
| [a-z] | A lower case letter | my beans were impatient to be hoed! |
| [0-9] | A single digit | Chapter 1: Down the Rabbit Hole |
The caret is the last bracket operator, and it is the one that trips people. When the caret is the first character inside brackets, the class is negated and matches any single character not in the set. Anywhere else inside brackets it is an ordinary caret. Python's documentation states both halves: the caret "has no special meaning if it's not the first character in the set", and [^^] matches any character except a caret.
| Pattern | Matches | First match in the example |
|---|---|---|
| [^A-Z] | Not an upper case letter | Oyfn pripetchik |
| [^Ss] | Neither S nor s | I have no exquisite reason for't |
| [^.] | Not a period | our resident Djinn |
| [e^] | Either e or a caret | look up ^ now |
| a^b | The pattern a^b, as the slide claims | look up a^b now (no match) |
| a\^b | The literal string a^b, escaped | look up a^b now |
Notice also that the third row needed no backslash. Inside brackets, special characters other than the backslash lose their special meaning; the Python documentation gives [(+*)] as a class that matches any of the literal characters (, +, * and ). So [^.] means "not a period" with a plain period, while outside brackets a bare period is the wildcard from the next concept and a literal period must be written \.. The two contexts have different grammars, and reading a pattern means always knowing which one you are in.
Quick check
Where must a caret sit to negate a bracket character class?
Recall
Why does [^.] need no backslash while a bare . outside brackets matches any character?
British and American spelling give the cleanest first example. You want one regex for color and colour. The u is optional, and colou?r says exactly that: the question mark makes the character before it appear zero or one times (SLP3, section 2.6.2). The same trick covers a plural: koalas?.
Now try sheep. Their language is baa!, baaa!, baaaa!, any number of a's of at least two. The Kleene star gives zero or more of the previous element, so baaa*! works: two literal a's, then any number more. The Kleene plus gives one or more, so baa+! says the same thing more readably. Both reject b!. Note what ba*! would do: with zero a's allowed, it accepts b!, which no sheep says. This is the recurring hazard of the star: it also matches nothing.
Quantifiers and the wildcard (slides 62 and 63, SLP3 Fig 2.11)
- *
- Zero or more occurrences of the previous character or expression. baa* matches ba, baa, baaa and so on.
- +
- One or more occurrences. baa+ requires at least one a after the first, so it rejects ba.
- ?
- Zero or one occurrence, meaning optional. colou?r matches both color and colour.
- {n}
- Exactly n occurrences. a{3} matches aaa and nothing shorter or longer.
- {m,n}
- Between m and n occurrences, inclusive. a{2,4} accepts aa, aaa and aaaa.
- {m,} and {,n}
- At least m, or at most n. Omitting m sets a lower bound of zero and omitting n sets no upper bound (Python re documentation). JavaScript, and so the tester below, needs {0,n}: a bare {,n} is treated as literal text.
- .
- Any single character except a newline in most flavors.
- .*
- Any string of zero or more characters: the wildcard under the star.
A Quantifier applies to whatever is immediately before it: one character, one class, or one parenthesized group. So [0-9]+ is an integer of one or more digits, and on "Chapter 12 of 3" it finds 12 at 8 and 3 at 14. SLP3 asks why not [0-9]*, and the sheep already answered: the star matches the empty string, so [0-9]* would "match" at every position of a text with no digits at all. Reach for + whenever at least one is required.
The wildcard
The period matches any single character. In Python's default mode that is any character except a newline, and the DOTALL flag lifts the exception; in JavaScript the dot excludes the line terminators and the s flag lifts it (Python re documentation; MDN). Under a star it becomes the most powerful and most dangerous idiom in the language: .* is any string of zero or more characters. a.*b is an a, then anything, then a b, and rose.*rose finds two roses with anything between.
Greedy versus lazy
Here is the question that decides whether .* does what you meant. Run <.*> on the string <a><b>. You probably wanted the first tag. You get the whole string, because regular expressions always match the largest string they can; SLP3 says the patterns are greedy. The star grabs everything up to the end and then backs off only as far as needed for the final > to match, which is the last one. Adding a question mark after a quantifier makes it lazy: *? and +? match as little as possible, expanding only when the rest of the pattern cannot otherwise succeed (MDN, Python re documentation).
| Pattern | String | Matches | Length of first match |
|---|---|---|---|
| <.*> | <a><b> | one match, <a><b> at 0 | 6 |
| <.*?> | <a><b> | two matches, <a> at 0 and <b> at 3 | 3 |
| a.*b | xaxbxbx | one match, axbxb at 1 | 5 |
| a.*?b | xaxbxbx | one match, axb at 1 | 3 |
Worked example
Predicting a.*b against a.*?b on xaxbxbx
Find where a match can start
The only a is at index 1, so both patterns start there.Greedy
.* takes everything to the end, xbxbx, then gives back one character at a time until a b can match. The last b is at index 5, so the match is axbxb, indices 1 to 5.Lazy
.*? starts by taking nothing and tries b at index 2; it is an x, so the star extends by one. Now b at index 3 matches. The match is axb, indices 1 to 3.Result
Greedy: axbxb (5 characters). Lazy: axb (3 characters). Same start, different end.
Quick check
Which span does the lazy pattern a.*?b match first inside the string xaxbxbx?
Quick check
What does the star quantifier require of the element before it?
Recall
Predict the first match of a.*b and of a.*?b on the string xaxbxbx, with indices.
Everything so far matched characters. The last two regex tools match positions. ^The matches The only at the start of a line, $ finds a trailing space at the end of one, and ^The dog\.$ matches a line that is exactly "The dog." and nothing else (SLP3, section 2.6.3).
These are anchors, and the crucial property is that they consume nothing. An anchor matches the empty string at a position that satisfies a condition, so the\b the can match "the the": the boundary sits between the e and the space without using up either. In Python, ^ matches the start of the string, and in MULTILINE mode also immediately after each newline; $ matches the end of the string or just before the final newline, and in MULTILINE mode before every newline (Python re documentation). JavaScript's m flag does the same job, which is why the tester below has one.
That is what "line-based formats" on the slide means in practice: real files are one record per line, and the multiline flag turns ^ and $ into per-line assertions. A comments filter on a corpus file, and a date extractor on a log, are the same pattern shape.
| Pattern | Text (one record per line) | Matches with m | Matches without m |
|---|---|---|---|
| ^#.*$ | # comment, text line, # another | 2 matches: # comment and # another, each at the start of its line | 0 |
| ^\d{4}-\d{2}-\d{2} | 2026-09-16 boot, user logged in, 2026-09-17 shutdown | 2 matches: the timestamps opening lines 1 and 3 | 1 |
Without the flag, ^ sees only the start of the whole string, so the comments filter finds nothing and the date pattern catches only a first line that happens to begin with a timestamp. With the flag, each newline resets ^ and $, which is exactly how you scan a log or strip comment lines from a file. Flip the m flag on the ^The preset in the tester below to feel the difference.
| Anchor | Position it asserts | Example |
|---|---|---|
| ^ | Start of the string, or of a line with the multiline flag | ^The finds The at index 0 of "The dog" |
| $ | End of the string, or of a line with the multiline flag | \.$ finds the period at index 7 of "The dog." |
| \b | Between a word character and a non-word character, or a string edge | \bthe\b finds the at index 0 of "the other theme" |
| \B | Any position that is not a word boundary | \Bthe finds the inside other |
The word boundary
The slide says \b is useful for matching whole words, and the canonical example is the word "the". Plain the also matches inside other and theme. \bthe\b requires a boundary on each side, so on "the other theme" it lights only the first word. A word character is a letter, a digit or an underscore, and the boundary is the seam between such a character and anything else, or a string edge (Python re documentation). Python's own examples make the rule vivid: r'\bat\b' matches at, at., (at) and the at in "as at ay", but not attempt or atlas.
Digits count as word characters too, which produces the example SLP3 uses to check that you have understood the definition rather than memorized "letters". \b99\b matches in "There are 99 bottles" and in "$99", because a dollar sign is not a word character, but not in "299", because the 2 is. On "There are 299 bottles, $99 each" it finds exactly one match, the 99 at index 24.
SLP3 tells the story of the word "the" as a precision and recall exercise. the misses The at the start of a sentence, a false negative, so you write [tT]he. Now it hits other and there, false positives, so you write \b[tT]he\b. Every regex you write for a real corpus goes through the same two-sided tuning, and the boundary is usually the second fix.
The six aliases
Some classes are needed so often that they get a backslash shorthand. Slide 65 lists six, in three complementary pairs. Each lower case alias is a class, and its upper case partner is the negation of that class.
| Alias | Expansion | Matches | First match in the example |
|---|---|---|---|
| \d | [0-9] | Any digit | Party of 5 |
| \D | [^0-9] | Any non-digit | Blue moon |
| \w | [a-zA-Z0-9_] | Any alphanumeric or underscore | Daiyu |
| \W | [^\w] | A non-alphanumeric | !!!! |
| \s | [ \r\t\n\f] | Whitespace: space, tab, newline | in Concord |
| \S | [^\s] | Non-whitespace | in Concord |
The expansions in the table are the SLP3 definitions, and they are ASCII. Real engines differ at the edges, and the differences matter for Arabic. In Python 3, a pattern on a str treats \d as any Unicode decimal digit, including Arabic-Indic digits, \w as any Unicode alphanumeric plus the underscore, so Arabic letters count, and \s as anything str.isspace() accepts, which includes the non-breaking space; the ASCII flag switches all three back to the table above, with \s as [ \t\n\r\f\v] (Python re documentation). JavaScript keeps \w at letters, digits and underscore and \d at [0-9], while its \s covers all Unicode whitespace and line terminators (MDN). So a boundary such as \b, which is defined in terms of \w, falls in different places for the same Arabic sentence in the two languages. This is the "language-dependent" warning from slide 57 in its most practical form.
A backslash works both ways
Inside a pattern, a backslash before a special character makes it literal: \. is a period, \^ is a caret. The same backslash before an ordinary letter creates an alias such as \d or \b. The full table of escaped specials, from K\*A\*P\*L\*A\*N to the raw-string question, comes in the next part.
Quick check
Which pattern matches the standalone word the but not other or theme?
Quick check
Which alias expands to the class [a-zA-Z0-9_]?
Recall
Expand \w and \s, and name their negations.
Recall
What are the three meanings of the caret, and which one does Python or JavaScript apply to a caret outside brackets?
Recall
Write a regex for a word starting with a capital letter and say what each piece does.
Check every row yourself
The tester below loads every example from slides 59 to 65, the greedy and lazy pairs, and the ELIZA rule with its captured group. Load a preset, read the first match, then edit the pattern and watch the highlight move. Try a^b to see the erratum with your own eyes, and switch the m flag off on the ^The preset to see anchors change meaning.
HE SAYS YOU ARE DEPRESSED MUCH OF THE TIME.- Match 1at index 0HE SAYS YOU ARE DEPRESSED MUCH OF THE TIME.$1 = DEPRESSED
The tester runs the browser's JavaScript RegExp engine. Its \s and \d follow the ECMAScript definitions, and . excludes the four line terminators unless s is on, so a Python result can differ by a character in edge cases. Patterns that would match the empty string, such as a*, advance one character per empty match.
Recap
If you remember nothing else
- ELIZA is a cascade of keyword-triggered decomposition and reassembly rules: .* YOU ARE (DEPRESSED|SAD) .* becomes I AM SORRY TO HEAR YOU ARE \1. The Eliza effect names our habit of reading understanding into such echoes.
- NLP uses regex for tokenization and pre-tokenization, normalization of dates and numbers, filtering and cleaning, and feature extraction. Regex is language- and task-dependent.
- Literals match themselves, abc is concatenation, a|b is disjunction, parentheses set scope. [mM], [a-z] and [^0-9] each match exactly one character.
- The caret has three faces: an anchor outside brackets, negation when first inside brackets, a literal elsewhere inside brackets. In Python and JavaScript a caret outside brackets is always an anchor, so a literal needs \^.
- * is zero or more, + one or more, ? zero or one, {n} exactly n, {m,n} between m and n. The dot is any character except a newline, so .* is any string.
- Quantifiers are greedy. *? and +? are lazy: <.*> eats all of <a><b>, <.*?> stops at <a> and then finds <b>.
- ^ and $ anchor line edges, \b is a zero-width word boundary. \bthe\b matches the but not other, and \b99\b matches in $99 but not in 299.
- \d is [0-9], \D is [^0-9], \w is [a-zA-Z0-9_], \W is [^\w], \s is [ \r\t\n\f], \S is [^\s]. Escape a literal special character with a backslash.
Sources
- Speech and Language Processing, 3rd edition draft, chapter 2: Words and TokensBookJurafsky and Martin, StanfordDraft of August 19, 2026. Section 2.6 and Figs 2.8 to 2.14 are the tables on these slides; section 2.6.7 gives the ELIZA rules, and the opening page reports ELIZA's reception, citing Weizenbaum (1976).(opens in a new tab)
- ELIZA: A Computer Program for the Study of Natural Language Communication Between Man and MachinePaperCommunications of the ACM 9(1):36-45, January 1966 (doi:10.1145/365153.365168)Transcript on pp. 36-37, decomposition and reassembly rules and the 50-keyword dictionary on p. 38.(opens in a new tab)
- re: Regular expression operationsDocsPython documentationDot and DOTALL, quantifiers, the <a> b <c> non-greedy example, MULTILINE, \b, aliases in Unicode and ASCII mode, sets and [^^].(opens in a new tab)
- QuantifierDocsMDN Web DocsGreedy versus lazy, and the /a*?/ example.(opens in a new tab)
- WildcardDocsMDN Web DocsThe dot excludes line terminators unless the s flag is set.(opens in a new tab)
- Character class escapeDocsMDN Web DocsJavaScript definitions of \d, \w and \s.(opens in a new tab)
- Mastering Regular Expressions, 3rd editionBookO'Reilly (Jeffrey Friedl), 2006Backtracking and greediness in depth.(opens in a new tab)
- ELIZA effectArticleWikipediaThe slide's wording comes from an earlier revision of this article, whose current lead describes the effect as a tendency to project human traits onto rudimentary computer programs. The quoted definition of the specific form and the cash machine example are from Hofstadter, Fluid Concepts and Creative Analogies, Basic Books, 1995.(opens in a new tab)
Part 09: Regular expressions in practice
Escaping, precedence and groups, substitutions with backreferences, lookarounds, the cheat sheets, the GPT-2 pre-tokenizer regex, and the engineering habits that keep patterns correct and fast.
6 concepts, slides 66-78
Why this part matters
Part 08 gave you the alphabet of regular expressions. This part is about using it under real conditions: patterns that must match a literal period, patterns that must reorder a date, patterns that must enforce three rules at once, and one pattern that quietly decides how every GPT-2 style tokenizer sees text.
Most BPE tokenizers you will train or inspect, GPT-2's included, start with a regex pre-tokenizer, every cleaning script for an Arabic corpus is a chain of substitutions, and the exam asks you to work precedence, substitution and lookahead by hand. The cheat sheets on slides 73 to 75 are here as reference tables you can come back to, and the workbench lets you test every claim in the browser.
By the end you can
- Escape metacharacters and predict what (cat|dog)s versus cat|dogs matches using the precedence ladder.
- Write a capture-group substitution that reorders a date and a backreference that finds a doubled word.
- Write a lookahead-based password check and explain why lookahead consumes nothing.
- Explain each alternative of the GPT-2 pre-tokenizer on a given string and why it needs \p{L}.
- Recognize catastrophic backtracking, give an example, and name two ways to make a pattern Unicode-aware.
Suppose you want to find every mention of Dr. in a corpus. The obvious pattern Dr. also matches Drs and Dry, because a period in a pattern means any character. Written as Dr\., it matches only the period. That is the whole idea of escaping: fourteen characters, . ^ $ * + ? ( ) [ ] { } | \, mean something to the engine, and a backslash returns them to their plain selves.
Escapes from SLP3 Figure 2.14, plus the two that run the other way
- \*
- an asterisk, as in K*A*P*L*A*N
- \.
- a period, as in Dr. Livingston, I presume
- \?
- a question mark, as in Why don't they come and lend a hand?
- \n
- a newline (the backslash gives a plain letter a special meaning)
- \t
- a tab (same direction: plain letter, special meaning)
Notice that the backslash works in both directions. \. turns a special character into a literal one, while \n and \t turn plain letters into a newline and a tab. When a pattern needs a literal backslash it doubles: \\. In Python, write patterns as raw strings, r'...', so the string literal does not eat the backslashes before the regex engine sees them (Python re documentation).
The precedence ladder
Escaping decides what a character means. Precedence decides how the operators bind, and it is where most wrong answers come from. Take the pattern cat|dogs and run it on the string cats dogs cat dog. Students expect cats and dogs. The engine returns cat (inside cats), dogs, and cat again, because the disjunction operator | has the lowest precedence of all: it splits the entire expression into cat on one side and dogs on the other. Parentheses fence the alternation in, so (cat|dog)s reads as cat or dog, then s, and returns cats and dogs.
| Pattern | Reads as | Matches |
|---|---|---|
| cat|dogs | cat, or dogs | cat, dogs, cat |
| (cat|dog)s | cat or dog, then s | cats, dogs |
SLP3 (section 2.6.4) gives the full ladder. Parentheses bind tightest, then the counters (the quantifiers), then plain sequences and anchors, and last of all the disjunction. This is why the* matches theeeee and not thethe: the star applies to the single letter e, not to the whole sequence. It is why the|any matches the or any but never thany. And it is why guppy|ies fails to match guppies: it means guppy or ies, so the fix is gupp(y|ies).
Operator precedence, highest first (SLP3, section 2.6.4)
- 1 Parenthesis
- ( )
- 2 Counters
- * + ? {}
- 3 Sequences and anchors
- the, ^my end$
- 4 Disjunction
- |
The same ladder explains a subtler example from SLP3. Column [0-9]+ * matches one column label such as Column 1 followed by any number of spaces, because the star applies only to the final space. To repeat over a whole row of labels, Column 1 Column 2 Column 3, the sequence has to be fenced first. SLP3 writes (Column [0-9]+ +)*, which needs a space after every label, so on this row it stops before Column 3; (Column [0-9]+ *)* allows zero trailing spaces and takes the whole row.
Grouping without remembering
Parentheses do two jobs at once. They set the scope of an operator, and they create a capture group that stores whatever the group matched. Sometimes you want only the first job. Thenon-capturing form (?:...) groups without allocating a register, which keeps the numbering of the groups you do care about stable. SLP3 (2.17) uses it to skip fourteen dates and capture only the fifteenth: (?:\d\d/\d\d/\d\d\d\d\s+){14}(\d\d/\d\d/\d\d\d\d). The repeated dates are grouped so that the counter {14} applies to the whole date, and only the last date lands in group 1.
Recall
Why does guppy|ies fail to match guppies, and what is the fix?
Quick check
Which whole word does the pattern cat|dogs match but (cat|dog)s never matches?
Real patterns are assembled from small pieces, tested, and grown. Start with an email address. It has a local part, an at sign and a domain, so the very simplified pattern on slide 69 is three pieces glued together: [A-Za-z0-9._]+@[A-Za-z0-9._]+. A character class of letters, digits, dots and underscores, repeated at least once, on both sides of the @. The same compose-then-test loop gives a course-code ID pattern in one line: two to four uppercase letters, an optional space, three digits, [A-Z]{2,4}\s?\d{3}, which accepts ICS 582 and COE558 and rejects ICS58 (only two digits) and ICSSS 582 (five letters). The habit is the same in both: build the smallest piece that covers the positives, then run it against the nearest negatives.
| Address | What matched | Verdict |
|---|---|---|
| m.alraimi@kfupm.edu.sa | m.alraimi@kfupm.edu.sa | whole address, fine |
| first-last@x.com | last@x.com | hyphen is not in the class, the local part is truncated |
| a+b@y.org | b@y.org | plus is not in the class either |
| me@localhost | me@localhost | accepted although there is no top-level domain |
The table is an error analysis, and it exposes two failure modes that pull in opposite directions. The class is too narrow: hyphens and plus signs are legal in local parts, so the pattern silently truncates them and matches the wrong substring. That is a recall problem: real addresses the pattern should catch, it catches only in part. The domain side is too loose: it accepts me@localhost, which has no top-level domain. That is a precision problem: strings the pattern should reject, it accepts. Widening the class to [A-Za-z0-9._+-]+ raises recall; requiring a dot followed by at least two letters at the end, \.[A-Za-z]{2,}$, raises precision. Each fix is one small piece, tested on representative samples, and the point of slide 69 is to iterate this way rather than to write a pattern that fits the one address in front of you.
Capture, then reuse
Once a pattern matches, each capture group has stored a substring, and a substitution can put them back in any order. Slide 70 shows the classic sed notation s/(\d{4})-(\d{2})-(\d{2})/$2$3$1/. Work it on an ISO date.
Worked example
Reordering 2026-01-25
Name the groups
(\d{4})-(\d{2})-(\d{2}) matches four digits, a hyphen, two digits, a hyphen, two digits. Group 1 is 2026, group 2 is 01, group 3 is 25. The hyphens are matched but not captured.Apply the slide's replacement
$2$3$1 writes group 2, then 3, then 1 with nothing between them: 01252026. The separators vanished because a replacement only contains what you write into it.Write the separators yourself
Python syntax: re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\2/\3/\1', s) gives 01/25/2026. Swapping the first two references, \3/\2/\1, gives 25/01/2026.Three replacements, three outputs
The pattern never changes. Only the replacement string decides the order and the separators.
| Replacement | Output | Reading |
|---|---|---|
| $2$3$1 | 01252026 | slide 70: month, day, year, separators dropped |
| \2/\3/\1 | 01/25/2026 | US order with slashes written by the replacement |
| \3/\2/\1 | 25/01/2026 | day, month, year: the slide 72 target |
The syntax of the reference differs by tool. sed and Perl write $1 or \1 inside s/.../.../; JavaScript's replace uses $1 and $& for the whole match; Python's re.sub uses \1 in a raw string and \g<0> for the whole match (Python re documentation). SLP3 (section 2.6.7) uses the same trick to move US dates to European order: re.sub(r"(\d{2})/(\d{2})/(\d{4})", r"\2-\1-\3", s) turns 10/15/2011 into 15-10-2011.
A backreference is not only for replacements. Inside the pattern itself, \1 means "the same text that group 1 just captured", so (\w+) \1 finds a doubled word. SLP3's careful version, \b([A-Za-z]+)\s+\1\b, restricts the group to letters, adds word boundaries and allows any whitespace; on the the cat sat on on the mat it returns the and on. This is the mechanism behind the faster-they-ran exercise in the next concept.
Looking without moving
The email and date patterns consume text left to right. Some rules cannot be written that way. A password must be at least eight characters, contain a capital letter, and contain a digit, and the three rules overlap: the same characters count for all of them. Lookahead solves this. (?=...) requires that a pattern could match starting here, (?!...) forbids it, and neither consumes anything. SLP3 (section 2.6.8) puts it precisely: the match pointer does not advance, just as with anchors.
So ^(?=.*[A-Z])(?=.*\d).{8,}$ reads: at the start of the line, check that somewhere ahead there is a capital; still at the start, check that somewhere ahead there is a digit; now, still at the start, consume at least eight characters to the end. Because the two checks both begin at position 0 they can look at the same characters, which a consuming pattern could never do.
| Candidate | Length | Capitals | Digits | Result |
|---|---|---|---|---|
| Passw0rdX | 9 | P, X | 0 | accepted |
| password | 8 | none | none | rejected |
| PASSWORD1 | 9 | all | 1 | accepted |
| Pw1short | 8 | P | 1 | accepted, exactly 8 |
Negative lookahead is just as useful. SLP3's equation 2.18, ^(?![tT])(\w+)\b, captures a word at the start of a line only if it does not begin with t or T. Without the lookahead you would need a class that lists every other letter.
Recall
What does re.sub(r'(\d{4})-(\d{2})-(\d{2})', r'\2\3\1', '2026-01-25') return, and why is there no separator?
Quick check
After the lookahead ^(?=.*\d) succeeds on Passw0rdX, where does the match pointer stand?
Slide 72 is a set of six problems with no answers. Each of them uses one tool from the previous two concepts, and together they cover everything the exam can ask about escaping, each capture group and its backreference, and lookahead. Try each one in the workbench before revealing the solution. The check button compares your matches against the expected list and tells you what is missing or extra.
Match the two tags <Begin> and <\End> and nothing else.
/…/gType a pattern to see live results.
Patterns run in JavaScript syntax inside a worker with a 600 ms limit. Python differs in three places: backreferences in replacements are \1 not $1, named groups are (?P<name>...), and \p{L} needs the regex module.
The six problems map one to one onto the tools of the previous two concepts. The tags problem rewards a negated class or an escaped backslash: <[^>]+> reads as "any run of non-closing-brackets inside brackets". The Apple problem is the one where a global i flag backfires, because it would also lowercase Juice, so the case-insensitivity has to live per letter inside a class like [Aa], or inside a scoped group like (?i:apple). The password problem is the argument for lookahead: three overlapping rules, all checked from the same position, none consuming input. The faster-they-ran sentence needs backreferences, capturing the repeated words once and reusing them as \1 and \2. The date swap is the classic capture-group substitution. The boxes problem shows that the whole-match reference, $& or \g<0>, lets you skip a group entirely when you do not need to rearrange anything.
Recall
Match the tags <Begin> and <\End> in the string <Begin> Hello World <\End>, and nothing else.
Recall
Match Apple Juice, apPLe Juice and APPLE Juice but not Apple juice.
Recall
Write a password check: at least 8 characters, at least one capital letter, at least one digit.
Recall
Match both 'The faster they ran, the faster we ran' and 'The faster they read, the faster we read' with one pattern.
Recall
Convert 2026-01-25 to 25/01/2026 with capture groups.
Recall
Convert 'the 35 boxes' to 'the <35> boxes'.
Slides 73 to 75 are the reference card for the whole regex block: every quantifier, character class, capture group and anchor in one place. They are reproduced here as tables so you can scan them during revision, with the two engine differences that the card leaves implicit: how line anchors behave under multiline mode, and which engines allow a variable-length lookbehind.
Alternation and quantifiers
- a|b
- alternation: a or b
- ?
- zero or one of the preceding element
- +
- one or more
- *
- zero or more
- *?
- zero or more, lazy: stop as early as the rest of the pattern allows
- {N}
- exactly N
- {N,M}
- between N and M
The lazy form matters more than its size suggests. Greedy .* grabs as much as it can and backs off only if the rest of the pattern fails; .*? takes as little as possible and grows only when it must. Part 08 showed the difference on HTML tags.
Pattern collections
- [A-Z]
- one uppercase ASCII letter
- [a-z]
- one lowercase ASCII letter
- [0-9]
- one ASCII digit
- [asdf]
- one of a, s, d, f
- [^asdf]
- any one character except a, s, d, f
Groups
- (...)
- capturing group: match and remember, referenced as \1 or $1
- (?:...)
- non-capturing group: scope for | or a quantifier, nothing remembered
- (?<name>...)
- named group; Python spells it (?P<name>...)
General tokens
- .
- any character except newline
- \n
- newline
- \t
- tab
- \s
- one whitespace character
- \S
- one non-whitespace character
- \w
- one word character: letter, digit or underscore
- \W
- one non-word character
- \b
- word boundary, zero width
- \B
- not a word boundary, zero width
- ^
- start of string, or start of line under m
- $
- end of string, or end of line under m
- \\
- a literal backslash
Take one input and walk the flags. On the two-line string cat\ncar the pattern ^ca.$ shows the three flags in action. With no flags it matches nothing: ^ wants the very first position, and the dot cannot be a newline, so $ after a 3-letter match can never reach the end. Turning on m gives cat and car, one per line. Adding g reports both; without g a JavaScript exec returns only the first. The i flag is independent: it would let the pattern tolerate Cat too. Each flag flips exactly one assumption, anchor meaning, case, or how many results are returned.
Flags
- g
- global: report every match, a JavaScript and regex101 flag; Python uses findall and sub instead
- m
- multiline: ^ and $ also match at each line break
- i
- case-insensitive
| Syntax | Name | Example | Result |
|---|---|---|---|
| (?=...) | positive lookahead | foo(?=bar) | foo in foobar only |
| (?!...) | negative lookahead | foo(?!bar) | foo in foobaz only |
| (?<=...) | positive lookbehind | (?<=#)\d+ | 42 and 8 in #42 and 7 and #8 |
| (?<!...) | negative lookbehind | (?<!#)\b\d+ | 7 in the same string |
One detail to read closely in the table: (?<!#)\b\d+ needs its \b. Without the boundary, the engine could pick up the 2 of #42, because the position before that digit has 4 in front of it, not #, so the negative lookbehind does not object and the lookahead for a digit succeeds. The \b forces the match to begin where a word begins, so the 2 is ruled out and only 7 survives.
Recall
Which lookbehinds does Python re accept, and what do JavaScript and the regex module allow instead?
Part 07 showed you this pattern as a list of six rules; now that you can read regex syntax, walk it character by character. Before GPT-2's BPE merges touch a sentence, one regex cuts it into pieces. On We're 350 dogs! Um, lunch? the pieces are We, 're, ␣350, ␣dogs, !, ␣Um, ,, ␣lunch and ?, nine in all, where ␣ marks a leading space that stays inside the piece. That regex is the pre-tokenizer, and the whole behavior of the tokenizer on numbers, punctuation and spaces is decided by it.
Recall
From part 07: what does a pre-tokenizer do, and why do BPE merges normally not cross its pieces?
The pattern from the GPT-2 repository's encoder.py is 's|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+. It is a disjunction of six alternatives, and the engine tries them left to right at every position, taking the first that matches. Read it as six rules in priority order.
| Alternative | Meaning | Pieces it produced |
|---|---|---|
| 's|'t|'re|'ve|'m|'ll|'d | a contraction suffix, tried first | 're |
| ?\p{L}+ | an optional space then one or more Unicode letters | We, ␣dogs, ␣Um, ␣lunch |
| ?\p{N}+ | an optional space then one or more Unicode digits | ␣350 |
| ?[^\s\p{L}\p{N}]+ | an optional space then a run that is neither space, letter nor digit | !, comma, ? |
| \s+(?!\S) | whitespace not followed by a non-space, so the last space is left for the next word; also any trailing whitespace | ␣␣ in Hello␣␣␣world |
| \s+ | a single whitespace character directly before a non-space that the rule above cannot take: a lone newline or tab before a word | ⏎ in a⏎b |
Order is the design. Contractions come first so that We're becomes We plus 're: at position 0 no contraction matches, so the letter rule takes We and stops at the apostrophe; at position 2 the contraction rule fires before the punctuation rule can grab the apostrophe alone. Had the letter rule come first the result would be the same for We, but if punctuation came before contractions you would get ' and then re, and the model would never learn that 're is one unit.
The space handling is the subtle part. Letters and digits carry an optional leading space, so a word is normally tokenized together with the space before it: ␣dogs is one piece, and the model learns that word-initial pieces look different from word-internal ones. Runs of spaces are handled by \s+(?!\S): whitespace that is not followed by a non-space. On Hello␣␣␣world that alternative takes the first two spaces and stops, because the third space is followed by w, and the third space is then picked up by ?\p{L}+ as part of ␣world. The result is Hello, ␣␣, ␣world. Trailing whitespace at the end of a document also goes to \s+(?!\S), because at the end of the string nothing follows and the negative lookahead succeeds. The final plain \s+ fires only in the one case the rule above cannot handle: a single whitespace character directly before a non-space that no ␣? prefix will take, which means a lone newline or tab before a word. On a⏎b it produces the ⏎ piece.
Why regex and not re
The slide imports the third-party regex module under the name re. The reason is \p{L} and \p{N}. These are Unicode property classes: every character whose General_Category is Letter, and every character whose category is Number, as defined in Unicode Standard Annex 44. Python's built-in re does not support them; the regex module does, and so does JavaScript with the u flag (regex module documentation, MDN). The payoff is script independence. On مرحبا بالعالم ١٢٣ the same pattern returns مرحبا, ␣بالعالم and ␣١٢٣: Arabic letters are letters and Arabic-Indic digits are digits without any special case in the pattern. A pattern written with [A-Za-z] and [0-9] would still cut at the spaces, because the negated punctuation class excludes whitespace, but all three pieces would come out through the punctuation rule, indistinguishable from runs of symbols.
For the exam, two ways to make a pattern Unicode-aware: use property classes such as \p{L} and \p{N} instead of ASCII ranges, and apply Unicode normalization (NFC or NFKC, from part 05) to the input before matching so that composed and decomposed forms of the same letter cannot split a match.
Run the GPT-2 pre-tokenizer pattern and read which alternative produced each piece.
/'s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+/guType a pattern to see live results.
Patterns run in JavaScript syntax inside a worker with a 600 ms limit. Python differs in three places: backreferences in replacements are \1 not $1, named groups are (?P<name>...), and \p{L} needs the regex module.
What the pieces are for
The purpose of the cut is to separate words, numbers, punctuation, contractions and whitespace before BPE sees the text. Then the merges run inside each piece. This is literally how encoder.py is written: it loops over re.findall(self.pat, text) and calls self.bpe(token) on each piece separately (GPT-2 repository). A merge can join d and ogs inside ␣dogs, but no merge can ever join dogs with the ! that follows it, because they were never in the same piece. Byte-level operation, from part 06, applies inside each piece too, which is why the tokenizer is also a byte-level one.
The cut lines are a design choice, and later tokenizers move them. tiktoken's r50k pattern for GPT-2 is the same language hardened for speed: '(?:[sdmt]|ll|ve|re)| ?\p{L}++| ?\p{N}++| ?[^\s\p{L}\p{N}]++|\s++$|\s+(?!\S)|\s, with possessive quantifiers ++ that refuse to backtrack and an explicit end-of-string whitespace rule. cl100k_base, used by GPT-4, caps numbers at three digits with \p{N}{1,3}+ and makes contractions case-insensitive (tiktoken repository). Hugging Face's ByteLevel pre-tokenizer applies the same GPT-2 pattern unless you switch its use_regex option off (Hugging Face tokenizers documentation). SuperBPE (introduced in part 07) goes the other way: after a first stage that learns subwords inside pieces, a second stage is allowed to merge across whitespace, and Liu et al. (2025) report up to 33 percent fewer tokens at a 200k vocabulary. Every one of these is a change to the pre-tokenizer, not to the BPE algorithm.
Recall
Explain each alternative of the GPT-2 pattern on We're 350 dogs!
Recall
Name two ways to make a pattern Unicode-aware.
Quick check
In the GPT-2 pre-tokenizer, which alternative produces the piece ' 350' with its leading space?
Slide 78 closes the regex block with four engineering habits. Three of them you have already practiced in this part without being told. The email pattern grew from an error analysis of four addresses: start simple, add cases as failures appear. The workbench tasks are unit tests: a fixed list of inputs with expected outputs, rerun after every edit. The GPT-2 pattern was explicit about Unicode: property classes and, from part 05, normalization before matching. The fourth habit is the one that bites in production.
Catastrophic backtracking
Consider (a+)+b against aaaaaaaa!. The pattern says: one or more groups, each of one or more a's, followed by b. There is no b, so the answer is no. But a backtracking engine does not know that until it has tried every way of splitting the eight a's between the inner plus and the outer plus: all eight in one group, seven then one, one then seven, two then six, and so on. The number of ways to partition n a's into ordered runs is 2^(n-1), and each of them ends at the same exclamation mark. Add one a and the work doubles. This is catastrophic backtracking.
Worked example
Measuring the explosion
Set up two patterns
(a+)+b and a+b, both run against n a's followed by an exclamation mark, in Python re and in JavaScript.Time the nested pattern
Python time roughly quadruples every two extra characters (see the table below), which is doubling per character. JavaScript follows the same curve, roughly seven to fourteen times faster.Time the flat pattern
a+b on the same strings stays under 60 microseconds in Python at every n.a+b has only one way to split any run of a's, so the work is at most polynomial (each start position backs off once), never exponential.Same strings, same answer, more than four orders of magnitude apart
Run time depends on the structure of the pattern and the shape of the input, not on the length of the pattern.
| n | Python re | JavaScript |
|---|---|---|
| 20 | 43 ms | 8.3 ms |
| 22 | 175 ms | 31 ms |
| 24 | 711 ms | 126 ms |
| 26 | 2900 ms | 506 ms |
Russ Cox (2007) documents the classic case: a pattern of a? repeated n times followed by a repeated n times, matched against a string of n a's. At n = 29 Perl needs over sixty seconds, while a Thompson NFA, which simulates all states at once, takes about twenty microseconds. OWASP lists (a+)+ among its evil patterns because a single crafted input can pin a server's CPU, an attack it calls ReDoS. Both sources agree on the cause: an engine that explores alternatives one at a time, on a pattern with overlapping ways to match the same text.
Watch the engine time as the input grows. Then change the pattern to ^a+b and try again.
/^(a+)+b/gType a pattern to see live results.
Patterns run in JavaScript syntax inside a worker with a 600 ms limit. Python differs in three places: backreferences in replacements are \1 not $1, named groups are (?P<name>...), and \p{L} needs the regex module.
Three fixes, in order of preference. Rewrite the pattern so that quantifiers do not nest over the same characters: a+b matches exactly the same strings as (a+)+b. Lazy quantifiers do not prevent the explosion either: (a+?)+b backtracks just like (a+)+b, because laziness only changes the order in which splits are tried, not how many exist. Use possessive quantifiers or atomic groups where the engine offers them: tiktoken's \p{L}++ is precisely this, a plus that refuses to give characters back. Or run a linear-time engine such as RE2, Go's regexp or Rust's regex crate, which reject backreferences and lookaround in exchange for a guarantee that time is linear in the input.
Recall
Give an example of catastrophic backtracking and a fix.
Quick check
Why does (a+)+b take seconds on aaaaaaaaaaaaaaaaaaaaaaaaaa! while a+b takes microseconds?
Recap
If you remember nothing else
- The metacharacters . ^ $ * + ? ( ) [ ] { } | \ need a backslash to match literally; \n and \t go the other way and give a plain letter a special meaning.
- Precedence: parentheses, then counters, then sequences and anchors, then |. (cat|dog)s matches cats and dogs; cat|dogs matches cat and dogs.
- Capture groups only record. \1 in the pattern or $1 (Python \1) in the replacement reuses them; (?:...) groups without recording.
- (\d{4})-(\d{2})-(\d{2}) with \3/\2/\1 turns 2026-01-25 into 25/01/2026; slide 70's $2$3$1 gives 01252026 because the separators were never captured.
- Lookahead (?=...) and (?!...) are zero-width; stacking them expresses overlapping rules such as a password check.
- \d is Unicode Nd in Python re but ASCII only in JavaScript; \p{L} and \p{N} need the regex module or the u flag.
- The GPT-2 pre-tokenizer tries contractions, letters, numbers, punctuation and whitespace in that order; BPE merges run inside each piece.
- \s+(?!\S) eats a run of spaces but leaves the last one attached to the next word.
- Nested quantifiers such as (a+)+b backtrack exponentially: seconds at 26 characters in Python, microseconds for a+b.
Sources
- Speech and Language Processing, chapter 2: Regular Expressions, Tokenization, Edit DistanceBookJurafsky and Martin, draft of August 19, 2026Sections 2.6.4 to 2.6.9, Figures 2.14 and 2.15; precedence ladder, escaping table, substitutions, lookahead, BPE pre-tokenization(opens in a new tab)
- re: Regular expression operationsDocsPython documentationUnicode \d, fixed-width lookbehind, \g<0>, scoped flags, MULTILINE(opens in a new tab)
- regex: alternative regular expression moduleDocsPyPI\p{...} property classes and variable-length lookbehind(opens in a new tab)
- Unicode character class escape: \p{...}, \P{...}DocsMDN Web DocsRequires the u or v flag; \d is ASCII only in JavaScript(opens in a new tab)
- Lookbehind assertionDocsMDN Web DocsVariable-length lookbehind in JavaScript since ES2018(opens in a new tab)
- Unicode Standard Annex #44: Unicode Character DatabaseDocsUnicode ConsortiumGeneral_Category values L, N and Nd(opens in a new tab)
- encoder.pyDocsOpenAI, gpt-2 repositoryThe pre-tokenizer pattern and the per-piece bpe() loop(opens in a new tab)
- Language Models are Unsupervised Multitask LearnersPaperRadford et al., OpenAI, 2019Section 2.2: no merges across character categories, exception for spaces(opens in a new tab)
- openai_public.pyDocsOpenAI, tiktoken repositoryr50k and cl100k_base pre-tokenizer patterns(opens in a new tab)
- Pre-tokenizersDocsHugging Face tokenizers documentationByteLevel use_regex option for the GPT-2 pattern(opens in a new tab)
- SuperBPE: Space Travel for Language ModelsPaperLiu et al., 2025Pretokenization curriculum, superwords across whitespace, up to 33 percent fewer tokens(opens in a new tab)
- Regular Expression Matching Can Be Simple And FastArticleRuss Cox, 2007Backtracking versus Thompson NFA; sixty seconds versus twenty microseconds(opens in a new tab)
- Regular expression Denial of Service (ReDoS)DocsOWASPEvil patterns including (a+)+(opens in a new tab)
- regex101: build, test and debug regexDocsregex101Practice site named on slide 72; substitution tab, Python and JavaScript flavors(opens in a new tab)
Part 10: Minimum edit distance: alignments and search
Measuring how far apart two strings are with insertions, deletions and substitutions, seeing the answer as an alignment, and recognizing the problem as a shortest path that dynamic programming solves.
4 concepts, slides 79-86
Why this part matters
Edit distance is the first dynamic programming algorithm in this course, and its shape returns in the Viterbi decoder and in CKY parsing. It is also the metric under word error rate, so any speech or Arabic ASR work in the research project will report it. It is a reliable exam item: define it, write the recurrence, fill a table, recover an alignment.
This part builds the idea in four moves. First the definition and why anyone needs it. Then the alignment view, which turns a distance into columns you can read and add up, and which exposes a trap: the slides define substitution at cost 1, but the worked table in part 11 (taken from SLP3) charges 2, and the same pair of words comes out as 5 or 8 depending on that choice. Then the search view, which shows why brute force fails and what dynamic programming remembers. Finally the recurrence itself and the cost function it plugs in.
By the end you can
- Define minimum edit distance, name the three operations, and say what each costs under unit costs.
- Read and cost an alignment under unit costs and under substitution cost 2, and declare which convention you use.
- Explain why naive search over edit sequences is exponential and why the grid of prefix pairs is O(mn).
- Write the initialization and the recurrence for D[i,j] and justify D[i,0] = i.
- Compute a word error rate from an alignment and describe weighted substitution and transposition extensions.
A speech recognizer hears "turn on the kitchen light" and writes "turn on a kitchen light please". A spell checker sees "acress". An OCR engine reads the two letters "rn" as an "m". In each case there is a string we have and a string we want, and the question is the same: how much editing separates them?
The answer is the Minimum edit distance: the minimum total cost of the editing operations that turn a source string into a target string. Three operations are allowed. An insertion adds one symbol, a deletion removes one, and a substitution replaces one with another. Each operation has a cost, most often 1, and the distance is the cheapest way of getting from source to target, not the first way you happen to find. With every operation at cost 1 (and a substitution of a letter by itself at cost 0) this quantity is the Levenshtein distance, after the 1966 paper that introduced it for error correcting codes. Nothing in the definition says the symbols must be letters. Take words as the symbols and the same definition measures how far a recognized sentence is from its transcript, which is exactly how speech recognition uses it.
Where edit distance is used, and the mechanism in each case
- Spelling correction
- Generate candidates within edit distance 1 of the typo and rank them: for acress, the candidates are actress, cress, caress, access, across and acres.
- OCR post-processing
- Map a recognized string with a misread rn or m to the nearest entry of a lexicon.
- Speech recognition (WER)
- Align the recognized word sequence to the reference transcript and count the word-level insertions, substitutions and deletions.
- Approximate matching
- Search a text for a pattern while allowing a bounded number of edits, for example in DNA or in fuzzy search boxes.
- Alignment
- Make explicit which characters correspond, which is the view the next concept builds on.
The spelling application is older than most of NLP. Damerau found in 1964 that a single wrong, missing, extra or transposed letter accounts for about 80 percent of spelling errors, which is why candidate generation at edit distance 1 works so well: Kernighan, Church and Gale's spelling corrector lists exactly the six one-edit candidates for "acress" shown in the table and then picks among them with a probabilistic model. Approximate matching generalizes the search direction: instead of comparing two whole strings, you look for places in a long text that a pattern would match after at most k edits.
Word error rate, computed
The Word error rate (WER) is edit distance applied to words. Align the recognizer's hypothesis to the human reference at the word level, count the insertions, substitutions and deletions on that alignment, and divide by the number of words in the reference. The denominator is the reference, never the hypothesis, and because insertions are counted the rate can exceed 100 percent.
Worked example
WER for the kitchen light
Reference and hypothesis
Reference: "turn on the kitchen light" (5 words). Hypothesis: "turn on a kitchen light please" (6 words).Align at the word level
turn and on match, the becomes a (one substitution), kitchen and light match, please has nothing above it (one insertion).Count and divide
I + S + D = 1 + 1 + 0 = 2, over 5 reference words.Result
WER = 2 / 5 = 40 percent. A 2-word reference recognized as 5 words with three insertions would give 3 / 2 = 150 percent, which is legal and common on noisy audio.
SLP3's own CallHome example has 6 substitutions, 3 insertions and 1 deletion over 13 reference words, a WER of 76.9 percent. The standard scorer is NIST's sclite in the SCTK toolkit, which does the alignment and the arithmetic for you but reports exactly these counts.
Recall
Define minimum edit distance and name the three operations. What is it called when every operation costs one?
Recall
How does WER use edit distance, and can it exceed 100 percent?
Quick check
A 10-word reference is recognized with 2 substitutions, 1 deletion and 2 insertions. What is the WER?
Lay the two words on top of each other as on slide 82, with a star for a gap, and read the ten columns from left to right: I over a star, N over E, T over X, E over E, a star over C, N over U, and then T I O N over T I O N. The row underneath, d s s _ i s, names the columns that cost something: delete I, substitute N by E, substitute T by X, insert C, substitute N by U. Five operations, and five is the minimum edit distance.
What you just read is an Alignment: a correspondence between the two strings that says, for every symbol, what it lines up with. A column with the same letter top and bottom is a match and costs nothing. A column with two different letters is a substitution. A letter over a gap is a deletion (the source letter is consumed without producing anything), and a gap over a letter is an insertion. The cost of an alignment is the sum of its column costs, and the minimum edit distance is simply the cost of the cheapest alignment. The practical half of the story is that the dynamic programming table does not just give the number, it stores a Backpointer in each cell, and following those pointers back from the last cell recovers one cheapest alignment. Part 11 does that recovery by hand.
Two cost conventions, and the same words come out as 5 or 8
Here is the trap this lecture sets, and the reason this section exists. Slides 85 and 86 define the Substitution cost as 1 for two different letters. The filled table you will meet in part 11 (slides 89 and 91) is copied from SLP3, and SLP3 fills it with a substitution cost of 2. Levenshtein proposed both: in his second version insertions and deletions cost 1 and substitutions are simply not allowed, and since any substitution can be written as a deletion followed by an insertion, that is the same as allowing it at cost 2. Both are legitimate cost functions. They give different numbers, and they can disagree about which alignment is best.
| Operation | Unit costs (slides 85, 86) | SLP3 table costs (substitution 2) |
|---|---|---|
| Match (same letter) | 0 | 0 |
| Insertion | 1 | 1 |
| Deletion | 1 | 1 |
| Substitution (different letters) | 1 | 2 |
| Column | Operation | Unit costs | Substitution 2 |
|---|---|---|---|
| I over * | delete | 1 | 1 |
| N over E | substitute | 1 | 2 |
| T over X | substitute | 1 | 2 |
| E over E | match | 0 | 0 |
| * over C | insert | 1 | 1 |
| N over U | substitute | 1 | 2 |
| T I O N over T I O N | four matches | 0 | 0 |
| Total | 5 | 8 |
Read the totals again: 1 + 1 + 1 + 0 + 1 + 1 = 5 under unit costs and 1 + 2 + 2 + 0 + 1 + 2 = 8 with substitutions at 2. Both describe the same alignment. If an exam asks for the distance from intention to execution and you answer 8 without saying why, the grader who has the slide 85 recurrence in mind will mark it wrong, and the reverse is also true.
Try it: label the columns yourself
The editor below shows three alignments of intention and execution, with the four trailing matches already labeled. Label every remaining column, watch the two totals update, and then compare the presets. The third one pairs the letters position by position with no gaps at all: five substitutions. Under unit costs it ties the slide alignment at 5. With substitutions at 2 it costs 10 and loses to 8. The cost function does not only change the total; it changes which alignments are optimal.
Read each column of the alignment (source letter on top, target letter below, * for a gap) and label it match, substitution, insertion or deletion. The last 4 columns (T I O N) come pre-labeled as matches. The totals update from your labels under both cost conventions. Press check to grade the columns.
| Alignment | Operations | Unit costs | Substitution 2 |
|---|---|---|---|
| Slide 82: d s s _ i s | 1 del, 3 sub, 1 ins | 5 | 8 |
| SLP3 figure 2.19: delete i, n to e, t to x, insert u, n to c | 1 del, 3 sub, 1 ins | 5 | 8 |
| Position by position, no gaps | 5 sub | 5 | 10 |
Worked example
A second pair: kitten to sitting
Write the alignment
k over s, i over i, t over t, t over t, e over i, n over n, and a gap over g.Name the columns
substitution, match, match, match, substitution, match, insertion.Cost it both ways
Unit costs: 1 + 0 + 0 + 0 + 1 + 0 + 1 = 3. Substitution at 2: 2 + 0 + 0 + 0 + 2 + 0 + 1 = 5.Result
Distance 3 under unit costs, 5 under substitution cost 2. The alignment is optimal under both, because no cheaper one exists for either function.
Recall
Cost the slide 82 alignment d s s _ i s under unit costs and under substitution cost 2.
Quick check
Under unit costs, what is the minimum edit distance from intention to execution?
The obvious algorithm is search. Start at intention and try one edit: delete a letter and you get ntention, insert a letter and you get intecntion, substitute a letter and you get inxention. Those are three of the children. Count all of them for a 26-letter alphabet: 9 possible deletions, 26 × 10 = 260 insertions and 25 × 9 = 225 substitutions, which is 494 children for one step. A blind search that needs five steps to reach execution touches on the order of 494^5, about 3 × 10^13 strings. SLP3 puts it plainly: the space of all possible edits is enormous, so we cannot search naively.
The visual shows the observation that rescues the problem. Delete the i of intention and then substitute t by x and you reach nxention. Substitute t by x first and then delete the i and you reach nxention again. Two paths, one state, and once you know the cheapest way to reach a state there is no reason to explore any other route into it. SLP3 states the idea directly: many distinct edit paths end up in the same string, so rather than recomputing all those paths we can remember the shortest path to a state each time we see it. Remembering subproblem answers and combining them is Dynamic programming, Bellman's table-driven method from 1957.
Why only prefix pairs matter
Remembering strings is still too many states. The step that makes the table small is to notice which states can matter at all. Suppose exention lies on an optimal path from intention to execution. Then the part of the path from intention to exention must itself be optimal, because if a shorter way to exention existed we could splice it in and shorten the whole path, a contradiction. This is the Optimal substructure argument in SLP3, and it means the only thing worth remembering about a partial solution is how much of the source it has consumed and how much of the target it has produced: the pair (i, j). For two 9-letter words there are only (m + 1)(n + 1) = 10 × 10 = 100 such pairs. Put as a picture: edits are moves in a grid from (0, 0) to (m, n), each move is one operation, and dynamic programming finds the best path efficiently.
| Move | What it does | Operation | Recurrence term |
|---|---|---|---|
| Down, (i - 1, j) to (i, j) | Consume a source letter, produce nothing | Deletion | D[i - 1, j] + del |
| Right, (i, j - 1) to (i, j) | Produce a target letter from nothing | Insertion | D[i, j - 1] + ins |
| Diagonal, (i - 1, j - 1) to (i, j) | Pair a source letter with a target letter | Substitution or match | D[i - 1, j - 1] + sub(x_i, y_j) |
A path from the top-left corner to the bottom-right corner that uses only these three moves is an alignment: each step is one column. Reading the slide 82 alignment as moves gives down, diagonal, diagonal, diagonal, right, then five diagonals, which is the staircase in the figure. Wagner and Fischer showed in 1974 that filling the grid solves the problem in time proportional to the product of the two lengths, O(mn) time and, with backpointers stored, O(mn) space. Each cell is filled once with three comparisons.
Recall
Why is the search tree over edit sequences exponential while the grid is polynomial?
Quick check
In the DP grid with source letters down the rows, which move is an insertion?
Quick check
Why does dynamic programming beat naive search over edit sequences?
Three small questions give you the whole initialization. What is the distance from int to the empty string? You must delete i, n and t, so 3. From the empty string to ex? Insert e and x, so 2. From empty to empty? Nothing to do, 0. Write those as D[3, 0] = 3, D[0, 2] = 2 and D[0, 0] = 0 and you have the first row and column of every table you will ever fill.
Slide 85 sets the notation. The source x has length m, the target y has length n, and D[i, j] is the minimum edit distance between the prefix x[1..i] and the prefix y[1..j]. The answer to the whole problem is the last cell, D[m, n]. The Edit distance recurrence says how to fill every other cell from three neighbors.
Each line is one possible last column of an optimal alignment of the two prefixes. Either the last column is x_i over a gap, in which case the rest is an optimal alignment of x[1..i-1] with y[1..j] and we pay a deletion; or it is a gap over y_j, the rest aligns x[1..i] with y[1..j-1] and we pay an insertion; or it is x_i over y_j, the rest aligns the two shorter prefixes and we pay the substitution cost, which is zero when the letters agree. There is no fourth shape a column can take, so the minimum over these three is exact. That is the optimal substructure of the previous concept written as an equation.
Now the exam favorite: why is D[i, 0] = i? The target prefix is empty, so every one of the i source letters has to disappear, and the only operation that removes a source letter is a deletion. No insertion can help, since it would produce a target letter where none is wanted, and no substitution can help, since it leaves a letter in place. At one unit per deletion the cost is exactly i. The mirror argument gives D[0, j] = j. In the general form of SLP3's pseudocode the border is built incrementally, D[i, 0] = D[i-1, 0] + del(x_i), which reduces to i when deletions cost one.
A table you can fill in one minute
Before the 10 × 10 table of part 11, fill a tiny one under unit costs: cat to cats. The first row and column are the initialization. Every other cell is the minimum of the cell above plus one, the cell to the left plus one, and the diagonal cell plus zero or one.
| ε | c | a | t | s | |
|---|---|---|---|---|---|
| ε | 0 | 1 | 2 | 3 | 4 |
| c | 1 | 0 | 1 | 2 | 3 |
| a | 2 | 1 | 0 | 1 | 2 |
| t | 3 | 2 | 1 | 0 | 1 |
Worked example
Two cells in full
D[1, 1], c against c
Above: D[0, 1] + 1 = 2. Left: D[1, 0] + 1 = 2. Diagonal: D[0, 0] + sub(c, c) = 0 + 0 = 0. Minimum 0.D[3, 4], cat against cats
Above: D[2, 4] + 1 = 3. Left: D[3, 3] + 1 = 1. Diagonal: D[2, 3] + sub(t, s) = 1 + 1 = 2. Minimum 1, from the left, which is an insertion of s.Result
D[3, 4] = 1: one insertion turns cat into cats, and the backpointer of the last cell already says so.
What the cost function can encode
Slide 86 spells out the unit Substitution cost: sub(a, b) = 0 if a = b, else 1. It also says the cost can be weighted, and this is where edit distance stops being a counting exercise and becomes a model. SLP3's caption to its pseudocode notes that costs can be specific to the letter, and its text adds that for spelling correction substitutions are more likely between letters that are next to each other on the keyboard. Kernighan, Church and Gale estimated four confusion matrices (deletion, addition, substitution and reversal counts) from the typos in 44 million words of 1988 AP newswire and used them as the channel probabilities of a noisy-channel spelling corrector; their top candidate agreed with the majority of three human judges in 87 percent of 329 cases. Set a substitution cost to the negative log of such a probability and the cheapest alignment becomes the most probable one.
| Cost function | sub(a, b) | Name | intention to execution |
|---|---|---|---|
| Unit (slides 85 and 86) | 0 if a = b, else 1 | Levenshtein distance | 5 |
| Substitution cost 2 (SLP3 tables) | 0 if a = b, else 2 | Levenshtein's no-substitution variant | 8 |
| Confusion-weighted | A value from a confusion matrix | Noisy-channel spelling correction | Depends on the matrix |
A cost function can also grow a fourth operation. Damerau's 1964 study listed transposition of two adjacent letters as one of the four single-error types. The simplest extension adds a fourth line to the Wagner-Fischer table, D[i-2, j-2] + cost, that applies when x_{i-1} = y_j and x_i = y_{j-1}; this restricted form is often called optimal string alignment distance, and it can overcount when a transposed pair is edited again. Lowrance and Wagner's 1975 algorithm handles that general case, and both are commonly labelled Damerau-Levenshtein distance. The typo teh for the costs 2 with the three classic operations (delete one letter, insert it elsewhere) but only 1 with transposition. The slide recurrence does not include this move; treat it as an optional fourth line you can add when the application calls for it.
Recall
Write the recurrence for D[i, j] with its initialization and say where the answer sits.
Recall
Why is D[i, 0] = i?
Recall
flaw versus lawn: what are the Hamming distance and the edit distance, and why do they differ?
Quick check
Which operation explains why D[i, 0] = i under unit costs?
Recap
If you remember nothing else
- Minimum edit distance is the cheapest sequence of insertions, deletions and substitutions from source to target; with unit costs it is the Levenshtein distance.
- Applications: spelling correction, OCR post-processing, word error rate for speech recognition, approximate matching and alignment.
- WER = (I + S + D) / reference words, read off a word-level alignment; insertions count, so it can exceed 100 percent.
- An alignment is a set of columns; its cost is the sum of column costs; the distance is the cost of the cheapest alignment; several alignments can tie.
- intention to execution costs 5 under unit costs and 8 when a substitution costs 2. Always state the convention.
- The search tree over strings explodes (about 494 children per step for a 9-letter word); the grid over prefix pairs has (m + 1)(n + 1) = 100 cells.
- Down consumes a source letter (deletion), right produces a target letter (insertion), diagonal pairs two letters (substitution or match).
- D[0,0] = 0, D[i,0] = i, D[0,j] = j; D[i,j] is the minimum of the three neighbors plus their operation cost; the answer is D[m,n].
- sub(a,b) = 0 if a = b else 1 is one choice; substitution cost 2, confusion-weighted costs and transposition (Damerau-Levenshtein) are others.
Sources
- Speech and Language Processing, chapter 2: Words and Tokens, section 2.9 Minimum Edit DistanceBookJurafsky and Martin, draft of August 2026Definition, alignment figure 2.17, search figure 2.18, path figure 2.19, cost-2 table figure 2.20, pseudocode 2.21, backtrace 2.22(opens in a new tab)
- Speech and Language Processing, chapter 16: Automatic Speech Recognition, section 16.6 Word Error RateBookJurafsky and Martin, draft of August 2026WER formula, the CallHome example with 76.9 percent, and why the rate can exceed 100 percent(opens in a new tab)
- Binary codes capable of correcting deletions, insertions and reversalsPaperSoviet Physics Doklady 10, 1966 (V. I. Levenshtein)Origin of the distance and of the no-substitution variant equivalent to substitution cost 2(opens in a new tab)
- The String-to-String Correction ProblemPaperJournal of the ACM 21(1), 1974 (Wagner and Fischer)The dynamic programming algorithm in time proportional to the product of the lengths(opens in a new tab)
- A technique for computer detection and correction of spelling errorsPaperCommunications of the ACM 7(3), 1964 (Damerau)Single-error types including transposition; about 80 percent of errors are one edit away(opens in a new tab)
- An Extension of the String-to-String Correction ProblemPaperJournal of the ACM, 1975 (Lowrance and Wagner)Adds adjacent transposition to the Wagner-Fischer recurrence in its general, unrestricted form(opens in a new tab)
- A Spelling Correction Program Based on a Noisy Channel ModelPaperCOLING 1990 (Kernighan, Church and Gale)Four confusion matrices estimated from 44 million words of 1988 AP newswire; 87 percent agreement with judges on 329 cases(opens in a new tab)
- Error Detecting and Error Correcting CodesPaperBell System Technical Journal 29(2), 1950 (Hamming)Hamming distance: substitutions only, equal lengths(opens in a new tab)
- Dynamic ProgrammingBookPrinceton University Press, 1957 (Bellman)The table-driven method of combining subproblem solutions(opens in a new tab)
- SCTK, the NIST Scoring Toolkit (sclite)DocsNISTStandard WER scorer that performs the word-level alignment(opens in a new tab)
Part 11: Filling the DP table and recovering the alignment
The full algorithm, a cell by cell computation for intention to execution, backpointers and backtrace to read off the alignment, the time and space complexity, and the lecture in one page.
5 concepts, slides 87-94
Why this part matters
The edit distance table is the most examinable algorithm in this lecture. SLP3 sets it as a by-hand exercise, and every spelling, transliteration or speech evaluation you will run in your research is this table applied to characters or words: word error rate is literally an edit distance divided by a length.
Part 10 defined the problem and the recurrence. This part makes the algorithm run. You will fill the 10 x 10 table for intention to execution cell by cell, see why the slide's table ends in 8 while the unit-cost answer is 5, store arrows as you go, and then walk those arrows home to read an alignment off the table. The same fill-then-backtrace pattern returns later in the course as Viterbi decoding and CKY parsing, so the effort pays three times.
By the end you can
- Initialize and fill an edit distance table by hand under a stated cost convention and read the distance from D[n,m].
- Store argmin arrows while filling and backtrace them into an alignment with operation letters.
- Explain what a tie in the argmin means for the number of optimal alignments.
- State time and space complexity, and how two rows or Hirschberg's recursion reduce space.
- Explain why slide 89 ends in 8 while unit costs give 5, and pass an exam under either convention.
Start with a route you can check by eye. Take intention, delete the i to get ntention, replace n by e to get etention, replace t by x to get exention, insert u to get exenution, and replace n by c to get execution. Five edits, each of cost 1, and the string arrives exactly where it should.
- intention, delete i, gives ntention
- ntention, substitute n by e, gives etention
- etention, substitute t by x, gives exention
- exention, insert u, gives exenution
- exenution, substitute n by c, gives execution
SLP3 states that the unit-cost Minimum edit distance between these two words is exactly 5, so this ladder is not merely a route, it is an optimal one. The interesting question is what optimality implies about the middle rungs. Suppose exention really lies on a shortest path. Then the way we reached exention must itself be the shortest way to reach exention: if a cheaper route to exention existed, we could splice it in and get a cheaper route to execution, which contradicts the assumption that our path was shortest. Every prefix of a shortest path is a shortest path. That property is called Optimal substructure, and it is the license for Dynamic programming: instead of remembering whole edit sequences, remember the best cost for every intermediate state once, and build longer answers out of shorter ones.
The states themselves are what part 10 introduced: a pair of prefixes, the first i letters of the source and the first j letters of the target. The naive search over edit sequences explodes because thousands of different sequences pass through the same intermediate string, and each one is scored again from scratch. A table indexed by prefix pairs collapses all of those into one cell, and the Alignment you read from the table at the end is just the ladder above, laid out as a path through cells (SLP3, section 2.9.1).
Recall
Why does the fact that exention lies on an optimal path from intention to execution tell you anything about the path from intention to exention?
Compute the top-left corner of the intention to execution table by hand, using the slide's convention where a substitution costs 2. The empty string needs nothing to become the empty string, so D[0,0] = 0. Turning the one-letter prefix i into nothing takes one deletion, D[1,0] = 1. Building e from nothing takes one insertion, D[0,1] = 1. The first interior cell compares i with e: D[1,1] = min(1 + 1, 1 + 1, 0 + 2) = 2, and all three candidates tie.
That small calculation is the whole algorithm, repeated. SLP3's pseudocode (its Fig. 2.21, reproduced on slide 88) makes it precise. Let n be the length of the source and m the length of the target, and create a matrix D with n + 1 rows and m + 1 columns, the extra row and column standing for the empty prefix. The first column is D[i,0] = D[i-1,0] + del-cost(source[i]), because the only way to turn a length-i prefix into nothing is to delete each of its letters. The first row is D[0,j] = D[0,j-1] + ins-cost(target[j]), because the only way to build a length-j prefix from nothing is to insert each letter. With unit insertion and deletion those two lines reduce to D[i,0] = i and D[0,j] = j. Then, row by row and column by column, every interior cell is the minimum of three candidates, and the answer is D[n,m].
The Edit distance recurrence looks only at three neighbors: the cell above (delete the source letter source[i], written x_i in the formula), the cell to the left (insert the target letter target[j], written y_j), and the diagonal cell (substitute one for the other, or match for free when they are equal). Nothing else in the two strings is consulted. That locality is what makes the fill order work: going row by row from the top-left, all three neighbors are already computed when you reach a cell. Any order with that property is fine, including column by column or along anti-diagonals, which is how the hover figure below animates it.
Two conventions, two right answers
Slide 86 defined a unit Substitution cost, but the table on slide 89 is SLP3's Fig. 2.20, which charges 2 for a substitution. SLP3 explains the choice: a substitution can always be simulated by one deletion and one insertion, so charging 2 makes the metric coincide with the version of Levenshtein's distance that allows only insertions and deletions. Under that convention intention to execution costs 8; under unit costs it is 5, the length of the ladder from the previous concept. Both numbers are correct in their own world, and an exam answer is correct only if it names the world it lives in.
| Convention | Insert | Delete | Substitute | Match | Distance | Optimal alignments |
|---|---|---|---|---|---|---|
| Unit (Levenshtein) | 1 | 1 | 1 | 0 | 5 | 7 |
| SLP3 table (slide 89) | 1 | 1 | 2 | 0 | 8 | 134 |
Here is the full table from the slide with the source intention down the rows and the target execution across the columns, followed by the same table under unit costs. Compare any substitution cell: D[1,1] is 2 in the first and 1 in the second, and that one-point gap propagates until the corners read 8 and 5.
| # | e | x | e | c | u | t | i | o | n | |
|---|---|---|---|---|---|---|---|---|---|---|
| # | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| i | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 6 | 7 | 8 |
| n | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 7 | 8 | 7 |
| t | 3 | 4 | 5 | 6 | 7 | 8 | 7 | 8 | 9 | 8 |
| e | 4 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 9 |
| n | 5 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 10 |
| t | 6 | 5 | 6 | 7 | 8 | 9 | 8 | 9 | 10 | 11 |
| i | 7 | 6 | 7 | 8 | 9 | 10 | 9 | 8 | 9 | 10 |
| o | 8 | 7 | 8 | 9 | 10 | 11 | 10 | 9 | 8 | 9 |
| n | 9 | 8 | 9 | 10 | 11 | 12 | 11 | 10 | 9 | 8 |
| # | e | x | e | c | u | t | i | o | n | |
|---|---|---|---|---|---|---|---|---|---|---|
| # | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
| i | 1 | 1 | 2 | 3 | 4 | 5 | 6 | 6 | 7 | 8 |
| n | 2 | 2 | 2 | 3 | 4 | 5 | 6 | 7 | 7 | 7 |
| t | 3 | 3 | 3 | 3 | 4 | 5 | 5 | 6 | 7 | 8 |
| e | 4 | 3 | 4 | 3 | 4 | 5 | 6 | 6 | 7 | 8 |
| n | 5 | 4 | 4 | 4 | 4 | 5 | 6 | 7 | 7 | 7 |
| t | 6 | 5 | 5 | 5 | 5 | 5 | 5 | 6 | 7 | 8 |
| i | 7 | 6 | 6 | 6 | 6 | 6 | 6 | 5 | 6 | 7 |
| o | 8 | 7 | 7 | 7 | 7 | 7 | 7 | 6 | 5 | 6 |
| n | 9 | 8 | 8 | 8 | 8 | 8 | 8 | 7 | 6 | 5 |
Worked example
Three cells of the slide's table, substitution cost 2
D[1,1], i versus e
Up gives D[0,1] + 1 = 2, left gives D[1,0] + 1 = 2, diagonal gives D[0,0] + 2 = 2. All three tie, so D[1,1] = 2 and the cell stores three arrows.D[4,2], e versus x
Up gives D[3,2] + 1 = 6, left gives D[4,1] + 1 = 4, diagonal gives D[3,1] + 2 = 6. Left wins alone, so D[4,2] = 4 with a single left arrow.D[7,7], i versus i
The letters match, so the diagonal is free: D[6,6] + 0 = 8. Up and left would give 9 + 1 and 9 + 1, so D[7,7] = 8 with one diagonal arrow.Result
Continuing to the corner, D[9,9] = 8. Under unit costs the same three cells read 1, 4 and 5, and the corner reads 5.
Worked example
A table you can finish in a minute: cat to cut, unit costs
Borders
Row # is 0 1 2 3 and column # is 0 1 2 3.Row c
c matches c, so D[1,1] = 0; then D[1,2] = 1 and D[1,3] = 2 by inserting u and t.Row a
D[2,1] = 1 (delete a), D[2,2] = min(1 + 1, 0 + 1, 1 + 1) = 1 (substitute a by u), D[2,3] = 2.Row t
D[3,1] = 2, D[3,2] = 2, and t matches t so D[3,3] = D[2,2] + 0 = 1.Result
Distance 1, alignment c a t over c u t with one substitution.
| # | c | u | t | |
|---|---|---|---|---|
| # | 0 | 1 | 2 | 3 |
| c | 1 | 0 | 1 | 2 |
| a | 2 | 1 | 1 | 2 |
| t | 3 | 2 | 2 | 1 |
Now run the same procedure yourself. The simulator fills any pair of short words one cell at a time, prints the three candidates for each new cell, stores the arrows, and keeps both conventions side by side so you can watch the corner change from 5 to 8 with one toggle. The backtrace button belongs to the next concept; try it after reading on.
| # | e | x | e | c | u | t | i | o | n | |
|---|---|---|---|---|---|---|---|---|---|---|
| # | ||||||||||
| i | ||||||||||
| n | ||||||||||
| t | ||||||||||
| e | ||||||||||
| n | ||||||||||
| t | ||||||||||
| i | ||||||||||
| o | ||||||||||
| n |
D[0,0] = 0: the empty string is already the empty string.
| Convention | Substitution cost | Distance | Optimal alignments |
|---|---|---|---|
| Unit (Levenshtein) | 1 | 5 | 7 |
| SLP3 table (slide 89) (shown) | 2 | 8 | 134 |
Quick check
In the recurrence, which candidate corresponds to deleting the source letter source[i]?
Quick check
Slide 89 ends at 8 for intention to execution, but unit costs give 5. What explains the difference?
Recall
Why is the first column initialized to i and the first row to j?
Recall
Which three cells does D[i,j] depend on, and what operation does each represent?
Put your finger on the 8 in the bottom-right corner of slide 91. That cell holds a single diagonal arrow, because n matches n and the free diagonal was the only candidate that reached 8. Step diagonally to (8,8), still 8, o matches o. Diagonal again to (7,7) for i, and to (6,6) for t. At (5,5) three arrows are drawn; the bold path takes the diagonal, substituting n by u for 2, which is why the value drops from 8 to 6 at (4,4). That cell has only a left arrow: insert c. Then (4,3) matches e for free, (3,2) substitutes t by x, (2,1) substitutes n by e, and (1,0) points up, deleting i, into (0,0).
| Cell | Value | Arrow taken | Meaning |
|---|---|---|---|
| (9,9) | 8 | diagonal | n matches n, cost 0 |
| (8,8) | 8 | diagonal | o matches o, cost 0 |
| (7,7) | 8 | diagonal | i matches i, cost 0 |
| (6,6) | 8 | diagonal | t matches t, cost 0 |
| (5,5) | 8 | diagonal | n to u, cost 2 (one of three arrows) |
| (4,4) | 6 | left | insert c, cost 1 |
| (4,3) | 5 | diagonal | e matches e, cost 0 (one of two arrows) |
| (3,2) | 5 | diagonal | t to x, cost 2 (one of three arrows) |
| (2,1) | 3 | diagonal | n to e, cost 2 (one of three arrows) |
| (1,0) | 1 | up | delete i, cost 1 |
| (0,0) | 0 | stop | origin reached |
Reverse the list and you have an edit sequence: delete i, n to e, t to x, match e, insert c, n to u, then four matches. Its cost is 1 + 2 + 2 + 0 + 1 + 2 + 0 + 0 + 0 + 0 = 8, the corner value, and written as rows it is SLP3's Fig. 2.17: INTE*NTION over *EXECUTION with the operation letters d s s i s under the changed columns. This is a different five-edit shape from the ladder in the first concept, yet it costs the same under both conventions, 8 here and 5 at unit costs: the ladder substitutes n by c and then inserts u, the bold path inserts c and then substitutes n by u, and both keep the four free matches at the end. The two diverge exactly at the three-arrow tie in (5,5): the left arrow leads to the ladder, the diagonal to the bold path. That is the tie rule of the next subsection in action.
The general mechanism is what slide 90 states. While filling, each cell records which of its three candidates achieved the minimum, the argmin, as a Backpointer. Up means the source letter was deleted, left means the target letter was inserted, diagonal means substitution or match. After the fill, start at D[n,m] and follow arrows until D[0,0]. SLP3 puts it in one sentence: each complete path between the final cell and the initial cell is a minimum distance Alignment. Every step decreases i, j or both, so the walk takes at most n + m steps, which is why SLP3's lecture slides list the Backtrace as O(n + m).
How to read an arrow
- Up
- delete source[i], move to (i-1, j); two bold cells in one column
- Left
- insert target[j], move to (i, j-1); two bold cells in one row
- Diagonal
- substitute or match, move to (i-1, j-1); cost 0 when the letters are equal
Ties are alternative alignments
A cell like (1,1) on slide 91 carries three arrows because three candidates reached the same minimum. SLP3 says this directly: some cells have multiple backpointers because the minimum extension could have come from multiple previous cells. Each choice at a tie leads to a different complete path, and every complete path has the same total cost, so ties are exactly the reason multiple optimal alignments exist. For intention to execution there are 134 such paths under the cost-2 convention and 7 under unit costs (both counted for this part by walking every pointer set). Erickson shows a smaller case in his textbook, ALGORITHM to ALTRUISTIC, whose table has exactly three optimal paths. Go back to the simulator, press Backtrace, then Next alignment, and watch the highlighted path change at the teal-dotted cells while the distance stays fixed.
Quick check
A backpointer cell carries three arrows. What does that tell you about the alignments?
Recall
A cell has two arrows. What does this mean for the alignments?
Recall
Fill the unit-cost table for cat to cut and give the distance and one alignment.
The intention to execution table has 10 x 10 = 100 cells, and each cost a glance at three neighbors. Two documents of 100,000 characters each would need 10^10 cells, which is the scale where the accounting starts to matter and where the Stanford CS 262 notes on alignment begin their discussion of space.
Time is the easy half. There are (n + 1)(m + 1) cells, each computed in constant time from three already-known neighbors, so the fill runs in O(mn), the bound Wagner and Fischer gave in 1974 and the one Erickson states for his edit distance chapter. Space is where the choices begin. Storing the whole table, as the simulator does, also takes O(mn). But look at the recurrence again: row i reads only row i - 1 and the cells to its own left. If all you want is the distance, keep two rows, the previous one and the one being filled, and throw the rest away. Put the shorter string across the columns and the memory is O(min(m,n)), exactly what slide 92 says.
The price of that saving is the third bullet of the slide. The backpointers lived in the rows you discarded, so the two-row version cannot Backtrace. Erickson puts it plainly: by throwing away most of the table, we apparently lose the ability to walk backward. If you need the alignment as well, either keep the full table with its arrows, or recompute cleverly. Hirschberg's 1975 algorithm is the clever recomputation: run the two-row fill forward from the start and backward from the end, meet in the middle column to find which cell of that column the optimal path passes through, then recurse on the two halves. It recovers the full alignment in O(m + n) space while staying O(mn) in time; the CS 262 proof shows the constant roughly doubles, because every level of the recursion refills about half the area of the level above.
| Goal | Time | Space | What to store |
|---|---|---|---|
| Distance only | O(mn) | O(min(m,n)) | Two rows: the previous one and the one being filled |
| Distance and alignment | O(mn) | O(mn) | The whole table with an arrow set in every cell |
| Alignment in linear space | O(mn), about twice the constant | O(m+n) | Hirschberg's divide-and-conquer recursion, no stored table |
Quick check
You need only the distance between two strings of lengths m and n. What is the least memory the standard algorithm needs?
Recall
You need only the distance between two long strings. How much memory do you need and why?
Slide 93 closes the lecture with seven lines, and they tell one story. A word is not a universal primitive, so Tokenization is a design decision rather than a fact about text. Whatever units you pick, their frequencies follow a heavy tail (Zipf for rank against frequency, Heaps for vocabulary growth), so a fixed word vocabulary always leaks unseen words, and subwords are the answer.
Morphology shows that words do have meaningful pieces, morphemes, but that segmenting them cleanly is hard and differs across languages. Unicode and UTF-8 make multilingual text representable at all, and their bytes are the universal fallback when nothing else fits. BPE then learns a compact, reusable subword vocabulary from data, with a regex pre-tokenizer doing the practical work of splitting and normalizing before it. Finally, once text has become strings of units, the Minimum edit distance table you just filled is the foundational dynamic program for saying how similar two of those strings are.
- Word is not a universal primitive; tokenization is a design decision.
- Vocabulary growth follows a heavy tail (Zipf, Heaps), which motivates subwords.
- Morphology differs across languages; morphemes are meaningful but not always easy to segment.
- Unicode and UTF-8 make multilingual text possible, and bytes are a universal fallback.
- BPE learns a compact, reusable subword vocabulary.
- Regex is the practical tool for pre-tokenization and normalization.
- Minimum edit distance is the foundational DP algorithm for string similarity.
Recap
If you remember nothing else
- D[i,0] = i deletions, D[0,j] = j insertions, D[0,0] = 0.
- D[i,j] is the minimum of up plus delete, left plus insert, and diagonal plus substitute, where substitute costs 0 on a match.
- intention to execution costs 5 under unit costs and 8 under SLP3's cost-2 substitution; slide 89 uses cost 2.
- Store the argmin arrows while filling; backtrace from D[n,m] to D[0,0] in O(n+m) steps; several arrows in a cell mean several optimal alignments (134 at cost 2, 7 at cost 1 for this pair).
- Time O(mn). Space O(mn) with the table, O(min(m,n)) with two rows when only the distance is needed. Hirschberg recovers the alignment in O(m+n) space.
- Lecture takeaways: tokenization is a design decision; heavy tails motivate subwords; morphemes are meaningful but hard to segment; Unicode and UTF-8 with bytes as the fallback; BPE learns a compact vocabulary; regex handles pre-tokenization and normalization; edit distance is the foundational DP for string similarity.
Sources
- Speech and Language Processing, 3rd edition, chapter 2: Words and TokensBookStanford University (Jurafsky and Martin)Section 2.9, Eq. 2.19 and 2.20, Figs. 2.17 and 2.19 to 2.22 in the August 19, 2026 release(opens in a new tab)
- Speech and Language Processing, online manuscript indexDocsStanford University (Jurafsky and Martin)Cited on slide 94 as the January 6, 2026 manuscript(opens in a new tab)
- Minimum Edit Distance lecture slidesDocsStanford University (Jurafsky)Time O(nm), space O(nm), backtrace O(n+m); remember where you came from(opens in a new tab)
- The String-to-String Correction ProblemPaperJournal of the ACM 21(1):168-173, 1974 (Wagner and Fischer)The O(mn) dynamic programming algorithm(opens in a new tab)
- A linear space algorithm for computing maximal common subsequencesPaperCommunications of the ACM 18(6):341-343, 1975 (Hirschberg)Alignment recovery in O(mn) time and linear space(opens in a new tab)
- Binary codes capable of correcting deletions, insertions, and reversalsPaperDoklady Akademii Nauk SSSR 163(4):845-848, 1965 (Levenshtein)Origin of the Levenshtein distance(opens in a new tab)
- Algorithms, chapter 3: Dynamic ProgrammingBookJeff Erickson, University of IllinoisEdit distance in O(mn) time and space; ALGORITHM to ALTRUISTIC with three optimal paths(opens in a new tab)
- Algorithms, appendix D: Advanced Dynamic Programming, D.1 Saving SpaceBookJeff Erickson, University of IllinoisTwo-row trick, loss of the backward walk, and Hirschberg's O(m+n) space bound(opens in a new tab)
- CS 262 lecture 3 notes: Linear Space AlignmentDocsStanford UniversityTwo-column scoring, Hirschberg's O(N+M) space and O(MN) time with about twice the constant(opens in a new tab)
- Introduction to Algorithms, 4th editionBookMIT Press, 2022 (Cormen, Leiserson, Rivest and Stein)Dynamic programming chapter: optimal substructure and overlapping subproblems(opens in a new tab)