Concepts / Encoding and Decoding Text with a Tokenizer

Encoding and Decoding Text with a Tokenizer

SimpleTokenizerV2 handles words outside the vocabulary by replacing each one with the special <|unk|> token during encoding.

  • Programming

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.

Key point

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.

TokenInteger ID
hello0
world1
<|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.

Split texton punctuation andwhitespaceRemove empty piecesand strip spacesIn vocabulary?check each pieceKeep the wordfor known wordsLook up integer IDfor every remaining itemList of IDsreturned by encodeReplace with <|unk|>for unknown wordspieceseach pieceyesno
For each piece produced by the split, does it appear in the vocabulary, and which integer ID does it end up with?

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.

  • [0, 1, 2]

  • [0, 2, 1]

  • [0, there, 1]

  • [2, 0, 1]

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

Worked example

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.

  1. 1
    Stage 1: split

    The text is split on whitespace into hello, there, world.

  2. 2
    Stage 2: clean

    No empty pieces appear and no spaces need stripping, so the list is unchanged.

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

  4. 4
    Stage 4: look up IDs

    hello maps to 0, <|unk|> maps to 2, and world maps to 1, giving the list 0, 2, 1.

Result

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:

python
Notes on the code
  • self.str_to_int is 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.

helloID 0<|unk|> (was there)ID 2worldID 1
Given each word's position in the sentence, which integer ID does each item receive, and which item receives the <|unk|> ID?

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.

hello there worldoriginal texthello, there, worldafter split and cleanuphello, <|unk|>, worldthere replaced0, 2, 1IDs from encodehello <|unk|> worldoutput of decodeencode stage 1 and 2encode stage 3encode stage 4decode
At which step does the original text there disappear, and what does decode return in its place?

Mistakes Beginners Make with Unknown Words

  • Common mistake

    Expecting decode to return the original sentence after encoding

    What it looks like

    encode then decode on hello there world returns hello <|unk|> world

    Why it fails

    The replacement stage discards the text there, and only the placeholder ID survives into the list of IDs.

    Do this instead

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

    Believing there receives its own ID, such as 3, because the lookup runs on the raw split

    Why it fails

    The source places the replacement between cleanup and lookup, so the lookup only ever sees the placeholder in place of there.

    Do this instead

    Follow the four stages in order: split, clean, replace unknowns, then look up IDs.

  • Common mistake

    Removing <|unk|> from the vocabulary

    What it looks like

    A vocabulary with only hello and world, and no <|unk|> entry

    Why it fails

    The lookup for a replaced word needs the placeholder to be a real key, so the conversion fails for the unknown word.

    Do this instead

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

EASY

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

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.

Related concepts

More concepts starting with E