Text Normalization › Text Normalization and Unicode in NLP

Michael BrenndoerferUpdated March 19, 20254 min read
Language AI HandbookAll chapters
Part 1 of 67Text as Data
Chapter 2Text Normalization › Text Normalization and Unicode in NLP
Reading section 1 of 15 · Text Normalization and Unicode in NLP · 4 minSection 1 of 15 · 4 min

Book contents

Section 1 of 15 · 4 min read

Text Normalization › Text Normalization and Unicode in NLPOne section · Sections in this chapter1 / 15 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

In the previous chapter, we saw how a single character like "é" can be represented in multiple ways: as a single precomposed code point (U+00E9) or as a base letter plus a combining accent (U+0065 + U+0301). Both look identical on screen, but Python considers them different strings. This seemingly minor issue can break string matching, corrupt search results, and introduce subtle bugs into your NLP pipelines that are extraordinarily difficult to diagnose.

Text normalization is the process of transforming text into a consistent, canonical form. It goes beyond encoding to address a deeper question: when should two different byte sequences be considered the "same" text? The answer is not always obvious. For a search engine, "café" and "cafe" should probably match. For a literary database preserving original orthography, they should not. For a deduplication system, "SOCIÉTÉ GÉNÉRALE" and "Societe Generale" should be recognized as the same company name. These decisions are not technical trivia but fundamental design choices that shape how your system understands language.

Think of text normalization as the typographer's equivalent of a style guide. A style guide tells writers how to handle capitalization and hyphenation, alongside abbreviation, so that readers encounter consistent, readable prose. Normalization tells your software how to handle Unicode forms and case, plus whitespace and special characters, so that two strings that mean the same thing compare as equal. Without a shared style guide, the same piece of information can appear in dozens of incompatible forms, and your pipeline breaks down quietly on whichever variant it hasn't seen before.

The stakes are higher than they appear. Modern text comes from an enormous variety of sources: web pages scraped from servers in Tokyo, PDFs converted from scanned documents, user input typed on mobile keyboards, log files generated by services running in Frankfurt, chat messages copied and pasted from different operating systems. Each source has its own conventions for representing text, and none of them agreed in advance to use the same Unicode normalization form, the same approach to whitespace, or the same policy on ligatures and full-width characters. When you merge these sources into a training corpus or feed them into a retrieval pipeline, the inconsistencies pile up invisibly.

This chapter covers the four Unicode normalization forms (NFC, NFD, NFKC, NFKD), case folding versus simple lowercasing, accent and diacritic handling, whitespace normalization, ligature expansion, full-width to half-width conversion, and how to assemble these techniques into a reliable normalization pipeline. Each technique targets a specific class of variation. Mastering them gives you the confidence to process text from any source without silent failures.

Throughout the chapter, we will work through concrete Python examples using the standard library's unicodedata module, which gives you direct access to Unicode character properties and normalization algorithms. By the end, you will have both the conceptual framework and the practical code to handle the trickiest normalization challenges in real production systems.

Historical Context

Unicode normalization was standardized in Unicode 3.1 (2001) as part of Unicode Standard Annex #15, developed by the Unicode Consortium to address a problem that had plagued multilingual computing since the 1980s: different character encoding systems used incompatible representations for the same characters. Before Unicode, each language community had its own encoding (ISO-8859-1 for Western European, Shift-JIS for Japanese, GB2312 for Chinese), and mixing text from multiple encodings in a single document was fraught with errors. Unicode unified these into a single character set, but introduced a new challenge: the same abstract character could now be represented in multiple equivalent ways using combining character sequences. The four normalization forms were designed to resolve this ambiguity by providing deterministic transformation rules. The Unicode Consortium continues to refine these rules with each major version of the standard, which is why normalization behavior can vary slightly across Python versions.