Building a Basic Tokenizer
The tokenizer splits text with re.split at punctuation, the double dash, and whitespace, keeping matched punctuation through a capturing group.
A Story Is a Stronger Test Than a Sentence
A tokenizer that works on one sentence has not yet been proven. The real test is whether it still behaves correctly on a much longer text. In this article you apply a basic tokenizer to an entire short story by Edith Wharton and check three things: how many tokens come out, whether the first tokens look right, and how those tokens are then turned into integers.
Before you run anything, notice what your guess is based on. Most people estimate a count by counting words and punctuation marks. That is close to what the tokenizer does, but the gap between a rough guess and the exact count is exactly the kind of detail a step-by-step trace can settle for you. For this story the tokenizer produces 4690 tokens once blank and whitespace-only items are removed.
Run the code that accompanies this section first and watch what it does. You do not need to understand every piece yet. The sections that follow send you back to the trace whenever you need to check one specific stage.
The Single re.split Call at the Core
The core of the tokenizer is a single call to the re.split function from Python's re module. You give it a pattern and a string, and it cuts the string wherever the pattern matches. Nothing else in the tokenizer decides where a boundary falls. Everything that follows is a consequence of that one call.
The pattern in this tokenizer matches three kinds of boundary. The first is a character class listing commas, periods, colons, semicolons, question marks, underscores, exclamation marks, parentheses, and quotation marks. The second is the double dash. The third is any whitespace character. Each time the pattern matches one of these, the string is cut at that position.
Tracing the Split Stage by Stage
Take the short text Hello, world. It is small enough to trace by hand, and it has the same structure as the full story: words, punctuation, and spaces. Follow the three stages in order: split, keep the pieces, then remove the empty ones.
['Hello', ',', '', ' ', 'world', '.', '']
['Hello', ',', 'world', '.']- The parentheses make the whole alternation a capturing group, so each matched boundary is returned in the list.
- The list comprehension keeps only items that still contain something after stripping spaces.
Tracing Hello, world. one stage at a time
Starting from the raw text Hello, world., what list of pieces does the split produce, and what remains after the empty and whitespace-only items are removed?
- 1Find the boundaries
The comma after Hello matches the punctuation class, and the space after the comma matches the whitespace class. The period after world also matches the punctuation class.
- 2Cut and keep the matches
Because the pattern has a capturing group, each matched boundary is kept as its own item. The string is cut into Hello, the comma, an empty string between the comma and the space, the space itself, world, the period, and an empty string after the period.
- 3Remove empty and whitespace-only items
The two empty strings and the single space hold nothing after stripping spaces, so they are dropped. The comma and the period hold visible characters, so they stay.
The pieces are Hello, comma, empty, space, world, period, empty. After filtering, the tokens are Hello, comma, world, period, which is four tokens from a seven-item raw list.
Read the pipeline from top to bottom. The first stage decides where the boundaries fall, and the words and punctuation marks come out of that stage in the order they appeared in the text. The second stage only removes items that contain nothing visible. It never reorders or merges anything. That is why the token list keeps the original sequence of the text.
Before reading the next section, check your understanding of the filter stage. Given the text Yes; no., which items does the split produce, and which of them survive the filter?
Hints
- Find every character matched by the punctuation class or the whitespace class first.
- Remember that an empty string between two adjacent boundaries still counts as an item before the filter runs.
Keeping Punctuation with a Capturing Group
Notice that the punctuation survives in the output of the trace above. That result depends on a detail of the pattern: the parentheses. When a pattern contains a capturing group, re.split returns the matched text along with the pieces between matches. Without the group, the matched characters are used only as cut points and are thrown away.
The left column shows what you get when the pattern has no capturing group. The commas and periods are gone, and the list holds only the words and the empty strings around the cut points. The right column shows the same split with the parentheses in place. The matched punctuation now appears as its own item, sitting between the words it separated.
Dropping Whitespace-Only Items Before Counting
Because the split happens at every whitespace character, the raw result also contains empty strings and strings that hold only spaces. Those are not tokens. A second pass removes them, keeping only items that still contain something after stripping spaces. This is why the story's count of 4690 tokens is the number of meaningful items, not the number of items the split first produced.
In the diagram, the seven raw items become four tokens. The three dropped items are the two empty strings and the single space. The visible items, the word Hello, the comma, the word world, and the period, are untouched and keep their original order. Counting before this filter would overstate the number of tokens by exactly the number of blank items.
Turning Tokens into Integer Token IDs
A list of tokens is still a list of Python strings. The next step converts those tokens from strings into an integer representation, producing token IDs. The course describes this conversion as an intermediate step that sits between the tokens and the embedding vectors that come after it. Each unique token is mapped to a single integer, so the token list becomes a list of numbers.
The diagram shows one possible assignment for the four tokens of Hello, world. Each token appears exactly once in the mapping, and it receives one integer. The ID values here were chosen for illustration; the lookup itself works by checking each token in the token list against the mapping and writing down its integer. The result is a list of IDs that stands in for the list of tokens.
Keep the two stages separate in your head. Tokenization decides where the boundaries fall. Token ID conversion decides how each resulting token is represented as a number. The ID step depends on the token list being right, so a boundary error in the first stage carries straight into the IDs. That is why you verify the token list before you trust any IDs built from it. The embedding vectors come after the IDs, so they are built from numbers that already reflect the tokenizer's choices.
Finding Where the Output Diverges
When the tokenizer's output looks wrong, the fastest way to find the problem is to check each intermediate result in order rather than rereading the whole pipeline. A quick visual check of the first thirty tokens reveals whether the boundaries fall in the right places. Look at the order of the items, whether any word is cut in half, and whether any punctuation mark is stuck to a word.
- 1Print the raw list returned by
re.splitand confirm that the punctuation and the double dash appear as separate items. - 2Print a slice of the first thirty tokens and check that every word is separate and no word is cut in half.
- 3Check that no punctuation mark is stuck to a neighbouring word, which would mean a boundary is missing from the pattern.
- 4Check the case of each token. The tokenizer does not lowercase anything, so a capital letter such as the one in HAD should still appear as written in the source.
- 5Only after the token list passes these checks, convert the tokens to IDs and inspect the first few IDs against the tokens they came from.
The first stage at which the actual output differs from your expected output is the place to look. If the punctuation is missing, the problem is in the capturing group or the character class. If a word has been split in two, a boundary pattern is matching too eagerly. If the count is too high, the filter may not be removing whitespace-only items. Each of these symptoms points to a single stage, which is why checking the pipeline in order is faster than guessing.
Mistakes Beginners Make with the Pipeline
Common mistake Counting the raw split output as the token count
What it looks likeThe split of Hello, world. produces seven items, not four.
Why it failsThe raw list still contains empty strings and whitespace-only strings, which are not tokens.
Do this insteadRun the whitespace-removal pass first, then count the remaining items.
Common mistake Dropping the parentheses from the pattern
What it looks likeWithout the capturing group, the commas and periods disappear from the output.
Why it failsWithout a capturing group, matched boundaries are used only as cut points and are not returned.
Do this insteadKeep the parentheses so that matched punctuation is returned as its own token.
Common mistake Lowercasing tokens to make them look cleaner
What it looks likeChanging HAD to had before checking the output.
Why it failsThe tokenizer does not lowercase anything, so case differences are part of the tokens as produced.
Do this insteadCompare tokens against the source as written, and make any case change in a separate, deliberate step.
Common mistake Trusting the token IDs without checking the tokens
What it looks likeReading the integer IDs for a list of words that was already cut in the wrong places.
Why it failsThe ID conversion works on whatever token list it receives, so a boundary error is passed on unchanged.
Do this insteadVerify the token list with a visual check of the first thirty tokens before building IDs.
Practice: Predict the Split Before Running It
What do you think happens?
Given the text Yes; no., which tokens remain after the split and the whitespace-removal pass?
Reveal answer
['Yes', ';', 'no', '.']
The semicolon and the period are matched by the punctuation class, and the space is matched by the whitespace class. The split leaves Yes, semicolon, an empty string, space, no, period, and an empty string. The empty strings and the space are removed, which leaves four tokens.
Choose a short sentence of your own that contains a comma, a double dash, and a question mark. Trace the split one stage at a time on paper, writing down the list after the split and the list after the filter. Then run the tokenizer and compare the two results. Note the first stage where your prediction and the real output differ, if they do.
Hints
- Mark every boundary character before you write the list.
- Keep the empty strings in your first list, since they only disappear in the filter stage.
Key Takeaways
- A regular-expression tokenizer uses one
re.splitcall to cut text at punctuation, the double dash, and whitespace. - A capturing group keeps the matched punctuation in the output, while the whitespace-removal pass drops empty and whitespace-only items before counting.
- The story's 4690 tokens are the meaningful items left after filtering, not the raw split output.
- Token ID conversion maps each token to an integer as an intermediate step before embedding vectors, so the token list must be verified first.
- When output looks wrong, check the first thirty tokens and then each stage in order to find the first point of divergence.