Text Preprocessing and Tokenization

Before any language model can predict a word, translate a sentence, or classify a review, it must first answer a deceptively hard question: where does one "unit" of text end and the next begin. Consider the string `"Dr. Smith paid 3.50fortheapp—it′samazing!!"‘Anaivesplitonwhitespaceorperiodsshatters‘Dr.‘intoafalsesentenceboundary.Italsomangles‘it′s‘and‘3.50 for the app—it's amazing!!"` A naive split on whitespace or periods shatters `Dr.` into a false sentence boundary. It also mangles `it's` and `3.50` into fragments no downstream model can use consistently. This is the problem of tokenization: converting raw, messy character streams into a sequence of discrete units. These units may be words, subwords, or characters, and a statistical or neural model can consume them. Tokenization is not a preliminary chore to rush past. It is a design decision that shapes everything built on top of it. The vocabulary size it produces determines the size of embedding tables and softmax layers. The granularity it chooses determines whether a model can handle a novel word like `unfriendable` at all. Otherwise, it silently maps the word to an out-of-vocabulary symbol and loses all its meaning. Every large language model in production — GPT-style transformers, BERT, machine translation systems — sits on top of a tokenizer. The tokenizer's choices leak into the model's biases, its handling of rare languages, and even its arithmetic mistakes. The big picture is a pipeline, not a single function. Raw text is first normalized (case-folding, Unicode cleanup, punctuation handling). It is then segmented into sentences, and finally tokenized into words or subword pieces. Each stage has its own classical algorithms. These include rule-based regular expressions, statistical sentence-boundary classifiers, and, in modern systems, data-driven subword algorithms like Byte-Pair Encoding. Understanding this pipeline is the entry point to nearly all of classical and neural NLP. It is the interface between unstructured human text and the structured, numeric world of N-gram Language Models and the Bag-of-Words Model. This article builds that pipeline from the ground up. It offers precise definitions and step-by-step algorithms with pseudocode. It includes a worked trace on a genuinely tricky sentence and a diagram of the full pipeline. Finally, it gives an honest accounting of complexity, correctness, and the traps that catch even experienced engineers.

Key takeaways
  • Tokenization converts raw text into discrete units (words, subwords, characters) that models can process numerically.
  • The pipeline is normalize → segment into sentences → tokenize into tokens, each with its own algorithms.
  • Rule-based tokenizers use regular expressions and exception lists; they are fast but brittle on edge cases.
  • Subword tokenization (Byte-Pair Encoding, WordPiece) solves the out-of-vocabulary problem used by nearly all modern LLMs.
  • Sentence segmentation is genuinely ambiguous (periods mean many things) and is usually solved with a classifier, not a regex alone.
  • Choices made here — vocabulary size, granularity, normalization — directly control model size, speed, and downstream accuracy.
The core insight

Tokenization is the algorithmic bridge between unstructured character streams and the discrete symbol sequences that statistical and neural language models require, and how you draw that bridge changes what the model can ever learn.

The idea

Text preprocessing is the umbrella term for the transformations applied to raw text before any statistical or learned model sees it. It has three canonical stages. Normalization standardizes superficial variation — case, Unicode encoding, whitespace, accents — so that `Café`, `café`, and `CAFÉ` are not treated as three unrelated symbols unless that distinction matters for the task. Sentence segmentation (or sentence boundary detection) splits a document into individual sentences, which matters because many downstream algorithms — parsers, translation systems, many classic language models — operate one sentence at a time. Tokenization is the final and most consequential step: splitting a sentence (or document) into a sequence of tokens, the atomic units the model will assign IDs to and embed as vectors. The central design tension in tokenization is granularity. A word-level tokenizer treats each token as a full word, delimited roughly by whitespace and punctuation; it produces intuitive units but suffers from an unbounded vocabulary and cannot represent words never seen during training (the out-of-vocabulary, or OOV, problem). A character-level tokenizer uses individual characters as tokens; its vocabulary is tiny and there is no OOV problem, but sequences become very long and the model must learn to compose meaning from scratch out of characters, which is statistically harder. Subword tokenization is the modern compromise: common words remain single tokens, while rare or novel words are decomposed into meaningful or frequency-derived pieces (`unfriendable` → `un`, `friend`, `able`). This gives a bounded vocabulary, no OOV problem (any string can be built from a base alphabet), and reasonably short sequences. Formally, given a raw string ss over an alphabet Σ\Sigma (typically Unicode code points, or bytes), tokenization defines a function

T:Σ∗→V∗T: \Sigma^* \to V^*

mapping the string to a sequence of tokens drawn from a fixed vocabulary VV, such that the tokens can be deterministically reassembled (or nearly so) into ss. The vocabulary VV and the function TT are typically learned jointly from a large text corpus rather than hand-specified, which is what distinguishes modern subword tokenizers from older whitespace-and-regex tokenizers. This connects directly forward to Morphology and Stemming, since good subword splits often approximate morpheme boundaries, and to N-gram Language Models and the Bag-of-Words Model, both of which are defined over whatever token sequence this pipeline produces.

How it works

The pipeline runs in three ordered stages. Each stage is described below with its dominant classical algorithm, followed by the modern subword algorithm (Byte-Pair Encoding) used at training and inference time in most contemporary systems.

Stage 1 — Normalization. Typical steps, applied in this order because later steps assume earlier ones are done: 1. Unicode normalization — collapse equivalent representations of the same character (e.g., `é` as one code point vs. `e` + combining accent) into a canonical form (commonly Unicode Normalization Form NFC or NFKC). 2. Case folding — optionally lowercase all text, trading away case information (useful for retrieval, harmful for tasks where `US` vs `us` or named-entity capitalization matters). 3. Whitespace and control-character cleanup — collapse runs of whitespace, strip non-printing characters. 4. Punctuation and contraction handling — decide whether `it's` becomes one token, two (`it`, `'s`), or is left untouched, and whether trailing punctuation is split off a word.

Stage 2 — Sentence segmentation. The naive approach splits on `.`, `!`, `?`. The problem is that `.` is wildly ambiguous: it ends sentences, but also appears in abbreviations (`Dr.`), decimal numbers (`3.14`), ellipses (`...`), initials (`J. R. R. Tolkien`), and URLs. A robust segmenter therefore treats sentence-boundary detection as a binary classification problem: for every candidate boundary character, decide boundary or not boundary using features like:

  • Is the token before the `.` in a known abbreviation list (`Dr`, `Mr`, `vs`, `etc`)?
  • Is the character after the `.` uppercase, lowercase, or a digit?
  • Is the `.` followed by whitespace and then a capital letter?
  • Length and case pattern of the preceding token.

This can be implemented with hand-written rules, a decision tree, or a small logistic-regression classifier trained on labeled boundary/non-boundary examples — the same statistical-classifier idea reused throughout classical NLP.

Stage 3 — Word/subword tokenization. The classical rule-based approach applies an ordered cascade of regular expressions (split on whitespace, then peel off punctuation, then special-case contractions and abbreviations). The modern data-driven approach is Byte-Pair Encoding (BPE): start from individual characters (or bytes) and iteratively merge the most frequent adjacent pair into a new symbol, building a vocabulary of subword units directly from corpus statistics. ``` ALGORITHM: Byte-Pair Encoding — Vocabulary Learning INPUT: corpus of words with frequencies, e.g. {"low": 5, "lower": 2, "newest": 6, "widest": 3} target vocabulary size V OUTPUT: ordered list of merge rules, final vocabulary 1. Represent every word as a sequence of characters, plus a special end-of-word marker "_", e.g. "low" -> [l, o, w, _] 2. vocab <- set of all individual characters/symbols seen 3. merges <- empty ordered list 4. while |vocab| < V: pairs <- count all adjacent symbol-pair occurrences across the ENTIRE corpus (respecting word frequencies) if pairs is empty: break // no more pairs to merge best <- pair with the highest count // ties broken deterministically new_symbol <- concatenate(best.left, best.right) replace every occurrence of best in the corpus with new_symbol add new_symbol to vocab append best -> new_symbol to merges 5. return merges, vocab ``` ``` ALGORITHM: Byte-Pair Encoding — Tokenizing New Text (inference) INPUT: word w, learned ordered list of merges OUTPUT: sequence of subword tokens 1. symbols <- list of characters of w, with end-of-word marker "_" 2. for each merge rule (a, b) -> ab, IN THE ORDER LEARNED during training: while (a, b) occurs as adjacent symbols in `symbols`: replace that occurrence with the merged symbol ab 3. return symbols ``` Two correctness-critical details are easy to miss. First, in training, pair counts must be computed over the whole corpus (weighted by word frequency) at every iteration, not just once at the start — after a merge, new adjacent pairs appear that did not exist before. Second, in inference, merges must be applied in the exact order they were learned, because BPE is a greedy, order-dependent algorithm: applying merges out of order can produce a different, invalid segmentation not seen during training. The full preprocessing pipeline, and where BPE sits inside it, is shown below.

Figure
The text preprocessing and tokenization pipeline
Raw text "Dr. Smith paid Normalize case, Unicode, ws Sentence Segment boundary classifier Tokenize (BPE) merge rules applied candidate "." after "Dr" → check abbreviation list → NOT boundary candidate "." after "app" → next char is space+capital-ish → boundary Output: 2 sentences BPE on "lower" l o w e r _ merge (l,o) → lo merge (lo,w) → low merge (e,r) → er tokens: [low, er, _] Final token stream fed to model [Dr, ., Smith, paid, $, 3.50, ...] then IDs [1042, 5, 87, 233, 4, 981, ...]

Complexity & correctness

Normalization is a linear scan: each character is inspected and rewritten a constant number of times, so it runs in O(n)O(n) time and O(n)O(n) space for a string of length nn. Unicode normalization (NFC/NFKC) is also O(n)O(n) in practice, since it is implemented as a bounded lookup and local recombination per character cluster.

Rule-based sentence segmentation and tokenization apply a fixed cascade of regular expressions or a finite-state scan over the string. Each character is visited a constant number of times across the cascade (assuming a bounded number of rules), giving O(n)O(n) time. A classifier-based segmenter evaluates a constant-size feature vector at each candidate boundary (there are O(n)O(n) candidates, one per punctuation mark), so it remains O(n)O(n) overall, with a constant-factor overhead per boundary for the classifier's feature extraction and decision.

BPE training is more expensive. Let nn be the total number of (whitespace-separated) word tokens in the corpus, and let VV be the target vocabulary size (number of merges performed). A naive implementation recomputes all adjacent-pair counts from scratch at every iteration, costing O(n)O(n) per iteration and O(nV)O(nV) overall. Efficient implementations maintain a priority queue of pair counts and only update counts for pairs adjacent to a merge site, giving an amortized cost close to O(nlog⁡n+n)O(n \log n + n) across all merges in practice, since each merge only invalidates a bounded number of local pairs. Either way, training is a one-time offline cost paid once per corpus/vocabulary choice, not paid per inference call.

BPE inference (tokenizing one new word of length LL characters) applies at most ∣merges∣|merges| candidate merge rules, each requiring a scan of the current symbol list; a straightforward implementation costs O(L⋅M)O(L \cdot M) where MM is the number of merge rules, but because each merge strictly reduces the symbol count and most rules do not match, well-engineered tokenizers (using a priority queue keyed by merge rank) bring this down to O(Llog⁡L)O(L \log L) per word.

Correctness argument for BPE. The key invariant is: at every point in training, the current segmentation of the corpus is the unique result of greedily merging the most frequent pair first, given the merges done so far. This is true by construction — step 4 of the training algorithm literally selects the argmax pair and applies it corpus-wide before recomputing statistics, so no other segmentation is reachable at that step for a deterministic implementation with fixed tie-breaking. This greedy invariant is also what guarantees the inference algorithm reproduces training-consistent segmentations: because inference applies merges in the same order they were learned (i.e., the same greedy priority), the segmentation of a word seen during training is provably identical to its training-time segmentation, since it must follow the same locally-optimal merge order.

Honest tradeoffs. BPE's frequency-greedy criterion is not linguistically motivated — it will happily merge non-morphemic character pairs if they are frequent (e.g., merging across a genuine morpheme boundary), so subword units do not always align with true morphology, a gap that motivates studying Morphology and Stemming separately. Sentence segmentation via hand-written abbreviation lists is not robust to unseen abbreviations or new domains (biomedical text, social media); this is exactly why the field moved to trainable classifiers instead of static rule lists. Word-level tokenizers guarantee interpretable tokens but an unbounded, corpus-dependent vocabulary; character-level tokenizers guarantee a small vocabulary but longer sequences and a harder learning problem for whatever model consumes them, since more computation is spent per unit of meaning; subword methods sit at a Pareto-favorable point but introduce a training-time hyperparameter (target vocabulary size VV) that must be tuned rather than derived.

Worked example

Trace the sentence: `"Dr. Smith paid $3.50 for the app—it's amazing!! Isn't that great?"`

Step 1: Normalization. Lowercase (task-dependent choice; here we keep case for named entities but normalize whitespace and the em-dash to a standard hyphen): ``` Dr. Smith paid $3.50 for the app-it's amazing!! Isn't that great? ```

Step 2: Sentence segmentation. The classifier scans each `.`, `!`, `?`:

Candidate boundaryPreceding tokenFollowing charDecisionReasoning
`.` after "Dr"`Dr`space, then capital `S`not a boundary`Dr` is in the abbreviation list
`.` inside `3.50``3`digit `5`not a boundarypreceded/followed by digits ⇒ decimal point
`!!` after "amazing"`amazing`space, capital `I`boundaryexclamation mark, followed by space + capital
`?` after "great"`great`end of stringboundaryquestion mark at string end

Result: two sentences. ``` S1: Dr. Smith paid $3.50 for the app-it's amazing!! S2: Isn't that great? ```

Step 3: Word tokenization (rule-based baseline). Split on whitespace, then peel punctuation, special-casing abbreviations and contractions: ``` S1 -> [Dr., Smith, paid, ,3.50,for,the,app,−,it,′s,amazing,!,!]S2−>[Is,n′t,that,great,?]‘‘‘Notethedeliberatechoices:‘Dr.‘staysfused(abbreviationexception),‘, 3.50, for, the, app, -, it, 's, amazing, !, !] S2 -> [Is, n't, that, great, ?] ``` Note the deliberate choices: `Dr.` stays fused (abbreviation exception), `3.50` splits the currency symbol from the amount but keeps the decimal number intact (a digit-aware rule), and `it's` / `Isn't` split into a stem and a clitic (`it` + `'s`, `Is` + `n't`) using a small contraction lookup table — this is a classic hand-crafted exception list, and different tokenizers (e.g., the Penn Treebank tokenizer vs. spaCy's) disagree on the exact split point for `n't`.

Step 4: Subword tokenization (BPE), assuming `amazing` is rare in the training corpus but `Smith`, `paid`, `for`, `the` are common enough to be single tokens. Suppose the learned merges include `am`, `az`, `ama`+`zing` not yet fully fused. A plausible BPE output for the rare word: ``` amazing -> [am, az, ing] (three subword pieces, none is "amazing" itself) ``` while common short words remain intact: ``` Smith -> [Smith] paid -> [paid] ``` The full subword token stream for S1 might look like: ``` [Dr., Smith, paid, $, 3, ., 50, for, the, app, -, it, 's, am, az, ing, !, !] ``` Every piece here is drawn from a fixed, bounded vocabulary learned once from a training corpus — this is the property that lets a model handle `amazing` even if the literal word `amazing` never appeared during vocabulary construction, so long as its subpieces did. This is precisely the OOV-robustness property that makes subword tokenization the default choice in essentially every modern transformer-based system, feeding directly into the fixed-size embedding lookup tables discussed when studying language models built on top of this token stream.

Common pitfalls & traps

PitfallWhy it happensConsequence
Splitting on every `.`Ignoring abbreviations, decimals, ellipsesSentences fragmented mid-abbreviation (`Dr.` becomes its own "sentence")
Lowercasing before named-entity-sensitive tasksNormalization applied blindly regardless of downstream taskLoses the `US` (country) vs `us` (pronoun) distinction
Applying BPE merges out of learned orderTreating merge rules as an unordered setProduces segmentations never seen in training, hurting the model
Ignoring word frequency weights during BPE pair countingCounting unique word types instead of corpus occurrencesRare-but-diverse words dominate merge selection, distorting the vocabulary
Assuming whitespace = word boundary universallyWhitespace-based tokenization is Indo-European-centricFails on Chinese, Japanese, Thai, and other scripts without spaces
Fixed abbreviation lists that don't generalizeRule lists are hand-curated for one domainSilently mis-segments new domains (medical, legal, social media)
Forgetting to handle Unicode normalizationTreating precomposed and decomposed accented characters as differentSame visible word counted as two distinct vocabulary entries

Pioneers & history

Tokenization as an explicit, statistically-grounded problem grew out of two threads: information-theoretic data compression, and the corpus-linguistics tradition of building annotated, hand-tokenized reference corpora (like the Penn Treebank) that made the ambiguity of "what is a token" concrete and measurable. Byte-Pair Encoding itself began life as a compression algorithm, later repurposed for open-vocabulary neural translation, illustrating how ideas migrate across subfields of computer science.

Origins · 1 minClaude Elwood Shannon and how this was discovered
CE
Claude Elwood Shannon
1916–2001 · Bell Labs, founder of information theory

Shannon's 1948 theory of communication supplied the statistical vocabulary — entropy, symbol frequency, code length — that every subword tokenizer implicitly relies on: BPE's greedy frequency-based merging is, in essence, a crude entropy-reduction heuristic borrowed directly from the compression tradition Shannon founded.

History
1948
— Shannon publishes "A Mathematical Theory of Communication," founding information theory and the statistical view of symbol sequences.
1994
— Philip Gage publishes the original Byte-Pair Encoding algorithm as a general-purpose data compression technique.
1993
— The Penn Treebank release standardizes a rule-based tokenization scheme still referenced by NLP tools today.
2016
— Sennrich, Haddow, and Birch adapt BPE for open-vocabulary neural machine translation, popularizing subword tokenization in neural NLP.
2018
— WordPiece and SentencePiece formalize alternative subword-merging criteria used in BERT and subsequent transformer models.

Where this matters

Every major transformer-based language model — BERT, GPT-family models, T5, and their successors — begins with a learned subword tokenizer (BPE, WordPiece, or Unigram/SentencePiece variants), whose vocabulary size (commonly 30,000 to 100,000+ tokens) is fixed before training and baked permanently into the model's embedding table. Search engines rely on normalization and tokenization to match a user's query against indexed documents despite case and punctuation differences. Machine translation systems use subword tokenization specifically to handle rare names, morphologically rich languages (Finnish, Turkish), and code-switching without ever hitting an unknown-word wall. Speech-to-text and text-to-speech pipelines depend on sentence segmentation to chunk long transcripts into units suitable for prosody modeling. Even simple applications — spell checkers, autocomplete, keyword search — sit on the same normalize-segment-tokenize foundation described here, making this pipeline one of the few pieces of NLP infrastructure that essentially every downstream system, from the classical Bag-of-Words Model to the largest modern LLMs, depends on directly.

Ask the tutor: How would you design a tokenizer that handles both English and Chinese text robustly in the same vocabulary?

Why is tokenization considered a design decision rather than a solved preprocessing detail?

Because the choice of tokenizer directly determines the model's vocabulary size, embedding table size, sequence lengths, and even what kinds of errors it will make. A word-level tokenizer with a 50,000-word vocabulary cannot represent a word outside that vocabulary at all, forcing an out-of-vocabulary symbol that discards meaning. A subword tokenizer changes this tradeoff entirely, at the cost of sometimes splitting words in linguistically unintuitive places. Since every downstream layer — embeddings, attention, the softmax output — operates on whatever tokens the tokenizer produces, mistakes or biases introduced at this stage (e.g., splitting non-English scripts inefficiently) propagate through the entire system and are very costly to fix after a model has already been trained.

How does Byte-Pair Encoding differ from WordPiece, since both produce subword vocabularies?

Both start from characters and iteratively merge pairs into a larger vocabulary, but they use different merge-selection criteria. BPE greedily merges whichever adjacent pair occurs most frequently in the corpus. WordPiece instead merges the pair that maximizes the likelihood of the training corpus under a unigram language model built from the resulting vocabulary, which can be understood as merging the pair whose combination most increases the corpus's statistical likelihood rather than simply its raw frequency. In practice, the two algorithms often produce broadly similar vocabularies, and both are deterministic, corpus-driven, and applied with a fixed, ordered set of merge rules at inference time.

Does lowercasing text during normalization ever hurt a model?

Yes — lowercasing throws away information that some tasks need. Named entity recognition relies on capitalization to distinguish proper nouns from common words (`Apple` the company vs. `apple` the fruit). Acronym disambiguation, sentiment analysis (all-caps often signals emphasis or shouting), and code-related text (case-sensitive identifiers) all suffer if case is collapsed indiscriminately. Modern large-scale tokenizers typically preserve case and instead let the model itself learn to treat `apple` and `Apple` as related but distinct tokens, since the model has enough capacity and data to learn this relationship without a hand-imposed normalization decision that cannot be undone later.

Why does whitespace-based tokenization fail for languages like Chinese or Thai?

Whitespace-based tokenization assumes words are separated by spaces, which is a property specific to certain writing systems (English, French, German, and similar). Chinese, Japanese, and Thai text is written with no spaces between words at all, so a whitespace-based rule would treat an entire sentence, or an entire paragraph, as a single "word" token. These languages instead require dedicated word-segmentation algorithms (dictionary-based maximum matching, statistical segmenters, or subword methods operating directly on characters/bytes), which is one reason byte-level or character-level BPE — operating on raw bytes rather than assuming space-delimited words — has become popular for building multilingual tokenizers that work uniformly across scripts.

What exactly is the "out-of-vocabulary" (OOV) problem, and how does subword tokenization solve it?

In a fixed-vocabulary word-level tokenizer, any word not seen during vocabulary construction has no valid token ID; it must be mapped to a generic unknown-word placeholder, which discards essentially all of its meaning for the model. This is the OOV problem, and it is especially damaging for rare names, technical terms, typos, and morphologically complex words. Subword tokenization solves it structurally, not by making the vocabulary larger, but by guaranteeing that any string can be decomposed into a sequence of smaller pieces (down to individual characters or bytes) that are guaranteed to be in the vocabulary. A truly novel word simply gets split into more, smaller pieces rather than being discarded entirely.

How should a beginner decide on a vocabulary size when training a BPE tokenizer?

Vocabulary size is a tradeoff between sequence length and per-token information content. A very small vocabulary (close to character-level) yields long sequences that are computationally expensive to process but need almost no embedding-table memory. A very large vocabulary yields short sequences and richer per-token meaning, at the cost of a much larger embedding table and softmax layer, plus increased risk of learning rare, poorly-estimated token embeddings from insufficient training examples. In practice, most modern systems settle empirically in the 30,000–100,000 range, tuned against the specific corpus size, language diversity, and compute budget available, rather than derived from a single closed-form rule.

Is sentence segmentation still necessary now that many neural models process long, unsegmented text directly?

Sentence segmentation remains useful even for models that can technically ingest long spans of text, because many pipeline components still operate more effectively at sentence granularity: parsers, coreference resolvers, sentence-level embedding models, and translation systems commonly still process one sentence at a time or use sentence boundaries as strong structural signals. Some modern large language models sidestep explicit sentence segmentation by treating segmentation as an emergent property learned implicitly from raw text, but for many classical NLP pipelines, and for tasks requiring alignment between languages or between transcript and audio, explicit sentence boundaries remain a necessary, non-optional preprocessing step.

Before this

Where this leads