Kurs
RAG uygulamalarında, tüm belgeyi gömerek bağlamı daha iyi yakalamak ile daha isabetli erişim için belgeyi küçük parçalara ayırmak arasında sürekli bir ödünleşim vardır.
Belgenin tamamını gömmek genel resmi yakalar ancak önemli ayrıntıları kaçırabilir; kısa parçalar ise ayrıntıları korur ama çoğu zaman genel bağlamı kaçırır.
Geç parçalama, belgeyi daha küçük ve yönetimi kolay parçalara bölerken tam belge bağlamını koruyarak bir çözüm sunar.
Bu yazıda, geç parçalamayı geleneksel naif parçalama yöntemlerine daha iyi bir alternatif olarak tanıtacak ve adım adım nasıl uygulayacağınızı göstereceğim.
Naif Parçalama ve RAG'deki Sınırlamaları
Bir RAG hattında, belgeler gömülmeden ve bir vektör veritabanında saklanmadan önce daha küçük parçalara ayrılır. Her parça bağımsız olarak işlenir ve sorgular yapıldığında erişim için kullanılır. Ancak bu "naif parçalama" yaklaşımı, çoğunlukla önemli uzun erimli bağlamı kaybeder.
Sorun, geleneksel parçalamanın bilgilerin nasıl bağlandığını dikkate almadan belgeleri bölmesinden kaynaklanır. Örneğin Paris hakkında bir belgede, "şehir" ifadesi "Paris"in geçtiği yerden farklı bir parçaya düşebilir. Tam bağlam olmadan, erişim modeli bu göndermeleri ilişkilendirmekte zorlanabilir ve bu da daha az doğru sonuçlara yol açar. Bu sorun, kilit bağlamın birden fazla bölüme yayıldığı uzun belgelerde daha da belirgindir.
Geç Parçalama: Belge Bölmede Bağlamı Korumak
Geç parçalama, belgenin ne zaman bölündüğünü değiştirerek sorunu çözer. Belgeyi önce parçalara ayırmak yerine, geç parçalama belgenin tamamını uzun bağlamlı bir modelle gömer. Ancak bundan sonra belge daha küçük parçalara bölünür.
Geç parçalamanın başlıca faydaları şunlardır:
- Bağlamı korur: Geç parçalama, önce tüm belgeyi gömerek her parçanın genel bağlamı korumasını sağlar. Böylece metin boyunca yer alan göndermeler ve bağlantılar parça gömmelerinde bozulmadan kalır.
- Daha iyi erişim: Geç parçalama ile oluşturulan parça gömmeleri daha zengin ve daha isabetlidir; model belgeyi daha iyi anladığı için RAG sistemlerinde erişim sonuçlarını iyileştirir.
- Uzun metinleri işler: Belgeyi bir kerede işleyemeyen, belirli belirteç (token) sınırlarına sahip geleneksel modeller için çok uzun belgelerde etkilidir.
Jina’nın 8192 belirtece kadar destekleyen jinaai/jina-embeddings-v2-base-en gibi uzun bağlamlı modellerini kullanarak, geç parçalama büyük metin bölümlerini parçalara ayırmadan önce etkili biçimde gömmeye olanak tanır.
Geç Parçalamanın Uygulanması
İşte Jina’nın uzun bağlamlı gömme modelini kullanarak geç parçalamayı uygulamanıza yardımcı olacak adım adım bir rehber. Jina’nın API anahtarını ücretsiz olarak buradan alabilirsiniz ve demo olarak aşağıdaki girdi metnini kullanacağız:
input_text = """Berlin is the capital and largest city of Germany, both by area and by population.
Its more than 3.85 million inhabitants make it the European Union's most populous city, as measured by population within city limits.
The city is also one of the states of Germany, and is the third smallest state in the country in terms of area."""
Adım 1: Parçaları ve aralık ek açıklamalarını alın
Önce, giriş metninizi parçalara ayırmak için Jina API anahtarınızı ve aşağıdaki yardımcı işlevi kullanın. Bu parçalar, daha sonra belge gömmesini bölmeye yardımcı olan aralık ek açıklamalarıyla birlikte gelir. Jina’nın API’si, parçaların anlamlı olmasını ve anlamlarını korumasını sağlamak için paragraf veya cümle sonları gibi doğal sınırları kullanır.
import json
import requests
def custom_tokenize_jina_api(input_text: str):
url = '<https://segment.jina.ai/>'
headers = {
'Content-Type': 'application/json',
'Authorization': 'Bearer ENTER_YOUR_JINA_API_KEY'
}
data = {
"content": input_text,
"tokenizer": "o200k_base",
"return_tokens": "true",
"return_chunks": "true",
"max_chunk_length": "1000"
}
# Make the API request
response = requests.post(url, headers=headers, json=data)
response_data = response.json()
chunks = response_data.get("chunks", [])
i = 1
j = 1
span_annotations = []
for x in response_data['tokens']:
if j == 1:
j = len(x)
else:
j = len(x) + i
span_annotations.append((i, j))
i = j
return chunks, span_annotations
chunks, span_annotations = custom_tokenize_jina_api(input_text)
print(chunks)
print(span_annotations)
['Berlin is the capital and largest city of Germany, both by area and by population.\\n\\n', "Its more than 3.85 million inhabitants make it the European Union's most populous city, as measured by population within city limits.\\n\\n", 'The city is also one of the states of Germany, and is the third smallest state in the country in terms of area.']
[(1, 17), (17, 44), (44, 69)]
Adım 2: Metni belirteçlere ayırın ve belirteç düzeyinde belge gömmeleri oluşturun
Önce, Jina’nın embeddings-v2-base-en modeli gibi uzun bağlamlı modellere uyumlu bir belirteçleyici kullanarak belgenin tamamını belirteçlere ayırın. Ardından, her belirteç için gömme oluşturmak üzere uzun bağlamlı bir dönüştürücü modeli kullanın. Bu, belgenizdeki her kelime ya da belirtecin, anlamını yakalayan kendine özgü bir gömmeye sahip olması demektir.
from transformers import AutoModel
from transformers import AutoTokenizer
# load model and tokenizer
tokenizer = AutoTokenizer.from_pretrained('jinaai/jina-embeddings-v2-base-en', trust_remote_code=True)
model = AutoModel.from_pretrained('jinaai/jina-embeddings-v2-base-en', trust_remote_code=True)
inputs = tokenizer(input_text, return_tensors='pt')
model_output = model(**inputs)
model_output[0].shape
torch.Size([1, 71, 768]) # 71 represents number of tokens in the entire document
Adım 3: Geç parçalama
Tüm belge için belirteç gömmelerine sahip olduğunuzda, geç parçalamaya hazırsınız. Bu belirteç gömmelerini daha küçük parçalara bölmek için birinci adımdaki aralık ek açıklamalarını kullanın. Sonra, her parçanın içindeki gömmeleri ortalamak için ortalama havuzlama (mean pooling) uygulayın ve her parça için tek bir gömme oluşturun. Artık belgenin tamamına dair güçlü bağlamsal bilgi taşıyan parça gömmelerine sahibiz.
def late_chunking(
model_output: 'BatchEncoding', span_annotation: list, max_length=None
):
token_embeddings = model_output[0]
outputs = []
for embeddings, annotations in zip(token_embeddings, span_annotation):
if (
max_length is not None
): # remove annotations which go bejond the max-length of the model
annotations = [
(start, min(end, max_length - 1))
for (start, end) in annotations
if start < (max_length - 1)
]
pooled_embeddings = [
embeddings[start:end].sum(dim=0) / (end - start)
for start, end in annotations
if (end - start) >= 1
]
pooled_embeddings = [
embedding.detach().cpu().numpy() for embedding in pooled_embeddings
]
outputs.append(pooled_embeddings)
return outputs
embeddings = late_chunking(model_output, [span_annotations])[0]
len(embeddings)
3 # matches number of chunks in Step 1
Adım 4: Geç parçalama vs. geleneksel parçalama sonuçları
Geç parçalamanın faydalarını anlamak için bunu geleneksel parçalama ile karşılaştıralım:
embeddings_traditional_chunking = model.encode(chunks)
import numpy as np
cos_sim = lambda x, y: np.dot(x, y) / (np.linalg.norm(x) * np.linalg.norm(y))
q = "Berlin"
berlin_embedding = model.encode(q)
print(q)
print('\\n')
for chunk, new_embedding, trad_embeddings in zip(chunks, embeddings, embeddings_traditional_chunking):
print(chunk.strip())
print(f'Late chunking:', cos_sim(berlin_embedding, new_embedding))
print(f'Traditional chunking:', cos_sim(berlin_embedding, trad_embeddings))
print("------------------------------------------------------------------")
Berlin
Berlin is the capital and largest city of Germany, both by area and by population.
Late chunking: 0.84954596
Traditional chunking: 0.84862185
------------------------------------------------------------------
Its more than 3.85 million inhabitants make it the European Union's most populous city, as measured by population within city limits.
Late chunking: 0.82489026
Traditional chunking: 0.70843375
------------------------------------------------------------------
The city is also one of the states of Germany, and is the third smallest state in the country in terms of area.
Late chunking: 0.84980094
Traditional chunking: 0.7534553
------------------------------------------------------------------
İkinci ve üçüncü parçalarda görüldüğü gibi, geleneksel parçalama "Berlin" kelimesiyle karşılaştırıldığında benzerlik puanlarını %70-75 aralığında gösterir. Oysa tüm belgenin bağlamını koruyan geç parçalama ile bu puanlar %82-84’e yükselir. Bu da geç parçalamanın bağlamı daha iyi koruduğunu ve daha anlamlı gömmeler ürettiğini, dolayısıyla daha doğru arama sonuçları sağladığını gösterir.
Sonuç
Geç parçalama, özellikle RAG hatlarında, belge erişim sistemleri için önemli bir gelişmedir. Belge tamamen gömüldükten sonra bölmeyi bekleyerek, geç parçalama her parçanın tam bağlamı korumasını sağlar. Bu da daha doğru ve daha anlamlı gömmelerle sonuçlanır.
Ryan, LLM'ler kullanarak yapay zeka uygulamaları geliştirme konusunda uzmanlaşmış kıdemli bir veri bilimcisidir. Imperial College London'da Doğal Dil İşleme ve Bilgi Grafiklerinde doktora adayıdır ve aynı kurumda Bilgisayar Bilimleri alanında yüksek lisansını tamamlamıştır. Veri bilimi dışında, haftalık Substack bülteni The Limitless Playbook'u yayımlar; burada dünyanın önde gelen düşünürlerinden uygulanabilir tek bir fikri paylaşır ve zaman zaman temel yapay zeka kavramları hakkında yazar.

