Revisionsmodul, Embeddings und Container

Drei Schritte, die zusammen Phase 2 lauffaehig machen.

Revisionen (migrations/p2-004, revisionen.py)
    Ingest schreibt jede geaenderte Fassung eines Items nach
    raw_item_versionen fort. Daraus faellt ein Befund ab, den die
    Spezifikation nicht vorsehen konnte: welches Haus seine Schlagzeile
    nachtraeglich umschreibt, und wie. 206 von 3407 Items, acht
    Einstufungen von 'formal' bis 'neufassung'. Kein Modell, kein Lexikon,
    kein Netzzugriff - nur ein Wortvergleich zweier Fassungen.

    Fortschreibungen sind ueber ticker_markiert und kopf_stabil abziehbar,
    absichtlich als zwei Merkmale statt einer Kategorie: das eine ist eine
    Selbstauskunft des Hauses, das andere eine Beobachtung am Satzbau.

Embeddings (embeddings.py)
    multilingual-e5-large, 1024 Dimensionen, MIT. Jede Zeile wird auf feste
    128 Token aufgefuellt, nicht nur bis zur laengsten des Stapels - sonst
    haengt ein Vektor davon ab, mit welchen Nachbarn er kodiert wurde, und
    ein Slot-Lauf liefert andere letzte Stellen als ein Backfill.
    Nachgewiesen: 64/64 bitgleich einzeln wie im vollen Stapel, groesste
    Abweichung 0.00e+00, und im Betrieb 26 Slot-Vektoren unveraendert
    gegen den 3407er-Backfill.

    Darum transformers statt sentence-transformers: letzteres kapselt genau
    diese Stelle weg.

Container (Containerfile, phase2.py, deploy/)
    Debian trixie wie der Postgres-Container, torch aus dem CPU-Index.
    Die regelbasierten Module liefern darin fuer alle 3407 Items bitgleich
    dieselbe Nutzlast wie auf der Entwicklungsmaschine - Portabilitaet
    gemessen, nicht behauptet.

version.py bekommt den Schluessel 'revision'. Weil der Hash ueber den ganzen
Komponentensatz laeuft, aendert das auch die Version des Dublettenmoduls,
obwohl an dessen Verfahren nichts anders ist. Das ist die in version.py
beschriebene Semantik der Spezifikation, kein Versehen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NupWEBPzsyohVPG47HsTz8
This commit is contained in:
irrlicht
2026-09-08 07:41:27 +02:00
co-authored by Claude Opus 5
parent 50d497925f
commit cf89e012b2
16 changed files with 1980 additions and 7 deletions
+140
View File
@@ -154,3 +154,143 @@ Hamming-Grenze 330; 177 werden bestätigt. Nur die Jaccard-Bestätigung verwirft
nichts mehr — sie bleibt als Präzisionszusage, weil SimHash kollidieren kann.
Der Kettenschnitt trifft genau eine von 135 Komponenten und bleibt aus
demselben Grund.
# Befunde aus dem Revisionsmodul
Bestand vom 4.–7.9.2026, 3407 Items, 3974 Fassungen.
## Wieviel überhaupt geändert wird
| | Items |
|---|---|
| nie geändert | 3084 |
| mindestens eine neue Fassung | 323 |
| davon mit geändertem **Titel** | 206 (357 Übergänge) |
| davon nur Teaser geändert | 111 |
| URL geändert | 78 Übergänge |
Rund 6 Prozent aller Schlagzeilen werden nach der Veröffentlichung noch
angefasst. Das ist häufiger, als man erwartet, und es verteilt sich über alle
zwölf Häuser.
## Art der Änderung (erste gegen letzte Fassung)
| Einstufung | gesamt | ohne Fortschreibung |
|---|---|---|
| umformulierung | 83 | 77 |
| neufassung | 56 | 22 |
| kopfwechsel | 26 | 26 |
| erweiterung | 14 | 13 |
| kuerzung | 11 | 11 |
| formal | 9 | 9 |
| tippfehler | 6 | 6 |
| zurueckgenommen | 1 | 0 |
»Ohne Fortschreibung« zieht die 10 selbstbenannten Ticker und die 33 Items mit
stabiler Rubrik und ausgetauschtem Stand ab. Die Spalte zeigt, wozu das nötig
ist: bei `neufassung` bleiben von 56 nur 22 übrig — zwei Drittel der scheinbar
komplett neu geschriebenen Schlagzeilen sind Liveticker, die von einem Stand
zum nächsten weiterzählen.
## Umgeschriebene Schlagzeilen je Haus (ohne Fortschreibung)
spiegel 39, zeit 26, derstandard 18, faz 17, ntv 13, tagesschau 10, welt 10,
heise 9, taz 9, handelsblatt 8, dlf 3, sz 2.
Vorsicht bei der Deutung: die Zahlen hängen an der Feedgröße (zeit 550 Items,
sz 73) und an der Frage, ob ein Haus überhaupt denselben Item-Key beibehält,
wenn es die Überschrift ändert. Wer die URL mitwechselt, erscheint bei Ingest
als neues Item und taucht hier gar nicht auf. Als Rangliste taugt das nicht,
als Nachweis, dass es geschieht, sehr wohl.
## Warum der Pluskasten hier stehen bleiben muss
`normalisierung.normalisiere()` entfernt für das Dublettenmodul »++ … ++«, weil
verschiedene Häuser denselben Agenturtext verschieden einrahmen. Die tagesschau
führt ihren Liveticker aber vollständig darin:
++ Liveticker zur Sachsen-Anhalt-Wahl: Wahllokale haben geöffnet ++
Nach der Dublettennormalisierung bleibt davon der leere String. 31 der 357
Übergänge wären damit unvergleichbar gewesen. Deshalb `pluskasten=False`.
## Was das Verfahren nicht kann
* **Unmarkierte Fortschreibungen.** Die Welt schrieb »54,4 Prozent am
Nachmittag …« zu »Schon 65,3 Prozent …« um — ein Wahlabend-Dauerartikel ohne
jedes Tickerwort und ohne feste Rubrik. Er zählt als `neufassung`. `fassungen`
und `zahlen_geaendert` stehen in der Nutzlast, damit sich das nachträglich
eingrenzen lässt; automatisch erkannt wird es nicht.
* **Getrennt- und Zusammenschreibung.** »Drohnenattacken:« → »Drohnen-Attacken:«
gilt als `kopfwechsel`, nicht als `formal`, weil die Normalisierung
Satzzeichen entfernt und daraus zwei Wörter werden. Ein Fall im Bestand.
* **Das Gewicht einer Änderung.** `kopfwechsel` sagt, *wo* geändert wurde, nicht
wie schwer es wiegt. Für die Unterscheidung sind `hinzu` und `entfernt` da.
## Nebenwirkung auf die coder_version
`version.KOMPONENTEN` bekam den Schlüssel `revision`. Weil der Hash über den
ganzen Komponentensatz läuft, ändert sich damit auch die Version des
Dublettenmoduls (`10cea100` → `51e03f9b`), obwohl an dessen Verfahren nichts
anders ist. Das ist die in `version.py` beschriebene Semantik der
Spezifikation, kein Versehen. Der Backfill kostet 0,3 Sekunden; die alte
Version wurde verworfen.
# Befunde aus dem Embeddingmodul
## Die Stapelzusammensetzung ist das eigentliche Problem
Nicht die CPU, nicht die Bibliothek, nicht der Container: dass ein Encoder
stapelweise rechnet und die Matrizenform mit dem Stapel wechselt. Wer nur bis
zur längsten Zeile des Stapels auffüllt, bekommt für dieselbe Zeile
verschiedene letzte Stellen, je nachdem, wer mit ihr im Stapel lag.
Mit fester Länge (128 Token für jede Zeile) verschwindet der Effekt
vollständig — nicht »klein«, sondern exakt null:
| Vergleich | bitgleich | größte Abweichung |
|---|---|---|
| voller Stapel gegen einzeln | 64/64 | 0.00e+00 |
| voller Stapel gegen rückwärts | 64/64 | 0.00e+00 |
Der Preis: jede Zeile kostet 128 Token Rechenzeit, auch wenn sie 62 braucht.
Bei Median 62 ist das gut Faktor zwei. Für die Zusage aus Invariante 2 ist er
es wert — und bei 7 s je Slot fällt er nicht auf.
## Tokenlängen im Bestand
Median 62, p95 90, p99 107, Maximum 233. Über 128 liegen 3 von 3407 Items:
zweimal »tagesschau 20:00 Uhr« (Sendungsablauf mit vollständiger Themenliste)
und einmal eine DLF-Hochrechnung. 256 statt 128 würde die Rechenzeit
verdoppeln, um 0,09 Prozent der Items vollständig zu erfassen — nicht
lohnend.
## Was die Vektoren finden, was SimHash nicht findet
Hausübergreifende Paare über 0.94 Kosinus, die das Dublettenmodul nicht
zusammengeführt hat — dieselbe Geschichte, anderer Wortlaut:
0.994 faz / handelsblatt Wasserdefizit in Deutschland
0.992 spiegel / handelsblatt Drohne trifft Geheimdienstzentrale in Kiew
0.992 zeit / faz Michael Mendl gestorben ("Schauspieler" / "Charakterdarsteller")
0.991 tagesschau / welt Trump-Gesandte reisen nach Moskau und Kiew
0.985 heise / handelsblatt Isar-Aerospace-Rakete erfolgreich gestartet
Das ist der Beleg dafür, dass Modul 1 und Modul 5 verschiedene Fragen
beantworten und keins das andere ersetzt.
## Container
Die regelbasierten Module liefern im Container (Debian trixie, Python 3.13)
für alle 3407 Items bitgleich dieselbe Nutzlast wie auf der
Entwicklungsmaschine (Fedora, Python 3.14). Damit ist die Portabilität nicht
behauptet, sondern gemessen.
`sentence-transformers` fiel zugunsten von `transformers` weg: es kapselt das
Auffüllen der Stapel weg — also genau die Stelle, an der hier die
Reproduzierbarkeit entschieden wird — und zieht scipy und scikit-learn nach.
Ein Stolperstein beim Volume: der Container läuft als UID 10001, ein frisch
angelegtes Podman-Volume gehört root im User-Namespace. Ohne `:U` am
Volume-Argument scheitert der erste Schreibzugriff auf den Modell-Cache.
Dieselbe Klasse Problem wie bei pgAdmin, nur mit anderem Symptom.