Zum Inhalt
wannacode

Dokumente in Abschnitte teilen (Chunking)

Kurz gesagt: Chunking teilt ein Dokument nach Wörtern oder nach Sätzen in Abschnitte, mit einer Überlappung, damit jeder Abschnitt seinen eigenen Vektor bekommt und die Antwort nicht in der Mitte zerschnitten wird.

Problem

Die ganze Seite in einem Vektor

Der Shop hat Rückgaberegeln. Es ist eine Seite mit sechs Sätzen:

# die Rückgaberegeln: sechs Sätze
POLICY = [
"die ware kannst du 14 tage nach der lieferung zurückgeben",
"die ware muss dabei in der originalverpackung sein",
"ein kurier holt sie kostenlos ab",
"das geld ist in fünf tagen wieder auf deiner karte",
"ware mit eigenem aufdruck ist von der rückgabe ausgeschlossen",
"bei schäden schreibe uns innerhalb von zwei tagen",
]
# die Frage des Kunden
QUESTION = "wann bekomme ich mein geld zurück"

Der Kunde fragt „wann bekomme ich mein geld zurück“. Die Antwort ist der vierte Satz. Wenn die ganze Seite als ein Text im Index liegt, ist sie ein Vektor:

print(f"Wörter: {len(words)}")
doc = " ".join(POLICY)
print(f"ganzes Dokument: {cosine(v, space.of_text(doc)):.3f}")
print(f"der Antwortsatz: {cosine(v, space.of_text(POLICY[3])):.3f}")
Ausgabe
Wörter: 51
ganzes Dokument: 0.744
der Antwortsatz: 0.762

Die ganze Seite hat 0,744, der Antwortsatz allein 0,762. Der Vektor der Seite ist eine Mischung aus sechs Themen: Frist, Verpackung, Kurier, Geld, Ausnahmen, Schäden. Je länger die Seite, desto mehr verwischt sie. In einer Wissensbasis mit vielen Dokumenten verliert so eine Seite gegen kurze Texte, die die Frage gar nicht beantworten.

Und wenn sie doch gefunden wird, gehen wegen 10 Wörtern alle 51 in den Prompt. Echte Richtlinien haben Tausende Wörter, und drei solche Seiten füllen das Kontextfenster und die Rechnung.

Die Idee

Abschnitte

Das Dokument wird in Abschnitte (Chunks) geschnitten, bevor es in den Index kommt. Jeder Abschnitt ist ein eigener Eintrag: sein eigener Text, sein eigener Vektor und von welchem Dokument er stammt. Die Suche aus der vorigen Lektion findet den Abschnitt, nicht die ganze Seite.

Der einfachste Schnitt geht nach Anzahl der Wörter: die ersten 10 Wörter, die nächsten 10 und so weiter bis zum Ende. Für die Vektoren in der Lektion nehmen wir dasselbe Modell wie in der Lektion über Vektoren, mit zwei Unterschieden: Es lernt auch aus dem Text der Richtlinie (sonst sind ihre Wörter unbekannt), und es hat 16 Achsen statt 8. Alle Abschnitte handeln von Rückgaben, und mit 8 Achsen unterscheiden sie sich nur wenig.

space = Space(TEXT + POLICY, STOP, 16)
words = " ".join(POLICY).split(" ")
v = space.of_text(QUESTION)

Sieh es dir an

Abschnitte von 10 Wörtern

Die Funktion schneidet die Wörter in Abschnitte. Vorerst ist die Überlappung 0: Jeder Abschnitt beginnt dort, wo der vorige endet.

# Abschnitte von size Wörtern; die Schrittweite ist size - overlap
def chunk(words, size, overlap):
pieces = []
step = size - overlap
for start in range(0, len(words), step):
end = min(start + size, len(words))
pieces.append(" ".join(words[start:end]))
if end == len(words):
break
return pieces

Eine andere Funktion gibt jeden Abschnitt mit seiner Ähnlichkeit zur Frage aus und zeigt, welcher am nächsten ist:

# jeder Abschnitt mit seiner Ähnlichkeit; der nächste am Ende
def show(space, v, pieces):
best, top = 0, -1.0
for i, p in enumerate(pieces):
c = cosine(v, space.of_text(p))
print(f"{i + 1} {c:.3f} {p}")
if c > top:
best, top = i, c
print("→", best + 1)
show(space, v, chunk(words, 10, 0))
Ausgabe
1 0.589 die ware kannst du 14 tage nach der lieferung zurückgeben
2 0.512 die ware muss dabei in der originalverpackung sein ein kurier
3 0.793 holt sie kostenlos ab das geld ist in fünf tagen
4 0.645 wieder auf deiner karte ware mit eigenem aufdruck ist von
5 0.676 der rückgabe ausgeschlossen bei schäden schreibe uns innerhalb von zwei
6 0.434 tagen
→ 3

Am nächsten ist Abschnitt 3 (0,793). Er endet mit „das geld ist in fünf tagen“, und „wieder auf deiner karte“ steht in Abschnitt 4. Das Modell liest „das Geld ist in fünf Tagen“ und weiß nicht, wohin es zurückkommt. Der Schnitt fiel mitten in die Antwort.

Die Idee

Überlappung

Der Schnitt ist blind: Er weiß nicht, wo der Satz endet. Die Lösung ist eine Überlappung: Jeder Abschnitt beginnt etwas vor dem Ende des vorigen. Bei Abschnitten von 10 Wörtern und einer Überlappung von 4 beginnt jeder Abschnitt 6 Wörter nach dem vorigen, und seine letzten 4 Wörter sind die ersten 4 des nächsten.

So wiederholen sich die Ränder, und ein Satz, der in einem Abschnitt zerschnitten ist, ist im Nachbarabschnitt ganz. Der Preis sind mehr Abschnitte und wiederholte Wörter.

Sieh es dir an

Mit einer Überlappung von 4

show(space, v, chunk(words, 10, 4))
Ausgabe
1 0.589 die ware kannst du 14 tage nach der lieferung zurückgeben
2 0.586 nach der lieferung zurückgeben die ware muss dabei in der
3 0.403 muss dabei in der originalverpackung sein ein kurier holt sie
4 0.507 ein kurier holt sie kostenlos ab das geld ist in
5 0.762 das geld ist in fünf tagen wieder auf deiner karte
6 0.645 wieder auf deiner karte ware mit eigenem aufdruck ist von
7 0.626 eigenem aufdruck ist von der rückgabe ausgeschlossen bei schäden schreibe
8 0.702 ausgeschlossen bei schäden schreibe uns innerhalb von zwei tagen
→ 5

Jetzt ist Abschnitt 5 am nächsten (0,762), und er enthält den ganzen Satz: „das geld ist in fünf tagen wieder auf deiner karte“. Es sind 8 Abschnitte statt 6.

# Abschnitte von size Wörtern; die Schrittweite ist size - overlap
def chunk(words, size, overlap):
pieces = []
step = size - overlap
for start in range(0, len(words), step):
end = min(start + size, len(words))
pieces.append(" ".join(words[start:end]))
if end == len(words):
break
return pieces
v = space.of_text(QUESTION)
show(space, v, chunk(words, 10, 0))
Schritt für Schritt, in Worten
  1. Die Rückgaberegeln: 51 Wörter. Orange ist der Satz mit der Antwort: „das geld ist in fünf tagen wieder auf deiner karte“.
  2. Abschnitte von 10 Wörtern ohne Überlappung: 6 Abschnitte, einer nach dem anderen.
  3. Die Ähnlichkeit jedes Abschnitts zur Frage. Am nächsten ist Abschnitt 3 (0,793).
  4. Aber Abschnitt 3 endet mit „in fünf tagen“: „wieder auf deiner karte“ steht im nächsten Abschnitt. Die Antwort ist in der Mitte zerschnitten.
  5. Überlappung 4: Jeder Abschnitt beginnt 6 Wörter nach dem vorherigen, die Ränder wiederholen sich. 8 Abschnitte.
  6. Am nächsten ist Abschnitt 5 (0,762), und er enthält den ganzen Satz.
  7. Ohne Überlappung ist die Antwort auf zwei Abschnitte verteilt; mit Überlappung enthält ein Abschnitt sie ganz. Der Preis: 8 Abschnitte statt 6 und wiederholte Wörter.

Probier es aus

Sag die Ausgabe voraus

Ein Satz von 8 Wörtern in Abschnitten von 4 mit einer Überlappung von 1. Wie viele Abschnitte kommen heraus?

ws = "die lieferung in eine filiale kostet 3 euro".split(" ")
print(len(chunk(ws, 4, 1)))
Antwort

Probier es aus

Ergänze

Wie viele Wörter später beginnt jeder nächste Abschnitt? Was gehört an die Stelle von ___?

# Abschnitte von size Wörtern; die Schrittweite ist size - overlap
def chunk(words, size, overlap):
    pieces = []
    step = size - 
    for start in range(0, len(words), step):
        end = min(start + size, len(words))
        pieces.append(" ".join(words[start:end]))
        if end == len(words):
            break
    return pieces

Probier es aus

Finde den Fehler

Ein Kollege schneidet mit einer Überlappung von 4, aber ein Teil der Richtlinie steckt in keinem Abschnitt. In welcher Zeile steckt der Fehler?

Wähle die Zeile mit dem Fehler.

Die Idee

Nach Sätzen

Eine Anzahl von Wörtern ist grob. Dokumente haben natürliche Grenzen: Sätze, Absätze, Überschriften. Wenn ein Abschnitt ein ganzer Satz ist, wird die Antwort nicht zerschnitten:

show(space, v, POLICY)
Ausgabe
1 0.589 die ware kannst du 14 tage nach der lieferung zurückgeben
2 0.582 die ware muss dabei in der originalverpackung sein
3 0.163 ein kurier holt sie kostenlos ab
4 0.762 das geld ist in fünf tagen wieder auf deiner karte
5 0.586 ware mit eigenem aufdruck ist von der rückgabe ausgeschlossen
6 0.620 bei schäden schreibe uns innerhalb von zwei tagen
→ 4

Der Antwortsatz gewinnt (0,762). Aber sieh dir Satz 3 an: „ein kurier holt sie kostenlos ab“, 0,163. „sie“ ist die Ware aus dem Satz davor. Für sich allein sagt der Abschnitt weder, was der Kurier abholt, noch, dass es um Rückgaben geht. Fragt der Kunde „kommt ein kurier für die rückgabe“, hat dieser Abschnitt einen Teil seiner Bedeutung verloren.

Deshalb halten echte Systeme die Verbindung: Sie setzen den Titel des Dokuments und des Abschnitts vor jeden Abschnitt, fügen einen Satz von jedem Nachbarn hinzu oder schneiden nach Absätzen. Und sie behalten für jeden Abschnitt, woher er stammt: Dokument, Abschnitt, Stelle. So belegt die Antwort ihre Quelle.

Die Idee

Wann es wehtut

  • Zu kleine Abschnitte verlieren die Bedeutung: „innerhalb von 14 tagen“ ohne das Wort „rückgabe“ sagt nicht, wofür die 14 Tage gelten.
  • Zu große Abschnitte verwischen wie die ganze Seite und füllen den Prompt.
  • Die Überlappung bringt Wiederholungen. Die Abschnitte 5 und 6 haben dieselben Wörter. Wenn beide unter den ersten k sind, landet wiederholter Text im Prompt. Benachbarte Abschnitte eines Dokuments werden zusammengeführt, bevor sie zum Modell gehen.
  • Tabellen, Listen und Code schneidet man nicht nach Anzahl der Wörter. Eine zerschnittene Zeile einer Preistabelle ergibt keinen Sinn: Solche Dokumente schneidet man nach ihrer Struktur.
  • Änderst du den Schnitt, änderst du den Index. Alle Abschnitte werden neu geschnitten und neu berechnet.

Es gibt keine Größe, die für alle Dokumente richtig ist. Größe und Überlappung wählt man mit einem Testset von Fragen, wie k in der vorigen Lektion: siehe Bewertung.

Genauer, für Neugierige

Die Größe in echten Systemen. Abschnitte misst man in Tokens, nicht in Wörtern: oft 200 bis 800 Tokens mit einer Überlappung von 10 bis 20 % (Beispielzahlen). Wie viele Tokens ein Text hat, steht in der Lektion über Tokens. Hier sind die Abschnitte 10 Wörter lang und die Sätze fast genauso lang, deshalb ist die Überlappung groß.

Wann die Überlappung die Antwort ganz hält. Ein Satz von L Wörtern ist in irgendeinem Abschnitt ganz, wenn L ≤ overlap + 1. Bei einer Überlappung von 4 sind das Sätze bis 5 Wörter. Ein längerer Satz ist nur dann ganz, wenn ein Schnitt genau vor ihm fällt: Die Übung zeigt, dass das davon abhängt, wo die Schnitte fallen.

Schneiden nach Bedeutung. Manche Systeme schneiden dort, wo sich die Vektoren benachbarter Sätze voneinander entfernen: Ein Themenwechsel ist eine Grenze. Das kostet pro Satz einen Aufruf des Modells für Vektoren.

In Python. words[start:start + size] schlägt nicht fehl, wenn das Ende hinter dem letzten Wort liegt: Es gibt einfach weniger zurück. range(0, len(words), step) liefert die Anfänge aller Abschnitte auf einmal.

Übung

Eine Prüfung für den Schnitt

Löse es in deinem Editor.

Schreibe eine Prüfung: Finde für Abschnitte von 10 Wörtern und eine Überlappung von 0 bis 4 den Abschnitt, der der Frage am nächsten ist, und gib aus, wie viele Abschnitte es sind, die Ähnlichkeit und ob der Abschnitt den ganzen Antwortsatz enthält.

Ausgabe
Überlappung 0: 6 Abschnitte, 0.793, Antwort ganz: nein
Überlappung 1: 6 Abschnitte, 0.701, Antwort ganz: nein
Überlappung 2: 7 Abschnitte, 0.762, Antwort ganz: ja
Überlappung 3: 7 Abschnitte, 0.849, Antwort ganz: nein
Überlappung 4: 8 Abschnitte, 0.762, Antwort ganz: ja

Die Überlappungen 2 und 4 halten die Antwort ganz, 0, 1 und 3 nicht. Bei Überlappung 3 ist die Ähnlichkeit am höchsten (0,849), aber der Abschnitt endet mit „fünf tagen wieder“, und „auf deiner karte“ ist abgeschnitten. Eine höhere Ähnlichkeit bedeutet keine ganze Antwort. Ob die Antwort ganz ist, hängt davon ab, wo die Schnitte fallen, deshalb läuft so eine Prüfung bei jeder Änderung des Schnitts.

Weitere Aufgaben

  • Sätze bis 16 Wörter. Füge ganze Sätze zu einem Abschnitt zusammen, bis er über 16 Wörter geht. Welcher Abschnitt ist am nächsten?
  • Ohne Wiederholungen. Wenn sich zwei der ersten k Abschnitte überlappen, führe sie zu einem zusammen.

Test

Teste dich

1. Warum kommt ein langes Dokument nicht als ein einziger Vektor in den Index?
2. Abschnitte von 10 Wörtern mit einer Überlappung von 4. Wie viele Wörter liegen zwischen ihren Anfängen?
3. Was geht verloren, wenn jeder Satz ein eigener Abschnitt ist?
4. Die Überlappung ist 4, und die nächsten k = 3 Abschnitte sind 5, 8 und 6. Was ist das Problem?

Erstellt von Ivo Gergov im Rahmen der Kampagne von Evo Tech für zugängliche Programmierausbildung ·