Preserve Unicode and unknown characters
With a vocabulary in hand, encoding a text is a lookup: each character is replaced by its ID. The interesting part is what to do with a character that is not in the vocabulary, such as an emoji the training text never had. Dropping it would silently change the text. Instead, one ID is kept aside to mean unknown, and every unfamiliar character gets that ID.
Learn it
Words to know
- token ID
- The whole number that stands for a token.
- vocabulary
- The full list of tokens a model knows. A token's place in the list gives its ID.
- token
- One unit of text as the model sees it: a character, a piece of a word or a whole word.
How it works
- Build a dictionary from character to ID. The first character of
vocabularygets 1, the next 2, and so on. ID 0 is kept for unknown. - Go through the text one character at a time and look each one up.
- A character that is not in the dictionary becomes 0. Uppercase and lowercase are different characters, and spaces are looked up like anything else.
Worked example
Input text = "hi ho" vocabulary = [" ", "h", "i"] Output [2, 3, 1, 2, 0]
| Character | In the vocabulary? | ID |
|---|---|---|
| h | Yes, second | 2 |
| i | Yes, third | 3 |
| space | Yes, first | 1 |
| h | Yes, second | 2 |
| o | No | 0 |
The output has one ID per character, so the text keeps its length. The unknown o still holds its place, as a 0.
Why it matters
Every tokenizer needs a rule for text it has not seen. The workshop's GPT takes a stricter path and refuses a prompt with unfamiliar characters; either way the rule is fixed before training and kept afterwards.
Your turn
Encode a text as a list of token IDs, one per character.
text- any string; may be empty
vocabulary- a list of known characters, each once; the first has ID 1, the second ID 2, and so on
- Returns
- One ID per character of
text, in order. A character that is not invocabularygets ID 0.
Example 1
Input text = "hi ho" vocabulary = [" ", "h", "i"] Output [2, 3, 1, 2, 0]
h is 2, i is 3, the space is 1, and o is not in the vocabulary, so it becomes 0.
Example 2
Input text = "a🙂 a" vocabulary = ["a", " "] Output [1, 0, 2, 1]
The emoji is one character that the vocabulary does not have. It becomes a single 0.
Example 3
Input text = "A" vocabulary = ["a"] Output [0]
Capital A is not the same character as a, so it is unknown.
Example 4
Input text = "" vocabulary = [] Output []
An empty text encodes to an empty list.
Hints
Each hint gives away a little more. Open one only when you need it.
Hint 1: Where to start
Looking a character up in a list means scanning the list every time. Build a dictionary once and look up there.
Hint 2: The key step
enumerate(vocabulary, start=1)gives each character together with its ID, counting from 1.Hint 3: The outline
Build
ids = {character: i for i, character in enumerate(vocabulary, start=1)}. Return[ids.get(character, 0) for character in text].
Solution
From here on the page gives the method away. If you are still solving, go back to the hints.
Step by step
- Number the known characters from 1; 0 is kept for unknown. Starting at 1 leaves ID 0 free. If a real character had ID 0, the model could not tell it from an unknown one.
- Look up each character; an unfamiliar one becomes 0.
getwith a default never raises an error. A Python string is read one Unicode character at a time. This emoji is one such character; some emoji, such as flags, are several.
The same numbered steps are the comments in the solution.
Python
def solve(text, vocabulary):
# 1. Number the known characters from 1; 0 is kept for unknown
ids = {}
for i, character in enumerate(vocabulary, start=1):
ids[character] = i
# 2. Look up each character; an unfamiliar one becomes 0
return [ids.get(character, 0) for character in text]Watch out
enumeratecounts from 0 unless you passstart=1. Counting from 0 would give the first known character the unknown ID.- Do not lowercase or strip the text. The model was trained on the text as it is, and the output should be the same length as the input.
Check yourself
What happens to a character the vocabulary does not contain?
It is encoded as the reserved unknown ID, 0. It keeps its place in the sequence.
Why not drop unknown characters?
Dropping them changes the text without telling anyone, and the positions of everything after would shift.