Markov Text
Words by the Numbers
Chapter 5 ยท Markov Text
Words by the Numbers 4 counted which words sit next to each other. This chapter turns those counts around: if you know the word you just read, which word is likely to come next? Answer that question with the counts and you can also run it backwards and write text, one word at a time, by rolling dice weighted by the counts. The result is a Markov chain text generator. It is about twenty lines of Python, it has no idea what any word means, and its output still sounds almost right, which is the interesting part. The maths is light: counting, division and multiplying probabilities.
pydoc_data.topics module), about 74,500 word tokens of technical English. It is not typical prose, and its counts change a little between Python versions, so expect small differences on your machine. The generated passages depend on the random seed given in the code; other seeds give other text. The tiny sentences in the first snippet and in Exercise 1 are invented, illustrative examples. Background facts come from a web search that returned summaries of web pages; such fetched pages are often truncated or model-summarised, so treat the wording as a paraphrase. The search confirmed Markov's use of the poem Eugene Onegin and the figures quoted below; it did not give Shannon's exact word-level orders or example sentences, so those are left out and the reader is pointed to his paper (check). Statements marked (check) were not confirmed against a source.
Where the Idea Came From
Andrey Markov studied sequences in which the next item depends on the current one. In a 1913 lecture he applied the idea to text: according to the search summaries, he took the first 20,000 letters of Pushkin's verse novel Eugene Onegin, marked each letter as a vowel or a consonant (8,638 vowels and 11,362 consonants), and showed that the chance of a vowel depends on what came before it. An English translation of the paper appeared in 2006, per the same sources. In 1948 Claude Shannon's paper A Mathematical Theory of Communication used the same kind of model to produce approximations of English of increasing order; sources describe them as a series of Markov-chain models, but I could not confirm the orders or the sample sentences from a source, so look them up in the paper itself (check). Words by the Numbers 9 comes back to Shannon.
Predicting the Next Word
The model has one rule: the next word depends only on the word just read, not on anything earlier. For each word, keep a list of every word that has followed it. The list keeps repeats, so a word that followed three times out of four is three times as likely to be picked when you choose from the list with random.choice. Picking from the list is the same as picking by probability.
the was followed by cat twice, mat once and dog once, so the chance of cat after the is 2/4. To get the chance of a whole path, multiply the steps: cat after the (2/4) times sat after cat (1/2) is 1/4. The generated line also shows the model's one structural weakness: it stopped at dog, which ended the sample text, so no follower had ever been seen. Real generators need a rule for that; the next snippet jumps to a random word.
A Chain Built from Real Text
Now the same idea on the whole documentation text. The snippet builds the follower lists, counts how many words have only one kind of follower, and prints three short passages. Starting words are drawn from the full list of tokens, so common words start more often. When a word has no follower the code jumps to a random word, and the lists never run across the end of one documentation topic into the next.
Of the 3,288 words that have a follower, 38% were only ever followed by one kind of word, so the chain has no choice there. Read the passages in pieces: an extra argument for example and the traceback attached to the container are fluent fragments, and each pair of neighbouring words really occurs in the source. But no sentence lasts: the model has no memory of how the sentence began, so it wanders into something unrelated after a few words. That is the “almost right”. Every pair is plausible and the whole is not. Because the source is technical documentation, the nonsense also sounds technical (continue id continue or methods object rshift self args).
Short Memory, Long Memory
The fix is to remember more. An order-k chain uses the last k words as its context. Order 1 is what you just built; order 2 looks at the last two words (the bigrams of Words by the Numbers 4), order 3 at the last three. Generation works the same way: look up the context, pick a follower, slide the window along by one word. The snippet builds orders 1 to 3, generates 3,000 words from each, and measures two things. The first is how many contexts have only one possible follower. The second is how many 4-word runs in the generated text can be found word for word in the source, which measures copying.
As the memory grows the sentences get steadier (the order-3 sample, returns the value of the literal for example the annotation, reads almost like a real documentation sentence), but the freedom disappears. At order 3, 91% of the contexts have a single follower and 100% of the 4-word runs come straight from the source. The 100% is partly built in: an order-3 context plus its follower is itself a 4-word run from the source, so the figure could only fall below 100% where the code jumped to a random context. The generator has stopped writing and started quoting. Order 2 sits in the middle, with 64% of runs found in the source.
| Order | Contexts seen | Only one follower | 4-word runs found in source |
|---|---|---|---|
| 1 | 3,288 | 38% | 4% |
| 2 | 27,494 | 76% | 64% |
| 3 | 46,929 | 91% | 100% |
This is the sparsity problem from Words by the Numbers 4 seen from a new side. With about 74,500 words of source, most three-word contexts were followed by a single kind of word, so the model can only repeat what it saw. A longer memory needs far more text. The trade is general: low order gives variety with poor sense, high order gives sense by copying.
How Good Is the Guess?
Writing text is hard to score by eye, so test the prediction itself. Hold back every fifth documentation topic as a test set, build the counts from the rest, and for each test position guess the next word. Three guessers compete: always say the word that is commonest in the training text; say the commonest follower of the last word; and say the commonest follower of the last two words, falling back to the last word alone when that pair was never seen in training. A guess counts only if it is exactly the right word, so the scores will look low.
The ranking is the lesson. Always guessing the commonest word is right 6.6% of the time. Using the last word more than doubles that, to 16.5%, and adding the second word with a fallback reaches 29.8%. Even so, the two-word context was seen in training at only 67% of test positions, so a third of the time the guesser had to fall back. A score near 30% sounds poor, but the guesser is choosing among thousands of words in text that is not predictable by design. Guessing the next word is also the job modern language models do, with far bigger models and far more text (general background, not something I checked; check).
| Guesser | Right on held-out topics |
|---|---|
| Commonest word every time | 6.6% |
| Commonest follower of last word | 16.5% |
| Last two words, fall back to last one | 29.8% |
Write a short invented passage of your own (one or two sentences). Build the follower lists for a first-order chain. Print how many words and distinct bigrams it has, the followers of three chosen words as fractions, the chance of one exact three-word path, and a short generated line.
๐ View solutionOn the documentation text, for orders 1, 2 and 3, generate 2,000 words and report the number of dead-end jumps, the share of steps where the chain had a genuine choice between different followers, and the mean number of followers per context. How does the freedom fall as the order rises?
๐ View solutionHold out every fourth documentation topic as a test set. With a first-order model, report how often the right next word is the model's single best guess, how often it is within its three best guesses, and how often the last word was never seen in training.
๐ View solutionQuick Reference
- Markov chain (order 1): the next word depends only on the current word; order k uses the last k words
- Build:
followers[a].append(b)for each pair fromzip(words, words[1:]); keep the repeats - Generate:
random.choice(followers[word]), repeat; setrandom.seed(n)to repeat a run - Chance of a path: multiply the step probabilities, e.g. 2/4 × 1/2 = 1/4
- Dead end: a context with no follower; jump to a random context
- Order trade-off: low order gives variety and nonsense, high order gives fluency by copying (here, 100% of 4-word runs at order 3)
- Test fairly: build on some topics, score on held-out ones