Encoding and Decoding Text with a Tokenizer
SimpleTokenizerV2 handles words outside the vocabulary by replacing each one with the special <|unk|> token during encoding.
When a Word Has No Entry
A tokenizer turns text into integer IDs by looking up each token in a vocabulary. The vocabulary is a fixed table built from the text the tokenizer was given. That design raises an immediate problem. If a sentence contains a word that is not in the table, a plain lookup has no entry to return for that word, and 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 each unknown word with a special placeholder written as <|unk|>, which stands for unknown. Your job is to follow exactly how that replacement happens, and to notice what it costs.
The fix is simple: instead of failing on an unfamiliar word, the tokenizer substitutes a placeholder that is already in the vocabulary. Because the placeholder has its own integer ID, every token in the sentence, including the unfamiliar ones, ends up with an ID that the rest of the pipeline can handle.
The Vocabulary as a Lookup Table
Think of the vocabulary as a small dictionary that pairs each known token with a number. Encoding is the act of looking each token up in that dictionary and writing down its number. Decoding is the reverse: a second table maps each number back to its token. Both directions depend on the vocabulary being fixed, so a token that was never added to it has no number to look up.
| Token | Integer ID |
|---|---|
| hello | 0 |
| world | 1 |
| <|unk|> | 2 |
A small invented vocabulary used throughout this article
This three-entry vocabulary is the working example for the rest of the article. The word there is deliberately absent from it. Notice that the placeholder <|unk|> sits in the same table as the real words, with its own ID. That placement is what makes the replacement work.
Tracing an Unknown Word 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 flowchart below traces these stages for a single word, and the rest of this section runs them on a full sentence.
Now take the sentence hello there world. Stage one splits it on whitespace into three pieces: hello, there, and world. Stage two finds no empty pieces and no extra spaces to strip, so the list is unchanged. Stage three checks each piece. The words hello and world are in the vocabulary and stay as they are. The word there is not, so it is replaced by <|unk|>. After this stage the list reads hello, <|unk|>, world. The original text there no longer exists anywhere in the list.
What do you think happens?
Before you read the trace, predict the list of integer IDs that encode returns for hello there world, using the three-entry vocabulary above.
Reveal answer
[0, 2, 1]
After stage three the list is hello, <|unk|>, world. Stage four looks up each item: hello gives 0, <|unk|> gives 2, and world gives 1. The word there never reaches the lookup as its own text, so it never gets its own ID.
Encoding hello there world step by step
Encode the sentence hello there world with a vocabulary where hello is 0, world is 1, and <|unk|> is 2.
- 1Stage 1: split
The text is split on whitespace into hello, there, world.
- 2Stage 2: clean
No empty pieces appear and no spaces need stripping, so the list is unchanged.
- 3Stage 3: replace unknowns
hello is in the vocabulary and stays. there is not in the vocabulary and becomes <|unk|>. world is in the vocabulary and stays. The list is now hello, <|unk|>, world.
- 4Stage 4: look up IDs
hello maps to 0, <|unk|> maps to 2, and world maps to 1, giving the list 0, 2, 1.
encode returns [0, 2, 1]
The Line That Does the Replacing
Most of the replacement behavior sits inside a single line in the encode method. 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 comes right after it does the lookup:
self.str_to_intis the vocabulary, a mapping from each token to its integer ID- preprocessed is the list that already has unknown words replaced by <|unk|>
- Each item s is looked up once, and the result list keeps the same order as the pieces
Mapping Each Item to Its ID
After replacement, the list no longer holds the original words. It holds a mix of real tokens and the placeholder, and the lookup treats them the same way. The diagram below shows the final position-by-position mapping for hello there world. Each position receives exactly one ID, and the middle position receives the ID of the placeholder rather than any ID tied to the word there.
Read the map from left to right. The first and last positions keep their words and therefore keep their own IDs. The middle position has lost the word there and holds the placeholder, so it carries the ID for <|unk|>. From the pipeline's point of view, the three positions now look like three ordinary tokens, and the rest of the code can process them without any special case.
Where the Original Word Disappears
The decode method reverses the lookup. It 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.
Mistakes Beginners Make with Unknown Words
Common mistake Expecting decode to return the original sentence after encoding
What it looks likeencode then decode on hello there world returns hello <|unk|> world
Why it failsThe replacement stage discards the text there, and only the placeholder ID survives into the list of IDs.
Do this insteadTreat encode and decode as lossy for any word outside the vocabulary, and expect the placeholder wherever an unknown word appeared.
Common mistake Assuming the lookup happens before the replacement
What it looks likeBelieving there receives its own ID, such as 3, because the lookup runs on the raw split
Why it failsThe source places the replacement between cleanup and lookup, so the lookup only ever sees the placeholder in place of there.
Do this insteadFollow the four stages in order: split, clean, replace unknowns, then look up IDs.
Common mistake Removing <|unk|> from the vocabulary
What it looks likeA vocabulary with only hello and world, and no <|unk|> entry
Why it failsThe lookup for a replaced word needs the placeholder to be a real key, so the conversion fails for the unknown word.
Do this insteadKeep <|unk|> in the vocabulary with its own integer ID.
Common mistake Forgetting that the vocabulary is fixed, so a new word needs a new entry before it can be encoded as itself
Practice: Predicting IDs for a New Sentence
Using the same vocabulary (hello is 0, world is 1, <|unk|> is 2), predict the list of IDs that encode returns for the sentence world is hello. Work through the four stages before you check your answer, and then say which item in the list is the placeholder and which word it replaced.
Hints
- Stage one splits the sentence into world, is, and hello.
- Only is is missing from the vocabulary, so only that position becomes <|unk|>.
- After replacement the list is world, <|unk|>, hello, and each item is then looked up.
Explain in one or two sentences why the decoded text for world is hello will not contain the word is, even though the encoded list still has a position for it.
Hints
- Think about which stage the original word text is removed in.
- The ID at that position encodes the placeholder, not the word.
Key Takeaways
- A tokenizer's vocabulary is a fixed table, so a word missing from it has no entry for a plain lookup to return.
- SimpleTokenizerV2 encodes in four stages: split on punctuation and whitespace, remove empty pieces and strip spaces, replace unknown words with <|unk|>, then look up each item's integer ID.
- The placeholder <|unk|> must itself be a vocabulary entry, because the ID lookup runs on the replaced items too.
- Replacement is lossy: the original unknown word is lost at the replacement stage, so decode returns <|unk|> in its place and a round trip does not restore the input.