01.Kontexte
Bei der Entwicklung von RAG-Anwendungen ist der erste Schritt das Chunking des Dokuments. Ein effizientes Chunking des Dokuments kann die Genauigkeit des nachfolgenden Recall-Inhalts effektiv verbessern. Wie man effizient Chunking ist ein heißes Thema der Diskussion, gibt es wie feste Größe Chunking, zufällige Größe Chunking, Sliding-Window-Resampling, rekursive Chunking, basierend auf dem Inhalt der semantischen Chunking und andere Methoden. Das von Jina AI vorgeschlagene Late Chunking befasst sich mit dem Chunking-Problem aus einer anderen Perspektive, schauen wir es uns an.
02.Was ist Late Chunking?
Beim herkömmlichen Chunking können bei langen Dokumenten über weite Entfernungen hinweg kontextuelle Abhängigkeiten in den Dokumenten verloren gehen, was eine große Gefahr für das Abrufen und Verstehen von Informationen darstellt. Das heißt, wenn die Schlüsselinformationen in mehreren Textblöcken verstreut sind, verlieren die aus dem Kontext gerissenen Textfragmente wahrscheinlich ihre ursprüngliche Bedeutung, was zu einem schlechteren späteren Abruf führt.
Nehmen wir als Beispiel die Milvus 2.4.13 Release Note, die wie folgt in zwei Dokumentenblöcke unterteilt ist, und wenn wir dieWas ist neu in Milvus 2.4.13?
Der direkt relevante Inhalt befindet sich in Chunk 2, während die Milvus-Versionsinformationen in Chunk 1 sind. An diesem Punkt ist es für das Einbettungsmodell schwierig, diese Referenzen korrekt mit den Entitäten zu verknüpfen, was zu einer schlechten Qualität der Einbettung führt.
LLM hat Schwierigkeiten, ein solches Korrelationsproblem zu lösen, da sich die Funktionsbeschreibung nicht im selben Chunk wie die Versionsinformationen befindet und ein größeres Kontextdokument fehlt. Es gibt zwar eine Reihe von Heuristiken, die versuchen, dieses Problem zu lösen, wie z. B. die Neuabtastung mit gleitenden Fenstern, sich überschneidende Kontextfensterlängen und das Scannen mehrerer Dokumente, aber wie alle Heuristiken sind auch diese Methoden unzuverlässig; sie können in einigen Fällen funktionieren, aber es gibt keine theoretischen Garantien.
Beim traditionellen Chunking wird eine Pre-Chunking-Strategie angewandt, d. h. der Text wird zuerst gechunked und durchläuft dann das Embedding-Modell. Der Text wird zunächst anhand von Parametern wie Satz-, Absatz- oder voreingestellter Maximallänge geschnitten. Das Embedding-Modell verarbeitet diese Chunks dann einen nach dem anderen mit Hilfe von Methoden wie dem Durchschnitts-Pooling, dem Token Late Chunking bedeutet, dass vor dem Chunking zuerst das Embedding-Modell durchlaufen wird (das ist die Bedeutung von late). Beim Late Chunking hingegen wird das Embedding-Modell vor dem Chunking durchlaufen (das ist die Bedeutung von Late, erst Vektorisierung und dann Chunking). Wir wenden zunächst die Transformationsschicht des Embedding-Modells auf den gesamten Text an und generieren für jedes Token eine Folge von Vektoren, die umfangreiche Kontextinformationen enthalten. Anschließend werden diese Token-Vektoren gleichmäßig gepoolt, um das endgültige Chunk-Embedding zu erhalten, das den gesamten Textkontext berücksichtigt.
(Bildquelle: https://jina.ai/news/late-chunking-in-long-context-embedding-models/)
Late Chunking erzeugt eine Blockeinbettung, bei der jeder Block mehr Kontextinformationen kodiert und so die Qualität und Genauigkeit der Kodierung verbessert. Wir können lange Kontext-Embedding-Modelle unterstützen, indem wir lange Kontexte unterstützen, wie z. B. jina-embeddings-v2-base-de
Es kann bis zu 8192 Token Text verarbeiten (das entspricht 10 DIN-A4-Seiten), was im Grunde die kontextuellen Anforderungen der meisten langen Texte erfüllt.
Zusammenfassend können wir die Vorteile von Late Chunking in RAG-Anwendungen sehen:
- Verbesserte Genauigkeit: Durch die Erhaltung von Kontextinformationen liefert Late Chunking relevantere Inhalte für Abfragen als einfaches Chunking.
- Effiziente LLM-Aufrufe: Late Chunking reduziert die an den LLM übergebene Textmenge, da es weniger und relevantere Chunks zurückgibt.
03.Testen von Late Chunking
3.1 Implementierung der Late Chunking Base
Funktion sentence_chunker für das Originaldokument zum Absatz-Chunking, Rückgabe des Inhalts der Chunks und der Chunk-Markierungsinformation span_annotations (d.h. der Anfang und das Ende der Chunk-Markierung)
def sentence_chunker(document, batch_size=10000). nlp = spacy.blank("en") nlp.add_pipe("sentencizer", config={"punct_chars": None}) doc = nlp(Dokument) docs = [] for i in range(0, len(document), batch_size): batch = document[i : i + batch_size] docs.append(nlp(batch)) doc = Doc.from_docs(docs) span_annotations = [] chunks = [] for i, sent in enumerate(doc.sents): span_annotations.append((sent.start, sent.end)) chunks.append(gesendet.text) return chunks, span_annotations
Die Funktion document_to_token_embeddings übergibt das Modell jinaai/jina-embeddings-v2-base-de
Modell sowie den Tokeniser, der die Einbettung des gesamten Dokuments zurückgibt.
def document_to_token_embeddings(model, tokenizer, document, batch_size=4096). tokenisiertes_Dokument = tokenizer(Dokument, return_tensors="pt") Token = tokenisiertes_Dokument.tokens() outputs = [] for i in range(0, len(tokens), batch_size):: tokenised_document = tokenizer(document, return_tensors="pt") start = i end = min(i + batch_size, len(tokens)) batch_inputs = {k: v[:, start:end] for k, v in tokenised_document.items()} mit torch.no_grad(). model_output = model(**batch_inputs) outputs.append(model_output.last_hidden_state) model_output = torch.cat(outputs, dim=1) return model_output
Die Funktion late_chunking chunked das Embedding des gesamten Dokuments sowie die Markup-Informationen span_annotations der ursprünglichen Chunks.
def late_chunking(token_embeddings, span_annotation, max_length=None). outputs = [] for embeddings, annotations in zip(token_embeddings, span_annotation): if ( if ( max_length ist nicht None ): outputs = [] for embeddings, annotations in zip(token_embeddings, span_annotation): if ( max_length is not None): if ( annotations = [ (start, min(end, max_length - 1)) for (start, end) in annotations if start = 1: pooled_embeddings.apps = [] for start, end in annotations. pooled_embeddings.append( embeddings[start:end].sum(dim=0) / (end - start) ) pooled_embeddings = [ embedding.detach().cpu().numpy() for embedding in pooled_embeddings ] outputs.append(pooled_embeddings) return outputs
Wenn ein Modell verwendet wirdjinaai/jina-embeddings-v2-base-de
Late Chunking durchführen
tokeniser = AutoTokenizer.from_pretrained('jinaai/jina-embeddings-v2-base-de', trust_remote_code=True) model = AutoModel.from_pretrained('jinaai/jina-embeddings-v2-base-de', trust_remote_code=True) # Zunächst wird der Text wie üblich gechunkert, um die Anfangs- und Endpunkte der Chunks zu erhalten. chunks, span_annotations = sentence_chunker(document) # Dann betten Sie das gesamte Dokument ein. token_embeddings = document_to_token_embeddings(model, tokeniser, document) # Führen Sie dann das späte Chunking durch chunk_embeddings = late_chunking(token_embeddings, [span_annotations])[0]
3.2 Vergleich mit traditionellen Einbettungsmethoden
Nehmen wir die milvus 2.4.13 Release Note als Beispiel.
Milvus 2.4.13 führt die dynamische Replikabeladung ein, die es den Benutzern ermöglicht, die Anzahl der Replikate der Sammlung anzupassen, ohne die Sammlung freigeben und neu laden zu müssen. Sammlung.
Diese Version behebt außerdem mehrere kritische Fehler im Zusammenhang mit dem Massenimport, dem Parsen von Ausdrücken, dem Lastausgleich und der Fehlerbehebung.
Außerdem wurden die MMAP-Ressourcennutzung und die Importleistung erheblich verbessert, was die Effizienz des Systems insgesamt erhöht.
Wir empfehlen dringend ein Upgrade auf diese Version, um die Leistung und Stabilität zu verbessern.
Es werden das traditionelle Embedding, d.h. Chunking gefolgt von Embedding, und das Late Chunking approach Embedding, d.h. Embedding gefolgt von Chunking, durchgeführt. Dann wird die milvus 2.4.13
Vergleichen Sie die Ergebnisse mit diesen beiden Einbettungsansätzen
cos_sim = lambda x, y: np.dot(x, y) / (np.linalg.norm(x) * np.linalg.norm(y)) milvus_embedding = model.encode('milvus 2.4.13') for chunk, late_chunking_embedding, traditional_embedding in zip(chunks, chunk_embeddings, embeddings_traditional_chunking):: print(f'similarity_late(x)) print(f'similarity_late_chunking("milvus 2.4.13", "{chunk}")') print('late_chunking: ', cos_sim(milvus_embedding, late_chunking_embedding)) print(f'similarity_traditional("milvus 2.4.13", "{chunk}")') print('traditional_chunking: ', cos_sim(milvus_embedding, traditional_embeddings))
Aus den Ergebnissen: Wörter milvus 2.4.13
Die Ähnlichkeit der Late Chunking-Ergebnisse mit gechunkten Dokumenten ist höher als die der traditionellen Einbettung, weil Late Chunking zunächst eine Einbettung für die gesamte Textpassage durchführt, was dazu führt, dass die gesamte Textpassage milvus 2.4.13
Informationen, was wiederum die Ähnlichkeit bei nachfolgenden Textvergleichen erheblich verbessert.
similarity_late_chunking("milvus 2.4.13", "Milvus 2.4.13 führt eine dynamische Replikabeladung ein, die es den Benutzern ermöglicht, die Anzahl der Replikate anzupassen, ohne die Sammlung freigeben und neu laden zu müssen.") late_chunking: 0.8785206 similarity_traditional("milvus 2.4.13", "Milvus 2.4.13 führt eine dynamische Replikatauslastung ein, die es den Benutzern ermöglicht, die Anzahl der Sammlungsreplikate anzupassen, ohne die Sammlung freigeben und neu laden zu müssen.") late_chunking: 0.8785206 ohne die Sammlung freigeben und neu laden zu müssen.") traditional_chunking: 0.8354263 similarity_late_chunking("milvus 2.4.13", "Diese Version behebt auch mehrere kritische Fehler im Zusammenhang mit dem Massenimport, dem Parsen von Ausdrücken, dem Lastausgleich Lastausgleich und Fehlerbehebung.") late_chunking: 0.84828955 similarity_traditional("milvus 2.4.13", "Diese Version behebt auch mehrere kritische Fehler im Zusammenhang mit Bulk-Import, Expression-Parsing, Lastausgleich und Fehlerbehebung.") late_chunking: 0.84828955 Lastverteilung und Fehlerbehebung.") traditional_chunking: 0.7222632 similarity_late_chunking("milvus 2.4.13", "Zusätzlich wurden signifikante Verbesserungen bei der MMAP-Ressourcennutzung und der Importleistung vorgenommen, was die Gesamteffizienz des Systems erhöht."). Verbesserung der Gesamteffizienz des Systems.") late_chunking: 0.84942204 similarity_traditional("milvus 2.4.13", "Zusätzlich wurden erhebliche Verbesserungen bei der MMAP-Ressourcennutzung und der Importleistung vorgenommen, wodurch die Effizienz des Gesamtsystems erhöht wird.") late_chunking: 0.84942204 Verbesserung der Gesamteffizienz des Systems.") traditional_chunking: 0.6907381 similarity_late_chunking("milvus 2.4.13", "Wir empfehlen dringend ein Upgrade auf diese Version für bessere Leistung und Stabilität.") late_chunking: 0.85431844 similarity_traditional("milvus 2.4.13", "Wir empfehlen dringend ein Upgrade auf diese Version für bessere Leistung und Stabilität.") traditional_chunking: 0.71859795
3.3 Testen von Late Chunking in Milvus
Importieren von Late Chunking-Daten in Milvus
batch_data=[] for i in range(len(chunks)): data = { "Inhalt": chunks[i], "Einbettung": chunk_embeddings[i].tolist(), } batch_data.append(data) res = client.insert( collection_name=collection, data=batch_data, batch_data, batch_data.append(data) data=batch_data, ) )
Abfrageprüfung
Wir definieren die Cosinus-Ähnlichkeits-Abfragemethode sowie die Verwendung der nativen Milvus-Abfragemethode für Late Chunking.
def late_chunking_query_by_milvus(query, top_k = 3). query_vector = model(**tokenizer(query, return_tensors="pt")).last_hidden_state.mean(1).detach().cpu().numpy().flatten() res = client.search( collection_name=collection, data=[query_vector.tolist()], limit=top_k, output_fields=["id", "content"], ) return [item.get("entity").get("content") for items in res for item in items] def late_chunking_query_by_cosine_sim(query, k = 3). cos_sim = lambda x, y: np.dot(x, y) / (np.linalg.norm(x) * np.linalg.norm(y)) query_vector = model(**tokenizer(query, return_tensors="pt")).last_hidden_state.mean(1).detach().cpu().numpy().flatten() Ergebnisse = np.empty(len(chunk_embeddings)) for i, (chunk, embedding) in enumerate(zip(chunks, chunk_embeddings)): results[i] = cos_sim(query_vector, embedding) ergebnisse_ordnung = ergebnisse.argsort()[::-1] return np.array(chunks)[results_order].tolist()[:k]
Die Ergebnisse zeigen, dass die beiden Methoden den gleichen Inhalt liefern, was darauf hindeutet, dass die Ergebnisse der Abfrage nach Late Chunking in Milvus korrekt sind.
> late_chunking_query_by_milvus("Was sind die neuen Funktionen in milvus 2.4.13", 3) ['nn#### Featuresnn- Dynamische Replikatanpassung für geladene Sammlungen ([#36417](https://github.com/milvus-io/milvus/pull/ 36417))n- Sparse vector MMAP in wachsenden Segmenttypen ([#36565](https://github.com/milvus-io/milvus/pull/36565))... > late_chunking_query_by_cosine_sim("Was sind neue Funktionen in milvus 2.4.13", 3) ['nn#### Featuresnn- Dynamische Replikationsanpassung für geladene Sammlungen ([#36417](https://github.com/milvus-io/milvus/pull/ 36417))n- Sparse vector MMAP in wachsenden Segmenttypen ([#36565](https://github.com/milvus-io/milvus/pull/36565))...
04.Zusammenfassungen
Wir stellen den Hintergrund, die grundlegenden Konzepte und die zugrundeliegende Implementierung von Late Chunking vor, wie es entstanden ist, und stellen dann fest, dass Late Chunking gut funktioniert, indem wir es in Mivlus testen. Insgesamt macht die Kombination aus Genauigkeit, Effizienz und Einfachheit der Implementierung Late Chunking zu einem effektiven Ansatz für RAG-Anwendungen.
Referenz.
- https://stackoverflow.blog/2024/06/06/breaking-up-is-hard-to-do-chunking-in-rag-applications
- https://jina.ai/news/late-chunking-in-long-context-embedding-models/
- https://jina.ai/news/what-late-chunking-really-is-and-what-its-not-part-ii/
Beispiel-Code:
Link: https://pan.baidu.com/s/1cYNfZTTXd7RwjnjPFylReg?pwd=1234 Code extrahieren: 1234 Code läuft auf aws g4dn.xlarge Maschine