/** * Die Rechnungen hinter Labor und Führung — an einer Stelle. * * ── WARUM NICHT JE EINMAL DORT, WO SIE GEBRAUCHT WERDEN ───────────────────── * * Das Labor und die Führung zeigen dieselben drei Dinge in zwei Formen: einmal * zum Selbstbedienen, einmal als Vorführung beim Scrollen. Stünde die Softmax * zweimal im Quelltext, wären es zwei Aussagen, die beim ersten Ändern * auseinanderlaufen — und die Seite behauptete an zwei Stellen etwas * Verschiedenes über dieselbe Sache. * * Hier steht keine Anzeige und kein React. Reine Funktionen, die sich * nachrechnen lassen. */ /** * Das Muster, mit dem GPT-Modelle vorzerlegen — `cl100k_base`. * * Genau dieser Ausdruck steht in `tiktoken`. Er trennt Apostrophformen * („don't"), Wörter samt führendem Leerzeichen, Zahlen in Dreiergruppen, * Satzzeichen und Zeilenumbrüche. Erst **danach** fasst ein Modell die Stücke * nach gelernten Häufigkeiten zu Tokens zusammen — das fehlt hier, und deshalb * ist jede daraus gewonnene Zahl eine Untergrenze. * * Zwei Stellen sind gegenüber dem Original geändert, weil JavaScript sie nicht * kennt: die besitzergreifenden Quantoren `?+` und `++`. Ohne Rückverfolgung * verhalten sie sich auf diesen Zeichenklassen identisch — die Klassen * überschneiden sich nicht, es gibt nichts zurückzunehmen. * * Kein `tiktoken` als Abhängigkeit: Die Tabelle für `cl100k_base` ist 1,6 MB. * Für eine Vorführung, deren Aussage „Deutsch kostet mehr" lautet, ist das der * falsche Preis — die Aussage hängt an den Bytes, und die zählt der Browser. * * ── EIN EIGENER AUSDRUCK JE AUFRUF ────────────────────────────────────────── * * `zerlege` baut den regulären Ausdruck jedes Mal neu, statt einen mit `g` * behafteten wiederzuverwenden. Ein globaler Ausdruck trägt `lastIndex` mit * sich; zwei Aufrufe hintereinander auf denselben Text lieferten sonst beim * zweiten Mal weniger. `String.match` setzt ihn zwar zurück, aber die Falle * wartet auf den ersten, der hier `exec` benutzt. */ export function zerlege(text: string): string[] { const muster = /'(?:[sdmt]|ll|ve|re)|[^\r\n\p{L}\p{N}]?\p{L}+|\p{N}{1,3}| ?[^\s\p{L}\p{N}]+[\r\n]*|\s*[\r\n]|\s+(?!\S)|\s+/gu; return text.match(muster) ?? []; } /** Zeichen so gezählt, wie ein Mensch zählen würde — ein Emoji ist eines. */ export function zaehleZeichen(text: string): number { return [...text].length; } /** Bytes in UTF-8. Der Grund, warum ein Umlaut mehr kostet als ein „a". */ export function zaehleBytes(text: string): number { return new TextEncoder().encode(text).length; } /** * Skalierte Punktprodukt-Aufmerksamkeit. * * softmax(q · kᵀ / √d) * * Genau die aus „Attention Is All You Need", und genau die, die in jedem * heutigen Sprachmodell milliardenfach je Antwort läuft. * * ── DIE VERSCHIEBUNG VOR DEM EXPONENTIEREN ────────────────────────────────── * * `exp(x - max)` statt `exp(x)`. Das ist keine Feinheit: `exp` läuft bei * großen Werten über, und die Verschiebung ändert das Ergebnis nicht — sie * kürzt sich in Zähler und Nenner weg. Jede ernsthafte Umsetzung macht das, * und wer die Formel hier abschreibt, soll die richtige abschreiben. */ export function aufmerksamkeit( frage: readonly number[], schluessel: readonly (readonly number[])[], ): number[] { const wurzelD = Math.sqrt(frage.length); const roh = schluessel.map( (k) => k.reduce((summe, wert, i) => summe + wert * frage[i], 0) / wurzelD, ); const groesstes = Math.max(...roh); const exponenten = roh.map((wert) => Math.exp(wert - groesstes)); const summe = exponenten.reduce((a, b) => a + b, 0); return exponenten.map((wert) => wert / summe); } /** * Welches Wort zeigt am deutlichsten auf ein **anderes**? * * Nicht „welches hat die größte Streuung" — das ist das isolierteste Wort, und * das schaut auf sich selbst. Ein Wort, das sich selbst ansieht, ist richtig * gerechnet und lehrt nichts. Gesucht ist der Bezug, den man vorführen kann. */ export function deutlichsterBezug(vektoren: readonly (readonly number[])[]): number { const nachAussen = vektoren.map((frage, i) => Math.max(...aufmerksamkeit(frage, vektoren).filter((_, j) => j !== i)), ); return nachAussen.indexOf(Math.max(...nachAussen)); } /** * Die nächsten Nachbarn auf einer Karte — genau die Abfrage einer * Vektordatenbank, nur in zwei Dimensionen statt in Hunderten. */ export function naechsteNachbarn( anker: T, alle: readonly T[], anzahl: number, ): (T & { abstand: number })[] { return alle .filter((w) => w !== anker) .map((w) => ({ ...w, abstand: Math.hypot(w.x - anker.x, w.y - anker.y) })) .sort((a, b) => a.abstand - b.abstand) .slice(0, anzahl); }