# Turning characters into numbers Part 3 of the tutorial "tiny-gpt". Canonical: https://learn.welldun.ai/tiny-gpt/03-into-numbers/ A neural network cannot read a letter. Two small lookup tables turn text into numbers and back, and getting them wrong corrupts everything downstream quietly. --- The bigram never needed numbers. Its model was a table indexed by characters, and a Python lookup table will happily take `'t'` as a key and hand you back what followed it. Nothing after this part can do that. So before any of the promised context can be added, the text has to stop being text. ## Why does anything need to be a number? Because of what a neural network physically is. Underneath the diagrams it is a long chain of multiplications and additions, run over lists of numbers. There is no step in it that could take `'t'` and do something with it — not because nobody has written that step, but because multiplying by a letter is not an operation. This is the same wall the [other tutorial hit with handwriting](/neural-networks/03-into-numbers/), where drawings had to become lists of ink values first. Text has it worse in one way: a drawing is *already* numbers, and only needs rearranging. A letter is not a number at all, so somebody has to decide what number it will be. The thing that decides is called a **tokenizer**, and ours is about as simple as one can be: it gives every distinct character its own number. ## Two tables that must agree First, list every distinct character in the text and put the list in order. That list is the **vocabulary** — the complete set of symbols the model will ever be able to read or produce. Then build two lookup tables from it: one from character to number, one from number back to character. That is the entire tokenizer. Type into the box and watch a string make the round trip. The two tables are mirrors, and the only property that matters is that they stay mirrors. Encode a string, decode the result, and you must get exactly what you started with. Write that check the same day you write the pair: ```python decode(encode(text)) == text ``` It costs one line and it catches the mistake everyone makes here, which is `decode` returning `['c', 'a', 't']` instead of `"cat"` — a missing `"".join` that looks fine in a print statement and is wrong. ## The numbers are labels, not quantities In the vocabulary above, `t` gets 9 and `a` gets 1 — and that says nothing about `t` or `a`. `t` is not nine times anything, not larger, not more important. The numbers are seat numbers: they identify, nothing more. Sorting the characters gave us a *repeatable* set of seat numbers, not a meaningful one. Had we ordered them some other way, every number would change and nothing about the model would. The tokenizer assigns identity, not meaning. Where meaning comes from is a later problem, and it has a name. That problem is real, and you can see it from here: a network doing arithmetic on these numbers will happily conclude that `t` and `s` are close, because 9 and 8 are close, when nothing about the language says so. Fixing that is what embeddings do, a few parts from here. For now the numbers only have to be consistent. ## What happens to a character it has never seen? The vocabulary above was built from one short sentence, so most of the alphabet is missing from it. Type a `z` into the box and encoding stops with a `KeyError` — Python's way of saying the key you asked for is not in the table. That is the correct behaviour. The obvious alternative is worse. A tokenizer could quietly map anything unrecognised to a single spare number. Nothing would crash. The model would train on that number as though it were a real character, learn something confident and false about it, and you would find out weeks later, if ever. A loud crash beats silently corrupted data. The cost is a rule to remember: the model can only ever be given text made of characters it was trained on. Anything you prompt it with later has to live inside this vocabulary. ## Why the order has to be fixed Sorting looks like tidiness. It is not. The numbers a model learns from are meaningless except against the table that produced them. Train a model with one ordering, then rebuild the vocabulary a different way and decode with the new table, and every number now points at the wrong character. The model has not changed and is not broken — it is being read with the wrong key. The last cell below does exactly this on purpose. Encoding `cat sat` with a reversed ordering and decoding it with the original gives: ``` 'os tas ' ``` Not an error, not a warning. Just quietly the wrong text. Sorting is what makes the vocabulary something anyone can rebuild identically, on any machine, in any run. ## How big does a vocabulary get? Ten, for the sentence above. For real text, count what is actually available: twenty-six letters, twenty-six more if you keep capitals, ten digits, a space, a newline, and a dozen or so marks of punctuation. That lands under a hundred — and then it stops, however much more text you add, because English has no further characters to find. That ceiling is the whole argument for working one character at a time. Compare it with splitting text into words, where the vocabulary never stops growing: every name, every typo, every coined word is a new entry, and anything you failed to see during training arrives later as a word you have no number for. Real models split the difference, into the **tokens** [described earlier](/tiny-gpt/01-the-problem/) — chunks of roughly three or four characters, chosen so common words stay whole and rare ones break into pieces. That keeps the vocabulary finite, at tens of thousands rather than under a hundred, and the sequences shorter. The mechanism in this part is unchanged: build a vocabulary, map both ways, never lose the table. The price of characters is length. `hello` is one word but five characters, so every sentence the model reads is several times longer, and it has to hold more of it in view to see the same amount of language. That bill comes due next. ## Build it yourself Five cells: the vocabulary, the two tables, encode and decode, the crash, and a demonstration of what a wrong ordering costs. No libraries at all — the whole tokenizer is two dictionaries.