Concepts / Tokenization and Building a Vocabulary

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.

  • Programming

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.

  • [0, 1, 2]

  • [0, 2, 1]

  • [0, 2]

  • The encode call fails with an error

Reveal answer
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.

Piece from splittextIn vocabulary?Keep the wordLook up integer IDInteger IDReplace with <|unk|>yesnoplaceholder has its own ID
For each piece produced by the split, encode asks whether the piece is in the vocabulary. A known piece keeps its text, an unknown piece is swapped for the placeholder, and both paths then reach the ID lookup.
Worked example

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.

  1. 1
    Split

    The regular expression splits the text on whitespace, producing pieces that include hello, there, and world.

  2. 2
    Clean

    Empty pieces are removed and surrounding spaces are stripped, leaving hello, there, and world as the three items.

  3. 3
    Replace 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.

  4. 4
    Look 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.

Result

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.

python
Notes on the code
  • preprocessed is the list of items after splitting, cleaning, and replacing unknown words
  • self.str_to_int is the vocabulary, a dictionary from token string to integer ID
  • Every item, including a replaced <|unk|>, is looked up the same way
Key point

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.

helloID 0<|unk|>ID 2worldID 1
Each word in the sentence, after replacement, is paired with the integer ID it looks up in the vocabulary. The unknown word there occupies the middle position and carries the placeholder ID.

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
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.

hellohellothere<|unk|>worldworld
The input sentence contains the word there. After encoding and decoding, the position that held there now holds the placeholder text, and the original word is gone from the output.

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 like

    Encoding 'hello there world' and decoding the result gives 'hello <|unk|> world'.

    Why it fails

    Replacement is lossy. The original text of the unknown word is discarded during encode.

    Do this instead

    Expect 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 like

    Expecting [0, 1] instead of [0, 2, 1] for 'hello there world'.

    Why it fails

    The replacement keeps a position for the unknown word, and that position gets the placeholder ID.

    Do this instead

    Count 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 fails

    The lookup runs on replaced items too, so a missing placeholder entry makes the lookup for unknown words fail.

    Do this instead

    Confirm 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.

Related concepts

More concepts starting with T