Tokenization and Building a Vocabulary
The lookup line does not know which items were replaced. It treats the placeholder like any other vocabulary word. That is why the replacement stage and the lookup stage fit together cleanly.
The Gap a Fixed Vocabulary Leaves
A tokenizer converts text into integer IDs by looking up each token in a vocabulary. That vocabulary is a fixed table built from the text the tokenizer was given. The moment the text contains a word that is not in that table, a problem appears: a plain lookup has no entry to return for that word, so the conversion cannot finish cleanly unless the tokenizer has a rule for this case.
This article follows one answer to that problem. The class SimpleTokenizerV2 is a modified version of the simpler tokenizer that came before it. Where the earlier version could not account for a word outside its vocabulary, the new version replaces unknown words with a special placeholder, written <|unk|>, which stands for unknown. Your job is to follow exactly how that replacement happens, and to notice what the replacement costs.
What do you think happens?
Given a vocabulary where hello is 0, world is 1, and <|unk|> is 2, what list of IDs does the sentence 'hello there world' produce? Make your guess from the rules before reading on.
Reveal answer
[0, 2, 1]
The word there is not in the vocabulary, so it is replaced by <|unk|>, whose ID is 2. The remaining words keep their order: hello gives 0, the placeholder gives 2, and world gives 1.
Tracing a Sentence Through Encode
Encoding a sentence has four stages. First, a regular expression splits the text on punctuation and whitespace. Second, empty pieces are removed and surrounding spaces are stripped. Third, every piece is checked against the vocabulary, and any piece that is not present becomes <|unk|>. Fourth, each remaining item is looked up in the vocabulary to produce its integer ID. The replacement happens in the third stage, after the text has been split and cleaned but before the IDs are looked up.
Encoding 'hello there world'
Encode the sentence hello there world with a vocabulary containing hello as 0, world as 1, and <|unk|> as 2. The word there is not in the vocabulary.
- 1Split
The regular expression splits the text on whitespace, producing pieces that include hello, there, and world.
- 2Clean
Empty pieces are removed and surrounding spaces are stripped, leaving hello, there, and world as the three items.
- 3Replace unknown items
hello is in the vocabulary and stays. there is not in the vocabulary, so it becomes <|unk|>. world is in the vocabulary and stays. The list is now hello, <|unk|>, world.
- 4Look up IDs
hello maps to 0, <|unk|> maps to 2, and world maps to 1. The lookup succeeds for all three items because the placeholder is itself a vocabulary entry.
The encode output is [0, 2, 1].
The Single Line That Does the Replacement
Most of the change is contained in one line inside encode. That line checks each item against the vocabulary and substitutes the placeholder when the check fails. The whole behavior depends on that conditional expression, so it is worth reading slowly. The line that follows it converts every item into an ID with a list comprehension over the replaced items.
- preprocessed is the list of items after splitting, cleaning, and replacing unknown words
self.str_to_intis the vocabulary, a dictionary from token string to integer ID- Every item, including a replaced <|unk|>, is looked up the same way
The lookup line does not know which items were replaced. It treats the placeholder like any other vocabulary word. That is why the replacement stage and the lookup stage fit together cleanly.
Why the Placeholder Must Be in the Vocabulary
The ID lookup runs on the replaced items too. Therefore the placeholder must itself be a key in the vocabulary. The idea behind the fix is simple: instead of failing on an unfamiliar word, the tokenizer substitutes a placeholder that is already in the table, and that placeholder has its own integer ID. Every token in the sentence, including the unfamiliar ones, then ends up with an ID the rest of the pipeline can handle.
Reading the Vocabulary as a Lookup Table
Think of the vocabulary as a table where each row pairs a token string with its integer ID. In the small example used in this article, the rows are hello paired with 0, world paired with 1, and <|unk|> paired with 2. Encoding is the act of finding each item's row and reading off its ID. An item with no row is the case the tokenizer must handle, and the placeholder row is the answer it falls back on.
Predicting IDs for a New Sentence
Use the same procedure on a different input. Suppose the vocabulary is the same: hello is 0, world is 1, and <|unk|> is 2. Encode the sentence 'world there hello'. Split it, clean it, replace the unknown word, and then look up each item. The order of the output follows the order of the input words, so the unknown word's ID appears in the middle position, between the IDs of its neighbors.
What do you think happens?
What list of IDs does encode return for 'world there hello' with this vocabulary?
Reveal answer
[1, 2, 0]
world is 1, there is unknown and becomes <|unk|> with ID 2, and hello is 0. The unknown word keeps its position in the sequence.
Where the Original Word Disappears
Decoding reverses the lookup. The decode method maps each ID back to its string through the reverse dictionary, joins the strings with spaces, and then removes the spaces that appear just before certain punctuation marks. Decoding the list [0, 2, 1] therefore produces the text 'hello <|unk|> world'. The word there does not come back.
The loss happens in the replacement stage of encode, not in decode. Once there is swapped for <|unk|>, no later step has the original text available to restore. Decode only sees IDs, and the ID for the placeholder maps to the placeholder string, so the round trip returns a sentence that differs from the input at exactly the unknown position.
Common mistake Assuming decode restores the original sentence
What it looks likeEncoding 'hello there world' and decoding the result gives 'hello <|unk|> world'.
Why it failsReplacement is lossy. The original text of the unknown word is discarded during encode.
Do this insteadExpect the placeholder in place of any unknown word when you check a round trip.
Common mistake Thinking the unknown word is dropped from the ID list
What it looks likeExpecting [0, 1] instead of [0, 2, 1] for 'hello there world'.
Why it failsThe replacement keeps a position for the unknown word, and that position gets the placeholder ID.
Do this insteadCount the items after the split. Each one, replaced or not, produces exactly one ID.
Common mistake Forgetting that the placeholder must be in the vocabulary
Why it failsThe lookup runs on replaced items too, so a missing placeholder entry makes the lookup for unknown words fail.
Do this insteadConfirm that <|unk|> has its own row in the vocabulary before relying on the replacement.
Key Takeaways
- A fixed vocabulary has no entry for words it did not see, so the tokenizer needs a rule for unknown words.
- SimpleTokenizerV2 encodes by splitting, cleaning, replacing unknown items with <|unk|>, and then looking up integer IDs.
- The placeholder must be a vocabulary entry because the ID lookup runs on the replaced items as well.
- Replacement is lossy: decode returns <|unk|> where the original unknown word was, so the round trip does not restore the input.