// visualizations.jsx // Interactive sections for the article — original implementations. const { useState, useMemo, useEffect, useRef } = React; /* ------------------------------------------------------------------ TokenizerDemo — fake BPE-ish tokenizer for visualization. Splits on whitespace + common French sub-units. Not a real tokenizer. ------------------------------------------------------------------ */ function fakeTokenId(tok) { let h = 0; for (let i = 0; i < tok.length; i++) h = h * 31 + tok.charCodeAt(i) >>> 0; return 100 + h % 49900; } function tokenizeFr(text) { if (!text) return []; const pieces = text.match(/[\p{L}\p{M}]+|[0-9]+|[^\s\p{L}\p{M}0-9]+|\s+/gu) || []; const out = []; for (const p of pieces) { if (/^\s+$/.test(p)) {out.push({ s: p, kind: 'space' });continue;} if (/[^\p{L}\p{M}0-9]/u.test(p) && p.length <= 3) { out.push({ s: p, kind: 'punct' });continue; } if (p.length <= 5) { out.push({ s: p, kind: 'word' }); } else { const suffixes = ['ement', 'tions', 'tion', 'ment', 'ance', 'ence', 'eur', 'eux', 'ait', 'ais', 'ant', 'ées', 'ée', 'er', 'ez', 'es']; let rest = p;const parts = []; let matched = null; for (const suf of suffixes) { if (rest.toLowerCase().endsWith(suf) && rest.length - suf.length >= 3) { matched = suf;break; } } if (matched) { parts.push(rest.slice(0, rest.length - matched.length)); parts.push(rest.slice(rest.length - matched.length)); } else if (rest.length > 7) { parts.push(rest.slice(0, Math.ceil(rest.length / 2))); parts.push(rest.slice(Math.ceil(rest.length / 2))); } else { parts.push(rest); } parts.forEach((pp, i) => out.push({ s: pp, kind: i === 0 ? 'word' : 'subword' })); } } return out.map((t) => ({ ...t, id: fakeTokenId(t.s.toLowerCase()) })); } function TokenizerDemo() { const [text, setText] = useState("Recrutez votre premier Collaborateur IA."); const tokens = useMemo(() => tokenizeFr(text), [text]); const visible = tokens.filter((t) => t.kind !== 'space'); const charCount = text.length; return (
Découpez une phrase en jetons
Tapez du texte — chaque morceau coloré est un jeton, identifié par un numéro.
DÉMO · 01
setText(e.target.value)} placeholder="Tapez une phrase en français…" maxLength={140} />
{tokens.map((t, i) => t.kind === 'space' ?
Caractères
{charCount}
Jetons
{visible.length}
Ratio
{visible.length ? (charCount / visible.length).toFixed(1) : '—'}

Note. Démo pédagogique : un vrai tokenizer (BPE, SentencePiece) apprend ses fragments sur des milliards de mots et finit avec ~50 000 à 200 000 entrées dans son vocabulaire.

); } /* ------------------------------------------------------------------ EmbeddingMap — 2D projection of toy word vectors. Named axes, 3 modes : carte (nearest neighbors), distance, analogie. ------------------------------------------------------------------ */ const WORDS = [ // top-left : verbes RH (action × humain) { w: "recruter", x: 22, y: 22, cat: "verb-h" }, { w: "embaucher", x: 28, y: 30, cat: "verb-h" }, { w: "former", x: 16, y: 32, cat: "verb-h" }, { w: "encadrer", x: 30, y: 18, cat: "verb-h" }, // top-right : rôles humains (entité × humain) { w: "dirigeant", x: 78, y: 22, cat: "role" }, { w: "salarié", x: 72, y: 30, cat: "role" }, { w: "collaborateur", x: 86, y: 28, cat: "role" }, { w: "stagiaire", x: 70, y: 18, cat: "role" }, // bottom-left : verbes naturels (action × non-humain) { w: "chasser", x: 22, y: 70, cat: "verb-n" }, { w: "courir", x: 28, y: 80, cat: "verb-n" }, { w: "dormir", x: 14, y: 76, cat: "verb-n" }, { w: "voler", x: 32, y: 66, cat: "verb-n" }, // bottom-right : entités non-humaines (animaux + villes) { w: "chien", x: 62, y: 64, cat: "anim" }, { w: "chat", x: 68, y: 72, cat: "anim" }, { w: "lion", x: 60, y: 78, cat: "anim" }, { w: "Paris", x: 84, y: 66, cat: "city" }, { w: "Lyon", x: 90, y: 74, cat: "city" }, { w: "Nantes", x: 82, y: 82, cat: "city" }]; const CATS = { "verb-h": { label: "Verbes RH", dot: "var(--vermillon)" }, "role": { label: "Rôles humains", dot: "var(--ink)" }, "verb-n": { label: "Verbes naturels", dot: "var(--vermillon-deep)" }, "anim": { label: "Animaux", dot: "var(--ink-blue)" }, "city": { label: "Villes", dot: "var(--ink-60)" } }; const dist2D = (a, b) => Math.hypot(a.x - b.x, a.y - b.y); const simScore = (a, b) => Math.max(0, 1 - dist2D(a, b) / 90); const findWord = (w) => WORDS.find((x) => x.w === w); const ANALOGIES = [ { id: "synonymes", label: "Synonymes", note: "Les mots qui veulent dire la même chose se collent. Le modèle apprend cette proximité sans qu'on la lui décrive — juste en lisant.", pairs: [ { from: "recruter", to: "embaucher" }, { from: "chien", to: "chat" }, { from: "Paris", to: "Lyon" }] }, { id: "action-agent", label: "Action → agent", note: "Les vecteurs « action humaine → agent humain » et « action animale → agent animal » sont à peu près parallèles. La géométrie encode la relation — personne ne l'a écrite à la main.", pairs: [ { from: "recruter", to: "dirigeant" }, { from: "chasser", to: "lion" }] }, { id: "humain-nature", label: "Humain → nature", note: "Même direction pour « passer du monde du travail au monde naturel », que l'on parte d'un verbe ou d'un nom. Les axes ne sont jamais étiquetés explicitement — ils émergent.", pairs: [ { from: "recruter", to: "chasser" }, { from: "dirigeant", to: "lion" }] }]; function EmbeddingMap() { const [mode, setMode] = useState("carte"); const [hover, setHover] = useState(null); const [picked, setPicked] = useState([]); const [analogyId, setAnalogyId] = useState(ANALOGIES[0].id); const neighbors = useMemo(() => { if (mode !== "carte" || !hover) return []; const me = findWord(hover); if (!me) return []; return WORDS. filter((w) => w.w !== hover). map((w) => ({ w, sim: simScore(me, w), d: dist2D(me, w) })). sort((a, b) => a.d - b.d). slice(0, 3); }, [mode, hover]); const onPointClick = (word) => { if (mode !== "distance") return; setPicked((prev) => { if (prev.length === 0) return [word]; if (prev.length === 1) return prev[0] === word ? [] : [prev[0], word]; return [word]; }); }; const pickedSim = picked.length === 2 ? simScore(findWord(picked[0]), findWord(picked[1])) : null; const analogy = ANALOGIES.find((a) => a.id === analogyId); const lines = useMemo(() => { if (mode === "carte" && hover) { const me = findWord(hover); return neighbors.map((n, i) => ({ x1: me.x, y1: me.y, x2: n.w.x, y2: n.w.y, rank: i, sim: n.sim })); } if (mode === "distance" && picked.length === 2) { const a = findWord(picked[0]);const b = findWord(picked[1]); return [{ x1: a.x, y1: a.y, x2: b.x, y2: b.y, rank: 0, sim: pickedSim }]; } if (mode === "analogie" && analogy) { return analogy.pairs.map((p, i) => { const a = findWord(p.from);const b = findWord(p.to); return { x1: a.x, y1: a.y, x2: b.x, y2: b.y, rank: i, arrow: true }; }); } return []; }, [mode, hover, neighbors, picked, analogy, pickedSim]); const emphasized = useMemo(() => { const s = new Set(); if (mode === "carte" && hover) { s.add(hover); neighbors.forEach((n) => s.add(n.w.w)); } if (mode === "distance") picked.forEach((p) => s.add(p)); if (mode === "analogie" && analogy) { analogy.pairs.forEach((p) => {s.add(p.from);s.add(p.to);}); } return s; }, [mode, hover, neighbors, picked, analogy]); const hint = mode === "carte" ? "Survolez un mot — le modèle trace ses trois plus proches voisins. Mêmes catégories, sens proches." : mode === "distance" ? "Cliquez deux mots successivement. L'écart dans l'espace devient une mesure de proximité sémantique." : "Choisissez une relation. Les flèches montrent que les mêmes directions encodent les mêmes relations."; return (
Chaque jeton devient une coordonnée
{hint}
DÉMO · 02
{["carte", "distance", "analogie"].map((m) => )}