Majid Al-RaimiFull guide

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
Understood
0/46 concepts

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

  1. State the token and type definitions and compute N and |V| for a given string under a stated policy for case and punctuation.
  2. 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-.
  3. Read the SLP3 corpus table, compute the type to token ratio, and explain both the falling ratio and the Switchboard genre effect.
  4. 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.

  1. Words, with Zipf and Heaps. How to count units, and the two laws that govern how frequency and vocabulary behave as a corpus grows.
  2. Morphemes. The meaningful parts inside a word, which explain why walk, walks and walked are one lemma but three types.
  3. Corpora. Where the text comes from, and why genre, domain and time shift every count in this part.
  4. Unicode. How characters become bytes, so that a tokenizer can promise to represent any script without an unknown symbol.
  5. Tokenization with BPE. The algorithm that learns its own units from data instead of trusting spaces.
  6. Sentence segmentation. The period that ends a sentence versus the period inside Dr. or U.S.
  7. Regular expressions. The pattern language behind pre-tokenizers, cleaners and feature extractors.
  8. 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.

Words (Zipf and Heaps), morphemes, corpora, Unicode, tokenization (BPE), sentence segmentation, regular expressions, minimum edit distance.

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

  1. 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.
  2. 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.
  3. 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.
  4. Lowercase

    Folding They to they changes nothing here, because no other they appears. In another sentence it would merge two distinct items into one.
  5. Four answers, one sentence

    PolicyTokens NTypes |V|
    Whitespace only1614
    Punctuation split1816
    The picnic sentence under two counting policies
    The sentence never changed. The counting policy did.
The picnic sentence segmented by a punctuation-aware policy: seventeen boundaries, the three teal ones isolating the comma and the period, giving 18 tokens and 16 types

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.

QuestionA task that says yesA task that says no
Is punctuation its own token?Language models and parsers, where a period or a question mark carries meaningBag-of-words retrieval, where punctuation only adds noise
Do we lowercase?Information retrieval, where a query rarely matches the case of the documentNamed 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 sentimentA grammar checker may replace every number by one placeholder
Do we keep disfluencies?Speech recognition, where uh predicts a restart and identifies the speakerParsing and translation, which want the fluent sentence underneath
Tokenization questions and the tasks that answer them differently

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?

16 tokens and 14 types without punctuation (the occurs three times); 18 tokens and 16 types once the comma and the period count as tokens.

Recall

Give two reasons Jurafsky and Martin give for keeping uh and um in speech recognition.

They 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 add that uh signals a minor delay and um a major one.

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.

N=wVc(w)N = \sum_{w \in V} c(w)
Tokens are the sum of type counts: 2 + 2 + 1 + 1 = 6
Six tokens in running order collapse into four type stacks: to and be twice each, or and not once, and the stack heights add back up to N = 6

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.

InteractiveToken and type counter: every toggle is a tokenization policy
Tokens N16occurrences
Types |V|14distinct
|V| / N0.88types per token

16 tokens, 14 types

Token stream
Theypicnickedbythepool,thenlaybackonthegrassandlookedatthestars.
Type set with counts
  • 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.

CorpusGenreTypes |V|Tokens NTypes per 1,000 tokensTokens per type
ShakespearePlays and poems31 thousand884 thousand35.128.5
Brown corpus15 written genres, 196138 thousand1 million38.026.3
SwitchboardTelephone conversation20 thousand2.4 million8.3120
COCA8 balanced genres2 million440 million4.5220
Google n-gramsWeb text13 million1 trillion0.01376,923
Rough numbers of wordform types and tokens for English corpora (SLP3 Fig. 2.1 and the lecture slide), with computed ratios

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.

Types against tokens with both axes on a log scale: Heaps' law is a straight line of slope about 0.5, well under the slope-1 line where every token would be new, and Switchboard sits below the fit because conversation reuses a small vocabulary

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'.

A token is one occurrence of a unit in running text, and N counts them. A type is a distinct vocabulary item, and |V| counts them. N = 6, |V| = 4 (to, be, or, not).

Recall

Why does Switchboard have fewer types than Shakespeare despite almost three times as many tokens?

Genre and domain. Conversational telephone speech reuses a small everyday vocabulary, so |V| depends on the corpus and its genre, not only on N.

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

  1. Input

    "The San Francisco-based restaurant," they said, "doesn't charge $10".
  2. Separate all punctuation

    Each quotation mark, comma and the final period becomes its own token, so restaurant," turns into restaurant, , and ".
  3. Keep hyphenated words together

    Francisco-based stays one token. The convention treats a hyphenated compound as a unit.
  4. 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.
  5. Separate the currency sign

    $10 becomes $ and 10.
  6. 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.
Two Penn Treebank splits: a tick drops between does and n't and between children and 's, then the halves slide apart, matching footnote 8 of Marcus, Santorini and Marcinkiewicz

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".

DecisionOptionsHelpsHurts
Case foldingKeep case, lowercase everything, or truecaseRetrieval recall: a query for apple matches AppleNamed entity recognition: Apple, Bush, the Fed and General Motors lose their capital evidence
PunctuationAttach to the word, separate it, or drop itParsers and sentence segmenters, which read commas and periods as structureDropping loses question marks and quotes; naive splitting breaks m.p.h., Ph.D., $45.55 and 01/02/06
NormalizationAccents, precomposed versus decomposed letters, curly versus straight quotes, hyphen versus dashMatching and a compact vocabulary: one quote type, not fourMeaningful distinctions vanish, such as accented pairs in French or a minus sign versus a hyphen
NumbersKeep raw, map to <NUM>, or segment into digit chunks<NUM> shrinks |V|; digit chunks let a model generalize arithmeticRaw numbers make every price a new type; <NUM> throws away magnitude
Preprocessing decisions with their options and consequences

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.

BoundaryExampleTreebank defaultAlternative
Hyphensstate-of-the-artKeep the hyphenated word together as one tokenSplit into state, of, the, art so each piece is a known type
AbbreviationsU.S., Dr., e.g.Keep the internal periods; the trailing period is ambiguous with a sentence endStrip periods entirely, at the cost of merging US the country with us the pronoun
Cliticswe'll, I'm, possessive 'sSplit off the clitic: we 'll, I 'm, children 's, does n't, wo n'tKeep the contraction whole, so doesn't and does not are unrelated types
Social media#NLP, @user, URLsSplits # and @ off their word and breaks a URL at its colon, slashes and question markA social-media tokenizer keeps hashtags, handles and URLs whole with dedicated patterns
Ambiguous boundaries, the Penn Treebank default and the alternative

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'.

does + n't, and children + 's. Contractions and the genitive are split into their component morphemes; punctuation is separated; hyphenated words stay together.

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

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

  1. State Zipf's law and compute f(R) from f(1) and α.
  2. Explain why a power law is a straight line on log-log axes, and diagnose the head and tail deviations on a real corpus.
  3. State Heaps' law, compute |V| for given k, β and N, and compare the English and Arabic Wikipedia fits.
  4. Argue from Heaps' law why OOV words are unavoidable and why about half of all types are hapax legomena.
  5. 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.

WordFrequency fRank Rf × R
the3,33213,332
and2,97225,944
a1,77535,325
he877108,770
turned5120010,200
applausive18,0008,000
Tom Sawyer, from Manning and Schütze, Table 1.3 (the book prints 5,235 for "a", a transposition of 1,775 × 3 = 5,325)
f(R)1Rα,α1f(R) \propto \frac{1}{R^{\alpha}}, \qquad \alpha \approx 1
Zipf's law: frequency as a power law in rank

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.

f(R)f(1)Rαf(R) \approx \frac{f(1)}{R^{\alpha}}
The form you compute with

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.

Twenty bars shrinking by 1 / R, then a long tail of hairlines. The top three bars fall short of the teal curve fitted through the bulk: the head of a real corpus is flatter than the law.

Worked example

From f(1) to f(R)

  1. Fix the anchor

    Suppose the top word occurs f(1) = 1,000,000 times in a large Corpus.
  2. Apply α = 1

    f(10) = 1,000,000 / 10 = 100,000 and f(1000) = 1,000,000 / 1000 = 1,000.
  3. 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.
  4. Three exponents side by side

    αf(10)f(100)f(1000)f(100,000)
    1.00100,00010,0001,00010
    0.93117,49013,8041,62222
    1.1079,4336,3105013

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?

With α = 1, about 2,000,000 / 1,000 = 2,000. With α = 0.9, 1000^0.9 = 10^2.7 ≈ 501, so about 2,000,000 / 501 ≈ 3,990. The smaller exponent leaves the rank 1,000 word almost twice as frequent.

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.

logf=logCαlogR\log f = \log C - \alpha \log R
A power law is linear in log-log coordinates

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.

A straight line of slope −α fitted through the bulk on log-log axes, then the empirical curve laid over it: bent at the head, on the line in the middle, and broken into integer steps at 3, 2 and 1 in the tail.

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.

SimulatorZipf explorer

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.

Line anchor
10⁰10¹10²10⁰10¹10²rank Rfrequency fsample corpus441 / R^0.93
0.93
Fit versus sample
rankpredictedobserved
1051.848
1006.16
5001.41

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?

Taking logs of f = C R^−α gives log f = log C − α log R, which is linear in log R. The slope is −α, so the steeper the line, the faster frequency falls with rank.

Recall

Name the two visible departures from a Zipf line on a real corpus and give the cause of each.

Head flattening: the top few words drop off more slowly than R^−α, which Mandelbrot models with a shift (R + ρ); it shows as a bulge above a line pinned to f(1) (the English curve) or a shortfall under a line fitted through the bulk. Tail staircase: counts are integers, so all types with frequency 1, then 2, then 3 share one height and draw as horizontal steps.

Quick check

On a log-log rank-frequency plot, what does the slope of the fitted straight line equal?

Heaps' law: the vocabulary never stops growing

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.

CorpusInstances NTypes |V|
Shakespeare884 thousand29 thousand
Brown corpus1 million38 thousand
Switchboard telephone speech2.4 million20 thousand
COCA440 million2 million
Google n-grams1 trillion13 million
Bigger corpus, bigger vocabulary, no ceiling (SLP3 chapter 2, Figure 2.1; the Google count includes only types seen 40 or more times)

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.

V=kNβ,0<β<1|V| = k N^{\beta}, \qquad 0 < \beta < 1
Heaps' (Herdan's) law

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.

Three fitted vocabulary curves drawn out to a million tokens. The English curve's slope drops from 187 to 27 new types per thousand tokens, and the dotted ceiling fades because there is none.

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.

Corpuskβ|V| at 10⁴|V| at 10⁶|V| at 10⁸New types per 1,000 tokens
English Wikipedia14.9470.5833,21047,049689,531187 at N = 10⁴
Arabic Wikipedia73.3210.5339,936115,6721,346,56849 at N = 2.5 × 10⁵
Undotted Arabic Wikipedia90.4910.5069,56398,3121,010,661not reported
Slide 14 fits with |V| computed at three corpus sizes

Worked example

English fit at one million tokens

  1. Exponent

    N^β = (10^6)^0.583 = 10^(6 × 0.583) = 10^3.498.
  2. Evaluate the power

    10^3.498 ≈ 3,147.8.
  3. Multiply by k

    |V| ≈ 14.947 × 3,147.8 ≈ 47,049.
  4. 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.
SimulatorHeaps explorer

Vocabulary against corpus size

0500,0001,000,000121.5K0tokens Ntypes |V|dotted: straight line to the same endpoint
14.947
0.583
Vocabulary at three corpus sizes
N|V|new types per 1,000
10⁴3,210187.2
10⁶47,04927.4
10⁸689,5314
Formula|V| = 14.947 · N^0.583Faint curves are the three slide fits for comparison.
Growth per 100× more tokens14.7×= 100^0.583Below 100 because β < 1, above 1 because β > 0: slower, never zero.

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.

Sourcekβ
SLP3, current draftnot given0.44 to 0.56
IR book, typical30 to 100≈ 0.5
Reuters-RCV1 (IR book)440.49
Earlier SLP3 drafts, larger corporanot given0.67 to 0.75
Slide 14 fits14.9 to 90.50.506 to 0.583
Reported Heaps' law constants across sources

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?

14.947 × 10^(4 × 0.583) = 14.947 × 10^2.332 ≈ 3,210 and 14.947 × 10^3.498 ≈ 47,049. The ratio is 100^0.583 ≈ 14.7, not 100.

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.

CountZipf predictionTom SawyerSample corpus
Exactly once50%49.8%56%
Exactly twice16.7%16.1%15%
Share of types with a given count: Zipf prediction against two real corpora

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.

One hundred types from a corpus. About half light up as seen once, sixteen as seen twice, and a dashed square outside the grid stands for the type that was never seen at all.

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 unitsNeural, subword units
Unit of the vocabularyWhole wordsSubword pieces, or bytes as the floor
What happens to an unseen wordMapped to <UNK> or given smoothed massComposed from known pieces, so never unknown
Where the fix livesIn the probability estimate: smoothing, backoff, interpolationIn the tokenizer: BPE, WordPiece, byte fallback
CostProbability mass spent on events never seenLonger token sequences, less interpretable units
Covered inLecture 3 and SLP3 chapter 3Part 6 of this lecture
Word-level n-gram models versus subword neural models

Recall

In one sentence, why does Heaps' law guarantee OOV words?

Because |V| = k N^β keeps increasing for every N (its derivative is always positive), new text always brings types absent from any finite training corpus.

Recall

Define hapax legomenon and give the typical share of types it accounts for.

A type occurring exactly once in a corpus. Typically about half of all types: 49.8% in Tom Sawyer, 56% in the three-chapter sample corpus.

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

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

  1. Segment a word into morphemes and label each one as root or affix, free or bound.
  2. Tell a root from a stem, and name the affix positions (prefix, suffix, infix, circumfix) plus compounding.
  3. Decide whether a process is inflection or derivation using lexeme identity, part of speech and productivity, including conversion.
  4. Tell concatenative from non-concatenative form, with English, Turkish and Arabic examples.
  5. 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.

unhappiness pulls apart into a prefix, a root and a suffix. The root carries the meaning; the two affixes cannot stand alone.

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.

KindDefinitionExamplesStands alone?
Free morphemeCan occur by itself as a wordcat, walk, happy, snow, manYes
Bound morphemeNever occurs alone, always attached to another morphemeun-, re-, -s, -ed, -ing, -nessNo
Free versus bound morphemes

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.

ProcessPositionExampleIn English
PrefixBefore the stemun-happy, re-newCommon
SuffixAfter the stemwalk-ed, walk-ing, cat-sVery common
InfixInside the stemabso-bloody-lutely (expletive infixation)Rare, marginal
CircumfixAround the stem, two parts at onceGerman ge-spiel-t, playedRare; en-light-en is sometimes analysed as one
CompoundingTwo roots joined, no affix at allsnow-man, lap-topCommon
Where an affix attaches, and what compounding does instead

Worked example

Segment carefully and label every piece

  1. Find the root

    care is a free morpheme that carries the meaning. It can be a word on its own.
  2. Peel the first affix

    -ful is a bound suffix that turns the noun care into the adjective careful.
  3. Peel the second affix

    -ly is a bound suffix that turns the adjective into the adverb carefully.
  4. Result

    care-ful-ly: root (free) + suffix (bound) + suffix (bound). Three morphemes, each with its own job, exactly as SLP3 segments it.
InteractiveMorpheme splitter: cut the word where meaning changes

Click the gap between two letters to place a morpheme boundary. Click again to remove it. Then check your cuts against the answer key.

u
n
h
a
p
p
i
n
e
s
s
Your split: unhappiness

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.

FormTransliterationMeaningPattern
كَتَبَkatabahe wroteCaCaCa
كِتَابkitābbookCiCāC
كَاتِبkātibwriterCāCiC
One root, three patterns

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.

un- (prefix, bound), happy (root, free), -ness (suffix, bound). The i in happi is a spelling change, not a morpheme.

Recall

What is the difference between a root and a stem?

The root is the core lexical material, such as happy. The stem is the base that takes inflection, such as unhappiness in unhappinesses. For a bare word like walk the two are the same string.

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.

walked and walks fold out of the same dictionary card as walk. happiness gets a card of its own, in a new category.

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.

ProcessWhat changesLexeme identityPart of speechEnglish exampleArabic example
InflectionGrammar: tense, number, case, agreementSame lexeme (usually)Unchangedwalk to walk-ed, walk-skatab-tu كَتَبْتُ, I wrote
DerivationMeaning or categoryNew lexemeOften changeshappy to happi-nesskitāb كِتَاب book versus kātib كَاتِب writer
ConversionCategory only, no change in formNew lexemeChangesan email to to emailNot covered on the slide
Inflection, derivation and conversion side by side

Four tests to decide

  1. Lexeme: would a dictionary list the two forms under one entry? One entry means inflection.
  2. Category: did the part of speech change? A change points to derivation, though derivation can also keep it, as un-happy does.
  3. 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.
  4. 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?

  1. Lexeme test

    walk and walked are one dictionary entry. Same lexeme.
  2. Category test

    Both are verbs. The part of speech did not change.
  3. Productivity test

    Every regular English verb takes -ed, and its meaning is always grammatical, past tense or past participle. Obligatory and predictable.
  4. 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.
  5. Result

    Inflection, on all four tests.

Worked example

Label the affixes of un-happi-ness

  1. 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.
  2. -ness

    Adjective to noun, a new lexeme with a new category. Derivational.
  3. 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.

Inflection. walk and walked are the same lexeme and the same part of speech, and -ed is obligatory and fully productive with a predictable grammatical meaning, past tense or past participle.

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.

The three radicals of K-T-B sit in fixed slots. Three different vowel templates thread through the same slots to give kataba, kitāb and kātib.

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.

MechanismHow the form changesEnglishOther languagesCut with scissors?
ConcatenativeA contiguous affix or root is added in sequencecat-s, walk-ed, un-happy, snow-manTurkish ev-ler-im-den, house-PL-1SG.POSS-ABL, from my housesYes
Non-concatenative: internal changeA vowel inside the root changessing, sang, sungGerman umlaut plural Vater, Väter (illustration)No
Non-concatenative: templaticA vowel pattern is interleaved with a consonantal rootNone in EnglishArabic كَتَبَ kataba, يَكْتُبُ yaktubu, كِتَاب kitāb from K-T-BNo
Non-concatenative: suppletionThe whole form is replaced by an unrelated onego, went; good, betterNot on the slideNo
Concatenative versus non-concatenative form

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.

English: ablaut in sing, sang, sung, or suppletion in go, went. Arabic: root-and-pattern forms kataba, yaktubu, kitāb from the root K-T-B.

Recall

Gloss ev-ler-im-den morpheme by morpheme.

house-PL-1SG.POSS-ABL, from my houses. ev is the free root; -ler, -im and -den are bound suffixes for plural, first person singular possessor and ablative case.

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

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

  1. Apply two diagnostics (selectivity, paradigm membership) to decide clitic versus affix, including katab-tu, kitab-u-hu and the borderline al-.
  2. Classify a language form as isolating, agglutinative, fusional or polysynthetic and explain why these are tendencies per subsystem, not boxes.
  3. Place English and Arabic on the typology map and name one fusional pocket in English.
  4. Define Greenberg's index of synthesis, order the eight languages on it, and compute it on a short text.
  5. 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.

Top lane: the clitic 's docks onto a preposition, a verb, an adjective and an adverb. Bottom lane: the affix -ed docks onto verbs only, and the other hosts are crossed out.

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.

FormTransliterationMeaningSyntactic roleSelectivityParadigmVerdict
وَبِالْبَيْتwa-, bi-and; with, by, inConjunction and preposition, function wordsLow: nouns, verbs, pronouns, particlesNoneProclitic
كِتَابُهُ-huhis, himPronoun, possessor or objectLow: nouns, verbs, prepositionsNone in the host (a pronoun set, not a host paradigm)Enclitic
كَتَبْتُ-tuI (subject agreement)Agreement marker on the verbHigh: verb stems onlyOne cell of the perfective conjugationInflectional affix
الْبَيْتal-theDefinite article, function elementModerate: nouns and adjectivesDefiniteness system (definite, indefinite)Borderline
Three Arabic cases plus the borderline article

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.

InteractiveClitic or affix? Sort by the diagnostics, not the spelling

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.

Arabic 1 / 8in wa-bi-al-bayt
wa-وَ
0 / 8 right

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.

InteractiveOne string, three token counts: pick a clitic splitting policy
No splitting. Every clitic combination is its own type, so the vocabulary balloons.
wa-bi-al-bayt, and in the house1 token
وَبِالْبَيْتwabialbayt
wa-sa-yaktubūna-hā, and they will write it1 token
وَسَيَكْتُبُونَهَاwasayaktubunaha
Whitespace1 / 1tokensBoth words stay whole
ATB3 / 3tokensal- and sa- stay attached
D34 / 4tokensEvery clitic split

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

  1. Whitespace

    وَبِالْبَيْت is one token. Every combination of conjunction, preposition, article and noun becomes its own vocabulary Type.
  2. ATB

    wa+ bi+ albayt: three tokens. The conjunction and preposition are split, the article stays on the noun.
  3. D3

    wa+ bi+ al+ bayt: four tokens. Every clitic is separated and the bare noun bayt is recovered.
  4. 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.

Selectivity: -tu attaches only to verb stems, while -hu attaches to nouns, verbs and prepositions. Paradigm membership and syntactic role: -tu fills one cell of the perfective conjugation and marks agreement, while -hu is a pronoun doing the job of a separate word. So -tu is an inflectional affix and -hu is an enclitic.

Recall

Why is 'it is written attached' not a valid test, and what does al- show?

Arabic writes every clitic attached with no apostrophe, so orthography cannot separate clitics from affixes. al- is selective (nouns and adjectives) and part of the definiteness system, yet D3 splits it and ATB keeps it, so it is borderline: the diagnostics disagree and so do the tools.

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.

English and Turkish package the same four morphemes: three words versus one word with four clean seams. Spanish habl-o is a different meaning, two morphemes, and its -o fuses four features into one segment.

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).

TypeDefinitionExample languageExample formGloss
IsolatingLittle morphology; grammar carried by word order and function wordsMandarin Chinesewǒmen míngtiān qù Běijīngwe tomorrow go Beijing
AgglutinativeMany affixes, each with one clear function and a clean boundaryTurkishev-ler-im-denhouse-PL-1SG.POSS-ABL, from my houses
FusionalOne affix bundles several grammatical featuresSpanishhabl-ospeak-1SG.PRS.IND, I speak
PolysyntheticVery complex words encode sentence-like content, many morphemes per wordInuktitut on the slide; Koryak in SLP3t-ə-nk'e-mejŋ-ə-jetemə-nni-kone word: I sewed a lot of yurt covers in the middle of a night
Four tendencies with the slide's example forms

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.

SubsystemEnglishArabic (MSA)
Word formationProductive derivation and compounding, concatenativeTemplatic root-and-pattern, non-concatenative
Verb inflectionLimited: -s, -ed, -ing; fusional pockets am/is/are, go/wentLargely fusional; upwards of 5,400 forms per verb
Attached function elements's, 'm, 've, marked by apostrophewa-, bi-, li-, pronominal enclitics, written solid
Borderline elementn'tal-
English and Arabic by subsystem (slide 26)

Recall

Classify Turkish, Spanish, Mandarin and Inuktitut by typology and give one form each.

Turkish agglutinative, ev-ler-im-den (one function per suffix). Spanish fusional, habl-o (-o bundles person, number, tense and mood). Mandarin isolating, wǒmen míngtiān qù Běijīng (free words and word order). Inuktitut polysynthetic: one word encodes a clause, very high morphemes per word, as in SLP3's Koryak t-ə-nk'e-mejŋ-ə-jetemə-nni-k, "I sewed a lot of yurt covers in the middle of a night".

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.

index of synthesis=MW\text{index of synthesis} = \frac{M}{W}
Greenberg 1960: morphemes M over words W in a long text

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.

Greenberg's eight languages rise from the axis in order. English is flagged in teal. Arabic is not in Greenberg's sample, so no dot is drawn for it.
LanguageGreenbergSlideBand
Vietnamese1.061.1Analytic
Persian (Farsi)1.521.5Analytic
English1.681.7Analytic
Anglo-Saxon (Old English)2.122.1Synthetic
Yakut2.172.2Synthetic
Swahili2.552.5Synthetic
Sanskrit2.592.6Synthetic
Eskimo (Greenlandic)3.723.7Polysynthetic
Greenberg's index of synthesis (1960, p. 193) against the slide's rounding

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.

TTR=VN\text{TTR} = \frac{|V|}{N}
Type-token ratio: distinct types over running tokens

Worked example

What clitic splitting does to an Arabic vocabulary (Alotaiby et al. 2010)

  1. 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.
  2. 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.
  3. 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%.
  4. 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.

SideTokens NTypes |V|TTR
Inuktitut10,869,9951,563,8830.144
English20,367,59559,2340.003
Nunavut Hansard, parallel text (Schwartz et al. 2020, table 3.2)

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.

  1. 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.
  2. 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.
  3. 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.

Morphemes divided by words in a long text. English scores 1.68 (rounded to 1.7 on the slide), in the analytic band. Vietnamese is lowest at 1.06 and Eskimo (Greenlandic) highest at 3.72.

Recall

Name the three NLP responses on slide 28 and one tool or paper for each.

Subword tokenization (BPE, Sennrich et al. 2016; WordPiece, Wu et al. 2016). Morphological segmentation (MADAMIRA, CAMeL Tools with ATB or D3 schemes). Careful preprocessing of case, punctuation and clitic policy (the pre-tokenization step described in SLP3).

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

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

  1. Name the variation dimensions of a corpus and list the fields of a data statement or datasheet.
  2. Distinguish code point, glyph and encoding, and explain why saying Unicode is an encoding is wrong.
  3. Apply NFC, NFD and NFKC to precomposed and decomposed Latin and Arabic examples and say which tokenization bug each prevents.
  4. Encode any code point into UTF-8 bytes by hand using the four-row table, and decode bytes back.
  5. 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.

DimensionQuestion it answersArabic example
Language and varietyWhich language, dialect or register, and is there code switching?Modern Standard Arabic newswire versus Najdi tweets, or Arabizi in Latin letters
GenreWhat kind of text is it?Quranic commentary, a parliamentary transcript, a product review
TimeWhen was it written or spoken?Pre-2011 news versus post-2020 social media, where vocabulary shifted
DemographicsWho wrote or spoke it, by age, gender, region, education?Teenage gamers in Riyadh versus retired teachers in Cairo
DomainWhat is it about?Medical records, legal contracts, football commentary
Dimensions along which corpora vary
A corpus is a stack of strata: domain, genre, time, demographics and variety. A data statement is the tag that records all five.

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.

Any four of motivation and intended use, composition, collection process, preprocessing and annotation, ethical considerations, distribution constraints. Models inherit the biases and limitations of their training corpus, so results cannot be interpreted without knowing what the corpus contains.

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.

CharacterHexDecimalBinary
<3C600011 1100
=3D610011 1101
>3E620011 1110
?3F630011 1111
@40640100 0000
A41650100 0001
B42660100 0010
C43670100 0011
\5C920101 1100
]5D930101 1101
^5E940101 1110
_5F950101 1111
`60960110 0000
a61970110 0001
b62980110 0010
c63990110 0011
A fragment of the ASCII table, with the binary column the slide caption promised

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.

a=0x61=0x41+0x20=A    0x20\text{a} = 0x61 = 0x41 + 0x20 = \text{A} \;\lor\; 0x20
ASCII lowercase is one bit away from uppercase

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 (U+FB01) with the two letters f and i. Compatibility variants look different or carry formatting, so folding them loses information.

One glyph é can be stored as one code point or as two. NFC folds both spellings into the single precomposed form.

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.

FormOperationé (either spelling)ﻻ U+FEFBfi U+FB01
NFDCanonical decompositionU+0065 U+0301 (3 bytes)U+FEFB (3 bytes)U+FB01 (3 bytes)
NFCCanonical decomposition, then canonical compositionU+00E9 (2 bytes)U+FEFB (3 bytes)U+FB01 (3 bytes)
NFKDCompatibility decompositionU+0065 U+0301 (3 bytes)U+0644 U+0627 (4 bytes)f i (2 bytes)
NFKCCompatibility decomposition, then canonical compositionU+00E9 (2 bytes)U+0644 U+0627 (4 bytes)f i (2 bytes)
The four normalization forms of UAX 15, Table 1

Worked example

NFD and NFC on a Latin and an Arabic letter

  1. 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.
  2. 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.
  3. 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.
  4. 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.
  5. 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.

A code point is the abstract integer identifier Unicode assigns to a character, such as U+0061. A glyph is the shape a font renders. One glyph to many code points: é as U+00E9 or U+0065 U+0301. One code point to many glyphs: beh U+0628 drawn in isolated, initial, medial and final form.

Recall

Name two normalization forms and one tokenization bug that normalization prevents.

NFC and NFD (or NFKC and NFKD). Without normalization, é stored as U+00E9 and as U+0065 U+0301 tokenize differently, so the same word gets two vocabulary entries and equality and dictionary lookups fail.

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.

RangeCode point bitsPayload bitsByte 1Byte 2Byte 3Byte 4
U+0000 to U+007F0xxxxxxx70xxxxxxx
U+0080 to U+07FF00000yyy yyxxxxxx11110yyyyy10xxxxxx
U+0800 to U+FFFFzzzzyyyy yyxxxxxx161110zzzz10yyyyyy10xxxxxx
U+010000 to U+10FFFF000uuuuu zzzzyyyy yyxxxxxx2111110uuu10uuzzzz10yyyyyy10xxxxxx
Code point ranges to UTF-8 byte templates (RFC 3629, section 3)

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.

n(c)={1c<272c<2113c<2164c10FFFF16n(c) = \begin{cases} 1 & c < 2^{7} \\ 2 & c < 2^{11} \\ 3 & c < 2^{16} \\ 4 & c \le \text{10FFFF}_{16} \end{cases}
Byte count from the code point, using the payload capacity of each row
The 16 bits of U+0628 flow into the two-byte template. Prefix bits 110 and 10 are fixed. The 11 payload bits land in order and spell D8 A8.

Worked example

Encode the Arabic letter beh, U+0628

  1. 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.
  2. Take the low 11 bits

    Drop the five leading zeros: 11000 101000. The template is 110yyyyy 10xxxxxx, so yyyyy = 11000 and xxxxxx = 101000.
  3. Fill the templates

    Byte 1 is 110 + 11000 = 11011000 = D8. Byte 2 is 10 + 101000 = 10101000 = A8.
  4. Result

    U+0628 encodes as D8 A8, two bytes.

Worked example

Encode the grinning face emoji, U+1F600

  1. Pick the row

    0x1F600 is above 0xFFFF, so it takes the four-byte row with 21 payload bits: 0 0001 1111 0110 0000 0000.
  2. 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.
  3. 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.
  4. Result

    U+1F600 encodes as F0 9F 98 80, four bytes.

Worked example

Encode the letter a, U+0061

  1. 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.
  2. Result

    U+0061 encodes as 61, the same number as its ASCII code. Nothing changed.
CharacterCode pointRangeBytesUTF-8 (hex)
aU+0061U+0000 to U+007F161
éU+00E9U+0080 to U+07FF2C3 A9
بU+0628U+0080 to U+07FF2D8 A8
U+20ACU+0800 to U+FFFF3E2 82 AC
U+4E2DU+0800 to U+FFFF3E4 B8 AD
U+FEFBU+0800 to U+FFFF3EF BB BB
😀U+1F600U+010000 to U+10FFFF4F0 9F 98 80
Seven characters across the four rows

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.

SimulatorUTF-8 encoder: from code point to bytes
Code points9charsWhat Python len() counts
UTF-16 units10unitsWhat JavaScript length counts
UTF-8 bytes16bytesWhat is stored and sent
Bytes per char1.78ratio1.00 for pure ASCII
  1. HU+0048
    U+0000 to U+007F
    1 byte, 7 payload bits
    bits 1001000
    01001000
    0xxxxxxx
    48
  2. iU+0069
    U+0000 to U+007F
    1 byte, 7 payload bits
    bits 1101001
    01101001
    0xxxxxxx
    69
  3. U+0020
    U+0000 to U+007F
    1 byte, 7 payload bits
    bits 0100000
    00100000
    0xxxxxxx
    20
  4. كU+0643
    U+0080 to U+07FF
    2 bytes, 11 payload bits
    bits 11001000011
    11011001
    110yyyyy
    10000011
    10xxxxxx
    D9 83
  5. تU+062A
    U+0080 to U+07FF
    2 bytes, 11 payload bits
    bits 11000101010
    11011000
    110yyyyy
    10101010
    10xxxxxx
    D8 AA
  6. اU+0627
    U+0080 to U+07FF
    2 bytes, 11 payload bits
    bits 11000100111
    11011000
    110yyyyy
    10100111
    10xxxxxx
    D8 A7
  7. بU+0628
    U+0080 to U+07FF
    2 bytes, 11 payload bits
    bits 11000101000
    11011000
    110yyyyy
    10101000
    10xxxxxx
    D8 A8
  8. U+0020
    U+0000 to U+007F
    1 byte, 7 payload bits
    bits 0100000
    00100000
    0xxxxxxx
    20
  9. 😀U+1F600
    U+010000 to U+10FFFF
    4 bytes, 21 payload bits
    bits 000011111011000000000
    11110000
    11110uuu
    10011111
    10uuzzzz
    10011000
    10yyyyyy
    10000000
    10xxxxxx
    F0 9F 98 80
U+0000 to U+007F0xxxxxxx
U+0080 to U+07FF110yyyyy 10xxxxxx
U+0800 to U+FFFF1110zzzz 10yyyyyy 10xxxxxx
U+010000 to U+10FFFF11110uuu 10uuzzzz 10yyyyyy 10xxxxxx

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.

Hi ب😀 as nine bytes. Carets mark lead bytes by their prefix, continuation bytes carry 10, and brackets group each character's span.

Worked example

Decode D8 A8 back to a code point

  1. 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.
  2. Read the continuation byte

    A8 is 10101000. It starts 10 as required, so keep the six bits after the prefix: 101000.
  3. Concatenate

    11000 + 101000 = 11000101000 = 0x628.
  4. 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?

2, 2, 3 and 4. The first two fall in U+0080 to U+07FF, the third in U+0800 to U+FFFF, and the fourth in U+010000 to U+10FFFF.

Recall

Encode ñ, U+00F1, by hand.

0x00F1 is 0000 0000 1111 0001, in the two-byte row. Payload 00011 110001, so byte 1 is 110 + 00011 = 11000011 = C3 and byte 2 is 10 + 110001 = 10110001 = B1. Result C3 B1.

Recall

Why is UTF-8 backward compatible with ASCII, and how does a decoder find where a character starts?

Code points below 0x80 map to the single byte 0xxxxxxx with the same value, so an ASCII file is unchanged. Lead bytes begin 0, 110, 1110 or 11110 and continuation bytes always begin 10, so a byte starting 10 can never be a start and any position resynchronizes within three bytes.

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.

PropertyCharacter vocabularyByte vocabulary
Base symbolsAbout 160,000 assigned characters (over 130,000 when GPT-2 was written)Exactly 256 byte values
Unknown input possibleYes, any unseen characterNo, every byte is in the base
Base units for كتاب4 characters8 bytes
Interpretability of a base unitA letter you can readHalf of an Arabic letter
StructureGiven by the scriptRecovered by learned merges
Character base versus byte base

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?

4 and 8. Python's len counts code points, the encoded form has two bytes per letter, and a byte-level tokenizer starts from the eight bytes.

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

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

  1. Name the four properties a tokenization balances and place words, characters and subwords on the coverage versus compactness spectrum.
  2. Write the BPE training pseudocode from memory and explain the role of k.
  3. 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.
  4. Encode an unseen word by replaying the merge list in order and explain why order matters.
  5. Explain how a leading space marker versus an end-of-word marker changes the learned units.

What a tokenizer must balance

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.

SplitTokens NDistinct typesVocabulary |V|
Words644
Characters with marker2977
BPE, k = 415611
The same six-word string under three tokenizations

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.

Coverage (few or no unknown tokens), compactness (short sequences), meaningfulness (units correlate with meaning) and cross-lingual applicability. Word tokens fail on coverage: the word vocabulary never closes, so new text always brings out-of-vocabulary words.

Words, characters and subwords: the trade-offs

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.

The same string as 29 character tiles, 15 subword tiles or 6 word tiles. The bar under each row is its token count. The vocabulary each scheme needs on a real corpus runs the other way, dozens of characters against tens of thousands of subwords and an open-ended set of words, which a six-word corpus is too small to show.
WordsCharactersSubwords
Vocabulary sizeVery large and open-ended; grows with the corpus under Heaps' lawTiny: the letters of a script, or 256 byte valuesFixed by design, typically 32k to 200k units
Sequence lengthShortest: one token per wordLongest: one token per characterIn between: frequent words stay whole, rare words split
Out-of-vocabularyYes: rare words, misspellings, new namesNone: every string is spelled from the base symbolsAlmost none: unseen words are composed from pieces
MeaningfulnessHighest; aligns with lexicons and syntaxSpread across many tokens; the model must learn to reassemble itOften morpheme-like, but only by accident of frequency
Best usePOS tagging, parsing, lexicon-driven systems that need explicit word boundariesNoisy OCR or typo-heavy text, very low-resource languages, specialised tasksModern language models and machine translation
Words, characters and subwords on the four criteria

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.

Characters have no OOV and a tiny vocabulary, and they are robust to typos and OCR noise, so they suit noisy or very low-resource text. Subwords keep a fixed mid-sized vocabulary with sequences far shorter than characters and still compose unseen words from pieces, so they get most of the coverage benefit at a fraction of the length.

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

  1. 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}.
  2. 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.
  3. 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.

Vfinal=Vinitial+k|V_{\text{final}}| = |V_{\text{initial}}| + k
Each merge adds exactly one entry, so k fixes the vocabulary size

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).

Four brackets close in the order the trainer learned them, then the character row gives way to four tokens. Merged pieces are accent, leftover characters teal.

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.

SimulatorBPE merge stepper: train on a corpus, then encode a new word
Next: merge 1
Merges k0applied
Vocabulary7entries7 initial + 0
Corpus29symbolsfrom 29
Shorter by0%
Corpus after 0 merges
  • 2_new
  • 2_renew
  • 1set
  • 1_reset
Vocabulary (7)
_enrstw

Grey chips are the initial symbols, teal chips are learned merges in the order they were learned, and the accent chip is the newest.

Pair counts deciding merge 1
RankPairCountFirst seen atNote
1(n, e)4position 1winner, tie broken by rule
2(e, w)4position 2tie at the maximum
3(_, r)3position 4
4(r, e)3position 5
5(_, n)2position 0
6(e, n)2position 6
7(s, e)2position 10
8(e, t)2position 11
9(e, s)1position 15
  1. characters_renewed
  2. 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.

The trainer counts adjacent pair frequencies on the training corpus and produces a vocabulary plus an ordered merge list. The encoder splits new text into characters or bytes and replays that merge list in order without counting anything.

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

  1. Merge 1: count every adjacent pair

    PairCountNote
    (n, e)4winner: tied with (e, w), seen first in the corpus
    (e, w)4tie 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.

  2. Merge 2: recount on the new corpus

    PairCountNote
    (ne, w)4winner: 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.

  3. Merge 3: a second tie

    PairCountNote
    (_, r)3winner: tied with (r, e), seen first
    (r, e)3tie 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.

  4. Merge 4: the tie resolves itself

    PairCountNote
    (_r, e)3winner: 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.

  5. 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.
MergeNew token (count)Corpus symbols|V|Corpus
0start2972 _ n e w, 2 _ r e n e w, 1 s e t, 1 _ r e s e t
1ne (4, tie)2582 _ ne w, 2 _ r e ne w, 1 s e t, 1 _ r e s e t
2new (4)2192 _ new, 2 _ r e new, 1 s e t, 1 _ r e s e t
3_r (3, tie)18102 _ new, 2 _r e new, 1 s e t, 1 _r e s e t
4_re (3)15112 _ new, 2 _re new, 1 s e t, 1 _re s e t
The trace at a glance

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 1ne (tied with ew, seen first)ew (tied with ne, ew < ne)
Merge 2new 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 tokensne, new, _r, _reew, new, _r, _re
Corpus after k = 42 × _ new, 2 × _re new, s e t, _re s e t2 × _ new, 2 × _re new, s e t, _re s e t
The same corpus under two tie rules, k = 4

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 sitsBefore the word: _new, _reAfter the word: new·, set·
First word of the stringNo marker: set stays s e tMarker like every word: s e t ·
Prefix re- is learned as_re, distinct from word-internal rere, identical to word-internal re
Used byThis deck, current SLP3, GPT-2 (Ġ), SentencePiece (▁)Sennrich et al. 2016, older SLP3 drafts
Leading space marker versus end-of-word suffix

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.

Vocabulary: _, e, n, r, s, t, w, ne, new, _r, _re (11 items). Corpus: 2 × _ new, 2 × _re new, 1 × s e t, 1 × _re s e t, 15 symbols instead of 29.

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?

Nothing. The paper's minimal listing and the Hugging Face tutorial take the first pair encountered in the corpus. The released subword-nmt code takes the lexicographically largest tied pair, and other libraries use yet other rules, so an exam answer should state the rule it uses.

Recall

How does the leading space marker change what is learned?

Word-initial pieces such as _re become tokens distinct from word-internal re. The first word of the string carries no marker, so set and _set are different strings. With Sennrich's end-of-word suffix instead, every word ends in the marker and the prefix would be learned as plain re.

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

  1. _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.
  2. _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.
  3. 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.
  4. 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.

SimulatorEncoder replay: a frozen merge list applied to any word
k = 4
Merge list, in learned order
  1. 1.ne
  2. 2.new
  3. 3._r
  4. 4._re
  1. characters_renewed
  2. 1. n+e_renewed
  3. 2. ne+w_renewed
  4. 3. _+r_renewed
  5. 4. _r+e_renewed
  6. 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.

A playhead sweeps the frozen merge list left to right while the string below changes at each stamp. The crossed-out tally is the rule: the encoder never counts.

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.

encode(w)=mk(m2(m1(chars(w))))\text{encode}(w) = m_k(\cdots m_2(m_1(\text{chars}(w))))
The merges compose in learned order; nothing is recomputed

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).

TrainerEncoder
InputA training corpus with word countsOne new string
Counts pair frequenciesYes, at every stepNever
OutputA vocabulary plus an ordered merge listA token sequence
RunsOnce, before any model is trainedOn every input, forever after
Hyperparameterk, the number of mergesNone; the merge list is fixed
Trainer and encoder side by side

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?

Start from _ r e n e w a l. Merge 1 gives _ r e ne w a l, merge 2 gives _ r e new a l, merge 3 gives _r e new a l, and merge 4 gives _re new a l, four tokens. Nothing in the list ever joins a to l, and no merge produces a bare re, so segmentations such as _r e new a l or _ re new al are unreachable.

Recall

Why must the encoder apply merges in the order they were learned?

Later merges are built from earlier ones: new can only form after ne exists, and _re only after _r. The model was also trained on token sequences produced by that exact order, so a different order produces segmentations it has never seen.

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

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

  1. Read a real tokenization such as the slide sentence and explain every split by a pre-tokenizer rule or a BPE merge.
  2. State the pros and the con of byte-level BPE with a concrete byte split of an Arabic letter.
  3. Compute a tokenization premium and name three costs of over-segmenting a low-resource language.
  4. Explain what SuperBPE changes in the BPE curriculum and what it trades away.
  5. Walk the tokenizer design checklist and give one consequence for each choice.
  6. Disambiguate a period with rule-based, statistical and Punkt-style evidence.

From the BPE algorithm to a shipping tokenizer

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.

TokenizerVocabularyTokensPieces
o200k (GPT-4o)200K13Any | how | , | ·she's | ·seen | ·Jane | 's | · | 224 | 123 | ·flowers | ·anyhow | !
cl100k (GPT-4)~100K14Any | how | , | ·she | 's | ·seen | ·Jane | 's | · | 224 | 123 | ·flowers | ·anyhow | !
gpt250,25714Any | how | , | ·she | 's | ·seen | ·Jane | 's | ·224 | 123 | ·flowers | ·any | how | !
The slide sentence under three real tokenizers (measured with tiktoken; · marks a leading space)

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.

Merges join symbols inside a piece. The pair e and ' straddles a fence, so it is never counted, and 's stays a separate token.

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?

It runs a regex that splits text into words with their leading space, digit runs, punctuation, contractions and whitespace. Pair counts and merges are computed only inside a piece, so a pair split across two pieces is never a candidate and the final tokens are always parts of 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

  1. Code points

    كتاب is four code points: U+0643 kaf, U+062A ta, U+0627 alif, U+0628 ba.
  2. UTF-8 bytes

    Each is two bytes: D9 83, D8 AA, D8 A7, D8 A8. Eight bytes, so the base encoding is eight symbols.
  3. Apply each vocabulary's merges

    TokenizerTokensByte groupsWhat a human sees
    gpt25D9 | 83 | D8 AA | D8 A7 D8 | A8Kaf split in two, fourth token straddles alif and the lead byte of ba
    cl100k3D9 83 | D8 AA | D8 A7 D8 A8Every token is whole letters
    o200k1D9 83 D8 AA D8 A7 D8 A8The whole word is one token
  4. 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.
Four letters over eight bytes. The gpt2 token brackets ignore the letter boundaries: t1 and t2 split kaf, and t4 crosses from alif into ba.

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.

InteractivePre-tokenize, merge, read the bytes

GPT-2 rules: digits unbounded, with the leading space attached, contractions split into their own piece. Vocabulary about 50,257 tokens.

Anyhow,she'sseenJane's224123flowersanyhow!
Pieces11from regexupper bound on merges
Tokens13after mergesillustrative merge list
Tokens per word1.86fertility7 whitespace words, illustrative merge list
Bytes48UTF-848 characters

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.

Pros: no OOV symbols, since the base vocabulary is the 256 bytes, and it works for any script and for noisy text. Con: a token can cut a multi-byte character, for example gpt2 puts the two bytes of kaf, D9 and 83, in different tokens.

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.

TextWordscl100k tokenso200k tokensTokens per word (cl100k)
The book is on the table.6771.17
الكتاب على الطاولة.31264.0
One sentence, two languages, measured with tiktoken

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.

premiumAB=t(sA)t(sB)\text{premium}_{A \mid B} = \frac{|t(s_A)|}{|t(s_B)|}
Tokenization premium: tokens for a sentence in A over tokens for its translation in B (Petrov et al. 2023)
LanguageGPT-2 / RoBERTa tokenizerChatGPT / GPT-4 (cl100k)ByT5 (bytes)
Portuguese1.941.48n/a
German2.141.58n/a
Chinese (Simplified)3.211.910.93
Standard Arabic4.403.041.60
Burmese16.8911.703.51
Shan18.7615.053.94
Premium relative to English on FLORES-200 parallel text (Petrov et al. 2023, Tables 1 and 5)

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

  1. 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.
  2. 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.
  3. 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?

The merge budget was spent on the frequent, high-resource language, so words of the low-resource language fragment into small pieces (Arabic premium 3.04 under cl100k). Consequences: more compute and less usable context per word, and higher API cost with lower quality.

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 upper row fuses into the lower one: frequent multiword units such as By the way and Milky Way become single tokens.

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.

TokenizerPiecesWhere the space goesDetokenization
BERTHello | worldSpace discardedNot reversible: detokenization cannot say where the spaces were
GPT-2Hello | ·worldSpace glued onto the next pieceReversible: every piece carries its leading space
SentencePiece▁Hello | ▁worldSpace kept as an explicit ▁ symbolLosslessly reversible: replace ▁ by a space and the original text is restored
Three whitespace policies on Hello world (· is a glued space, ▁ is U+2581)

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.

Pre-tokenization, normalization, special tokens, handling of numbers, URLs and emoji, and multilingual coverage. For example, normalization with lowercasing and accent stripping makes Héllò and hello identical, which shrinks the vocabulary but loses case and diacritics permanently.

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.

ExerciseSentence segmentation: decide what each mark does

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

0 of 8 marks decided

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.

One glyph, three readings. The boundary reading is the one a capitalized next token supports; the lexicon and the digit test claim the others.

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%
ApproachHow it decidesNeedsStrengths and limits
Rule-basedHand-written heuristics and a hand-built abbreviation listNoneFast and transparent; brittle on new domains and languages
Statistical (supervised)P(boundary | context)Annotated sentence boundariesAdapts to a domain; needs labeled data per language
Punkt (unsupervised)Abbreviations as tight collocations of a truncated word and a period, plus sentence startersRaw text onlyLanguage independent; 98.74% mean accuracy over eleven languages
Three ways to decide what a period does

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?

The period also marks abbreviations, initials, ordinals and decimals, while a question mark almost always ends a sentence. Features: whether the token before the period is in an abbreviation list, and whether the next token is capitalized or a frequent sentence starter.

Recall

What does Punkt learn without any labeled data?

Which words are abbreviations, detected as tight collocations of a truncated word with a following period, plus collocations that span a period and words that frequently start sentences.

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

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

  1. Explain how ELIZA produced replies from keyword substitution rules and name the Eliza effect.
  2. Read and write literals, concatenation, disjunction and bracket classes, including ranges and negation.
  3. Apply * + ? {n} {m,n} and the wildcard, and predict greedy versus lazy matches on a given string.
  4. Use ^ $ \b to match whole words and line-based formats, and explain why anchors consume no characters.
  5. 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.

SpeakerLine
UserI need some help, that much seems certain.
ELIZAWHAT WOULD IT MEAN TO YOU IF YOU GOT SOME HELP
UserPerhaps I could learn to get along with my mother.
ELIZATELL ME MORE ABOUT YOUR FAMILY
UserMy mother takes care of me.
ELIZAWHO ELSE IN YOUR FAMILY TAKES CARE OF YOU
UserMy father.
ELIZAYOUR FATHER
UserYou are like my father in some ways.
The ELIZA transcript on slide 56, with Weizenbaum's original spelling restored on line 6

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

  1. User types

    He says I'm depressed much of the time.
  2. Uppercase

    HE SAYS I'M DEPRESSED MUCH OF THE TIME.
  3. Swap pronouns

    I'M becomes YOU ARE: HE SAYS YOU ARE DEPRESSED MUCH OF THE TIME.
  4. 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.
  5. Reassemble

    The replacement I AM SORRY TO HEAR YOU ARE \1 copies whatever the parentheses captured into the slot marked \1.
  6. 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.
The parenthesized alternative captures DEPRESSED, and the reassembly frame copies it into the reply. Nothing else in the sentence is read.
Weizenbaum (1966)MeaningModern regex
0 in a decomposition ruleAn indefinite number of words.*
A positive integer n in a decomposition ruleExactly n words((?:\S+ ){n}), one group holding exactly n words
3 in a reassembly ruleInsert the third component the decomposition found\1 style backreference to a capture group
Keyword dictionary, about 50 entriesWhich rule set to try, ranked by keywordA cascade of re.sub calls tried in order
Weizenbaum's 1966 notation next to the regex you would write today

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?

The program: keyword-triggered decomposition and reassembly rules with pronoun swapping echo the user's own words back, so every reply is on topic. The people: the Eliza effect, our tendency to read understanding into any behavior that looks conversational.

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.

PatternMatchesFirst match in the example
[mM]aryMary or maryMary Ann stopped by Mona's
[abc]a, b or cIn uomini, in soldati
[1234567890]Any digitplenty of 7 to 5
Slide 59: bracket disjunction (SLP3 Fig 2.8). The marked span is the first match.

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].

PatternMatchesFirst match in the example
[A-Z]An upper case letterwe should call it 'Drenched Blossoms'
[a-z]A lower case lettermy beans were impatient to be hoed!
[0-9]A single digitChapter 1: Down the Rabbit Hole
Slide 60: ranges with a dash (SLP3 Fig 2.9)

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.

PatternMatchesFirst match in the example
[^A-Z]Not an upper case letterOyfn pripetchik
[^Ss]Neither S nor sI have no exquisite reason for't
[^.]Not a periodour resident Djinn
[e^]Either e or a caretlook up ^ now
a^bThe pattern a^b, as the slide claimslook up a^b now (no match)
a\^bThe literal string a^b, escapedlook up a^b now
Slide 61: caret inside brackets (SLP3 Fig 2.10)
Three faces of one symbol: an anchor at the line start outside brackets, a negating gate when first inside brackets, a plain character elsewhere inside brackets.

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?

Inside brackets special characters except the backslash lose their meaning, so the period is literal and the class means "not a period". Outside brackets the period is the wildcard, so a literal one must be written \..

Counting, the wildcard, and greedy versus lazy

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).

On <a><b>, the greedy <.*> sweeps across both tags in one match; the lazy <.*?> stops at the first closing bracket and then finds <b> as a second match.
PatternStringMatchesLength of first match
<.*><a><b>one match, <a><b> at 06
<.*?><a><b>two matches, <a> at 0 and <b> at 33
a.*bxaxbxbxone match, axbxb at 15
a.*?bxaxbxbxone match, axb at 13
Greedy and lazy on the same strings, computed with the JavaScript engine

Worked example

Predicting a.*b against a.*?b on xaxbxbx

  1. Find where a match can start

    The only a is at index 1, so both patterns start there.
  2. 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.
  3. 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.
  4. 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.

Greedy a.*b gives axbxb, indices 1 to 5. Lazy a.*?b gives axb, indices 1 to 3.

Anchors and the six aliases

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.

PatternText (one record per line)Matches with mMatches without m
^#.*$# comment, text line, # another2 matches: # comment and # another, each at the start of its line0
^\d{4}-\d{2}-\d{2}2026-09-16 boot, user logged in, 2026-09-17 shutdown2 matches: the timestamps opening lines 1 and 31
Line-based formats under the m flag, computed with the JavaScript engine

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.

AnchorPosition it assertsExample
^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."
\bBetween a word character and a non-word character, or a string edge\bthe\b finds the at index 0 of "the other theme"
\BAny position that is not a word boundary\Bthe finds the inside other
Anchors (slide 64)

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.

Ticks mark every word boundary in the string. \bthe\b lights only the standalone the. The the inside other has letters on both sides, and the the that opens theme has a boundary on its left but an m after its e, so \bthe\b fails at both.

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.

AliasExpansionMatchesFirst match in the example
\d[0-9]Any digitParty of 5
\D[^0-9]Any non-digitBlue moon
\w[a-zA-Z0-9_]Any alphanumeric or underscoreDaiyu
\W[^\w]A non-alphanumeric!!!!
\s[ \r\t\n\f]Whitespace: space, tab, newlinein Concord
\S[^\s]Non-whitespacein Concord
Aliases (slide 65, SLP3 Fig 2.13), with the first match marked

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.

\w is [a-zA-Z0-9_], negated by \W. \s is [ \r\t\n\f], negated by \S. Python adds \v and, for str patterns, every Unicode whitespace character.

Recall

What are the three meanings of the caret, and which one does Python or JavaScript apply to a caret outside brackets?

Start-of-line anchor outside brackets, negation when first inside brackets, a literal caret elsewhere inside brackets. Outside brackets both engines always read it as an anchor, so a literal caret there must be written \^.

Recall

Write a regex for a word starting with a capital letter and say what each piece does.

\b[A-Z][a-z]*: a word boundary so the match starts a word, one upper case letter, zero or more lower case letters. \b[A-Z]\w* also accepts digits and underscores in the rest of the word.

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.

InteractiveRegex tester: every table row from the slides, run live in your browser
HE SAYS YOU ARE DEPRESSED MUCH OF THE TIME.
  1. Match 1at index 0HE SAYS YOU ARE DEPRESSED MUCH OF THE TIME.$1 = DEPRESSED
Group 1 holds the word ELIZA copies into I AM SORRY TO HEAR YOU ARE \1.
Matches1with g
First match0index43 characters long
Groups1capturednumbered $1, $2 in JavaScript

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

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

  1. Escape metacharacters and predict what (cat|dog)s versus cat|dogs matches using the precedence ladder.
  2. Write a capture-group substitution that reorders a date and a backreference that finds a doubled word.
  3. Write a lookahead-based password check and explain why lookahead consumes nothing.
  4. Explain each alternative of the GPT-2 pre-tokenizer on a given string and why it needs \p{L}.
  5. 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.

Where the vertical bar cuts depends on the parentheses. Same letters, two different sets of matches.
PatternReads asMatches
cat|dogscat, or dogscat, dogs, cat
(cat|dog)scat or dog, then scats, dogs
Two patterns on the string cats dogs cat dog

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?

Sequences bind tighter than |, so the pattern means the whole word guppy or the whole string ies. Fence the alternation: gupp(y|ies).

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.

AddressWhat matchedVerdict
m.alraimi@kfupm.edu.sam.alraimi@kfupm.edu.sawhole address, fine
first-last@x.comlast@x.comhyphen is not in the class, the local part is truncated
a+b@y.orgb@y.orgplus is not in the class either
me@localhostme@localhostaccepted although there is no top-level domain
The simplified email pattern on four addresses

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

  1. 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.
  2. 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.
  3. 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.
  4. Three replacements, three outputs

    The pattern never changes. Only the replacement string decides the order and the separators.
ReplacementOutputReading
$2$3$101252026slide 70: month, day, year, separators dropped
\2/\3/\101/25/2026US order with slashes written by the replacement
\3/\2/\125/01/2026day, month, year: the slide 72 target
One pattern, three replacement strings, on 2026-01-25

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.

Both lookaheads probe from position 0 and leave the pointer there. Only the final .{8,} consumes the string.

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.

CandidateLengthCapitalsDigitsResult
Passw0rdX9P, X0accepted
password8nonenonerejected
PASSWORD19all1accepted
Pw1short8P1accepted, exactly 8
The password pattern on four candidates

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?

01252026. The hyphens were matched but never captured, and the replacement writes only the three groups, so nothing puts a separator back.

Quick check

After the lookahead ^(?=.*\d) succeeds on Passw0rdX, where does the match pointer stand?

The slide 72 practice set, solved

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.

InteractiveRegex workbench: match, substitute and pre-tokenize with live feedback

Match the two tags <Begin> and <\End> and nothing else.

Literal//g
26 / 300
Result

Type 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.

<\\?\w+>: an opening bracket, an optional escaped backslash (the closing tag uses a backslash where HTML would use a slash), one or more word characters, a closing bracket. <[^>]+> also works: anything except a closing bracket, inside brackets. Both return <Begin> and <\End>.

Recall

Match Apple Juice, apPLe Juice and APPLE Juice but not Apple juice.

The first word is case-insensitive, the second is not, so a global i flag is wrong. Spell the classes: [Aa][Pp][Pp][Ll][Ee] Juice. Engines with scoped flags allow (?i:apple) Juice (Python re documentation).

Recall

Write a password check: at least 8 characters, at least one capital letter, at least one digit.

^(?=.*[A-Z])(?=.*\d).{8,}$. Two lookaheads from the start, then a consuming .{8,} to the end. On the four test lines it accepts Passw0rdX, PASSWORD1 and Pw1short and rejects password.

Recall

Match both 'The faster they ran, the faster we ran' and 'The faster they read, the faster we read' with one pattern.

Capture the two words that repeat and refer back to them: [Tt]he (\w+) they (\w+), the \1 we \2. Group 1 captures faster, group 2 captures ran or read, and \1 and \2 must reproduce them. A mixed sentence, ran then read, does not match.

Recall

Convert 2026-01-25 to 25/01/2026 with capture groups.

Pattern (\d{4})-(\d{2})-(\d{2}), replacement \3/\2/\1 in Python or $3/$2/$1 in JavaScript. The slashes are written by the replacement, not captured. For the slide's literal input 2026-25-01, the same pattern with \2/\3/\1 gives 25/01/2026; see the errata.

Recall

Convert 'the 35 boxes' to 'the <35> boxes'.

No group is needed if the tool can name the whole match: re.sub(r'\d+', r'<\g<0>>', s) in Python, or "the 35 boxes".replace(/\d+/g, "<$&>") in JavaScript. With a group, (\d+) and the replacement <\1>.

The cheat sheet as reference tables

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
SyntaxNameExampleResult
(?=...)positive lookaheadfoo(?=bar)foo in foobar only
(?!...)negative lookaheadfoo(?!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
Lookarounds, tested on foobar foobaz and on #42 and 7 and #8

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?

Python re accepts only fixed-width lookbehind, for example (?<=#) or (?<=a|b). JavaScript (ES2018) and the regex module allow variable-length lookbehind such as (?<=a+).

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?

It runs a regex that splits text into words with their leading space, digit runs, punctuation, contractions and whitespace before BPE. Pair counts and merges are computed only inside a piece, so a pair split across two pieces is never a candidate and the final tokens are always parts of pieces.
Eight cuts turn the sentence into nine pieces. Each piece keeps its own leading space, and BPE never merges across a cut.

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.

AlternativeMeaningPieces it produced
's|'t|'re|'ve|'m|'ll|'da contraction suffix, tried first're
?\p{L}+an optional space then one or more Unicode lettersWe, ␣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
The six alternatives on We're 350 dogs! Um, lunch? and on Hello world

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.

InteractiveRegex workbench: match, substitute and pre-tokenize with live feedback

Run the GPT-2 pre-tokenizer pattern and read which alternative produced each piece.

Literal/'s|'t|'re|'ve|'m|'ll|'d| ?\p{L}+| ?\p{N}+| ?[^\s\p{L}\p{N}]+|\s+(?!\S)|\s+/gu
26 / 300
Result

Type 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!

We by ?\p{L}+; 're by the contraction list; ␣350 by ?\p{N}+; ␣dogs by ?\p{L}+; ! by ?[^\s\p{L}\p{N}]+.

Recall

Name two ways to make a pattern Unicode-aware.

Use Unicode property classes (\p{L}, \p{N}, \p{Nd}) through the regex module or the JavaScript u flag instead of [A-Za-z] and ASCII \d; and normalize the input to NFC or NFKC before matching.

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.

Nested quantifiers branch at every a and every branch ends at the same dead end. A single plus has only one split per start position.

Worked example

Measuring the explosion

  1. 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.
  2. 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.
  3. 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.
  4. 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.
nPython reJavaScript
2043 ms8.3 ms
22175 ms31 ms
24711 ms126 ms
262900 ms506 ms
Measured time for (a+)+b on n a's and a ! (local runs; a+b was under 0.06 ms throughout)

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.

InteractiveRegex workbench: match, substitute and pre-tokenize with live feedback

Watch the engine time as the input grows. Then change the pattern to ^a+b and try again.

Literal/^(a+)+b/g
19 / 300
Result

Type 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.

(a+)+b on 26 a's and an exclamation mark takes about 2.9 s in Python because the engine tries every split of the a's. a+b matches the same strings in microseconds.

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

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

  1. Define minimum edit distance, name the three operations, and say what each costs under unit costs.
  2. Read and cost an alignment under unit costs and under substitution cost 2, and declare which convention you use.
  3. Explain why naive search over edit sequences is exponential and why the grid of prefix pairs is O(mn).
  4. Write the initialization and the recurrence for D[i,j] and justify D[i,0] = i.
  5. 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.

WER=I+S+Dwords in the reference\mathrm{WER} = \frac{I + S + D}{\text{words in the reference}}
Word error rate from a word-level minimum edit distance alignment (SLP3 section 16.6)

Worked example

WER for the kitchen light

  1. Reference and hypothesis

    Reference: "turn on the kitchen light" (5 words). Hypothesis: "turn on a kitchen light please" (6 words).
  2. 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).
  3. Count and divide

    I + S + D = 1 + 1 + 0 = 2, over 5 reference words.
  4. 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?

The minimum total cost of edit operations that turn a source string into a target string. The operations are insertion, deletion and substitution. With unit costs it is the Levenshtein distance.

Recall

How does WER use edit distance, and can it exceed 100 percent?

Align the hypothesis to the reference with a word-level minimum edit distance, then compute (I + S + D) divided by the number of reference words. Yes, because insertions are counted: SLP3's example gives (6 + 3 + 1) / 13 = 76.9 percent, and a short reference with many insertions goes past 100.

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.

INTE*NTION over *EXECUTION: ten columns, ten bars, five operations lit beneath. Deletions and insertions in teal, substitutions in the accent.

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.

OperationUnit costs (slides 85, 86)SLP3 table costs (substitution 2)
Match (same letter)00
Insertion11
Deletion11
Substitution (different letters)12
The two cost conventions used in this lecture
ColumnOperationUnit costsSubstitution 2
I over *delete11
N over Esubstitute12
T over Xsubstitute12
E over Ematch00
* over Cinsert11
N over Usubstitute12
T I O N over T I O Nfour matches00
Total58
The slide 82 alignment costed column by column under each convention

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.

InteractiveAlignment editor: label each column, then cost it two ways

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.

4 / 10 labeled
I*
NE
TX
EE
*C
NU
TT
II
OO
NN
Unit costs0totalins + del + sub, expected 5
Substitution costs 20totalins + del + 2 sub, expected 8
Counted so far0/0/0s/i/dsub / ins / del, matches cost nothing
Minimum over all alignments5 / 8unit / sub 2, intention to execution
AlignmentOperationsUnit costsSubstitution 2
Slide 82: d s s _ i s1 del, 3 sub, 1 ins58
SLP3 figure 2.19: delete i, n to e, t to x, insert u, n to c1 del, 3 sub, 1 ins58
Position by position, no gaps5 sub510
Three alignments of intention and execution under both conventions

Worked example

A second pair: kitten to sitting

  1. 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.
  2. Name the columns

    substitution, match, match, match, substitution, match, insertion.
  3. 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.
  4. 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.

Unit: 1 + 1 + 1 + 1 + 1 = 5 (one deletion, three substitutions, one insertion; the four matches cost nothing). Substitution cost 2: 1 + 2 + 2 + 1 + 2 = 8.

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.

One string fans out into hundreds of children, and two different edit orders land on the same string. Remembering the best cost per state is the whole trick.

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.

Rows consume source letters, columns produce target letters. From any cell, down is a deletion, right an insertion, diagonal a substitution or match. The faint staircase is the slide 82 alignment as a path.
MoveWhat it doesOperationRecurrence term
Down, (i - 1, j) to (i, j)Consume a source letter, produce nothingDeletionD[i - 1, j] + del
Right, (i, j - 1) to (i, j)Produce a target letter from nothingInsertionD[i, j - 1] + ins
Diagonal, (i - 1, j - 1) to (i, j)Pair a source letter with a target letterSubstitution or matchD[i - 1, j - 1] + sub(x_i, y_j)
The three moves in the grid, with source letters down the rows and target letters across the columns (slide 85 notation)

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.

tree nodes(2ΣL)dgrid cells=(m+1)(n+1)\text{tree nodes} \approx (2|\Sigma| L)^{d} \qquad \text{grid cells} = (m+1)(n+1)
Branching over strings versus cells over prefix pairs, with alphabet size |Σ|, word length L and distance d

Recall

Why is the search tree over edit sequences exponential while the grid is polynomial?

The tree's nodes are strings and each string has hundreds of one-edit children (about 494 for a 9-letter word over 26 letters), so the number of nodes grows as branching to the power of the distance. The grid's nodes are prefix pairs (i, j), only (m + 1)(n + 1) of them, each filled once, because many different edit orders arrive at the same pair.

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.

D[0,0]=0,D[i,0]=i,D[0,j]=j(unit costs)D[0,0] = 0, \qquad D[i,0] = i, \qquad D[0,j] = j \quad \text{(unit costs)}
Initialization: an empty target forces i deletions, an empty source forces j insertions
D[i,j]=min{D[i1,j]+del(xi)D[i,j1]+ins(yj)D[i1,j1]+sub(xi,yj)D[i,j] = \min \begin{cases} D[i-1,j] + \mathrm{del}(x_i) \\ D[i,j-1] + \mathrm{ins}(y_j) \\ D[i-1,j-1] + \mathrm{sub}(x_i, y_j) \end{cases}
The recurrence of slide 85 and SLP3 equation 2.19

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.

εcats
ε01234
c10123
a21012
t32101
D for source cat (rows) and target cats (columns), unit costs

Worked example

Two cells in full

  1. 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.
  2. 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.
  3. 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 functionsub(a, b)Nameintention to execution
Unit (slides 85 and 86)0 if a = b, else 1Levenshtein distance5
Substitution cost 2 (SLP3 tables)0 if a = b, else 2Levenshtein's no-substitution variant8
Confusion-weightedA value from a confusion matrixNoisy-channel spelling correctionDepends on the matrix
Three cost functions for the same recurrence

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.

D[0, 0] = 0, D[i, 0] = i, D[0, j] = j. Then D[i, j] = min(D[i-1, j] + del, D[i, j-1] + ins, D[i-1, j-1] + sub(x_i, y_j)). The answer is D[m, n].

Recall

Why is D[i, 0] = i?

The target prefix is empty, so all i source letters must be removed, and only deletion removes a letter. One unit each gives i, and no insertion or substitution can lower it.

Recall

flaw versus lawn: what are the Hamming distance and the edit distance, and why do they differ?

Hamming 4 (every position differs), edit distance 2 (delete f, insert n). Hamming only allows substitutions at fixed positions and needs equal lengths, so it cannot see that the shared law has simply shifted by one.

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

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

  1. Initialize and fill an edit distance table by hand under a stated cost convention and read the distance from D[n,m].
  2. Store argmin arrows while filling and backtrace them into an alignment with operation letters.
  3. Explain what a tie in the argmin means for the number of optimal alignments.
  4. State time and space complexity, and how two rows or Hirschberg's recursion reduce space.
  5. 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.

  1. intention, delete i, gives ntention
  2. ntention, substitute n by e, gives etention
  3. etention, substitute t by x, gives exention
  4. exention, insert u, gives exenution
  5. exenution, substitute n by c, gives execution
Five edits carry intention down to execution, one changed letter per rung, and no shorter ladder exists

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?

If the path from intention to exention were not optimal, a cheaper path to exention would exist, and appending the rest of the route to execution would give a cheaper total, contradicting optimality. So every prefix of an optimal path is optimal, which is exactly what lets a table of prefix pairs store the answer.

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].

D[i,j]=min{D[i1,j]+del-cost(xi)D[i1,j1]+sub-cost(xi,yj)D[i,j1]+ins-cost(yj)D[i,j] = \min \begin{cases} D[i-1,j] + \text{del-cost}(x_i) \\ D[i-1,j-1] + \text{sub-cost}(x_i, y_j) \\ D[i,j-1] + \text{ins-cost}(y_j) \end{cases}
The general recurrence, SLP3 Eq. 2.19: up, diagonal, left
D[i,j]=min{D[i1,j]+1D[i1,j1]+{2xiyj0xi=yjD[i,j1]+1D[i,j] = \min \begin{cases} D[i-1,j] + 1 \\ D[i-1,j-1] + \begin{cases} 2 & x_i \ne y_j \\ 0 & x_i = y_j \end{cases} \\ D[i,j-1] + 1 \end{cases}
The convention behind slide 89, SLP3 Eq. 2.20: a substitution counts as a deletion plus an insertion

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.

The border fills first, then a wave of cells each computed from its up, left and diagonal neighbors, ending at D[n,m]

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.

ConventionInsertDeleteSubstituteMatchDistanceOptimal alignments
Unit (Levenshtein)111057
SLP3 table (slide 89)11208134
Costs and outcomes for intention to execution under each convention (path counts computed for this part)

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.

#execution
#0123456789
i1234567678
n2345678787
t3456787898
e43456789109
n5456789101110
t656789891011
i767891098910
o8789101110989
n989101112111098
Slide 89: substitution cost 2, final distance 8
#execution
#0123456789
i1123456678
n2223456777
t3333455678
e4343456678
n5444456777
t6555555678
i7666666567
o8777777656
n9888888765
Unit costs: substitution 1, final distance 5

Worked example

Three cells of the slide's table, substitution cost 2

  1. 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.
  2. 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.
  3. 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.
  4. 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

  1. Borders

    Row # is 0 1 2 3 and column # is 0 1 2 3.
  2. 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.
  3. 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.
  4. Row t

    D[3,1] = 2, D[3,2] = 2, and t matches t so D[3,3] = D[2,2] + 0 = 1.
  5. Result

    Distance 1, alignment c a t over c u t with one substitution.
#cut
#0123
c1012
a2112
t3221
cat to cut under unit costs

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.

SimulatorMinimum edit distance: fill the table, then backtrace the arrows
PresetsInsertion and deletion always cost 1. Lowercase letters only, up to 12 each.
0 of 100 cells
#execution
#
i
n
t
e
n
t
i
o
n
Next cell to fill

D[0,0] = 0: the empty string is already the empty string.

Distance?sub 2
Cells100computed10 x 10
Two-row memory20numbersdistance only
ConventionSubstitution costDistanceOptimal alignments
Unit (Levenshtein)157
SLP3 table (slide 89) (shown)28134
The same pair under both conventions

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?

D[i,0] is the cost of turning the first i source letters into the empty string, which takes i deletions at cost 1 each. D[0,j] builds the first j target letters from nothing with j insertions.

Recall

Which three cells does D[i,j] depend on, and what operation does each represent?

D[i-1,j] above: delete source[i]. D[i,j-1] to the left: insert target[j]. D[i-1,j-1] on the diagonal: substitute source[i] by target[j], free when they are equal.

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).

CellValueArrow takenMeaning
(9,9)8diagonaln matches n, cost 0
(8,8)8diagonalo matches o, cost 0
(7,7)8diagonali matches i, cost 0
(6,6)8diagonalt matches t, cost 0
(5,5)8diagonaln to u, cost 2 (one of three arrows)
(4,4)6leftinsert c, cost 1
(4,3)5diagonale matches e, cost 0 (one of two arrows)
(3,2)5diagonalt to x, cost 2 (one of three arrows)
(2,1)3diagonaln to e, cost 2 (one of three arrows)
(1,0)1updelete i, cost 1
(0,0)0stoporigin reached
The bold path on slide 91, read from the corner to the origin

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
From the corner, footsteps retrace the stored arrows to the origin; at a tie cell a second dashed teal path branches off and reaches the origin at the same cost

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?

Two neighbors achieved the same minimum, so at least two distinct optimal alignments of equal cost pass through this cell. The backtrace may take either arrow.

Recall

Fill the unit-cost table for cat to cut and give the distance and one alignment.

Rows # 0 1 2 3, c 1 0 1 2, a 2 1 1 2, t 3 2 2 1. Distance 1. Alignment c a t over c u t with a single substitution of a by u; the backtrace is diagonal, diagonal, diagonal.

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.

A two-row window slides down the table; rows behind it are discarded, the corner value 5 still appears, but the path back to the origin is gone

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.

GoalTimeSpaceWhat to store
Distance onlyO(mn)O(min(m,n))Two rows: the previous one and the one being filled
Distance and alignmentO(mn)O(mn)The whole table with an arrow set in every cell
Alignment in linear spaceO(mn), about twice the constantO(m+n)Hirschberg's divide-and-conquer recursion, no stored table
What you want decides what you store

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?

Two rows, O(min(m,n)), because each row depends only on the previous row. The alignment would then be lost unless backpointers are stored or Hirschberg's recursion is used.

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