A language model does not read sentences: it handles numbers. The first step of any NLP project is therefore to turn text into a sequence of token identifiers drawn from a fixed vocabulary.
Normalizing text
Normalization makes different spellings of the same word comparable:
import re
import unicodedata
def normaliser(texte):
texte = texte.lower() # case
texte = unicodedata.normalize("NFD", texte) # separates letters and accents
texte = "".join(c for c in texte if unicodedata.category(c) != "Mn")
texte = re.sub(r"\s+", " ", texte).strip() # multiple spaces
return texte
print(normaliser(" L'Été est LÀ ! ")) # "l'ete est la !"
Every choice has a cost: removing accents confuses « pêche » (peach) and « pèche » (sins), lowercasing erases the difference between « Apple » and « apple ». For a TF-IDF classifier, strong normalization often helps; for a pre-trained Transformer model, you barely normalize: its tokenizer was designed for raw text. Essential rule: the same preprocessing must be applied in training and in production.
Tokenizing into words
The simplest approach splits on alphanumeric characters:
def tokeniser(texte):
return re.findall(r"\w+", normaliser(texte))
print(tokeniser("Le colis n'est pas arrivé."))
# ['le', 'colis', 'n', 'est', 'pas', 'arrive']
Libraries such as spaCy handle elisions, numbers or dates finely, and add lemmatization (« arrivées » → « arriver »). The drawback of word tokens: the vocabulary explodes and any word absent from training (typo, proper noun, neologism) becomes unknown.
Subwords and BPE
Modern models split into subwords: frequent words stay whole, rare words are broken down (« anticonstitutionnellement » → « anti », « constitution », « nellement »). There is no unknown word anymore.
The BPE (Byte Pair Encoding) algorithm learns this vocabulary:
- start with each word split into characters (or bytes, to cover all of Unicode);
- count the adjacent pairs of symbols in the corpus, weighted by word frequency;
- merge the most frequent pair into a new symbol;
- repeat until the desired vocabulary size (often 32,000 to 200,000 tokens).
from collections import Counter
corpus = {("b", "a", "s"): 5, ("b", "a", "l"): 3, ("v", "a", "s"): 2}
paires = Counter()
for mot, freq in corpus.items():
for a, b in zip(mot, mot[1:]):
paires[(a, b)] += freq
print(paires.most_common(1)) # [(('a', 's'), 7)] -> we merge "a" + "s"
WordPiece (BERT) and Unigram (SentencePiece) follow the same idea with other merge criteria.
Model tokenizers
With Hugging Face, each model provides its tokenizer:
from transformers import AutoTokenizer
tok = AutoTokenizer.from_pretrained("camembert-base")
enc = tok("Le colis n'est pas arrivé.")
print(enc["input_ids"]) # identifiers
print(tok.convert_ids_to_tokens(enc["input_ids"])) # word pieces
The number of tokens matters: it determines a model’s context limit and the cost of LLM APIs, which are billed per token. French generally produces slightly more tokens than English for the same content.
Key takeaways
Consistent normalization, tokenization suited to the model, and awareness of the number of tokens: these foundations condition everything else, from TF-IDF to LLMs.
Exercises
Solutions and tests stay on the server. In the student space, your code is run and checked, and the tutor gives hints before the solution.
Normalize and tokenize
Write
normaliser(texte)(lowercase, accent removal viaunicodedata, multiple spaces collapsed and edges stripped) andtokeniser(texte)that returns the list of words (re.findall(r"\w+", ...)) of the normalized text.import re import unicodedata def normaliser(texte): # TODO pass def tokeniser(texte): # TODO passOne BPE step
The corpus is a dictionary
{tuple_of_symbols: frequency}. Writecompter_paires(corpus)that returns aCounterof adjacent pairs weighted by frequency, andfusionner(paire, corpus)that returns a new corpus where each adjacent occurrence of the pair is replaced by the concatenation of the two symbols.from collections import Counter def compter_paires(corpus): # TODO pass def fusionner(paire, corpus): # TODO passWhy subwords?
What is the main advantage of subword tokenization (BPE) over word tokenization?
- It removes punctuation
- It avoids unknown words by breaking rare words into known pieces, with a vocabulary of controlled size
- It always produces fewer tokens
- It automatically translates the text
End-of-lesson quiz
Why avoid removing accents before a pre-trained Transformer model?
- Because it is too slow
- Because its tokenizer and weights were learned on raw text: modifying the text degrades the representations
- Because accents are special tokens
- Because it increases the cost
In BPE, which pair is merged at each step?
- The first pair in the corpus
- The most frequent adjacent pair
- The rarest pair
- A randomly drawn pair
Why count the tokens of a prompt before calling an LLM?
- To correct spelling
- Because cost and context limit are expressed in tokens
- Because the model only reads the first 100 words
- To choose the temperature
The rest of the course
- Bag of words, TF-IDF and text classification18 min
- Embeddings and semantic search18 min
- Transformers, attention and large language models20 min
- RAG: retrieval-augmented generation20 min
- Adapting a model: lightweight fine-tuning (LoRA) and evaluation20 min