Why Text Is the Hardest Data You'll Ever Touch
Why Text Is the Hardest Data You'll Ever Touch
The Sentence That Broke the Model
Here is a sentence that has ended more NLP projects than any phrase in the English language: *"not bad."*
Two words. Twelve characters. Roughly 0.0001% of the training data for any sentiment classifier worth deploying. And yet it is the canonical demonstration that something is fundamentally wrong with the way we have, for decades, been teaching machines to read.
Let me show you what happens. You build a sentiment classifier the way the textbooks told you to in 2012. You take a corpus of IMDB reviews, you strip the HTML, you lowercase everything, you split on whitespace, you build a vocabulary, and you count. Each review becomes a vector. The vector for *"not bad"* looks like this in its bag-of-words representation:
```python { "not": 1, "bad": 1, } ```
That's it. Two non-zero entries. Now you train a logistic regression — or a naive Bayes, or a linear SVM, the choice barely matters — and the model learns weights for every word. The weight on *"bad"* is, predictably, large and negative. The weight on *"not"* is, at best, mildly negative because in most training contexts *"not"* appears next to other negative words and contributes marginally to the negative class. When you ship the model and someone enters *"not bad,"* the classifier sees the word *"bad,"* applies its strongly learned negative weight, and confidently outputs something like *negative: 0.78*. Which is exactly wrong. A human reading *"not bad"* understands it as mild praise, somewhere between neutral and positive. The model reads it as a complaint.
You can patch this. You can add bigram features so the model sees *"not_bad"* as a single token and learns that *"not_bad"* correlates with positive reviews. You can add trigrams. You can add skip-grams. You can add hand-crafted negation features. You can do all of these things and you will improve accuracy on *"not bad"* specifically while breaking something else, because the underlying representation — a sparse vector of word counts with no notion of structure, no notion of order, no notion of how words modify each other — is the wrong abstraction. The model is not learning language. It is learning to count.
This is the central puzzle of natural language processing, and it is the puzzle this book is built to dissolve: text looks like data, smells like data, has a `.csv` file format, and stubbornly refuses to behave like data. Every other modality you will encounter as a machine learning practitioner has been tamed. Tabular data is structured. Images are dense and locally smooth. Audio is a continuous signal. But text is none of these things. It is high-dimensional, sparse, ambiguous, context-dependent, and culturally variable in ways that make its surface form almost adversarial to standard statistical methods. The fact that we have built systems that appear to handle it — chatbots that hold plausible conversations, translators that pass casual inspection, search engines that mostly understand what you mean — is one of the great technical achievements of the last decade. The fact that those same systems fail in embarrassing ways the moment you push them slightly outside their training distribution is the work that remains.
This chapter is the orientation. Before we open up the engine and look at the gears, we need to establish the map of the territory: why text is hard, what kinds of problems we are actually trying to solve, what mental model you should carry around in your head when you face a new NLP problem, and the four recurring ideas — tokenization, representation, modeling, evaluation — that will appear in every subsequent chapter. By the end of this chapter you should be able to look at any NLP problem and, within five minutes, classify it into one of four archetypes, identify what representation choice dominates its difficulty, and predict the major failure modes it will exhibit.
Why Text Refuses to Behave Like Data
To understand why text is hard, it helps to start with the data modalities we have already tamed, because the contrast is illuminating.
Tabular data is the easiest. Each row is an observation. Each column is a feature. The features have names, types, and units. A column called `age` means something specific; it lives in a one-dimensional space; similar values correspond to similar things in the world. There is essentially no ambiguity. You don't have to ask what `age = 35` means. You also don't have to ask what `age = 35` *doesn't* mean. The semantics are pinned down by the schema. The dimensionality is manageable. Most real-world tabular datasets have tens to thousands of features, and the feature space is dense: every row has a value for every column. A logistic regression trained on tabular data with carefully engineered features routinely beats a transformer, because the features already encode the structure the model needs.
Image data is moderately easy. An image is a grid of pixels. Each pixel is a triple of integers in a low-dimensional space (three channels, eight bits each). The data is dense (every pixel is informative). The structure is local (neighboring pixels are correlated). Translation invariance is built into the math: a cat in the upper-left corner is the same cat as one in the lower-right, and convolutional neural networks exploit this directly. The semantics are mostly recoverable from the visual surface: you don't need to know the cultural context of a photograph to recognize a dog in it. Image classification hit human-level performance on restricted datasets around 2015, and modern vision transformers can match humans on most natural image tasks.
Text data is hard in a way that is qualitatively different from either of these. Let me enumerate the reasons, because every one of them will recur throughout this book.
Text is high-dimensional. The vocabulary of a typical English corpus contains tens of thousands of words. The vocabulary of a large web crawl contains millions. A model that treats each word as a dimension lives in a space whose dimensionality is roughly the vocabulary size. There is no natural way to project this down to something manageable without losing information, and — crucially — we don't know in advance which information is which.
Text is sparse. In a one-hot or bag-of-words representation, the average document uses a tiny fraction of the available vocabulary. The famous *term-document matrix* for a corpus like TREC or Reuters is mostly zeros; its density is often below 0.1%. This sparsity is a double curse. It makes naive statistical estimation brittle (most word pairs never co-occur in training data, so we cannot estimate their joint statistics directly) and it makes the geometry of the space almost meaningless (the distance between any two random documents is dominated by the dimensions they don't share).
Text is ambiguous at every level. The word *"bank"* has at least four common meanings: a financial institution, the side of a river, a pool of replacement shots, and the act of tilting an aircraft. The sentence *"I saw the man with the telescope"* has two readings depending on whether the prepositional phrase attaches to the verb or the noun. The utterance *"Can you pass the salt?"* is, syntactically, a question about your physical capabilities, but pragmatically, a request. This ambiguity is not noise; it is intrinsic to how language works. Human readers resolve it constantly and unconsciously, by drawing on context, world knowledge, and conversational norms that no current model fully possesses.
Text is context-dependent to a degree that other modalities are not. Negation is the obvious example: *"not bad"* versus *"bad,"* *"I never said she stole my money"* versus *"I said she stole my money."* Scope matters: *"all of the students did not pass"* has a different meaning from *"none of the students passed,"* even though they look