Erişim Destekli Üretim (RAG)
RAG’in temellerinden gelişmiş popüler yöntemlere kadar inceleyeceğiz. Yerleştirmeler, vektör veritabanları, TF-IDF, BM25, hibrit arama, yerleştirme adaptörü, sorgu genişletme, graf ve hiyerarşik RAG’i öğreneceğiz.
1. RAG’in Temelleri
1.1 Bağlamsal Erişime Giriş
Bir yapay zeka modelinin belirli bağlamlarda faydalı olabilmesi için genellikle arka plan bilgisine erişmesi gerekir. Örneğin, müşteri destek asistanlarının kullanmakta oldukları işletmeyle alakalı bilgiye, hukuk analisti botlarının ise geçmiş davalarla alakalı geniş bir bilgi birikimine ihtiyaçları vardır. [1]
Geliştiriciler genellikle bir yapay zeka modelinin bilgisini Erişim Destekli Üretim (RAG: Retrieval-Augmented Generation) yöntemiyle artırırlar. RAG, bir bilgi tabanından ilgili bilgileri alıp kullanıcı istemine ekleyerek modelin yanıtlarını önemli ölçüde iyileştiren bir yöntemdir. Ancak geleneksel RAG çözümleri, bilgiyi kodlarken bağlamı ortadan kaldırır ve bu durum çoğunlukla sistemin bilgi tabanından ilgili bilgileri alamamasına yol açar.[1]
Bu yazıda, RAG’deki erişim adımını önemli ölçüde geliştiren bir yöntemden bahsedeceğiz. Bu yöntem “Bağlamsal Erişim” (Contextual Retrieval) olarak adlandırılır ve iki alt tekniği içerir: Bağlamsal Yerleştirmeler (Contextual Embeddings) ve Bağlamsal BM25 (Contextual BM25). Bu yöntem başarısız erişimlerin sayısını %49 oranında azaltabilir ve yeniden sıralama (reranking) ile birleştirildiğinde bu oran %67'ye ulaşabilir. Bu iyileştirmeler, erişim doğruluğunda önemli gelişmeler sağlayarak bağlantılı görevlerde daha iyi performansa doğrudan katkı sağlar.[1]
Daha Uzun Bir İstem Kullanma Hakkında Not
Bazen en basit çözüm en iyisidir. Bilgi tabanınız 200.000 token’dan (yaklaşık 500 sayfalık bir materyalden) küçükse, RAG veya benzeri yöntemlere gerek kalmadan, modele verdiğiniz istemde tüm bilgi tabanını ekleyebilirsiniz.
İstem önbellekleme (prompt caching) bu yaklaşımı önemli ölçüde daha hızlı ve daha uygun maliyetli hale getirmektedir. Geliştiriciler artık API çağrıları arasında sık kullanılan istemleri önbelleğe alabilir, gecikmeyi 2 kata kadar, maliyetleri %90'a kadar azaltabilir.[1]
Ancak, bilgi tabanınız büyüdükçe daha ölçeklenebilir bir çözüme ihtiyacınız olmaktadır. Bağlamsal Erişim (Contextual Retrieval) tam da burada devreye girer.[1]
RAG’e Giriş: Daha Büyük Bilgi Tabanlarına Ölçekleme
Daha büyük bilgi tabanları bağlam penceresine sığmadığında, RAG sık tercih edilen bir çözümdür. RAG, bir bilgi tabanını aşağıdaki adımlarla ön işlemden geçirerek çalışır [1]:
- Bilgi tabanını (belgelerin “korpusu”) daha küçük metin parçalarına ayırır; bu genellikle birkaç yüz token’i geçmemelidir; [1]
- Bu parçalar anlamı kodlayan vektör yerleştirmelerine dönüştürmek için bir yerleştirme (embedding) modeli kullanılır; [1]
- Bu yerleştirmeleri, anlamsal benzerlik temelinde arama yapmaya olanak tanıyan bir vektör veri tabanında saklar. [1]
Çalışma sırasında kullanıcı, modele bir sorgu girdiğinde, vektör veri tabanı sorguyla anlamsal benzerlik temelinde en alakalı parçaları bulmak için kullanılır. Daha sonra en alakalı parçalar, üretken modele (LLM’e) gönderilen isteme eklenir. [1]
Yerleştirme modelleri anlamsal ilişkileri yakalamada mükemmeldir, ancak kritik tam eşleşmeleri gözden kaçırabilirler. Neyse ki, bu durumlarda yardımcı olabilecek eski bir teknik bulunmaktadır. BM25 (Best Matching 25), tam kelime veya ifade eşleşmelerini bulmak için sözcüksel eşleştirme kullanan bir sıralama fonksiyonudur. Özellikle benzersiz tanımlayıcılar veya teknik terimler içeren sorgularda etkilidir.[1]
BM25, TF-IDF (Terim Frekansı-Ters Belge Frekansı) kavramı üzerine kuruludur. TF-IDF, bir terimin bir koleksiyondaki belge için ne kadar önemli olduğunu ölçer. BM25, belge uzunluğunu dikkate alarak ve terim frekansına bir doygunluk fonksiyonu uygulayarak bunu geliştirir; bu, yaygın kelimelerin sonuçları domine etmesini önler. [1]
BM25'in anlamsal yerleştirmelerin yetersiz kaldığı durumlarda nasıl başarılı olabileceğine bir örnek: Diyelim ki bir kullanıcı teknik destek veri tabanında “Hata kodu TS-999” sorgusunu yapıyor. Bir yerleştirme modeli genel olarak hata kodları hakkında içerik bulabilir ancak “TS-999” gibi tam eşleşmeleri gözden kaçırabilir. BM25, bu belirli metin dizisini arayarak ilgili dokümantasyonu belirler. [1]
RAG çözümleri, yerleştirme ve BM25 tekniklerini aşağıdaki adımlarla birleştirerek en uygun parçaları daha doğru şekilde getirebilir [1]:
- Bilgi tabanını (belgelerin “korpusu”) daha küçük metin parçalarına ayırın; genellikle birkaç yüz token’i geçmemelidir; [1]
- Bu parçalar için TF-IDF kodlamaları ve anlamsal yerleştirmeler oluşturun; [1]
- Tam eşleşmelere dayalı olarak BM25 ile en iyi parçaları bulun; [1]
- Anlamsal benzerliğe dayalı olarak yerleştirmeleri kullanarak en iyi parçaları bulun; [1]
- Bu sonuçlarını sıralama füzyon teknikleriyle birleştirin ve duplikasyonları önleyin; [1]
- En iyi K parçaları isteme ekleyerek yanıtı üretin. [1]
Hem BM25 hem de yerleştirme modellerinden faydalanarak geleneksel RAG sistemleri, hassas terim eşleştirmeyi daha geniş anlamsal anlayışla dengeleyerek daha kapsamlı ve doğru sonuçlar sağlayabilir. [1]
Bu yaklaşım, tek bir komut istemine sığabilecek olanın çok ötesinde, muazzam bilgi tabanlarına uygun maliyetli bir şekilde ölçeklenmenizi sağlar. Ancak bu geleneksel RAG sistemlerinin önemli bir sınırlaması vardır: Genellikle bağlamı yok ederler.[1]
Geleneksel RAG’deki Bağlam Bilmecesi
Geleneksel RAG’de, belgeler genellikle verimli bir şekilde erişim için daha küçük parçalara bölünür. Bu yaklaşım birçok uygulama için iyi çalışsa da, tek tek parçalar yeterli bağlamdan yoksun olduğunda sorunlara yol açabilir.[1]
Örneğin, bilgi tabanınıza yerleştirilmiş bir dizi finansal bilgi (örneğin, ABD SEC dosyaları) olduğunu ve şu soruyu aldığınızı düşünün: “ACME Corp’un 2023'ün 2. çeyreğindeki gelir büyümesi neydi?”[1]
İlgili bir bölüm şu metni içerebilir: “Şirketin geliri bir önceki çeyreğe göre %3 arttı.” Ancak, bu bölüm kendi başına hangi şirketten veya ilgili zaman diliminden bahsettiğini belirtmez, bu da doğru bilgiye ulaşmayı veya bilgiyi etkili bir şekilde kullanmayı zorlaştırır.[1]
Bağlamsal Erişim Tanımı
Bağlamsal Erişim, her bir parçaya yerleştirmeden önce parçaya özgü açıklayıcı bağlam ekleyerek (“Bağlamsal Yerleştirmeler”) ve BM25 dizinini oluşturarak (“Bağlamsal BM25”) bu sorunu çözer.[1]
SEC dosyalama toplama örneğimize geri dönelim. İşte bir parçanın nasıl dönüştürülebileceğine dair bir örnek:
original_chunk = "Şirketin geliri bir önceki çeyreğe göre %3 arttı."
contextualized_chunk = "Bu parça, ACME corp'un 2023'ün 2. çeyreğindeki performansına ilişkin bir SEC dosyasından alınmıştır; önceki çeyreğin geliri 314 milyon dolardı. Şirketin geliri bir önceki çeyreğe göre %3 arttı."
Geçmişte bağlamı kullanarak geri çağırmayı iyileştirmeye yönelik başka yaklaşımların da önerildiğini belirtelim. Diğer öneriler arasında şunlar yer almaktadır: Parçalara genel belge özetleri ekleme (deneylerimizde çok sınırlı kazanımlar gördük), varsayımsal belge yerleştirme (Hyde) ve özet tabanlı dizinleme (summary based indexing) (düşük performans artışı gördük). Bu yöntemler bu gönderide önerilenlerden farklıdır.[1]
Bağlamsal Geri Çağırmayı Uygulama
Elbette, bir bilgi tabanındaki binlerce hatta milyonlarca parçayı elle açıklamak çok fazla iş olurdu. Bağlamsal erişimi uygulamak için Claude’a yöneliyoruz. Modele, parçayı genel belgenin bağlamını kullanarak açıklayan, parçaya özgü bağlam sağlamasını söyleyen özlü bir istem yazdık. Her parça için bağlam oluşturmak üzere aşağıdaki Claude 3 Haiku istemini kullandık [1]:
<belge>
{{TÜM_BELGE}}
</belge>
İşte tüm belgenin içine yerleştirmek istediğimiz parça
<parça>
{{KÜME_İÇERİĞİ}}
</parça>
Lütfen bu parçayı genel belge içinde konumlandırmak için kısa ve öz bir bağlam verin ve parçanın arama erişimini iyileştirin. Sadece kısa ve öz bağlamla yanıt verin ve başka hiçbir şey vermeyin.
Sonuç olarak elde edilen genellikle 50–100 belirteçten oluşan bağlamsal metin, yerleştirilmeden ve BM25 indeksi oluşturulmadan önce parçaya eklenir. [1]
Ön işleme akışı pratikte şu şekilde görünür [1]:
Bağlamsal Erişim Maliyetlerini Azaltmak İçin İstem Önbelleğe Alma Özelliğini Kullanma
Yukarıda bahsettiğimiz özel istem önbelleğe alma özelliği sayesinde Claude ile Bağlamsal Alma benzersiz bir şekilde düşük maliyetle mümkündür. İstem önbelleğe alma ile her parça için referans belgesini geçirmeniz gerekmez. Belgeyi önbelleğe bir kez yüklersiniz ve daha sonra önceden önbelleğe alınmış içeriğe başvurursunuz. 800 belirteç parçası, 8k belirteç belgesi, 50 belirteç bağlam talimatı ve parça başına 100 belirteç bağlam varsayıldığında, bağlamsallaştırılmış parçalar üretmenin tek seferlik maliyeti milyon belge belirteci başına 1,02 ABD dolarıdır.[1]
Metodoloji
Çeşitli bilgi alanlarında (kod tabanlarında, kurgularda, ArXiv makalelerinde ve bilimsel makalelerde) deneyler yaptık; yerleştirme modelleri, bilgiye erişim stratejileri ve değerlendirme metrikleri üzerine çalıştık. [1]
Aşağıdaki göreceğiniz grafikler, tüm bilgi alanlarındaki ortalama performansı göstermektedir. Burada en iyi performans gösteren yerleştirme konfigürasyonu (Gemini Text 004) ve en iyi 20 parçayı getirme stratejisi kullanılmıştır. Değerlendirme metriği olarak recall@20–1 kullanıyoruz; bu, ilgili belgelerin ilk 20 parça içinde getirilememesi yüzdesini ölçer. Tam sonuçları ekte bulabilirsiniz. Bağlamsallaştırma, değerlendirdiğimiz her yerleştirme-kaynak kombinasyonunda performansı artırmaktadır. [1]
Performans İyileştirmeleri
Deneylerimiz şunu gösterdi:
- Bağlamsal Yerleştirmeler, ilk 20 parçaya erişim başarısızlığını %35 oranında azalttı (%5,7 → %3,7). [1]
- Bağlamsal Yerleştirmeler ve Bağlamsal BM25'in birleştirilmesi, ilk 20 parçaya erişim başarısızlığı oranını %49 (5,7% → %2,9) oranında azalttı. [1]
Uygulama Hususları
Bağlamsal Erişimi uygularken akılda tutulması gereken birkaç husus vardır [1]:
- Parça sınırları: Belgelerinizi parçalara nasıl böldüğünüze dikkat edin. Parça boyutu, parça sınırı ve parça örtüşmesi (overlap) seçimi, erişim performansını etkileyebilir.[1]
- Yerleştirme modeli: Bağlamsal Erişim, test ettiğimiz tüm yerleştirme modellerinde performansı iyileştirirken, bazı modeller diğerlerinden daha fazla fayda sağlayabilir. Gemini ve Voyage yerleştirmelerinin özellikle etkili olduğunu bulduk.[1]
- Özel bağlamsallaştırıcı istemleri: Sağladığımız genel istem iyi çalışsa da, kendi belirli alanınıza veya kullanım durumunuza göre uyarlanmış istemlerle (örneğin, bilgi tabanındaki diğer belgelerde tanımlanmış olabilecek anahtar terimlerin sözlüğünü ekleyerek) daha da iyi sonuçlar elde edebilirsiniz.[1]
- Parça sayısı: Bağlam penceresine daha fazla parça eklemek, ilgili bilgileri dahil etme şansınızı artırır. Ancak, daha fazla bilgi, modeller için dikkat dağıtıcı olabilir. Bu nedenle bunun bir sınırı vardır. 5, 10 ve 20 parça sunmayı denedik ve 20'yi kullanmanın bu seçenekler arasında en performanslısı olduğunu gördük ancak en iyi sonuç için sizler de kullanım durumunuz ile deneyip karara varmalısınız.[1]
Her zaman değerlendirme yapın: Yanıt üretimi, bağlamsallaştırılmış parçanın iletilmesi ve bağlam ile parçanın ne olduğu arasındaki ayrımın yapılmasıyla iyileştirilebilir.[1]
Yeniden Sıralama (Reranking) ile Performansı Daha da Artırın
Son bir adımda, daha fazla performans iyileştirmesi sağlamak için Bağlamsal Erişmi başka bir teknikle birleştirebiliriz. Geleneksel RAG’de, YZ sistemi potansiyel olarak ilgili bilgi parçalarını bulmak için bilgi tabanını arar. Büyük bilgi tabanlarında, bu ilk erişim genellikle değişen alaka ve öneme sahip çok sayıda parça (bazen yüzlerce) döndürür.[1]
Yeniden sıralama, yalnızca en alakalı parçaların modele iletilmesini sağlamak için yaygın olarak kullanılan bir filtreleme tekniğidir. Yeniden sıralama, model daha az bilgi işlediği için daha iyi yanıtlar sağlar ve maliyeti ve gecikmeyi azaltır. Temel adımlar şunlardır [1]:
- İlk olarak, potansiyel olarak en alakalı parçaları almak için ilk aramayı gerçekleştirin (biz ilk 150'yi kullandık).[1]
- En alakalı N parçayı, kullanıcının sorgusuyla birlikte yeniden sıralama modelinden geçirin.[1]
- Yeniden sıralama modelini kullanarak, her parçaya istemdeki alaka ve önemine göre bir puan verin, ardından ilk K parçayı seçin (biz ilk 20'yi kullandık).[1]
- Sonucu oluşturmak için en üstteki K parçayı bağlam olarak modele aktarın.[1]
Performans iyileştirmeleri
Piyasada çeşitli yeniden sıralama modelleri bulunmaktadır. Testlerimizi Cohere yeniden sıralama aracıyla yürüttük. Voyage da bir yeniden sıralama aracı sunmaktadır, ancak onu test etmek için zamanımız olmadı. Deneylerimiz, çeşitli alanlarda yeniden sıralama adımı eklemenin erişimi daha da iyileştirdiğini gösterdi. Özellikle, Yeniden Sıralanmış Bağlamsal Yerleştirme ve Bağlamsal BM25'in ilk 20 parça erişim başarısızlık oranını %67 (5,7% → 1,9%) oranında azalttığını bulduk.[1]
Maliyet ve Gecikme Hususları
Yeniden sıralamada önemli bir husus, özellikle çok sayıda parçayı yeniden sıralarken gecikme ve maliyet üzerindeki etkisidir. Yeniden sıralama, çalışma zamanında fazladan bir adım eklediğinden, yeniden sıralayıcı tüm parçaları paralel olarak puanlasa bile kaçınılmaz olarak az miktarda gecikme ekler. Daha iyi performans için daha fazla parçayı yeniden sıralamak ile daha düşük gecikme ve maliyet için daha az parçayı yeniden sıralamak arasında doğal bir denge vardır. Doğru dengeyi bulmak için kendi kullanım durumunuzda farklı ayarlar denemenizi öneririz.[1]
Aşağıda, veri kümeleri, yerleştirme sağlayıcıları, yerleştirmelere ek olarak BM25 kullanımı, bağlamsal erişim kullanımı ve Retrievals@20 için yeniden sıralama kullanımı genelindeki sonuçların bir dökümü bulunmaktadır.[1]
Sonuç
Yukarıda açıklanan tüm tekniklerin farklı kombinasyonlarını karşılaştıran çok sayıda test yürüttük (gömme modeli, BM25 kullanımı, bağlamsal alma kullanımı, yeniden sıralama kullanımı ve alınan en iyi K sonucun toplam sayısı), hepsi çeşitli farklı veri kümesi türlerinde. İşte bulduklarımızın bir özeti [1]:
- Embeddings+BM25, her zaman yerleştirmelerin tek başına kullanılmasından daha iyidir.[1]
- Voyage ve Gemini test ettiğimiz en iyi yerleştirmelere sahiptir.[1]
- En iyi 20 parçayı modele aktarmak, sadece en iyi 10 veya en iyi 5 parçayı aktarmaktan daha etkilidir.[1]
- Parçalara bağlam eklemek, erişim doğruluğunu önemli ölçüde artırır.[1]
- Yeniden sıralama, hiç sıralama olmamasından daha iyidir.[1]
- Tüm bu avantajları bir araya getirirsek: Performans iyileştirmelerini en üst düzeye çıkarmak için, bağlamsal yerleştirmeleri (Voyage veya Gemini’den) bağlamsal BM25 ile birleştirebiliriz, ayrıca bir yeniden sıralama adımı ekleyebilir ve 20 parçayı istemlere ekleyebiliriz.[1]
Bilgi tabanlarıyla çalışan tüm geliştiricileri, yeni performans düzeylerinin kilidini açmak için bu yaklaşımları denemek üzere kullanım kılavuzumuzu kullanmaya teşvik ediyoruz: [https://github.com/anthropics/anthropic-cookbook/tree/main/skills/contextual-embeddings]
📍
1.2 LanceDB Hibrit Arama
Arama motorlarının tam olarak aradığınızı nasıl bulduğunu hiç düşündünüz mü? Genellikle belirli kelimeleri arama ve bunların ardındaki anlamı anlama karışımını kullanırlar. Buna hibrit arama denir. Şimdi, bu karışımı kullanarak belgeleri bulmak için nasıl basit bir yol uygulayabileceğimizi görelim. [2]
BM25, bilgi arama sistemlerinde belgelerin belirli bir arama sorgusuyla ilişkisini tahmin etmek için kullanılan bir sıralama algoritmasıdır. Arama sözcüklerinizin bir belgede ne sıklıkla göründüğüne bakar ve en alakalı sonuçları sağlamak için belge uzunluğunu dikkate alır. Uzun belgelere veya çok kullanılan kelimelere önyargılı olmadan, dijital bir kütüphane gibi büyük belge koleksiyonlarını ayıklamak için mükemmeldir.[2]
BM25/Anahtar Kelime Araması Ne Zaman İdealdir?
- Büyük Belge Koleksiyonları: Çok sayıda bilgiyi sıralamanız gereken büyük veritabanları için mükemmeldir.[2]
- Önyargıyı Önleme: Terim sıklığı ve belge uzunluğu arasında denge kurmak için harikadır.[2]
- Genel Bilgi Erişimi: Çeşitli arama senaryolarında kullanışlıdır, basitlik ve etkinliğin bir karışımını sunar.[2]
BM25'in Temel Unsurları
- Terim Sıklığı (TF: Term Frequency): Bu, arama terimlerinizin bir belgede kaç kez göründüğünü sayar.[2]
- Ters Belge Sıklığı (IDF: Inverse Document Frequency): Bu, nadir terimlere daha fazla önem verir ve yaygın kelimelerin baskın olmamasını sağlar.[2]
- Belge Uzunluğu Normalizasyonu: Daha uzun belgelerin sonuçlara haksız yere hakim olmasını önleyen bir yöntemdir.[2]
- Sorgu Terim Doygunluğu (Query Term Saturation): Aşırı tekrarlanan terimlerin sonuçları çarpıtmasını önleyen bir yaklaşımdır.[2]
Pratik Uygulama: Hibrit Bir Arama Sistemi Oluşturma
Büyük bir dijital kütüphane için bir arama sistemi tasarladığınızı düşünün. Sadece belirli anahtar sözcükleri içeren belgeleri bulmasını değil, aynı zamanda her sorgunun arkasındaki bağlamı ve semantiği kavramasını da istersiniz. Şu şekilde çalışır [2]:
- Adım 1: BM25, arama anahtar kelimelerini içeren belgeleri hızlı bir şekilde getirir.[2]
- Adım 2: VectorDB bağlamsal olarak ilgili belgeleri bulmak için daha derin araştırmalar yapar.[2]
- Adım 3: Ensemble Retriever (Toplaşık Erişim) her iki sistemi de çalıştırır, bulgularını birleştirir ve sonuçları yeniden sıralayarak kullanıcıya ayrıntılı ve kapsamlı bir belge kümesi sunar.[2]
Hibrit Arama Nedir?
Hibrit arama, arama sonuçlarının doğruluğunu ve alakalılığını iyileştirmek için farklı yöntemleri veya yaklaşımları birleştiren bir arama tekniğidir. Bilgiye erişimş veya arama motorları bağlamında, hibrit arama genellikle daha kapsamlı ve kesin sonuçlar sunmak için anahtar kelime tabanlı aramayı ve anlamsal aramayı birleştirir.[2]
- Anahtar Kelime Araması: Aşina olduğumuz klasik yöntemdir. Bir kelime veya ifade aranırken, veritabanında veya belge koleksiyonunda bulunan terimlerin karakter kombinasyon bütününe veya yakından ilişkili olanlara odaklanılır.[2]
- Vektör Arama: Karşıtının aksine, vektör araması yalnızca kelimelerle yetinmez. Semantik anlam kullanarak çalışır ve sorgunun altta yatan bağlamını veya anlamını ayırt etmeyi amaçlar. Bu, kelimeleriniz bir belgeyle tam olarak eşleşmese bile, anlam alakalıysa, getirileceğini garanti eder.[2]
Örnek [2]:
# bm25 ve lancedb -hybrid aramanın kullanımına dair örnek
from langchain.vectorstores import LanceDB
import lancedb
from langchain.retrievers import BM25Retriever, EnsembleRetriever
from langchain.schema import Document
from langchain.embeddings.openai import OpenAIEmbeddings
from langchain.document_loaders import PyPDFLoader
# Yerleştirme işlemlerini başlat
embedding = OpenAIEmbeddings()
# tekli pdf'leri yükle
loader = PyPDFLoader("/content/Food_and_Nutrition.pdf")
pages = loader.load_and_split()
# BM25 erişicisini tanımlayalım
bm25_retriever = BM25Retriever.from_documents(pages)
bm25_retriever.k = 2 # En iyi 2 sonucu alalım
# Yoğun anlamsal arama/erişim için bir LanceDB vektör deposu oluşturalım
db = lancedb.connect('/tmp/lancedb')
table = db.create_table("pandas_docs", data=[
{"vector": embedding.embed_query("Hello World"), "text": "Hello World", "id": "1"}
], mode="overwrite")
# LanceDB alıcısını başlatalım
docsearch = LanceDB.from_documents(pages, embedding, connection=table)
retriever_lancedb = docsearch.as_retriever(search_kwargs={"k": 2})
# Şimdi her iki retriever'ı bir araya getirin, burada ağırlığı atayabilirsiniz.
# Toplaşık alıcısını başlat
ensemble_retriever = EnsembleRetriever(retrievers=[bm25_retriever, retriever_lancedb],
weights=[0.4, 0.6])
# Örnek müşteri sorgusu
query = "which food needed for building strong bones and teeth ?
which Vitamin & minerals importat for this?"
# İlgili belgeleri/ürünleri alın
docs = ensemble_retriever.get_relevant_documents(query)
# Bir topluluk arama motoru kullanarak, güçlü kemikler ve dişler gibi belgelerdeki her bir kelimeyi aramaya çalışır ve ayrıca benzerliğe göre en çok benzeyen belgeleri bulan lancedb'yi kullanarak arama yapar.from langchain.chat_models import ChatOpenAI
llm = ChatOpenAI(openai_api_key="sk-yourapikey")
# Ollama, Mistral gibi açık kaynaklı modelleri kullanmak istiyorsanız bunu kontrol edin
# https://github.com/lancedb/vectordb-recipes/blob/main/tutorials/chatbot_using_Llama2_&_lanceDB
qa = RetrievalQA.from_chain_type(llm=llm, chain_type="stuff", retriever=ensemble_retriever)
query = "what nutrition needed for pregnant women "
qa.run(query)
1.3 Farklı Vektör Veritabanı Teknolojilerinin Kısa Karşılaştırması
Vektör Veritabanları, büyük miktarlarda yüksek boyutlu vektör verilerini verimli bir şekilde depolamak, yönetmek ve dizinlemek için tasarlanmış veritabanları türüdür. Bu vektör veritabanları, makine öğrenimi modellerinin geçmiş girdileri hatırlaması için işi kolaylaştırmak için kullanılır ve bu da makine öğreniminin metin oluşturma, arama ve öneri için kullanılmasına olanak tanır. [2]
Bu nedenle, bu en iyi vektör veritabanları aynı zamanda yerleştirme modellerini işlevsel hale getirmek için belirli bir yöntem sağlar. Bu nedenle, bu makalede, geliştiriciler tarafından 2025'te kullanılabilecek en iyi 15 vektör veritabanlarının ayrıntılı bir genel bakışı sağlanmıştır. Ondan önce, vektör veritabanlarının ne olduğunu tartışalım. [2]
1.3.1 Vektör Veritabanları Nelerdir ve Nasıl Çalışırlar?
Vektör Veritabanları, vektörleştirilmiş verileri daha etkili bir şekilde işlemek için tasarlanmışlardır. Çok boyutlu alanda veri noktalarını yönetmede uzmanlaşmıştır, bu da onları Makine Öğrenmesi, Doğal Dil İşleme ve Yapay Zeka alanında daha iyi bir uygulama haline getirir. Bu vektör veri tabanlarının temel amacı, vektör yerleştirme benzerlik aramalarını ve yüksek boyutlu verilerin etkin bir şekilde işlenmesini kolaylaştırmaktır. [2]
Vektör veritabanları, yapay zeka ve makine öğrenimi uygulamalarında yüksek boyutlu vektör verilerini işlemek için olmazsa olmazdır. [2]
Vektör veritabanları genel olarak şu şekilde çalışırlar:
- Veri Alımı (Ingestion): Veriler alınır ve vektörlere, veri noktalarının sayısal gösterimlerine dönüştürülür. Örneğin, doğal dil işlemede, kelimeler veya cümleler word2vec veya BERT gibi yerleştirme teknikleri kullanılarak vektörlere dönüştürülür. [2]
- Vektör Yerleştirme: Veriler vektörlere dönüştürülerek orijinal verinin özü yüksek boyutlu bir uzayda yakalanır. [2]
- Dizinleme: Bu vektörler, KD ağaçları (k dimensional trees) veya Yerellik Duyarlı Karma (LSH: Locality-Sensitive Hashing) gibi yöntemler kullanılarak dizinlenir ve böylece verimli benzerlik aramaları sağlanır. [2]
- Benzerlik Araması: Sorgular, öneri sistemleri gibi uygulamalar için önemli olan Öklid uzaklığı veya kosinüs benzerliği gibi mesafe ölçümleri kullanılarak dizinli vektörlerle karşılaştırılır. [2]
- Erişim: En benzer vektörler hızla geri alınır ve bu sayede arama motorları ve yapay zeka destekli analizler gibi gerçek zamanlı uygulamalara olanak sağlanır. [2]
1.3.2 2025'in En İyi 15 Vektör Veritabanı
Yazılım geliştiricileri tarafından vektörleştirilmiş verileri verimli bir şekilde işlemek ve belirli vektör veritabanı özelliklerini kullanarak işi kolaylaştırmak için kullanılan birden fazla vektör veritabanı vardır. Her geliştiricinin 2025'te denemesi gereken en iyi vektör veritabanlarından bazıları aşağıda belirtilmiştir [3]:
1.3.2.1. Chroma
Chroma DB, Apache Lisansı 2.0 altında GitHub’da ücretsiz olarak sunulan açık kaynaklı vektör veritabanlarından biridir. Esas olarak doğal dil işleme (NLP) tarafından desteklenen Büyük Dil Modeli (LLM) uygulamalarının geliştirilmesini basitleştirmek için kullanılan lokalde yerleştirmek için tasarlanmıştır. Chroma, yoğunluk tahminleri, sorgular, filtreleme ve daha birçok özellik gibi yeteneklerle özellik açısından zengin bir ortam sağlamak için kullanılır. [3]
Temel Özellikler
- Chroma Python ve Javascript ile kullanılabilir. [3]
- Test, üretim ve geliştirme için benzer API’ler kullanılmaktadır. [3]
- Chroma’nın kod tabanı iyi organize edilmiş ve modülerdir, bu da yazılım geliştiricileri tarafından anlaşılmasını kolaylaştırır. [3]
- Chroma DB ayrıca vektör yerleştirmelerini depolamak için birden fazla yol sunar. [3]
1.3.2.2. Pinecone
Pinecone popüler vektör veritabanlarından biridir. Pinecone, esas olarak sorunsuz bir API ve zahmetsiz bir altyapı sunan bulut tabanlı bir vektör veritabanıdır. Pinecone ayrıca kullanıcının ihtiyacını ortadan kaldırmaya yardımcı olur ve kullanıcıların yapay zeka çözümlerini geliştirmeye ve genişletmeye odaklanmasını sağlar, ayrıca meta veri filtrelerini destekleme ve verileri işleme konusunda da mükemmeldir. [3]
Temel Özellikler
- Büyük veri kümelerini ve yüksek sorgu yüklerini yönetmek için Pinecone veritabanı kullanılır. [3]
- Bazı özellikleri yinelenen verilerin tespiti ve veri aramasıdır. [3]
- Pinecone ayrıca yüksek performanslı arama ve benzerlik eşleştirme özellikleri de sunmaktadır. [3]
- Diğer özelliklerinden bazıları ise veri tekrarlarını önleme ve sıralama takibidir. [3]
1.3.2.3. Deep Lake
Deep Lake, LLM tabanlı uygulamalara ve derin öğrenme için geliştirilmiş ünlü bir Yapay Zeka veritabanıdır. Bu nedenle, Deep Lake birden fazla veri türü için depolamayı destekler ve bu vektör veritabanı, eğitim sırasında veri akışı, sorgulama ve Llamalndex, LangChain ve daha birçok araçla entegrasyon gibi çeşitli özellikler sunar. [3]
Temel Özellikler
- Deep Lake çeşitli diğer araçlarla entegredir. [3]
- Sorgulama ve vektör araması Deep Lake vektör veritabanının bazı özellikleridir. [3]
- Deep Lake vektör veritabanı tüm veri tiplerini depolayabilir. [3]
- Eğitim sırasında veri akışı, veri versiyonlaması ve soyağacı (lineage) diğer özelliklerinden bazılarıdır. [3]
1.3.2.4. Vespa
Vespa, bir tür açık kaynaklı vektör veritabanıdır. Özellikle makine öğrenimi ile yapılan çıkarımlarla büyük miktarda veriyi düzenlemek, aramak ve depolamak için tasarlanmış bir veri hizmet motorudur. Vespa, yedeklilik konfigürasyonu, esnek sorgu ve sürekli yazma seçeneklerinde öne çıkan popüler vektör veritabanlarından biridir. [3]
Temel Özellikler
- Vespa yazmaları milisaniyeler içerisinde algılıyor ve ayrıca düğüm başına yüksek bir hızda yazmaya devam ediyor.[3]
- Vespa çoklu sorgu operatörlerini destekler.[3]
- Vespa ayrıca çıkarılan görüntünün yerleştirilmesine olanak tanır ve aynı zamanda etkili benzerlik aramalarına da olanak verir.[3]
- Yedeklilik konfigürasyonu (redundancy configuration)[3]
1.3.2.5. Milvus
Milvus, verimli benzerlik aramaları ve vektör yerleştirmesi için tasarlanmış bir diğer ünlü açık kaynaklı vektör veritabanıdır. Milvus, yapılandırılmamış veri aramasını basitleştirmek için kullanılır ve ayrıca birden fazla dağıtım ortamında daha iyi bir deneyim sağlar. Sohbet robotları, kimyasal yapı araması ve görüntü araması gibi uygulamalar için kullanılan en popüler vektör veritabanlarından biridir.[3]
Temel Özellikler
- Geliştiriciler Milvus’u kullanarak milisaniyeler içerisinde trilyonlarca vektör veri kümesinde arama yapabilirler.[3]
- Milvus basit yapılandırılmamış veri yönetiminden oluşur.[3]
- Bu vektör veritabanı son derece uyarlanabilir ve ölçeklenebilirdir.[3]
- Milvus veritabanı topluluk tarafından desteklenmektedir ve birleşik bir Lambda yapısına sahiptir.[3]
1.3.2.6. ScaNN
ScaNN, Scalable Nearest Neighbors olarak kısaltılır ve vektör benzerliğini daha etkili bir şekilde büyük ölçekte arama yöntemidir. Google’ın ScaNN’i ayrıca başlangıçta doğruluğu artıran yepyeni bir sıkıştırma yöntemi sunmuştur.[3]
Temel Özellikler
- Ayrıca, maksimum İç Çarpım araması için arama alanı daraltma ve nicemleme ile Öklid mesafesi gibi ek mesafe fonksiyonlarını içerir.[3]
- ScaNN, doğruluk ve sıkıştırmada bir artış sunar.[3]
- Büyük ölçekli vektör benzerliği aramalarını verimli bir şekilde gerçekleştirmek için kullanılır.[3]
- Ayrıca, vektör aramalarında verimlilik ve doğruluk arasındaki dengeyi sağlamak için de kullanılır.[3]
1.3.2.7. Weaviate
Weaviate de bir diğer ünlü açık kaynaklı vektör veritabanıdır. Dayanıklı, hızlı ve ölçeklenebilir bir bulut tabanlı veritabanıdır. Bu vektör veritabanı aracı, algoritmalar ve makine öğrenimi modelleri kullanarak fotoğrafları, metinleri ve birden fazla veriyi aranabilir bir vektör veritabanına dönüştürmek için kullanılır. Yazılım geliştiricileri, bu aracı, nihayetinde soru-cevap çıkarma, kategorileştirme ve özetleme sistemleri oluşturan içe aktarma işlemi sırasında verilerini vektörleştirmek için kullanırlar.[3]
Temel Özellikler
- Weaviate, yapay zeka destekli aramalar, otomatik kategorilendirme ve LLM’ler ile Soru-Cevap kombinasyonu için yerleşik modüllerden oluşur.[3]
- Bu vektör veritabanı, makine öğrenimi modellerini veritabanı kullanarak sorunsuz bir şekilde MLOps’a aktarmak için kullanılır.[3]
- Bu vektör veritabanı dağıtık ve bulut tabanlıdır.[3]
- Weaviate, Kubernetes üzerinde mükemmel bir şekilde çalışır.[3]
1.3.2.8. Qdrant
Qdrant, vektörler aramak, depolamak ve yönetmek için kullanımı kolay bir API ile üretime hazır bir hizmet sunan en iyi vektör veritabanlarından biridir. Qdrant vektör veritabanı, kapsamlı filtreleme desteği sağlamak için tasarlanmıştır. Qdrant’ın çok yönlülüğü, onu anlamsal eşleştirme veya sinir ağları için iyi bir seçenek haline getirir.[3]
Temel Özellikler
- Qdrant, sayısal aralıklar, metin eşleştirme, coğrafi konumlar ve daha birçok veri türü gibi geniş bir sorgu kriteri yelpazesini destekler.[3]
- Sorgu yürütmesini iyileştirmek için önbelleğe alınmış yük bilgilerini kullanan bir sorgu planlayıcısı vardır.[3]
- Qdrant işlevleri, orkestrasyon denetleyicilerinden veya harici veritabanlarından bağımsızdır.[3]
- Elektrik kesintileri sırasında önceden yazma özelliğine sahiptir.[3]
1.3.2.9. Vald
Vald, komşuları bulmaya yardımcı olmak için en hızlı ANN algoritması olan NGT’yi kullanan ölçeklenebilir, hızlı ve dağıtılmış bir vektör arama motorudur. Vald esas olarak dizin yedekleme, vektör dizinleme ve birden fazla özellik vektör verisi arasında arama yapmasına olanak tanıyan yatay ölçekleme sunar. Kullanımı kolaydır ve ayrıca son derece yapılandırılabilir.[3]
Temel Özellikler
- Kalıcılık (persistency) veya Nesne Depolama aracılığıyla Vald, otomatik yedekleme sağlar.[3]
- Vald, her biri benzersiz bir dizin tutan birden fazla ajana vektör dizinlerini dağıtmaya yardımcı olur.[3]
- Bu vektör veritabanı çeşitli programlama dillerini destekler.[3]
- Vald, son derece uyarlanabilir bir konfigürasyona sahiptir.[3]
1.3.2.10. Faiss
Faiss, hızlı ve yoğun vektör benzerliği araması ve gruplama için başka bir açık kaynaklı vektör veritabanıdır. Ayrıca, rastgele boyutlu vektör kümelerini aramak için çeşitli yöntemler içerir. Faiss vektör veritabanı, vektör kümesini koruyan ve ayrıca L2 veya nokta çarpım vektör karşılaştırması kullanarak bunlar arasında arama yapmak için kullanım sunan bir dizin türüne dayanır.[3]
Temel Özellikler
- Faiss, ağırlıklı olarak en küçük Öklid aramasından ziyade en büyük iç çarpım aramasını kullanır.[3]
- Belirtilen bir yarıçap içindeki tüm öğeleri sorgu konumuna göre döndürmeye yardımcı olur.[3]
- Faiss kullanarak kullanıcılar yalnızca bir vektör yerine birden fazla vektörü aynı anda arayabilir.[3]
- Faiss ayrıca birden fazla mesafe ölçütünü destekler.[3]
1.3.2.11. OpenSearch
OpenSearch, analitik, vektör arama ve klasik aramanın gücünü tek bir çözümde bir araya getiren başka bir vektör veritabanıdır. OpenSearch, yazılım geliştiricilerinin AI tarafından oluşturulan varlıkları yönetmesi, işletmeye alması ve entegre etmesi için gereken işi en aza indirerek YZ uygulama geliştirmeyi hızlandırmaya yardımcı olur.[3]
Temel Özellikler
- OpenSearch’in yardımıyla kullanıcılar, işbirlikçi filtreleme tekniklerini kullanarak ürün ve kullanıcı gömlemeleri oluşturabilir.[3]
- Veri kalitesi operasyonlarını desteklemek için OpenSearch kullanıcıları, benzerlik aramasını kullanarak veri çoğaltmayı ve desen eşleştirmeyi otomatikleştirebilir.[3]
- OpenSearch, vektör veri motorları, arama, kişiselleştirme ve veri kalitesi için kullanılır.[3]
- Anlamsal arama, üretken yapay zeka ajanları, çok modlu ve görsel arama, temel özelliklerinden bazılarıdır.[3]
1.3.2.12. Pgvector
Pgvector, vektör benzerliğini aramak ve ayrıca yerleştirmeleri tutmak için kullanılan bir PostgreSQL uzantısıdır. Pgvector ayrıca kullanıcıların tüm uygulama verilerini bir yerde depolamasına yardımcı olur ve kullanıcılar ACID uyumluluğu, JOIN’ler, zaman içinde kurtarma ve PostgreSQL’in diğer özelliklerinden yararlanabilir.[3]
Temel Özellikler
- Pgvector, tam ve yaklaşık en yakın komşu aramasını hesaplamak için kullanılır.[3]
- Ayrıca, PostgreSQL istemcisi olan herhangi bir dilde kullanılabilir.[3]
- İç çarpım, L2 mesafesi ve kosinüs mesafesini destekler.[3]
- Pgvector, kullanıcıların mevcut tablolara gömme sütunları eklemesine olanak tanır.[3]
1.3.2.13. Apache Cassandra
Apache Cassandra, birden fazla desteklenen emtia sunucusunda büyük miktarda veriyi işlemek ve aynı zamanda arıza olmadan yüksek kullanılabilirliği sürdürmek için tasarlanmış açık kaynaklı bir NoSQL veritabanı yönetim sistemidir. Apache Cassandra ayrıca, Float32 yerleştirmelerinin depolanmasına ve işlenmesine daha fazla olanak tanıyan yüksek boyutlu vektörlerin depolanmasını kolaylaştırmak için yeni bir veri türünden oluşur.[3]
Temel Özellikler
- Apache Cassandra, “VectorMemtableIndex” adı verilen yeni bir depolama ekli dizin (SAI) sunar.[3]
- Bu vektör veritabanı, Yaklaşık En Yakın Komşu (ANN) arama yeteneklerini de destekler.[3]
- Ayrıca, kullanıcıların veriler üzerinde ANN aramaları yapmasını kolaylaştırmak için yeni bir Cassandra Sorgu Dili (CQL) operatörü olan ANN OF’u sunar.[3]
- Zaten mevcut olan SAI çerçevesine bir uzantı olarak gelir.[3]
1.3.2.14. Elasticsearch
Elasticsearch, coğrafi, sayısal, yapılandırılmamış ve yapılandırılmış verileri işleyebilen, verileri yıldırım hızında arama ve kolayca ölçeklenebilen gelişmiş analitikler için depolamaya yardımcı olmak gibi çok çeşitli kullanım durumlarını ele almak üzere tasarlanmış bir tür açık kaynaklı, RESTful analitik motorudur.[3]
Temel Özellikler
- ElasticSearch, otomatik düğüm kurtarma ve veri yeniden dengeleme konusunda yardımcı olur.[3]
- ElasticSearch yardımıyla kullanıcılar, kümeleri güvenli tutmak için hataları tespit edebilir.[3]
- Elasticsearch, sıfırdan geliştirilen dağıtık mimaride çalışır.[3]
- Yüksek erişilebilirlik, kümelenme ve yatay ölçeklenebilirlik, bazı özelliklerindendir.[3]
1.3.2.15. ClickHouse
ClickHouse, kullanıcıların SQL sorguları çalıştırarak gerçek zamanlı olarak analitik raporlar üretmesini sağlayan çevrimiçi analitik işleme için sütun odaklı bir DBMS’dir. Gerçek sütun odaklı DBMS tasarımı ClickHouse’un önemli bir parçasıdır ve bu farklı tasarım, değerlere eşlik eden gerekli veriler olmadan kompakt bir depolama alanı sağlar ve bu da performansı daha da artırır.[3]
Temel Özellikler
- ClickHouse’un özelliklerinden biri, özellikle ClickHouse’un performansını artıran veri sıkıştırmasıdır.[3]
- ClickHouse, büyük sorguları hızlandırmak için çoklu sunucu ve çoklu çekirdek yapılandırmaları kullanır.[3]
- ClickHouse, güçlü SQL desteği sunar.[3]
- Verimli veri sıkıştırması, özelliklerinden biridir.[3]
En İyi Vektör Veritabanlarının Karşılaştırılması: Önemli Noktalar ve Kullanım Örnekleri
2. Gelişmiş RAG Yöntemleri
Basit RAG mimarisi faydalı olsa da, performansını artırmanın yolları mevcuttur. Sorgu genişletme (Query Expansion) tekniği, sorguyu yeniden yazarak daha doğrudan ilişkili belgeleri çekmeyi hedefler. Bu yaklaşımda iki temel teknik öne çıkar [4]:
- Çeşitlendirilmiş Sorgu Oluşturma: İsteğe bağlı sorgu, farklı ifadelerle veya kelimelerle yeniden formüle edilerek birden fazla sorguya dönüştürülür.[4]
- Cevap Tahmini: Beklenen cevabın nasıl görünebileceği önceden tahmin edilir. Bu sayede, belgeler koleksiyonunda sadece konuyu genel hatlarıyla ele alan değil, aynı zamanda bir cevap formatında sunulan içerikleri de bulma şansımız artar.[4]
Bu yöntemler, arama sonuçlarının kalitesini ve doğruluğunu artırmayı amaçlar.[4]
2.1 Sorgu Genişletme (Query Expansion)
Arama sistemlerinin hassasiyet oranını artırmak için yaygın kullanılan sorgu genişletme tekniğidir, bu çalışmada LLM’lerin üretken yeteneklerinden yararlanılarak ele alınmıştır. Geleneksel yöntemler, örneğin Sözde Alakalı Geri Bildirim (PRF: Pseudo-Relevance Feedback) gibi, iyi bir sözde alakalı belge kümesine dayanırken; önerilen yaklaşım, LLM’lerin yaratıcı gücünü ve modelin içerdiği bilgiyi kullanmaktadır. Çalışmada sıfır atış, az örnekli ve özellikle Chain-of-Thought (CoT) istemleri değerlendirilmiş; CoT istemlerinin, sorguları adım adım parçalara ayırarak orijinal sorguyla ilişkili çok sayıda terim üretebilmesi nedeniyle faydalı olduğu belirlenmiştir. MS-MARCO ve BEIR üzerinde yapılan deneyler, LLM’ler tarafından oluşturulan sorgu genişletmelerinin geleneksel yöntemlerden daha etkili olabileceğini göstermiştir.[4]
Makalenin adresi: [https://arxiv.org/abs/2305.03653]
Chroma kütüphanesi ile uygulanması [4]:
def augment_query_generated(query, model="gpt-3.5-turbo"):
messages = [
{
"role": "system",
"content": "You are a helpful expert financial research assistant. Provide an example answer to the given question, that might be found in a document like an annual report. "
},
{"role": "user", "content": query}
]
response = openai_client.chat.completions.create(
model=model,
messages=messages,
)
content = response.choices[0].message.content
return content
original_query = "Was there significant turnover in the executive team?"
hypothetical_answer = augment_query_generated(original_query)
joint_query = f"{original_query} {hypothetical_answer}"
print(word_wrap(joint_query))
results = chroma_collection.query(query_texts=joint_query, n_results=5, include=['documents', 'embeddings'])
retrieved_documents = results['documents'][0]
for doc in retrieved_documents:
print(word_wrap(doc))
print('')
retrieved_embeddings = results['embeddings'][0]
original_query_embedding = embedding_function([original_query])
augmented_query_embedding = embedding_function([joint_query])
projected_original_query_embedding = project_embeddings(original_query_embedding, umap_transform)
projected_augmented_query_embedding = project_embeddings(augmented_query_embedding, umap_transform)
projected_retrieved_embeddings = project_embeddings(retrieved_embeddings, umap_transform)
Birden Çok Sorguyla Genişletme (Expansion with Multiple Queries)
Ek sorgular önermek için LLM kullanılır. Orijinal ve yeni sorguların sonuçları alınır ve tüm yanıtlar LLM’e gönderilir. Bunu yaparak veri setimizin sorumuzun cevabını içeren diğer taraflarını da kapsamayı başarırız.[4]
Chroma kütüphanesi ile uygulanması [4]:
def augment_multiple_query(query, model="gpt-3.5-turbo"):
messages = [
{
"role": "system",
"content": "You are a helpful expert financial research assistant. Your users are asking questions about an annual report. "
"Suggest up to five additional related questions to help them find the information they need, for the provided question. "
"Suggest only short questions without compound sentences. Suggest a variety of questions that cover different aspects of the topic."
"Make sure they are complete questions, and that they are related to the original question."
"Output one question per line. Do not number the questions."
},
{"role": "user", "content": query}
]
response = openai_client.chat.completions.create(
model=model,
messages=messages,
)
content = response.choices[0].message.content
content = content.split("\n")
return content
original_query = "What were the most important factors that contributed to increases in revenue?"
augmented_queries = augment_multiple_query(original_query)
for query in augmented_queries:
print(query)
queries = [original_query] + augmented_queries
results = chroma_collection.query(query_texts=queries, n_results=5, include=['documents', 'embeddings'])
retrieved_documents = results['documents']
# Deduplicate the retrieved documents
unique_documents = set()
for documents in retrieved_documents:
for document in documents:
unique_documents.add(document)
for i, documents in enumerate(retrieved_documents):
print(f"Query: {queries[i]}")
print('')
print("Results:")
for doc in documents:
print(word_wrap(doc))
print('')
print('-'*100)
original_query_embedding = embedding_function([original_query])
augmented_query_embeddings = embedding_function(augmented_queries)
project_original_query = project_embeddings(original_query_embedding, umap_transform)
project_augmented_queries = project_embeddings(augmented_query_embeddings, umap_transform)
result_embeddings = results['embeddings']
result_embeddings = [item for sublist in result_embeddings for item in sublist]
projected_result_embeddings = project_embeddings(result_embeddings, umap_transform)
Çapraz Kodlayıcı Yeniden Sıralaması (Crossencoder Reranking)
Cross Encoder, gönderdiğimiz sorgu için erişim sonuçlarımızın alaka düzeyini yeniden sıralar. Yeniden sıralama, belirli bir sorguyla alaka düzeyine göre yeniden sıralamanın bir yoludur. Vektör veri tabanı sorgulanır ve ek sonuçlar istenir. Çıktı, en alakalı olanın en yüksek sıralamaya sahip olacağı şekilde yeniden sıralanır. En üst sıradaki sonuçlar seçilir. [4]
Chroma kütüphanesi ile uygulanması [4]:
from helper_utils import load_chroma, word_wrap, project_embeddings
from chromadb.utils.embedding_functions import SentenceTransformerEmbeddingFunction
import numpy as np
embedding_function = SentenceTransformerEmbeddingFunction()
chroma_collection = load_chroma(filename='microsoft_annual_report_2022.pdf', collection_name='microsoft_annual_report_2022', embedding_function=embedding_function)
chroma_collection.count()
Uzun Kuyruğu Yeniden Sıralama (Re-ranking the long tail)
Chroma kütüphanesi ile uygulanması [4]:
query = "What has been the investment in research and development?"
results = chroma_collection.query(query_texts=query, n_results=10, include=['documents', 'embeddings'])
retrieved_documents = results['documents'][0]
for document in results['documents'][0]:
print(word_wrap(document))
print('')
from sentence_transformers import CrossEncoder
cross_encoder = CrossEncoder('cross-encoder/ms-marco-MiniLM-L-6-v2')
pairs = [[query, doc] for doc in retrieved_documents]
scores = cross_encoder.predict(pairs)
print("Scores:")
for score in scores:
print(score) # First 2 docs have high score, all the second retrieved doc is much higher than the first one
print("New Ordering:")
for o in np.argsort(scores)[::-1]:
print(o+1)
Sorgu Genişletme ile Yeniden Sıralama (Re-ranking with Query Expansion)
Hepsini LLM’e göndermek yerine, artırılmış genişletilmiş sorgulardan orijinal sorgu için en iyi sonuçların tümünü elde etmek amacıyla çapraz sıralamayı kullanabiliriz. Bir yeniden sıralama modeli olarak bir çapraz kodlayıcı kullanmayı öğrendik ve yeniden sıralamayı hem tek bir sorgunun uzun kuyruk kısmından daha fazla yararlanmak için hem de genişletilmiş bir sorgunun sonuçlarını yalnızca orijinal sorguyla alakalı sonuçlara filtrelemek için nasıl uygulayabileceğimizi gördük. [4]
Chroma kütüphanesi ile uygulaması [4]:
original_query = "What were the most important factors that contributed to increases in revenue?"
generated_queries = [
"What were the major drivers of revenue growth?",
"Were there any new product launches that contributed to the increase in revenue?",
"Did any changes in pricing or promotions impact the revenue growth?",
"What were the key market trends that facilitated the increase in revenue?",
"Did any acquisitions or partnerships contribute to the revenue growth?"
]
queries = [original_query] + generated_queries
results = chroma_collection.query(query_texts=queries, n_results=10, include=['documents', 'embeddings'])
retrieved_documents = results['documents']
# Deduplicate the retrieved documents
unique_documents = set()
for documents in retrieved_documents:
for document in documents:
unique_documents.add(document)
unique_documents = list(unique_documents)
pairs = []
for doc in unique_documents:
pairs.append([original_query, doc])
scores = cross_encoder.predict(pairs)
print("Scores:")
for score in scores:
print(score)
print("New Ordering:")
for o in np.argsort(scores)[::-1]:
print(o)
Yerleştirme Adaptörleri (Embedding Adaptors)
Yerleştirme adaptörleri adı verilen bir teknik kullanarak erişim sisteminin performansını otomatik olarak iyileştirmek için, geri alınan sonuçların alaka düzeyine ilişkin kullanıcı geri bildirimlerini nasıl kullanabileceğimizi öğrenelim. Aptörleri yerleştirmek, daha iyi erişim sonuçları elde etmek için bir sorguyu doğrudan yerleştirmenin bir yoludur. Aslında, erişim sistemine, yerleştirme adaptörü adı verilen, yerleştirme modelinden sonra, ancak en alakalı sonuçları almadan önce gerçekleşen ek bir aşama ekleriz. Bir dizi sorgu için alınan sonuçlarımızın alaka düzeyine ilişkin kullanıcı geri bildirimlerini kullanarak yerleştirme adaptörümüzü eğitiriz. [4]
Chroma kütüphanesi ile uygulaması [4]:
from helper_utils import load_chroma, word_wrap, project_embeddings
from chromadb.utils.embedding_functions import SentenceTransformerEmbeddingFunction
import numpy as np
import umap
from tqdm import tqdm
import torch
embedding_function = SentenceTransformerEmbeddingFunction()
chroma_collection = load_chroma(filename='microsoft_annual_report_2022.pdf', collection_name='microsoft_annual_report_2022', embedding_function=embedding_function)
chroma_collection.count()
embeddings = chroma_collection.get(include=['embeddings'])['embeddings']
umap_transform = umap.UMAP(random_state=0, transform_seed=0).fit(embeddings)
projected_dataset_embeddings = project_embeddings(embeddings, umap_transform)
import os
import openai
from openai import OpenAI
from dotenv import load_dotenv, find_dotenv
_ = load_dotenv(find_dotenv()) # read local .env file
openai.api_key = os.environ['OPENAI_API_KEY']
openai_client = OpenAI()
Yerleştirme Adaptörleri İçin Veri Kümesi Oluşturmak
Bu yaklaşım için veri kümesine ihtiyacımız bulunur. RAG uygulamamızı kullanan kullanıcılarımız olmadığında elimizde veri bulunmaz. Bizim için bir veri kümesi oluşturması için bir modeli kullanabiliriz. Bunların hepsi doğru istemi oluşturmakla ilgilidir. Biz aslında modeli uzman yardımcı bir finansal araştırma asistanı olarak istemliyoruz. Bu, bir yıllık raporu analiz ederken sorulması gereken ve çıktının nasıl olması gerektiğini içeren 10 ila 15 kısa soruyu önermelidir. Bu, kullanıcıların aslında sistemimize karşı çalıştırmış olabileceği bazı sorguları üretecektir. [4]
def generate_queries(model="gpt-3.5-turbo"):
messages = [
{
"role": "system",
"content": "You are a helpful expert financial research assistant. You help users analyze financial statements to better understand companies. "
"Suggest 10 to 15 short questions that are important to ask when analyzing an annual report. "
"Do not output any compound questions (questions with multiple sentences or conjunctions)."
"Output each question on a separate line divided by a newline."
},
]
response = openai_client.chat.completions.create(
model=model,
messages=messages,
)
content = response.choices[0].message.content
content = content.split("\n")
return content
generated_queries = generate_queries()
for query in generated_queries:
print(query)
results = chroma_collection.query(query_texts=generated_queries, n_results=10, include=['documents', 'embeddings'])
retrieved_documents = results['documents']
def evaluate_results(query, statement, model="gpt-3.5-turbo"):
messages = [
{
"role": "system",
"content": "You are a helpful expert financial research assistant. You help users analyze financial statements to better understand companies. "
"For the given query, evaluate whether the following satement is relevant."
"Output only 'yes' or 'no'."
},
{
"role": "user",
"content": f"Query: {query}, Statement: {statement}"
}
]
response = openai_client.chat.completions.create(
model=model,
messages=messages,
max_tokens=1
)
content = response.choices[0].message.content
if content == "yes":
return 1
return -1
retrieved_embeddings = results['embeddings']
query_embeddings = embedding_function(generated_queries)
adapter_query_embeddings = []
adapter_doc_embeddings = []
adapter_labels = []
for q, query in enumerate(tqdm(generated_queries)):
for d, document in enumerate(retrieved_documents[q]):
adapter_query_embeddings.append(query_embeddings[q])
adapter_doc_embeddings.append(retrieved_embeddings[q][d])
adapter_labels.append(evaluate_results(query, document))
len(adapter_labels)
adapter_query_embeddings = torch.Tensor(np.array(adapter_query_embeddings))
adapter_doc_embeddings = torch.Tensor(np.array(adapter_doc_embeddings))
adapter_labels = torch.Tensor(np.expand_dims(np.array(adapter_labels),1))
dataset = torch.utils.data.TensorDataset(adapter_query_embeddings, adapter_doc_embeddings, adapter_labels)
Modelin ayarlanması [4]:
def model(query_embedding, document_embedding, adaptor_matrix):
updated_query_embedding = torch.matmul(adaptor_matrix, query_embedding)
return torch.cosine_similarity(updated_query_embedding, document_embedding, dim=0)
def mse_loss(query_embedding, document_embedding, adaptor_matrix, label):
return torch.nn.MSELoss()(model(query_embedding, document_embedding, adaptor_matrix), label)
# Initialize the adaptor matrix
mat_size = len(adapter_query_embeddings[0])
adapter_matrix = torch.randn(mat_size, mat_size, requires_grad=True)
min_loss = float('inf')
best_matrix = None
for epoch in tqdm(range(100)):
for query_embedding, document_embedding, label in dataset:
loss = mse_loss(query_embedding, document_embedding, adapter_matrix, label)
if loss < min_loss:
min_loss = loss
best_matrix = adapter_matrix.clone().detach().numpy()
loss.backward()
with torch.no_grad():
adapter_matrix -= 0.01 * adapter_matrix.grad
adapter_matrix.grad.zero_()
print(f"Best loss: {min_loss.detach().numpy()}")
Yerleştirme adaptörleri, sorgu yerleştirmelerini özel uygulamanıza göre özelleştirmeye yönelik güçlü ve basit bir tekniktir. Bunun işe yaraması için, burada oluşturduğumuza benzer sentetik bir veri kümesi veya kullanıcı verilerine dayalı bir veri kümesi toplamanız gerekir. Kullanıcı verileri genellikle en iyi sonucu verir çünkü bu, insanların erişim sisteminizi kendi özel görevleri için kullandıkları anlamına gelir. [4]
2.2 Chroma’nın Yerleştirme Adaptörleri İçin Teknik Raporu
Bu, Chroma’dan Suvansh Sanjeev ve Anton Troynikov tarafından hazırlanan ve yapay zeka uygulamalarında geniş etiketlenmiş veri setlerine ihtiyaç duymadan veya tüm yerleştirmelerin yeniden hesaplanmasına gerek kalmadan geri getirme doğruluğunu artırma zorluğunu ele alan teknik bir rapordur. Rapor, sorgu gömme alanını “sıkıştıran ve döndüren” basit, yalnızca sorguya özel lineer bir dönüşüm — bir matris çarpımı — uygulanmasının, ilgili belgeleri sorguya daha yakın hale getirip performansı artırabileceğini göstermektedir. Deneylerde, insan geri bildirimi ve etiketleme maliyetlerini düşürmek için rastgele örneklenmiş negatifler kullanılarak yalnızca 1.500 etiketli sorgu-belge çifti ile bu adaptör eğitildiğinde, CQADupstackEnglishRetrieval gibi veri setlerinde normalize edilmiş indirimli kümülatif kazanç (NDCG) %70'e kadar iyileşmiştir.[7]
Bu çalışma, lineer dönüşümler aracılığıyla yerleştirmeleri geliştirme fikrine dayanmakta ve geri getirme uygulamaları için pratik faydalarını titizlikle değerlendirmektedir. Kavramın yeni olmamasına rağmen, adaptasyonu yalnızca sorgu gömme üzerinde yoğunlaştırmanın belge temsilini bozulmadan koruduğunu vurgulamakta; bu da yaklaşımın hesaplama açısından verimli olmasını sağlamaktadır — özellikle nispeten küçük (~10.000) gömülü koleksiyonlarını işleyen sistemler için oldukça kullanışlıdır.[7]
Teknik yöntemin ötesinde, rapor çalışmasını temsil öğrenmesi ve geri getirmeyle desteklenen üretim (RAG) gibi daha geniş bir bağlam içerisinde konumlandırmaktadır. Yerleştirmelerin, yüksek boyutlu verileri temel semantiği yakalayan yoğun vektörlere sıkıştırarak transfer öğrenimini ve daha verimli sonraki işlemeyi mümkün kıldığını açıklamaktadır. RAG sistemlerinde, geliştirilmiş geri getirme kalitesi, daha alakalı bağlam sağlayarak üretken modellere doğrudan fayda sağlamaktadır. Ayrıca, rapor bu yerleştirmeleri daha da kişiselleştirmek ve iyileştirmek amacıyla insan geri bildirimini dahil etme potansiyelini de vurgulamaktadır.[7]
Yazarlar, yalnızca sorguya özel lineer adaptörleri, tam ince ayar gibi daha hesaplama yoğun alternatiflerle karşılaştırmakta ve performans açısından rekabetçi olduklarını tespit etmektedir. Tekrar üretimi ve ileri araştırmaları desteklemek amacıyla, tüm kodu, yardımcı araçları ve UC Berkeley Robotics and AI Lab’dan çıkan LaunchKit adlı hafif kütüphaneyi GitHub üzerinde yayımlamışlardır. Bu çalışmayı referans göstermekle ilgilenenler için, raporda tam bibliyografik referans sağlanmıştır.[7]
Alakalı Çalışmalar
Yerleştirmeler (embeddings) üzerinde doğrusal adaptörlerin daha önce araştırıldığı bilinmektedir. OpenAI tarafından hazırlanan Customizing Embeddings [https://cookbook.openai.com/examples/customizing_embeddings] adlı Jupyter Notebook, burada ortak doğrusal adaptör olarak adlandırdığımız bir adaptörün eğitilme sürecini göstermektedir. Bu yöntemde, yerleştirmeler aracılığıyla karşılaştırılan her iki öğe de aynı adaptör kullanılarak eşleştirilir. Ayrıca, bu çalışmada kullandığımız ve kaldırarak test ettiğimiz rastgele negatif örnekleme ile sentetik negatifler oluşturma yöntemini de ele almışlardır.[7]
Wang ve diğerlerinin “Improving Text Embeddings with Large Language Models” [https://arxiv.org/abs/2401.00368] adlı makalesinde, büyük dil modelleri (LLM) tarafından üretilen sentetik belgelerin, bilgi getirme (retrieval) görevleri için yerleştirmeleri ince ayar yapmak amacıyla nasıl kullanıldığı gösterilmekte ve bu yöntem Massive Text Embedding Benchmark (MTEB) bilgi getirme değerlendirme kriterlerinde test edilmektedir.[7]
SentenceTransformers [https://sbert.net/] paketi, önceden eğitilmiş yerleştirme modelleri ve bunları belirli görevler için ince ayar yapmaya yönelik bir çerçeve sunmaktadır. Bu paket, çeşitli ilgili yaklaşımları araştırmakta olup, özellikle Augmented SBERT yöntemine yer vermektedir. Bu yöntem, çapraz kodlayıcıları (cross-encoders) kullanarak veri sıralama modelleri (rerankers) ile veriyi zenginleştirip etiketleyerek çift kodlayıcıları (bi-encoders) eğitmek için kullanmaktadır. Yeniden sıralayıcılar (rerankers), belirli bir alana yönelik uyarlama için yüksek performans sağlayan ancak maliyeti yüksek bir yaklaşımdır.[7]
Yöntem
Bu proje boyunca, vektör yerleştirmeleri açıklamalı sorgu-belge çiftlerine uyarlayarak getirme (retrieval) performansını artırmaya yönelik aşağıdaki yaklaşımları inceledik:
- Yerleştirmeler üzerinde doğrusal adaptörler
- Ortak (Joint): Hem sorgular hem de belgeler aynı doğrusal adaptör ile eşlenir.
- Yalnızca sorgu (Query-only): Belge yerleştirmeleri olduğu gibi kalır, ancak sorgu yerleştirmelerini eşlemek için bir doğrusal adaptör eğitilir.
- Öncelikle sorgu (Query-first): Ortak bir doğrusal adaptör öğrenilir, ancak başlangıçta yalnızca sorgu adaptör ağırlıklarıyla başlatılır.
- Ayrı (Separate): Sorgular ve belgeler, ayrı doğrusal adaptörler ile eşlenir.
- İnce ayarlı (fine-tuned) yerleştirme modelleri
Çalışmamızda, hafif ve kolayca dağıtılabilir (deployable) yaklaşımlara odaklanıyoruz. Yayınladığımız kodda hem sentetik verilerin hem de yeniden sıralayıcı (reranker) modellerin kullanımını destekliyoruz, ancak bunlarla yalnızca ön deneyler yapıyoruz ve daha fazla keşif ve ayarlamayı gelecekteki çalışmalara bırakıyoruz.[7]
Deneysel Kurulum
Deneylerimizde, temel model olarak Sentence-Transformers kütüphanesinden önceden eğitilmiş all-MiniLM-L6-v2 yerleştirme modelini kullanıyoruz. Bu modelin üzerine adaptörler eğitiyor ve ince ayar yapıyoruz.[7]
Massive Text Embedding Benchmark (MTEB), getirme (retrieval) dahil çeşitli gömme görevleri için benchmark testlerini içermektedir. Deneylerimizde, benchmark değerlendirmeleri için mevcut MTEB altyapısını kullanıyor ve aşağıdaki getirme performans metriklerini takip ediyoruz:
- Ortalama Ortalama Hassasiyet (MAP — Mean Average Precision)
- Ortalama Karşılıklı Sıralama (MRR — Mean Reciprocal Rank)
- Normalleştirilmiş İndirimli Kümülatif Kazanç (NDCG — Normalized Discounted Cumulative Gain)
- Kesinlik (Precision)
- Duyarlılık (Recall)
Hiperparametre tarama ve deney başlatma altyapımız RLKit’ten uyarlanmış olup, LaunchKit adlı ayrı bir hafif depo olarak yayınlanmıştır. Kullanımı, depo içerisindeki README dosyasında belgelenmiştir. LaunchKit, deney günlüklerini VisKit ile uyumlu şekilde kaydeder. VisKit, hiperparametre taramalarının sonuçlarını incelemek ve görselleştirmek için kapsamlı özelliklere sahip hafif bir görselleştirme betiğidir.[7]
Ayrıca, verilerimizi hem negatif hem de pozitif belgelerle zenginleştirmek amacıyla rastgele negatif örnekleme yöntemini inceliyoruz. Getirme performansını değerlendirmek için farklı miktarlardaki eğitim verilerinin etkisini anlamak adına veri azaltma (data ablation) deneyleri yapıyoruz. Son olarak, ayrılmış test verisi (held-out test data) üzerinde getirme performansını değerlendiren deneyler gerçekleştiriyoruz. [7]
Hiperparametreler
Aşağıda, üzerinde gezindiğimiz hiperparametreler ve değerler için bir kılavuz bulunmaktadır. Bazı yinelemeli iyileştirmelerin gerçekleştirildiğini unutmayın, bu nedenle bu hiperparametre kümelerinin kartezyen ürünündeki her kombinasyonu çalıştırmadık. [7]
Sonuçlar
Bu bölümde, uyum sürecini daha iyi anlamamızı sağlayan temel sonuçları ve birkaç ablation çalışmasını gözden geçiriyoruz. Sonuçlar çeşitli MTEB kıyaslamalarından elde edilmiştir ve daha geniş kapsama alanı ve sıralama ağırlığı nedeniyle ortalama hassasiyet (MAP) ve normalize edilmiş indirimli kümülatif kazanç (NDCG) özet sonuçlar olarak seçilmiştir. Diğer metriklere ait grafikler ise depoda mevcuttur. Doğrusal adaptörler (linear adapters) ve ince ayar (finetuning) için elde ettiğimiz sonuçlar aşağıda gösterilmiştir. Test edilen tüm yaklaşımlar, önceden eğitilmiş gömme (embedding) modelinin temel performansını aşmaktadır. CQADupstackEnglishRetrieval kıyaslamasında, yalnızca sorguyu kullanan doğrusal adaptörün en iyi performansı gösterdiğini ve hatta ince ayar yöntemini bile geçtiğini tespit ettik.[7]
Kullandığımız gömme modeli olan all-MiniLM-L6-v2, İngilizce metinler üzerinde eğitildiğinden, İspanyolca bir kıyaslama olan SpanishPassageRetrievalS2S üzerinde göreceli olarak daha düşük performans göstermektedir. Ancak, doğrusal bir adaptörün bu kıyaslamada kayda değer bir performans sağlamak için yeterli olduğunu görüyoruz. En iyi sonucu ince ayar yaklaşımı verirken, doğrusal adaptörler de buna oldukça yakındır. Yine, yalnızca sorgu tabanlı doğrusal adaptör diğer yaklaşımlarla rekabet edebilecek düzeyde performans göstermektedir. İki dilin yapısal benzerlikleri nedeniyle, öğrenilen temsillerin doğrusal bir dönüşümle benzer bir yapıya sahip olduğunu ve doğrusal adaptörün bu dönüşümü öğrenebildiğini düşünüyoruz.[7]
Son olarak, Korece bir kıyaslama olan Ko-miracl üzerinde dil farkının, uyum sağlama yaklaşımlarımız için aşılması zor bir engel olduğunu görüyoruz. Ancak, temel modele kıyasla sağlanan iyileşme yine de dikkate değerdir ve yalnızca sorgu tabanlı adaptör, ince ayar yapılmış gömme modelinden bile daha iyi performans göstermektedir.[7]
Bu deneyler, doğrusal adaptörlerin potansiyelini ortaya koymaktadır; çünkü bu adaptörler birçok uygulama için çok daha hafif ve hesaplama açısından verimli olmalarına rağmen, ince ayar ile rekabet edebilecek düzeyde görünmektedir. Kalan deneylerimizde doğrusal adaptörleri çeşitli ablation çalışmalarıyla daha ayrıntılı olarak incelemeye odaklanıyoruz. [7]
Negatif Örnekleme
Negatif örneklemenin etkisini göstermek için iki farklı kıyaslamada iki tür doğrusal adaptör sunuyoruz. Kalan grafikler [https://github.com/suvansh/ChromaAdaptEmbed/tree/main/images/negative_ablation/] reposunda bulunabilir. Sonuç kesindir ve uzun zamandır önceki çalışmalar tarafından belirlenmiştir ilgisiz belgelerden rastgele örnekleme yoluyla negatif örnekleme, alma performansında önemli bir fayda elde etmenin ucuz bir yoludur. [7]
Veri Ablasyonu
Eğitim verisi miktarını değiştirmenin erişim performansı ölçümleri üzerindeki etkisini inceliyoruz. Kesikli çizgiler, önceden eğitilmiş yerleştirme modeli temel çizgisinin tüm veri kümesindeki performansını göstermektedir. Kırılma noktasının verilerin yaklaşık %20–35'i olduğunu görebiliriz. Eğittiğimiz alt kümeler rastgele örneklemlenmiştir, bu nedenle seçici örnekleme yoluyla daha yüksek veri verimliliği elde edilebilir. Ek olarak, y eksenlerinin ölçeğine dikkat ederek, burada kullanılan iki kıyaslama ölçütü arasında doygunluk eğrisinin çok farklı göründüğünü görüyoruz. Yerleştirme bağdaştırıcılarını eğitmek için, söz konusu belirli veri kümesinin özellikleri, verilerin daha büyük bir oranını kullanmanın getirilerini büyük ölçüde etkileme olasılığı yüksektir. Sezgimiz, daha fazla heterojenliğe sahip veri kümelerinin daha dik eğriler ürettiği ve daha büyük bir veri oranı üzerinde eğitim almayı hak ettiği yönündedir, ancak bu heterojenlik kavramının önceden nasıl değerlendirilebileceği net değildir. Bu eğrileri verilerin kolayca ölçülebilen özellikleriyle ilişkilendirmek, gelecekteki çalışmalar için muhtemelen ilginç bir yöndür. Büyük Dil Modelleri (LLM) eğitimleri için eğitim verilerinin karmaşıklığını ölçmek amacıyla gzip’i bir aracı olarak kullanmanın son sonuçları yararlı bir başlangıç noktası olabilir. [7]
Bekletme (Holdout) Performansı
Birden fazla veri bölümü içeren kıyaslamalardan seçim yapıyoruz ve yalnızca eğitim bölümü üzerinde eğitildikten sonra tutulan verilerdeki doğrusal bağdaştırıcıların alma performansını değerlendiriyoruz. Beklendiği gibi, tutulan verilerde performans kazanımları daha mütevazı. Özellikle, yalnızca sorgu bağdaştırıcısının burada birleşik doğrusal bağdaştırıcıya kıyasla hafif bir dezavantajda olduğu görülüyor. Ancak, yine de önceden eğitilmiş gömme modeli temel çizgisini geçiyor ve gerçek dünya dağıtımı için umut vadediyor.[7]
Tartışmalar
Çeşitli eşiklerde birçok geri çağırma performans metriğini değerlendirerek en iyi sonuçları elde etmek için hiperparametreleri taradık ve bazı belirgin desenler gözlemledik: Ortalama karesel hata, ikili çapraz entropiyi geride bırakırken, üçlü kayıp farklı senaryolarda öne çıktı. Doğrusal adaptörler için 0.003 öğrenme oranı genellikle en iyi sonuçları verdi ve yalnızca sorguya yönelik doğrusal adaptörün tutarlı bir şekilde etkili olduğu görüldü, bu da kişiselleştirilmiş geri çağırma için heyecan verici bir potansiyel sunuyor. Bu adaptörün ölçeklenebilirliği, kullanıcı bazında özelleştirilmiş sorgu adaptörlerinin düşük hesaplama maliyetiyle canlı sistemlerde eğitilmesine olanak tanıyor. Veri azaltma analizinde, iyileşme oranının veri miktarına göre değiştiğini ve bunun heterojenlik ile ilişkili olabileceğini düşündük. Ayrıca, sıralama modelini eğiterek performansı artırmayı denedik, ancak veri yetersizliği nedeniyle kararsız sonuçlar elde ettik. Sentetik veri kullanımını da inceledik, ancak bu yöntemin, gerçekçi veri üretme zorlukları nedeniyle anlamlı bir iyileşme sağlamadığını gördük. Gelecekte, doğrusal adaptörler yerine daha karmaşık sinir ağı tabanlı adaptörlerin kullanılması, kullanıcı tercihlerine daha iyi uyum sağlayarak geri çağırma performansını artırabilir. Ancak, daha yüksek temsil gücü, daha fazla eğitim verisi gerektirebilir. Bu tekniklerin daha fazla araştırılması, kişiselleştirilmiş geri çağırma sistemlerinin geliştirilmesine önemli katkılar sağlayabilir. [7]
Diğer Gelişmiş RAG Yöntemleri
RAG hala farklı teknikleri deneyebileceğiniz yeni bir alandır. Örneğin, yerleştirme laboratuvarında kullandığımızla aynı veri türünü kullanarak doğrudan yerleştirme modelinde ince ayar yapabilirsiniz. Ayrıca erişmek için llm’de ince ayar yapabilirsiniz (RA-DIT ve InstructRetro makalelerine bakın). Ek olarak, tam gelişmiş bir sinir ağı ve hatta bir transformatör katmanı kullanarak karmaşık bir yerleştirme adaptör modeliyle denemeler yapabilirsiniz. Benzer şekilde, yalnızca çapraz kodlayıcıyı kullanmak yerine bir alaka modelleme modeli kullanabilirsiniz. Son olarak, gözden kaçırılan kısım, alınan sonuçların kalitesinin genellikle verilerinizin, alma sisteminde depolanmadan önce nasıl parçalandığına bağlı olmasıdır. [4]
3. Graph RAG: RAG Sistemleriyle Bilgi Grafiği Entegrasyonu
Bilgi grafikleri, özellikle karmaşık soruların çözümünde veya soruya cevap vermeden önce anlaşılması gereken gizli bağlamlar olduğunda çok faydalıdır. İşte nasıl çalıştığına dair basit bir örnek [5]:
Örnek Durum:
- Bir seyahat acentesi platformunuz var.[5]
- Platformunuzda, seyahat destinasyonlarını içeren bir vektör veritabanı bulunuyor.[5]
- Ayrıca, her kullanıcının daha önce ziyaret ettiği yerleri içeren bir bilgi grafiğiniz var.[5]
Diyelim ki bir kullanıcı şöyle soruyor:
“Temmuz ayı için Asya’da, daha önce ziyaret etmediğim plaj destinasyonlarını önerir misiniz?” [5]
Bu durumda, bilgi grafiğini kullanarak:
- Kullanıcının geçmişte ziyaret ettiği yerleri belirleyebilir ve semantik arama sonuçlarından bu yerleri çıkarabilirsiniz.[5]
- Aynı zamanda, destinasyonların coğrafi konumu, aylık iklim durumu gibi gerçek verileri de bilgi grafiğine ekleyebilirsiniz. Böylece, anlamsal arama ve LLM (büyük dil modeli) çıkarımlarına çok fazla güvenmeden, daha sağlam ve güvenilir cevaplar üretebilirsiniz.[5]
RAG (Retrieval Augmented Generation) Sürecinde Bilgi Grafikleri Kullanmanın Avantajları:
- Bağlamsal Uygunluk:
Bilgi grafiği, çektiğiniz bilgilerin hem alakalı hem de bağlam açısından uygun olmasını sağlar. Bu, soruya daha zengin ve doğru bir arka plan sunar.[5] - Karmaşık Sorguların Çözümü:
Bilgi grafikleri, verileri birbirine bağlayan ilişkileri gösterdiğinden, karmaşık sorguların derinlemesine anlaşılmasına ve etkin şekilde işlenmesine olanak tanır.[5] - Veri Entegrasyonu:
Farklı veri türlerini birleştirmede başarılı olan bilgi grafikleri, RAG yanıtlarını daha kapsamlı ve bütünsel bir hale getirir.[5]
Bu şekilde, bilgi grafikleri hem sorguların daha iyi anlaşılmasına hem de daha doğru ve bağlamlı cevapların üretilmesine yardımcı olur.[5]
3.1 Bilgi Grafiklerinin Temelleri
Düğümler, Bilgi Grafiklerindeki veri kayıtlarıdır. Düğümler arasındaki ilişkiler de veri kayıtlarıdır. Hem düğümlerin hem de ilişkilerin özellikleri vardır. Düğümlere onları gruplandırmak için etiketler verilebilir. İlişkilerin her zaman bir tipi ve yönü vardır.[5]
İlişkilerin her zaman bir türü ve yönü vardır. Düğümler arasında ilişkiler vardır ve her ilişkinin özellikleri vardır. Grafik türleri birleştirilebilirlerdir. Resmi olarak düğümler ve ilişkiler, anahtar/değer özelliklerine sahip veri kayıtlarıdırlar.[5]
Bilgi Grafiklerinde Bilgi Sorgulama (Querying in Knowledge Graphs)
Cypher sorgulama dili kullanarak bilgi grafikleri ile nasıl etileşime geçebileceğimizi öğreneceğiz.[5]
from dotenv import load_dotenv
import os
from langchain_community.graphs import Neo4jGraph
load_dotenv('.env', override=True)
NEO4J_URI = os.getenv('NEO4J_URI') # host address and port
NEO4J_USERNAME = os.getenv('NEO4J_USERNAME')
NEO4J_PASSWORD = os.getenv('NEO4J_PASSWORD')
NEO4J_DATABASE = os.getenv('NEO4J_DATABASE') # Which db in the db nest
# Langchain kullanarak Neo4j başlatabiliriz
kg = Neo4jGraph(
url=NEO4J_URI, username=NEO4J_USERNAME, password=NEO4J_PASSWORD, database=NEO4J_DATABASE
)
Neo4j, verileri yerel olarak saklayıp yönetmenizi sağlayan bir grafik veritabanıdır. Bu yapı, hem geçiş performansı hem de işlem süresi açısından avantaj sağlayan özellik grafiği yaklaşımını benimser. Başlangıçta sadece bir grafik veritabanı olarak ortaya çıkan Neo4j, günümüzde çok sayıda araç, uygulama ve kütüphaneden oluşan zengin bir ekosisteme dönüşmüştür. Bu ekosistem, grafik teknolojilerini çalışma ortamınıza sorunsuzca entegre etmenize imkan tanır. Neo4j’yi nasıl kurup dağıtabileceğinizi öğrenmek için Neo4j belgelerine göz atabilirsiniz: [https://neo4j.com/docs/getting-started/get-started-with-neo4j/#neo4j-docs]. [5]
Person ve Movie diye iki düğüm olsun ve aşağıda gösterildiği şekilde alanları bulunsun [5]:
Cypher sorgu örnekleri:
# 1. Count all nodes in the graph.
cypher = """
MATCH (n)
RETURN count(n)
"""
result = kg.query(cypher)
print("Total nodes (default alias):", result) # e.g., [{'count(n)': 171}]
# 2. Count all nodes with alias 'numberOfNodes'.
cypher = """
MATCH (n)
RETURN count(n) AS numberOfNodes
"""
result = kg.query(cypher)
print("Total nodes (with alias):", result)
print(f"There are {result[0]['numberOfNodes']} nodes in this graph.")
# 3. Match only Movie nodes.
cypher = """
MATCH (n:Movie)
RETURN count(n) AS numberOfMovies
"""
result = kg.query(cypher)
print("Number of Movie nodes:", result) # e.g., [{'numberOfMovies': 38}]
# 4. Use a different variable name for clarity.
cypher = """
MATCH (m:Movie)
RETURN count(m) AS numberOfMovies
"""
result = kg.query(cypher)
print("Number of Movie nodes (using 'm'):", result)
# 5. Match only Person nodes.
cypher = """
MATCH (people:Person)
RETURN count(people) AS numberOfPeople
"""
result = kg.query(cypher)
print("Number of Person nodes:", result) # e.g., [{'numberOfPeople': 133}]
# 6. Match a specific Person by name ("Tom Hanks").
cypher = """
MATCH (tom:Person {name:"Tom Hanks"})
RETURN tom
"""
result = kg.query(cypher)
print("Details for Tom Hanks:", result)
# 7. Match a specific Movie by title ("Cloud Atlas").
cypher = """
MATCH (cloudAtlas:Movie {title:"Cloud Atlas"})
RETURN cloudAtlas
"""
result = kg.query(cypher)
print("Details for Cloud Atlas movie:", result)
# 8. Return only the 'released' property of the "Cloud Atlas" movie.
cypher = """
MATCH (cloudAtlas:Movie {title:"Cloud Atlas"})
RETURN cloudAtlas.released AS releasedYear
"""
result = kg.query(cypher)
print("Cloud Atlas released year:", result)
# 9. Return two properties: 'released' and 'tagline' for "Cloud Atlas".
cypher = """
MATCH (cloudAtlas:Movie {title:"Cloud Atlas"})
RETURN cloudAtlas.released AS releasedYear, cloudAtlas.tagline AS tagline
"""
result = kg.query(cypher)
print("Cloud Atlas released year and tagline:", result)
# 10. Conditional matching: Find movies released in the 1990s.
cypher = """
MATCH (nineties:Movie)
WHERE nineties.released >= 1990 AND nineties.released < 2000
RETURN nineties.title AS title
"""
result = kg.query(cypher)
print("Movies released in the 90s:", result)
# 11. Match multiple nodes: Find actors and the movies they acted in.
cypher = """
MATCH (actor:Person)-[:ACTED_IN]->(movie:Movie)
RETURN actor.name AS actorName, movie.title AS movieTitle LIMIT 10
"""
result = kg.query(cypher)
print("Actor and movie pairs (first 10):", result)
# 12. Find movies acted in by "Tom Hanks".
cypher = """
MATCH (tom:Person {name: "Tom Hanks"})-[:ACTED_IN]->(tomHanksMovies:Movie)
RETURN tom.name AS actorName, tomHanksMovies.title AS movieTitle
"""
result = kg.query(cypher)
print("Movies featuring Tom Hanks:", result)
# 13. Find co-actors who acted with Tom Hanks.
cypher = """
MATCH (tom:Person {name:"Tom Hanks"})-[:ACTED_IN]->(m)<-[:ACTED_IN]-(coActors)
RETURN coActors.name AS coActorName, m.title AS movieTitle
"""
result = kg.query(cypher)
print("Co-actors of Tom Hanks and corresponding movies:", result)
# 14. Display Emil Eifrem's ACTED_IN relationships.
cypher = """
MATCH (emil:Person {name:"Emil Eifrem"})-[actedIn:ACTED_IN]->(movie:Movie)
RETURN emil.name AS name, movie.title AS movieTitle
"""
result = kg.query(cypher)
print("Emil Eifrem ACTED_IN relationships:", result)
# 15. Delete Emil Eifrem's ACTED_IN relationships.
cypher = """
MATCH (emil:Person {name:"Emil Eifrem"})-[actedIn:ACTED_IN]->(movie:Movie)
DELETE actedIn
"""
kg.query(cypher)
print("Deleted Emil Eifrem's ACTED_IN relationships.")
# 16. Create a new Person node "Andreas".
cypher = """
CREATE (andreas:Person {name:"Andreas"})
RETURN andreas
"""
result = kg.query(cypher)
print("Created Person node 'Andreas':", result)
# 17. Create a WORKS_WITH relationship between Andreas and Emil Eifrem.
cypher = """
MATCH (andreas:Person {name:"Andreas"}), (emil:Person {name:"Emil Eifrem"})
MERGE (andreas)-[hasRelationship:WORKS_WITH]->(emil)
RETURN andreas, hasRelationship, emil
"""
result = kg.query(cypher)
print("Created WORKS_WITH relationship between Andreas and Emil Eifrem:", result)
3.2 RAG İçin Metin Hazırlama
"""
Script for preparing text for RAG (Retrieval-Augmented Generation)
by creating and populating a vector index for movie taglines in Neo4j.
"""
from dotenv import load_dotenv
import os
from langchain_community.graphs import Neo4jGraph
# Load environment variables from the .env file
load_dotenv('.env', override=True)
NEO4J_URI = os.getenv('NEO4J_URI')
NEO4J_USERNAME = os.getenv('NEO4J_USERNAME')
NEO4J_PASSWORD = os.getenv('NEO4J_PASSWORD')
NEO4J_DATABASE = os.getenv('NEO4J_DATABASE')
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
OPENAI_ENDPOINT = os.getenv('OPENAI_BASE_URL') + '/embeddings'
# Connect to the Neo4j knowledge graph using LangChain
kg = Neo4jGraph(
url=NEO4J_URI,
username=NEO4J_USERNAME,
password=NEO4J_PASSWORD,
database=NEO4J_DATABASE
)
# 1. Create a vector index for movie tagline embeddings (if it doesn't exist)
kg.query("""
CREATE VECTOR INDEX movie_tagline_embeddings IF NOT EXISTS
FOR (m:Movie) ON (m.taglineEmbedding)
OPTIONS { indexConfig: {
`vector.dimensions`: 1536,
`vector.similarity_function`: 'cosine'
}}
""")
# 2. Display the current vector indexes
indexes = kg.query("SHOW VECTOR INDEXES")
print("Vector Indexes:", indexes)
# 3. Populate the vector index:
# Calculate a vector representation for each movie tagline using OpenAI,
# and store it as a property 'taglineEmbedding' on each Movie node.
kg.query("""
MATCH (movie:Movie) WHERE movie.tagline IS NOT NULL
WITH movie, genai.vector.encode(
movie.tagline,
"OpenAI",
{
token: $openAiApiKey,
endpoint: $openAiEndpoint
}
) AS vector
CALL db.create.setNodeVectorProperty(movie, "taglineEmbedding", vector)
""", params={"openAiApiKey": OPENAI_API_KEY, "openAiEndpoint": OPENAI_ENDPOINT})
# 4. Retrieve and display one movie's tagline and its vector embedding
result = kg.query("""
MATCH (m:Movie)
WHERE m.tagline IS NOT NULL
RETURN m.tagline, m.taglineEmbedding
LIMIT 1
""")
if result:
movie_tagline = result[0]['m.tagline']
embedding = result[0]['m.taglineEmbedding']
print("Tagline:", movie_tagline)
print("Embedding (first 10 values):", embedding[:10])
print("Embedding length:", len(embedding))
else:
print("No movie taglines found.")
3.3 Benzerlik Araması (Similarity Search)
OPENAI_API_KEY = "your_openai_api_key_here"
OPENAI_ENDPOINT = "https://api.openai.com/v1" # or your custom endpoint
def similarity_search(kg, question, top_k=5):
"""
Performs a similarity search on movie tagline embeddings for the given question.
Parameters:
kg: The knowledge graph client instance with a .query() method.
question (str): The question to encode and search against.
top_k (int): The number of top results to return.
Returns:
List of query results with movie titles, taglines, and similarity scores.
"""
query = """
WITH genai.vector.encode(
$question,
"OpenAI",
{
token: $openAiApiKey,
endpoint: $openAiEndpoint
}
) AS question_embedding
CALL db.index.vector.queryNodes(
'movie_tagline_embeddings',
$top_k,
question_embedding
) YIELD node AS movie, score
RETURN movie.title, movie.tagline, score
"""
params = {
"openAiApiKey": OPENAI_API_KEY,
"openAiEndpoint": OPENAI_ENDPOINT,
"question": question,
"top_k": top_k
}
return kg.query(query, params=params)
if __name__ == "__main__":
# Assume `kg` is your pre-initialized knowledge graph client.
# For example, it might be created using a library like py2neo or another custom client.
# from your_kg_module import kg
#
# For this example, we assume that `kg` is already available.
# Query 1: Movies about love
question1 = "What movies are about love?"
print("Querying for movies about love:")
results1 = similarity_search(kg, question1)
for result in results1:
print(result)
# Query 2: Movies about adventure
question2 = "What movies are about adventure?"
print("\nQuerying for movies about adventure:")
results2 = similarity_search(kg, question2)
for result in results2:
print(result)
3.4 Metin Belgelerinden Belge Grafiği Oluşturma
10K Formu JSON: Halka açık şirketlerin her yıl Menkul Kıymetler ve Borsa Komisyonu (SEC) ile bir Form 10-K doldurmaları gerekmektedir. SEC’in EDGAR veritabanını kullanarak bu dosyalarda arama yapabilirsiniz [https://www.sec.gov/edgar/search/]. Önümüzdeki birkaç ders boyunca, NetApp adlı bir şirket için tek bir 10-K formu ile çalışacaksınız.[5]
Veri Temizleme
10-K formları, XML bileşenleri içeren metin dosyaları olarak indirilebilir. Bu dosyalarla çalışmak için şu adımları izleyebilirsiniz:
Dosya Temizleme: Regex (düzenli ifadeler) kullanarak dosyaları temizleyin.[5]
XML Ayrıştırma: Beautiful Soup kullanarak XML içeriğini Python veri yapılarına dönüştürün.[5]
Merkezi Dizin Anahtarı (CIK: Central Index Key)Bilgisini Çıkarma: SEC tarafından kullanılan şirket tanımlayıcısı olan CIK bilgisini dosyadan çıkarın.[5]
Belirli Bölümlerin Ayıklanması: Formdaki Madde 1, 1a, 7 ve 7a gibi belirli bölümleri ayıklayın.[5]
Yaklaşım:
- Metin Bölme:
Langchain metin ayırıcı kullanarak formu parçalara bölün.[5] - Grafik Oluşturma:
Her bölümü (yığın) bir düğüm olarak kabul edip, bu düğümlere ilgili meta verileri ekleyerek bir grafik oluşturun.[5] - Vektör Dizini:
Bir vektör dizini oluşturun ve her bölüm için metin gömme (embedding) vektörünü hesaplayıp dizine ekleyin.[5] - Benzerlik Araması:
İlgili parçaları bulmak için bu vektör dizini üzerinde benzerlik araması gerçekleştirin.[5]
#!/usr/bin/env python3
"""
Bu script, bir Form 10-K JSON dosyasını okuyup, belirli bölümlerindeki metni LangChain kullanarak parçalara ayırır,
bu parçaları Neo4j grafik veritabanında düğüm olarak ekler, vektör dizini oluşturur,
ve benzerlik araması ile RAG (Retrieval-Augmented Generation) tabanlı soru-cevap işlemleri gerçekleştirir.
"""
import json
import textwrap
# LangChain importları
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chat_models import ChatOpenAI
from langchain.chains import RetrievalQAWithSourcesChain
from langchain.embeddings import OpenAIEmbeddings
# Neo4j ve vektör arama ile ilgili kütüphane importları (ortamınıza göre uyarlayın)
from some_neo4j_module import Neo4jGraph, Neo4jVector
# =============================================================================
# KONFIGÜRASYON AYARLARI (Lütfen kendi ortamınıza göre düzenleyin)
# =============================================================================
NEO4J_URI = "bolt://localhost:7687"
NEO4J_USERNAME = "neo4j"
NEO4J_PASSWORD = "password"
NEO4J_DATABASE = "neo4j"
OPENAI_API_KEY = "your_openai_api_key"
OPENAI_ENDPOINT = "https://api.openai.com"
VECTOR_INDEX_NAME = "form_10k_chunks"
VECTOR_NODE_LABEL = "Chunk"
VECTOR_SOURCE_PROPERTY = "text"
VECTOR_EMBEDDING_PROPERTY = "textEmbedding"
# =============================================================================
# 1. FORM 10-K DOSYASINI OKU VE TEMEL BİLGİLERİ GÖSTER
# =============================================================================
first_file_name = "./data/form10k/0000950170-23-027948.json"
first_file_as_object = json.load(open(first_file_name))
# Dosya içindeki her anahtarın veri tipini yazdır
for k, v in first_file_as_object.items():
print(k, type(v))
# 'item1' bölümünün metnini al ve ilk 1500 karakterini görüntüle
item1_text = first_file_as_object['item1']
print(item1_text[0:1500])
# =============================================================================
# 2. LANGCHAIN İLE METİN AYIRICIYI AYARLA VE 'item1' METNİNİ PARÇALA
# =============================================================================
text_splitter = RecursiveCharacterTextSplitter(
chunk_size=2000, # Parça başına maksimum karakter sayısı
chunk_overlap=200, # Parçalar arası örtüşme
length_function=len, # Uzunluk hesaplaması için yerleşik len() fonksiyonu
is_separator_regex=False,
)
# 'item1' metnini parçalara ayır
item1_text_chunks = text_splitter.split_text(item1_text)
print(item1_text_chunks[0])
# =============================================================================
# 3. FORM 10-K'NİN BELİRLİ BÖLÜMLERİNİ (item1, item1a, item7, item7a) PARÇALAYAN YARDIMCI İŞLEV
# =============================================================================
def split_form10k_data_from_file(file):
"""
Verilen Form 10-K dosyasını açar, 'item1', 'item1a', 'item7' ve 'item7a'
bölümlerini metin parçalarına böler. Her bölümden yalnızca ilk 20 parça alınır.
Her parça, formId, chunkId, f10kItem, chunkSeqId, names, cik, cusip6 ve source gibi
meta verilerle birlikte kayıt altına alınır.
"""
chunks_with_metadata = [] # Parça kayıtlarını depolamak için liste
file_as_object = json.load(open(file)) # JSON dosyasını aç
for item in ['item1', 'item1a', 'item7', 'item7a']: # Belirtilen bölümleri işle
print(f'Processing {item} from {file}')
item_text = file_as_object[item] # Bölüm metnini al
item_text_chunks = text_splitter.split_text(item_text) # Metni parçala
chunk_seq_id = 0
for chunk in item_text_chunks[:20]: # Sadece ilk 20 parçayı kullan
# Dosya adından form ID'sini çıkar (örneğin, "0000950170-23-027948")
form_id = file[file.rindex('/') + 1:file.rindex('.')]
# Parça ve meta verileri içeren sözlüğü oluştur
chunks_with_metadata.append({
'text': chunk,
'f10kItem': item,
'chunkSeqId': chunk_seq_id,
'formId': f'{form_id}',
'chunkId': f'{form_id}-{item}-chunk{chunk_seq_id:04d}',
'names': file_as_object['names'],
'cik': file_as_object['cik'],
'cusip6': file_as_object['cusip6'],
'source': file_as_object['source'],
})
chunk_seq_id += 1
print(f'\tSplit into {chunk_seq_id} chunks')
return chunks_with_metadata
# İlk dosyadaki tüm parçaları al
first_file_chunks = split_form10k_data_from_file(first_file_name)
print(first_file_chunks[0])
# =============================================================================
# 4. METİN PARÇALARINI KULLANARAK NEO4J'DE GRAF DÜĞÜMLERİ OLUŞTURMA
# =============================================================================
merge_chunk_node_query = """
MERGE(mergedChunk:Chunk {chunkId: $chunkParam.chunkId})
ON CREATE SET
mergedChunk.names = $chunkParam.names,
mergedChunk.formId = $chunkParam.formId,
mergedChunk.cik = $chunkParam.cik,
mergedChunk.cusip6 = $chunkParam.cusip6,
mergedChunk.source = $chunkParam.source,
mergedChunk.f10kItem = $chunkParam.f10kItem,
mergedChunk.chunkSeqId = $chunkParam.chunkSeqId,
mergedChunk.text = $chunkParam.text
RETURN mergedChunk
"""
# =============================================================================
# 5. NEO4J GRAF ÖRNEĞİNE BAĞLAN
# =============================================================================
kg = Neo4jGraph(
url=NEO4J_URI,
username=NEO4J_USERNAME,
password=NEO4J_PASSWORD,
database=NEO4J_DATABASE
)
# Şimdilik tek bir parça için düğüm oluştur
kg.query(merge_chunk_node_query, params={'chunkParam': first_file_chunks[0]})
# Tekrarlı düğümlerin oluşturulmasını engellemek için benzersizlik kısıtlaması ekle
kg.query("""
CREATE CONSTRAINT unique_chunk IF NOT EXISTS
FOR (c:Chunk) REQUIRE c.chunkId IS UNIQUE
""")
kg.query("SHOW INDEXES")
# =============================================================================
# 6. TÜM PARÇALAR İÇİN DÖNGÜ YAP VE DÜĞÜMLERİ OLUŞTUR
# =============================================================================
node_count = 0
for chunk in first_file_chunks:
print(f"Creating `:Chunk` node for chunk ID {chunk['chunkId']}")
kg.query(merge_chunk_node_query, params={'chunkParam': chunk})
node_count += 1
# Toplam düğüm sayısını sorgula
kg.query("""
MATCH (n)
RETURN count(n) as nodeCount
""")
# =============================================================================
# 7. VEKÖR DİZİNİ OLUŞTUR
# =============================================================================
kg.query("""
CREATE VECTOR INDEX `form_10k_chunks` IF NOT EXISTS
FOR (c:Chunk) ON (c.textEmbedding)
OPTIONS { indexConfig: {
`vector.dimensions`: 1536,
`vector.similarity_function`: 'cosine'
}}
""")
kg.query("SHOW INDEXES")
# =============================================================================
# 8. PARÇALAR İÇİN GÖMME VEKTÖRLERİNİ HESAPLA VE DİZİNİ DOLDUR
# =============================================================================
kg.query("""
MATCH (chunk:Chunk) WHERE chunk.textEmbedding IS NULL
WITH chunk, genai.vector.encode(
chunk.text,
"OpenAI",
{
token: $openAiApiKey,
endpoint: $openAiEndpoint
}) AS vector
CALL db.create.setNodeVectorProperty(chunk, "textEmbedding", vector)
""", params={"openAiApiKey": OPENAI_API_KEY, "openAiEndpoint": OPENAI_ENDPOINT})
kg.refresh_schema()
print(kg.schema)
# =============================================================================
# 9. NEO4J VEKÖR DİZİNİNİ KULLANARAK BENZERLİK ARAMASI YAPAN YARDIMCI İŞLEV
# =============================================================================
def neo4j_vector_search(question):
"""Neo4j vektör dizinini kullanarak benzer düğümleri arar."""
vector_search_query = """
WITH genai.vector.encode(
$question,
"OpenAI",
{
token: $openAiApiKey,
endpoint: $openAiEndpoint
}) AS question_embedding
CALL db.index.vector.queryNodes($index_name, $top_k, question_embedding) yield node, score
RETURN score, node.text AS text
"""
similar = kg.query(vector_search_query, params={
'question': question,
'openAiApiKey': OPENAI_API_KEY,
'openAiEndpoint': OPENAI_ENDPOINT,
'index_name': VECTOR_INDEX_NAME,
'top_k': 10
})
return similar
# Örnek bir benzerlik araması gerçekleştir
search_results = neo4j_vector_search('In a single sentence, tell me about Netapp.')
print(search_results[0])
# =============================================================================
# 10. LANGCHAIN RAG (RETRIEVAL-AUGMENTED GENERATION) İŞ AKIŞINI KUR VE SORU-CEVAP YAP
# =============================================================================
neo4j_vector_store = Neo4jVector.from_existing_graph(
embedding=OpenAIEmbeddings(),
url=NEO4J_URI,
username=NEO4J_USERNAME,
password=NEO4J_PASSWORD,
index_name=VECTOR_INDEX_NAME,
node_label=VECTOR_NODE_LABEL,
text_node_properties=[VECTOR_SOURCE_PROPERTY],
embedding_node_property=VECTOR_EMBEDDING_PROPERTY,
)
retriever = neo4j_vector_store.as_retriever()
# RetrievalQAWithSourcesChain ile soru-cevap zinciri oluştur
chain = RetrievalQAWithSourcesChain.from_chain_type(
ChatOpenAI(temperature=0),
chain_type="stuff",
retriever=retriever
)
# Soruya verilen yanıtı güzel formatta yazdıran fonksiyon
def prettychain(question: str) -> str:
"""Zincirin cevabını güzel formatta yazdırır."""
response = chain({"question": question}, return_only_outputs=True)
print(textwrap.fill(response['answer'], 60))
# =============================================================================
# 11. FARKLI SORULAR SOR VE CEVAPLARI GÖR
# =============================================================================
question = "What is Netapp's primary business?"
prettychain(question)
prettychain("Where is Netapp headquartered?")
prettychain("""
Tell me about Netapp.
Limit your answer to a single sentence.
""")
prettychain("""
Tell me about Apple.
Limit your answer to a single sentence.
""")
prettychain("""
Tell me about Apple.
Limit your answer to a single sentence.
If you are unsure about the answer, say you don't know.
""")
prettychain("""
ADD YOUR OWN QUESTION HERE
""")
3.5 SEC Bilgi Grafiğinin Genişletilmesi
#!/usr/bin/env python3
"""
Bu script, SEC Bilgi Grafiğinin Genişletilmesi adlı blog yazısının
hem açıklama metinlerini hem de kod parçacıklarını tek bir Python dosyasında
birleştirir. Açıklama metinleri yorum (comment) olarak eklenmiştir.
"""
# SEC Bilgi Grafiğinin Genişletilmesi
# Paketleri içe aktarın ve Neo4j’yi kurun:
from dotenv import load_dotenv
import os
import textwrap
import csv
# Langchain
from langchain_community.graphs import Neo4jGraph
from langchain_community.vectorstores import Neo4jVector
from langchain_openai import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import RetrievalQAWithSourcesChain
from langchain_openai import ChatOpenAI
# Load from environment
load_dotenv('.env', override=True)
NEO4J_URI = os.getenv('NEO4J_URI')
NEO4J_USERNAME = os.getenv('NEO4J_USERNAME')
NEO4J_PASSWORD = os.getenv('NEO4J_PASSWORD')
NEO4J_DATABASE = os.getenv('NEO4J_DATABASE') or 'neo4j'
# Global constants
VECTOR_INDEX_NAME = 'form_10k_chunks'
VECTOR_NODE_LABEL = 'Chunk'
VECTOR_SOURCE_PROPERTY = 'text'
VECTOR_EMBEDDING_PROPERTY = 'textEmbedding'
# Neo4j Graph nesnesini başlatın.
kg = Neo4jGraph(
url=NEO4J_URI,
username=NEO4J_USERNAME,
password=NEO4J_PASSWORD,
database=NEO4J_DATABASE
)
# Form 13 koleksiyonunu okuyun
# Yatırım yönetimi firmaları, şirketlere yaptıkları yatırımları Form 13 adı verilen
# bir belgeyi doldurarak SEC’ye bildirmelidir. NetApp’e yatırım yapan yöneticiler için
# Form 13'ün bir koleksiyonunu yükleyeceksiniz. Not defterinin üst kısmındaki Dosya menüsünü
# kullanarak veri dizinine gidip CSV dosyasını teslim alabilirsiniz:
# [https://learn.deeplearning.ai/courses/knowledge-graphs-rag/lesson/7/expanding-the-sec-knowledge-graph]
all_form13s = []
with open('./data/form13.csv', mode='r') as csv_file:
csv_reader = csv.DictReader(csv_file)
for row in csv_reader: # Her satır bir sözlük olacak.
all_form13s.append(row)
# İlk 5 Form 13'ün içeriğine bakın:
print("İlk 5 Form 13 girdisi:")
print(all_form13s[0:5])
# Grafikte Company düğümleri oluşturun
# Company düğümleri oluşturmak için Form 13'lerde tanımlanan şirketleri kullanın.
# Şimdilik tek bir Company var: NetApp.
# Sadece ilk form ile çalışalım:
first_form13 = all_form13s[0]
cypher = """
MERGE (com:Company {cusip6: $cusip6})
ON CREATE
SET com.companyName = $companyName,
com.cusip = $cusip
"""
kg.query(cypher, params={
'cusip6': first_form13['cusip6'],
'companyName': first_form13['companyName'],
'cusip': first_form13['cusip']
})
cypher = """
MATCH (com:Company)
RETURN com LIMIT 1
"""
kg.query(cypher)
# Şirket adını Form 10-K ile eşleşecek şekilde güncelleyin:
cypher = """
MATCH (com:Company), (form:Form)
WHERE com.cusip6 = form.cusip6
RETURN com.companyName, form.names
"""
kg.query(cypher)
cypher = """
MATCH (com:Company), (form:Form)
WHERE com.cusip6 = form.cusip6
SET com.names = form.names
"""
kg.query(cypher)
# Şirket ile Form-10K düğümü arasında bir FILED ilişkisi oluşturun:
kg.query("""
MATCH (com:Company), (form:Form)
WHERE com.cusip6 = form.cusip6
MERGE (com)-[:FILED]->(form)
""")
# Yönetici düğümleri oluşturun
# NetApp’a yatırımlarını bildirmek üzere Form 13'ü dolduran şirketler için bir manager düğümü
# oluşturun. Listedeki ilk Form 13'ü dolduran tek yönetici ile başlayın.
cypher = """
MERGE (mgr:Manager {managerCik: $managerParam.managerCik})
ON CREATE
SET mgr.managerName = $managerParam.managerName,
mgr.managerAddress = $managerParam.managerAddress
"""
kg.query(cypher, params={'managerParam': first_form13})
kg.query("""
MATCH (mgr:Manager)
RETURN mgr LIMIT 1
""")
# Birden çok yönetici oluşmasını önlemek için bir benzersizlik kısıtı oluşturun.
kg.query("""
CREATE CONSTRAINT unique_manager
IF NOT EXISTS
FOR (n:Manager)
REQUIRE n.managerCik IS UNIQUE
""")
# Metin aramasını etkinleştirmek için yönetici adlarının tam metin dizinini oluşturun.
kg.query("""
CREATE FULLTEXT INDEX fullTextManagerNames
IF NOT EXISTS
FOR (mgr:Manager)
ON EACH [mgr.managerName]
""")
kg.query("""
CALL db.index.fulltext.queryNodes("fullTextManagerNames",
"royal bank") YIELD node, score
RETURN node.managerName, score
""")
# Form 13 dosyalayan tüm şirketler için düğümler oluşturun:
cypher = """
MERGE (mgr:Manager {managerCik: $managerParam.managerCik})
ON CREATE
SET mgr.managerName = $managerParam.managerName,
mgr.managerAddress = $managerParam.managerAddress
"""
# Tüm Form 13 girdileri üzerinden döngü:
for form13 in all_form13s:
kg.query(cypher, params={'managerParam': form13 })
kg.query("""
MATCH (mgr:Manager)
RETURN count(mgr)
""")
# Yöneticiler ve şirketler arasında ilişkiler kurun
# Form 13'teki verilere dayanarak şirketleri yöneticilerle eşleştirin.
# Yönetici ve şirket arasında bir OWNS_STOCK_IN ilişkisi oluşturun.
# Listedeki ilk Form 13'ü dolduran tek yönetici ile başlayın.
cypher = """
MATCH (mgr:Manager {managerCik: $investmentParam.managerCik}),
(com:Company {cusip6: $investmentParam.cusip6})
RETURN mgr.managerName, com.companyName, $investmentParam as investment
"""
kg.query(cypher, params={'investmentParam': first_form13})
cypher = """
MATCH (mgr:Manager {managerCik: $ownsParam.managerCik}),
(com:Company {cusip6: $ownsParam.cusip6})
MERGE (mgr)-[owns:OWNS_STOCK_IN {
reportCalendarOrQuarter: $ownsParam.reportCalendarOrQuarter
}]->(com)
ON CREATE
SET owns.value = toFloat($ownsParam.value),
owns.shares = toInteger($ownsParam.shares)
RETURN mgr.managerName, owns.reportCalendarOrQuarter, com.companyName
"""
kg.query(cypher, params={ 'ownsParam': first_form13 })
kg.query("""
MATCH (mgr:Manager {managerCik: $ownsParam.managerCik})
-[owns:OWNS_STOCK_IN]->
(com:Company {cusip6: $ownsParam.cusip6})
RETURN owns { .shares, .value }
""", params={ 'ownsParam': first_form13 })
# Form 13'leri dolduran tüm yöneticiler ile şirket arasında ilişkiler kurun:
cypher = """
MATCH (mgr:Manager {managerCik: $ownsParam.managerCik}),
(com:Company {cusip6: $ownsParam.cusip6})
MERGE (mgr)-[owns:OWNS_STOCK_IN {
reportCalendarOrQuarter: $ownsParam.reportCalendarOrQuarter
}]->(com)
ON CREATE
SET owns.value = toFloat($ownsParam.value),
owns.shares = toInteger($ownsParam.shares)
"""
# Tüm Form 13 girdileri üzerinden döngü:
for form13 in all_form13s:
kg.query(cypher, params={'ownsParam': form13 })
cypher = """
MATCH (:Manager)-[owns:OWNS_STOCK_IN]->(:Company)
RETURN count(owns) as investments
"""
kg.query(cypher)
# Şema güncellemesi ve yazdırılması:
kg.refresh_schema()
print(textwrap.fill(kg.schema, 60))
# Yatırımcı sayısını belirleyin
# Bir form 10-K yığınını bularak başlayın ve sonraki sorgularda kullanmak üzere kaydedin.
cypher = """
MATCH (chunk:Chunk)
RETURN chunk.chunkId as chunkId LIMIT 1
"""
chunk_rows = kg.query(cypher)
print("Chunk satırları:", chunk_rows)
chunk_first_row = chunk_rows[0]
print("İlk Chunk satırı:", chunk_first_row)
ref_chunk_id = chunk_first_row['chunkId']
print("Referans chunk id:", ref_chunk_id)
# Form 10-K yığınından şirketlere ve yöneticilere giden bir yol oluşturun.
cypher = """
MATCH (:Chunk {chunkId: $chunkIdParam})-[:PART_OF]->(f:Form)
RETURN f.source
"""
kg.query(cypher, params={'chunkIdParam': ref_chunk_id})
cypher = """
MATCH (:Chunk {chunkId: $chunkIdParam})-[:PART_OF]->(f:Form),
(com:Company)-[:FILED]->(f)
RETURN com.companyName as name
"""
kg.query(cypher, params={'chunkIdParam': ref_chunk_id})
cypher = """
MATCH (:Chunk {chunkId: $chunkIdParam})-[:PART_OF]->(f:Form),
(com:Company)-[:FILED]->(f),
(mgr:Manager)-[:OWNS_STOCK_IN]->(com)
RETURN com.companyName,
count(mgr.managerName) as numberOfinvestors
LIMIT 1
"""
kg.query(cypher, params={'chunkIdParam': ref_chunk_id})
# LLM için ek bağlam oluşturmak üzere sorguları kullanın
# Bir yöneticinin bir şirkete ne kadar hisse senedi yatırdığını belirten cümleler oluşturun.
cypher = """
MATCH (:Chunk {chunkId: $chunkIdParam})-[:PART_OF]->(f:Form),
(com:Company)-[:FILED]->(f),
(mgr:Manager)-[owns:OWNS_STOCK_IN]->(com)
RETURN mgr.managerName + " owns " + owns.shares +
" shares of " + com.companyName +
" at a value of $" +
apoc.number.format(toInteger(owns.value)) AS text
LIMIT 10
"""
kg.query(cypher, params={'chunkIdParam': ref_chunk_id})
results = kg.query(cypher, params={'chunkIdParam': ref_chunk_id})
print("Örnek yatırımcı bilgisi:")
print(textwrap.fill(results[0]['text'], 60))
# Düz bir Soru-cevap zinciri oluşturun.
# Yalnızca benzerlik araması yapılmaktadır, Cypher Query ile artırma yoktur.
vector_store = Neo4jVector.from_existing_graph(
embedding=OpenAIEmbeddings(),
url=NEO4J_URI,
username=NEO4J_USERNAME,
password=NEO4J_PASSWORD,
index_name=VECTOR_INDEX_NAME,
node_label=VECTOR_NODE_LABEL,
text_node_properties=[VECTOR_SOURCE_PROPERTY],
embedding_node_property=VECTOR_EMBEDDING_PROPERTY,
)
# Vector store üzerinden retriever oluşturun.
retriever = vector_store.as_retriever()
# Retriever kullanarak bir chatbot Question & Answer zinciri oluşturun.
plain_chain = RetrievalQAWithSourcesChain.from_chain_type(
ChatOpenAI(temperature=0),
chain_type="stuff",
retriever=retriever
)
# İkinci bir QA zinciri oluşturun.
# Yukarıdaki yatırım sorgusu tarafından bulunan cümleleri kullanarak benzerlik aramasını artırın.
investment_retrieval_query = """
MATCH (node)-[:PART_OF]->(f:Form),
(f)<-[:FILED]-(com:Company),
(com)<-[owns:OWNS_STOCK_IN]-(mgr:Manager)
WITH node, score, mgr, owns, com
ORDER BY owns.shares DESC LIMIT 10
WITH collect (
mgr.managerName +
" owns " + owns.shares +
" shares in " + com.companyName +
" at a value of $" +
apoc.number.format(toInteger(owns.value)) + "."
) AS investment_statements, node, score
RETURN apoc.text.join(investment_statements, "\n") +
"\n" + node.text AS text,
score,
{
source: node.source
} as metadata
"""
vector_store_with_investment = Neo4jVector.from_existing_index(
OpenAIEmbeddings(),
url=NEO4J_URI,
username=NEO4J_USERNAME,
password=NEO4J_PASSWORD,
database="neo4j",
index_name=VECTOR_INDEX_NAME,
text_node_property=VECTOR_SOURCE_PROPERTY,
retrieval_query=investment_retrieval_query,
)
# Retriever oluşturun.
retriever_with_investments = vector_store_with_investment.as_retriever()
# Retriever kullanarak yatırım bilgilerini içeren bir QA zinciri oluşturun.
investment_chain = RetrievalQAWithSourcesChain.from_chain_type(
ChatOpenAI(temperature=0),
chain_type="stuff",
retriever=retriever_with_investments
)
# Çıktıları karşılaştırın!
question = "In a single sentence, tell me about Netapp."
print("Plain Chain Output:")
print(plain_chain(
{"question": question},
return_only_outputs=True,
))
print("Investment Chain Output:")
print(investment_chain(
{"question": question},
return_only_outputs=True,
))
# Soru yatırımcılar hakkında soru sormadığı için LLM yatırımcı bilgilerini kullanmamıştır.
# Soruyu değiştirin ve tekrar sorun.
question = "In a single sentence, tell me about Netapp investors."
print("Plain Chain Output (investors):")
print(plain_chain(
{"question": question},
return_only_outputs=True,
))
print("Investment Chain Output (investors):")
print(investment_chain(
{"question": question},
return_only_outputs=True,
))
# Başka bilgiler almak için yukarıdaki sorguyu değiştirmeyi deneyin.
# Farklı sorular sormayı deneyin.
# (Not: Cypher sorgusunu değiştirirseniz, retriever ve QA zincirini sıfırlamanız gerekeceğini unutmayın.)
3.6 SEC Bilgi Grafiği ile Sohbet Etme
Bir bilgi grafiği oluşturmaya nasıl başlanılır?
Bir bilgi grafiği oluşturulurken önce minimum uygulanabilir grafik (MVG: Minimum Viable Graph) ile başlanılır. Ardından grafiği büyütmek için sırasıyla ayıklama, geliştirme, genişletme ve bunları tekrarlama yapılır. Ayıklama, ilginç bilgileri tanımlamaktır. Geliştirme, verileri güçlendirmektir. Genişletme, bağlamı genişletmek için bilgileri birbirine bağlamaktır.[5]
Birbirinden bahseden şirketleri çapraz bağlayarak; metinden çıkarılan kişileri, yerleri ve konuları ekleyerek, daha fazla form verisi veya ilgili veri kaynağı ekleyerek ve alaka düzeyini iyileştirmek ve geri bildirim sağlamak için kullanıcılar ekleyerek bilgi grafiğini büyütmeye devam edebilirsiniz.[5]
Extract (:Adresses) Düğümleri: Tam adres dizelerini ayıklayın ve şehir, eyalet ve ülkenin yanı sıra yükseklik ve boylamı elde etmek için coğrafi kodlama yapın.[5]
Jeo-uzamsal indeks ile geliştirin: Mesafe sorgularını etkinleştirin.[5]
Genişlet -[:LOCATED_AT]->ilişkiler. (:Yönetici), (:Şirket) düğümlerini (:Adres)’e bağlayın.[5]
# Paketleri içe aktarın ve Neo4j’yi kurun
from dotenv import load_dotenv
import os
import textwrap
# Langchain
from langchain_community.graphs import Neo4jGraph
from langchain_community.vectorstores import Neo4jVector
from langchain_openai import OpenAIEmbeddings
from langchain.chains import RetrievalQAWithSourcesChain
from langchain.prompts.prompt import PromptTemplate
from langchain.chains import GraphCypherQAChain
from langchain_openai import ChatOpenAI
# Load from environment
load_dotenv('.env', override=True)
NEO4J_URI = os.getenv('NEO4J_URI')
NEO4J_USERNAME = os.getenv('NEO4J_USERNAME')
NEO4J_PASSWORD = os.getenv('NEO4J_PASSWORD')
NEO4J_DATABASE = os.getenv('NEO4J_DATABASE') or 'neo4j'
OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')
# Note the code below is unique to this course environment, and not a
# standard part of Neo4j's integration with OpenAI. Remove if running
# in your own environment.
OPENAI_ENDPOINT = os.getenv('OPENAI_BASE_URL') + '/embeddings'
# Global constants
VECTOR_INDEX_NAME = 'form_10k_chunks'
VECTOR_NODE_LABEL = 'Chunk'
VECTOR_SOURCE_PROPERTY = 'text'
VECTOR_EMBEDDING_PROPERTY = 'textEmbedding'
kg = Neo4jGraph(
url=NEO4J_URI, username=NEO4J_USERNAME, password=NEO4J_PASSWORD, database=NEO4J_DATABASE
)
# Güncellenmiş SEC belgeleri grafiğini keşfedin
# Videoda tartışılan adres bilgilerini de içeren güncellenmiş bir grafikle çalışalım.
kg.refresh_schema()
print(textwrap.fill(kg.schema, 60))
# Rastgele bir Manager’ın adresini kontrol edin.
# Aşağıdaki sorgu tarafından döndürülen şirket videodakinden farklı olabilir.
kg.query("""
MATCH (mgr:Manager)-[:LOCATED_AT]->(addr:Address)
RETURN mgr, addr
LIMIT 1
""")
# Royal Bank adlı bir manager için tam metin araması.
kg.query("""
CALL db.index.fulltext.queryNodes(
"fullTextManagerNames",
"royal bank") YIELD node, score
RETURN node.managerName, score LIMIT 1
""")
# Royal Bank’ın adresini bulun.
kg.query("""
CALL db.index.fulltext.queryNodes(
"fullTextManagerNames",
"royal bank"
) YIELD node, score
WITH node as mgr LIMIT 1
MATCH (mgr:Manager)-[:LOCATED_AT]->(addr:Address)
RETURN mgr.managerName, addr
""")
# Hangi eyalette en çok yatırım şirketi olduğunu belirleyin.
kg.query("""
MATCH p=(:Manager)-[:LOCATED_AT]->(address:Address)
RETURN address.state as state, count(address.state) as numManagers
ORDER BY numManagers DESC
LIMIT 10
""")
# Hangi eyalette en çok yatırım şirketi olduğunu belirleyin.
kg.query("""
MATCH p=(:Company)-[:LOCATED_AT]->(address:Address)
RETURN address.state as state, count(address.state) as numCompanies
ORDER BY numCompanies DESC
""")
# Kaliforniya’da en çok yatırım firmasının bulunduğu şehirler hangileridir?
kg.query("""
MATCH p=(:Manager)-[:LOCATED_AT]->(address:Address)
WHERE address.state = 'California'
RETURN address.city as city, count(address.city) as numManagers
ORDER BY numManagers DESC
LIMIT 10
""")
# Kaliforniya’da en çok şirketin listelendiği şehir hangisidir?
kg.query("""
MATCH p=(:Company)-[:LOCATED_AT]->(address:Address)
WHERE address.state = 'California'
RETURN address.city as city, count(address.city) as numCompanies
ORDER BY numCompanies DESC
""")
# San Francisco’daki en iyi yatırım firmaları hangileridir?
kg.query("""
MATCH p=(mgr:Manager)-[:LOCATED_AT]->(address:Address),
(mgr)-[owns:OWNS_STOCK_IN]->(:Company)
WHERE address.city = "San Francisco"
RETURN mgr.managerName, sum(owns.value) as totalInvestmentValue
ORDER BY totalInvestmentValue DESC
LIMIT 10
""")
# Santa Clara’da hangi şirketler var?
kg.query("""
MATCH (com:Company)-[:LOCATED_AT]->(address:Address)
WHERE address.city = "Santa Clara"
RETURN com.companyName
""")
# Santa Clara yakınlarında hangi şirketler var?
kg.query("""
MATCH (sc:Address)
WHERE sc.city = "Santa Clara"
MATCH (com:Company)-[:LOCATED_AT]->(comAddr:Address)
WHERE point.distance(sc.location, comAddr.location) < 10000
RETURN com.companyName, com.companyAddress
""")
# Santa Clara yakınlarında hangi yatırım şirketleri var?
# Arama yarıçapını genişletmek için sorgudaki mesafeyi güncellemeyi deneyelim.
kg.query("""
MATCH (address:Address)
WHERE address.city = "Santa Clara"
MATCH (mgr:Manager)-[:LOCATED_AT]->(managerAddress:Address)
WHERE point.distance(address.location,
managerAddress.location) < 10000
RETURN mgr.managerName, mgr.managerAddress
""")
# Palo Alto Networks yakınlarında hangi yatırım şirketleri var?
# Tam metin aramanın yazım hatalarını işleyebileceğini unutmayın!
# Which investment firms are near Palo Aalto Networks?
kg.query("""
CALL db.index.fulltext.queryNodes(
"fullTextCompanyNames",
"Palo Aalto Networks"
) YIELD node, score
WITH node as com
MATCH (com)-[:LOCATED_AT]->(comAddress:Address),
(mgr:Manager)-[:LOCATED_AT]->(mgrAddress:Address)
WHERE point.distance(comAddress.location,
mgrAddress.location) < 10000
RETURN mgr,
toInteger(point.distance(comAddress.location,
mgrAddress.location) / 1000) as distanceKm
ORDER BY distanceKm ASC
LIMIT 10
""")
# Cypher hakkında daha fazla bilgiyi neo4j web sitesinden edinebilirsiniz:
# https://neo4j.com/product/cypher-graph-query-language/
3.7 LLM’e Cypher Sorguları Yazdırmak
Bir LLM’e Cypher yazmayı öğretmek için birkaç atışlık öğrenmeyi kullanalım. OpenAI’nin GPT 3.5-turbo modelini kullanacağız. Ayrıca LangChain içinde GraphCypherQAChain adlı yeni bir Neo4j entegrasyonu kullanacağız. [5]
#!/usr/bin/env python
"""
This script demonstrates how to generate Cypher statements for querying a graph database.
It shows how to define a prompt template, update it in various ways to support new query patterns,
and then run queries against the graph. The text explanations from the blog post are included as comments.
Note:
- Ensure you have the necessary modules and objects (e.g., PromptTemplate, GraphCypherQAChain, ChatOpenAI, kg)
imported from your libraries or defined in your environment.
- The code below assumes that these classes and the graph (kg) are already set up.
"""
import textwrap
# -----------------------------------------------------------------------------
# Initial Cypher generation template and prompt creation
# -----------------------------------------------------------------------------
# This template instructs the LLM to generate a Cypher statement that queries a graph database.
# It includes instructions, a schema placeholder, and examples.
CYPHER_GENERATION_TEMPLATE = """Task:Generate Cypher statement to
query a graph database.
Instructions:
Use only the provided relationship types and properties in the
schema. Do not use any other relationship types or properties that
are not provided.
Schema:
{schema}
Note: Do not include any explanations or apologies in your responses.
Do not respond to any questions that might ask anything else than
for you to construct a Cypher statement.
Do not include any text except the generated Cypher statement.
Examples: Here are a few examples of generated Cypher
statements for particular questions:
# What investment firms are in San Francisco?
MATCH (mgr:Manager)-[:LOCATED_AT]->(mgrAddress:Address)
WHERE mgrAddress.city = 'San Francisco'
RETURN mgr.managerName
The question is:
{question}"""
# Create a prompt template using the above template.
CYPHER_GENERATION_PROMPT = PromptTemplate(
input_variables=["schema", "question"],
template=CYPHER_GENERATION_TEMPLATE
)
# Initialize the Cypher QA chain with a ChatOpenAI instance.
cypherChain = GraphCypherQAChain.from_llm(
ChatOpenAI(temperature=0),
graph=kg,
verbose=True,
cypher_prompt=CYPHER_GENERATION_PROMPT,
)
# Define a helper function to pretty-print the generated Cypher query.
def prettyCypherChain(question: str) -> str:
response = cypherChain.run(question)
print(textwrap.fill(response, 60))
# Run some sample queries against the graph database.
prettyCypherChain("What investment firms are in San Francisco?")
prettyCypherChain("What investment firms are in Menlo Park?")
prettyCypherChain("What companies are in Santa Clara?")
prettyCypherChain("What investment firms are near Santa Clara?")
# -----------------------------------------------------------------------------
# Extending the prompt to teach new Cypher patterns to the LLM
# -----------------------------------------------------------------------------
# LLM’ye yeni Cypher kalıplarını öğretmek için istemi genişletin
# (Translation: Extend the prompt to teach new Cypher patterns to the LLM.)
CYPHER_GENERATION_TEMPLATE = """Task:Generate Cypher statement to query a graph database.
Instructions:
Use only the provided relationship types and properties in the schema.
Do not use any other relationship types or properties that are not provided.
Schema:
{schema}
Note: Do not include any explanations or apologies in your responses.
Do not respond to any questions that might ask anything else than for you to construct a Cypher statement.
Do not include any text except the generated Cypher statement.
Examples: Here are a few examples of generated Cypher statements for particular questions:
# What investment firms are in San Francisco?
MATCH (mgr:Manager)-[:LOCATED_AT]->(mgrAddress:Address)
WHERE mgrAddress.city = 'San Francisco'
RETURN mgr.managerName
# What investment firms are near Santa Clara?
MATCH (address:Address)
WHERE address.city = "Santa Clara"
MATCH (mgr:Manager)-[:LOCATED_AT]->(managerAddress:Address)
WHERE point.distance(address.location,
managerAddress.location) < 10000
RETURN mgr.managerName, mgr.managerAddress
The question is:
{question}"""
# Update the prompt with the new template and restart the QA chain.
CYPHER_GENERATION_PROMPT = PromptTemplate(
input_variables=["schema", "question"],
template=CYPHER_GENERATION_TEMPLATE
)
cypherChain = GraphCypherQAChain.from_llm(
ChatOpenAI(temperature=0),
graph=kg,
verbose=True,
cypher_prompt=CYPHER_GENERATION_PROMPT,
)
# Query using the new prompt template.
prettyCypherChain("What investment firms are near Santa Clara?")
# -----------------------------------------------------------------------------
# Expanding the query to extract information from Form 10K filings
# -----------------------------------------------------------------------------
# Form 10K yığınlarından bilgi almak için sorguyu genişletin
# (Translation: Extend the query to get information from Form 10K filings.)
CYPHER_GENERATION_TEMPLATE = """Task:Generate Cypher statement to query a graph database.
Instructions:
Use only the provided relationship types and properties in the schema.
Do not use any other relationship types or properties that are not provided.
Schema:
{schema}
Note: Do not include any explanations or apologies in your responses.
Do not respond to any questions that might ask anything else than for you to construct a Cypher statement.
Do not include any text except the generated Cypher statement.
Examples: Here are a few examples of generated Cypher statements for particular questions:
# What investment firms are in San Francisco?
MATCH (mgr:Manager)-[:LOCATED_AT]->(mgrAddress:Address)
WHERE mgrAddress.city = 'San Francisco'
RETURN mgr.managerName
# What investment firms are near Santa Clara?
MATCH (address:Address)
WHERE address.city = "Santa Clara"
MATCH (mgr:Manager)-[:LOCATED_AT]->(managerAddress:Address)
WHERE point.distance(address.location,
managerAddress.location) < 10000
RETURN mgr.managerName, mgr.managerAddress
# What does Palo Alto Networks do?
CALL db.index.fulltext.queryNodes(
"fullTextCompanyNames",
"Palo Alto Networks"
) YIELD node, score
WITH node as com
MATCH (com)-[:FILED]->(f:Form),
(f)-[s:SECTION]->(c:Chunk)
WHERE s.f10kItem = "item1"
RETURN c.text
The question is:
{question}"""
# Update the prompt and reset the QA chain with the new template.
CYPHER_GENERATION_PROMPT = PromptTemplate(
input_variables=["schema", "question"],
template=CYPHER_GENERATION_TEMPLATE
)
cypherChain = GraphCypherQAChain.from_llm(
ChatOpenAI(temperature=0),
graph=kg,
verbose=True,
cypher_prompt=CYPHER_GENERATION_PROMPT,
)
# Query the graph to get information about Palo Alto Networks.
prettyCypherChain("What does Palo Alto Networks do?")
# -----------------------------------------------------------------------------
# Updating the prompt for additional graph-related queries and checking schema
# -----------------------------------------------------------------------------
# Grafik hakkında farklı sorular sormak için aşağıdaki Cypher oluşturma istemini güncelleyebilirsiniz.
# Grafik yapısını hatırlatmak için “check schema” kısmını çalıştırabilirsiniz.
# (Translation: You can update the Cypher generation prompt below to ask different questions about the graph.
# To remind yourself of the graph structure, run the "check schema" part.)
# Check the graph schema
kg.refresh_schema()
print(textwrap.fill(kg.schema, 60))
# Update the Cypher generation template to include the full set of examples.
CYPHER_GENERATION_TEMPLATE = """Task:Generate Cypher statement to query a graph database.
Instructions:
Use only the provided relationship types and properties in the schema.
Do not use any other relationship types or properties that are not provided.
Schema:
{schema}
Note: Do not include any explanations or apologies in your responses.
Do not respond to any questions that might ask anything else than for you to construct a Cypher statement.
Do not include any text except the generated Cypher statement.
Examples: Here are a few examples of generated Cypher statements for particular questions:
# What investment firms are in San Francisco?
MATCH (mgr:Manager)-[:LOCATED_AT]->(mgrAddress:Address)
WHERE mgrAddress.city = 'San Francisco'
RETURN mgr.managerName
# What investment firms are near Santa Clara?
MATCH (address:Address)
WHERE address.city = "Santa Clara"
MATCH (mgr:Manager)-[:LOCATED_AT]->(managerAddress:Address)
WHERE point.distance(address.location,
managerAddress.location) < 10000
RETURN mgr.managerName, mgr.managerAddress
# What does Palo Alto Networks do?
CALL db.index.fulltext.queryNodes(
"fullTextCompanyNames",
"Palo Alto Networks"
) YIELD node, score
WITH node as com
MATCH (com)-[:FILED]->(f:Form),
(f)-[s:SECTION]->(c:Chunk)
WHERE s.f10kItem = "item1"
RETURN c.text
The question is:
{question}"""
# Update the prompt and reset the QA chain with the latest template.
CYPHER_GENERATION_PROMPT = PromptTemplate(
input_variables=["schema", "question"],
template=CYPHER_GENERATION_TEMPLATE
)
cypherChain = GraphCypherQAChain.from_llm(
ChatOpenAI(temperature=0),
graph=kg,
verbose=True,
cypher_prompt=CYPHER_GENERATION_PROMPT,
)
# Replace the placeholder with your own question to test the final setup.
prettyCypherChain("<<REPLACE WITH YOUR QUESTION>>")
4. Hippo RAG: RAG’e Hiyerarşik Yaklaşımlar
Düşmanca ve sürekli değişen doğal ortamlar içinde başarılı bir şekilde hayatta kalmak için, memeli beyinleri dünya hakkında büyük miktarda bilgi depolayacak ve felaket niteliğindeki unutmayı önlerken yeni bilgileri sürekli olarak entegre edecek şekilde evrimleşmiştir. Büyük dil modelleri (LLM’ler), geri çağırma destekli üretim (RAG) ile bile etkileyici başarılarına rağmen, ön eğitimden sonra büyük miktarda yeni deneyimi verimli ve etkili bir şekilde bütünleştirmekte hâlâ zorlanmaktadır. [6]
Bu çalışmada, insan uzun süreli belleğinin hipokampal indeksleme teorisinden ilham alan yeni bir geri çağırma çerçevesi olan HippoRAG’i tanıtıyoruz. HippoRAG, yeni deneyimler üzerinden daha derin ve daha verimli bilgi entegrasyonunu sağlamak için tasarlanmıştır. HippoRAG, büyük dil modellerini, bilgi grafikleri ve Kişiselleştirilmiş PageRank algoritmasını bir araya getirerek insan hafızasında neokorteks ve hipokampusun farklı rollerini taklit eder. [6]
HippoRAG’i mevcut RAG yöntemleriyle çok adımlı soru cevaplama (QA) görevinde karşılaştırıyoruz ve yöntemimizin en son yöntemlere kıyasla %20'ye kadar daha iyi performans gösterdiğini ortaya koyuyoruz. HippoRAG’in tek adımlı geri çağırma yöntemi, IRCoT gibi yinelemeli geri çağırma yaklaşımlarına kıyasla 10–20 kat daha ucuz ve 6–13 kat daha hızlı olurken benzer veya daha iyi performans sunmaktadır. Ayrıca, HippoRAG’in IRCoT ile birleştirilmesi, ek olarak büyük performans artışları sağlamaktadır. Son olarak, yöntemimizin mevcut yöntemlerin erişemediği yeni senaryoları ele alabileceğini gösteriyoruz. [6]
4.1 Giriş
Bilgi entegrasyonu gerektiren görevler, mevcut RAG sistemleri için özellikle zorludur. Yukarıdaki örnekte, potansiyel olarak binlerce Stanford profesörünü ve Alzheimer araştırmacısını tanımlayan metinlerden oluşan bir havuzdan, Alzheimer araştırmaları yapan bir Stanford profesörünü bulmak istiyoruz. Mevcut yöntemler metinleri izole şekilde kodladığından, Prof. Thomas’ı tespit etmekte zorlanırlar, çünkü bir metnin hem Stanford profesörü hem de Alzheimer araştırmacısı olduğunu aynı anda belirtmesi gerekir. Buna karşılık, bu profesörü tanıyan çoğu insan, beynimizin ilişkisel hafıza yetenekleri sayesinde onu hızla hatırlayacaktır. Bu yeteneğin, yukarıda mavi renkle gösterilen C şeklindeki hipokampusta yer alan indeks yapısından kaynaklandığı düşünülmektedir. Bu mekanizmadan ilham alan HippoRAG, LLM’lerin benzer bir ilişki grafiği oluşturmasına ve kullanmasına olanak tanıyarak bilgi entegrasyonu gerektiren görevleri ele almasını sağlar. [6]
Milyonlarca yıllık evrim, memeli beyinlerinin büyük miktarda dünya bilgisini saklama ve önceki bilgileri kaybetmeden yeni deneyimleri sürekli olarak entegre etme yeteneğini geliştirmesine yol açmıştır. Bu uzun süreli hafıza sistemi, insanların sürekli güncellenen geniş bir bilgi deposuna sahip olmasını ve bunun düşünme ile karar alma süreçlerinin temelini oluşturmasını sağlar. [6]
Büyük dil modelleri (LLM’ler) son yıllarda önemli ilerlemeler kaydetmiş olsa da, AI sistemlerinde sürekli güncellenen uzun süreli hafıza halen eksiktir. Model düzenleme gibi tekniklerin sınırlamaları nedeniyle, geri çağırma destekli üretim (RAG), LLM’ler için uzun süreli hafızaya yönelik standart bir çözüm haline gelmiştir. Ancak, mevcut RAG yöntemleri, yeni bilgileri pasaj sınırlarını aşarak bütünleştirme konusunda yetersizdir. Bilimsel makale incelemeleri, hukuki özetleme ve tıbbi teşhis gibi birçok gerçek dünya görevi, birden fazla belge arasındaki bilgilerin entegre edilmesini gerektirir. [6]
Bu sorunu çözmek için HippoRAG adlı yeni bir RAG çerçevesi öneriyoruz. HippoRAG, insan hafızasının hipokampal indeksleme teorisinden ilham alarak LLM’ler için uzun süreli hafıza işlevi görür. Model, neokorteksin algısal girdileri işleme yetisini taklit ederek bir bilgi grafiği (KG) oluşturur ve bunu yapay bir hipokampal indeks olarak kullanır. HippoRAG, bir sorgu aldığında, temel kavramları belirleyerek Kişiselleştirilmiş PageRank (PPR) algoritmasını çalıştırır ve çok adımlı akıl yürütmeyi tek adımlık geri çağırma sürecinde gerçekleştirir. [6]
Bu yöntem, mevcut RAG yöntemlerine kıyasla 3 ila 20 puanlık performans artışı sağlamaktadır. Ayrıca, HippoRAG’in çevrimiçi geri çağırma süreci, IRCoT gibi yinelemeli yöntemlerden 10 ila 30 kat daha ucuz ve 6 ila 13 kat daha hızlıdır. HippoRAG, IRCoT ile birleştirildiğinde ek olarak %4 ila %20 arasında performans artışı sunmaktadır. Son olarak, mevcut yöntemlerin sınırlamalarını vurgulayan ve HippoRAG’in bilgi entegrasyonu gerektiren görevlerde nasıl üstün performans sergilediğini gösteren bir vaka çalışması sunuyoruz. [6]
4.2 HippoRAG
İnsan uzun vadeli hafızasının üç bileşenini modelleyerek, onun örüntü ayrımı ve tamamlama işlevlerini taklit ediyoruz. Çevrimdışı indeksleme (Orta) için, pasajları açık bilgi grafiği (KG) üçlülerine dönüştürmek amacıyla bir büyük dil modeli (LLM) kullanıyoruz. Daha sonra, bu üçlüleri yapay hipokampal indeksimize eklerken, sentetik parahipokampal bölgelerimiz (PHR) eş anlamlılıkları tespit eder. Yukarıdaki örnekte, Profesör Thomas ile ilgili üçlüler çıkarılarak bilgi grafiğine entegre edilir. [6]
Çevrimiçi geri getirme (Alt) aşamasında, LLM neokorteksimiz bir sorgudan adlandırılmış varlıkları çıkarırken, parahipokampal geri getirme kodlayıcılarımız bunları hipokampal indeksimize bağlar. Ardından, bağlam tabanlı bir geri getirme sağlamak ve Profesör Thomas’ı çıkarmak için Kişiselleştirilmiş PageRank algoritmasından yararlanırız. [6]
Bu bölümde, öncelikle hipokampal bellek indeksleme teorisine kısa bir genel bakış sunulmakta, ardından HippoRAG’in indeksleme ve geri çağırma tasarımının bu teoriden nasıl ilham aldığı açıklanmakta ve son olarak metodolojimizin ayrıntılı bir açıklaması yapılmaktadır. [6]
Hipokampal Bellek İndeksleme Teorisi
Hipokampal bellek indeksleme teorisi, insan uzun süreli hafızasında yer alan bileşenleri ve devreleri tanımlayan köklü bir teoridir. Teyler ve Discenna, bu teoride insan hafızasının üç ana bileşenden oluştuğunu ve bu bileşenlerin iki temel amacı gerçekleştirdiğini öne sürer:
- Desen ayrımı — Farklı algısal deneyimlerin benzersiz olarak kodlanmasını sağlar. [6]
- Desen tamamlama — Eksik ipuçlarından eksiksiz anıların hatırlanmasına olanak tanır. [6]
Kodlama süreci, neokorteksin algısal uyarıları işlemesiyle başlar. Bu bilgiler parahipokampal bölgelere (PHR) yönlendirilir ve ardından hipokampus tarafından indekslenir. Hafıza geri çağırma sürecinde, hipokampus PHR üzerinden gelen kısmi uyarıları işler, ilgili anıları tamamlar ve bilgiyi neokortekse ileterek hatırlama işlemini gerçekleştirir. Bu yapı, yeni bilgilerin yalnızca hipokampal indeks üzerinde değişiklik yapılarak entegre edilmesini sağlar, böylece neokorteksin tamamının güncellenmesine gerek kalmaz. [6]
HippoRAG’in Genel Tasarımı
HippoRAG, insan hafızasındaki bu süreci taklit eden bir geri çağırma (retrieval) çerçevesidir. HippoRAG’in her bileşeni, insan uzun süreli hafızasındaki üç temel bileşene karşılık gelmektedir. [6]
- Çevrimdışı İndeksleme (Offline Indexing): HippoRAG, LLM tabanlı bir neokorteks kullanarak şeması olmayan bir bilgi grafiği (KG) oluşturur. OpenIE yöntemiyle pasajlardan bilgi çıkarır ve bu bilgileri bağımsız isim öbekleri olarak indeksler. Parahipokampal bölgeleri taklit eden geri çağırma kodlayıcıları (retrieval encoders), bu grafik üzerinde benzer kavramlar arasındaki ilişkileri belirler. [6]
- Çevrimiçi Geri Çağırma (Online Retrieval): HippoRAG, bir sorgu aldığında önemli varlıkları belirler ve bunları bilgi grafiğindeki düğümlerle eşleştirir. Hipokampusun desen tamamlama işlevini taklit etmek için Kişiselleştirilmiş PageRank (PPR) algoritmasını kullanarak, sorguya en uygun düğümleri seçer ve ilgili bağlamları belirler. [6]
Ayrıntılı Metodoloji
Çevrimdışı Dizinleme:
- Bir LLM, metin pasajlarından isim öbeklerini ve bunlar arasındaki ilişkileri çıkarmak için OpenIE tekniğini kullanır. [6]
- Elde edilen düğümler (isim öbekleri) ve ilişkiler (kenarlar) bir bilgi grafiği (KG) içinde saklanır. [6]
- Parahipokampal bölgeyi taklit eden geri çağırma kodlayıcıları, benzer isim öbekleri arasındaki ek bağlantıları belirleyerek desen tamamlamayı güçlendirir. [6]
Çevrimiçi Geri Çağırma:
- LLM, gelen sorgudaki önemli isim varlıklarını belirler. [6]
- Bu varlıklar bilgi grafiğinde en benzer düğümlerle eşleştirilir. [6]
- Kişiselleştirilmiş PageRank (PPR) algoritması, belirlenen düğümler üzerinden çalıştırılarak en alakalı bilgi düğümlerini seçer. [6]
- Seçilen düğümlerden elde edilen bilgiler, son geri çağırma (retrieval) aşamasında kullanılarak en uygun pasajlar sıralanır. [6]
Düğüm Özgüllüğü (Node Specificity)
HippoRAG, geri çağırmayı geliştirmek için düğüm özgüllüğü kavramını kullanır. Geleneksel bilgi getirme sistemlerinde terim sıklığı (IDF) gibi küresel önem sinyalleri kullanılırken, HippoRAG daha biyolojik olarak tutarlı bir yaklaşım benimser. Bir düğümün özgüllüğü, kaç farklı pasajdan türetildiğine bağlı olarak hesaplanır. Daha nadir görülen düğümler daha yüksek önem alarak, bilgi geri çağırma sırasında bağlamsal önceliklendirme yapılmasını sağlar. [6]
Bu yapı, HippoRAG’in insan beyninin bilgi entegrasyonu yeteneğini taklit ederek çok daha etkili ve hızlı bir bilgi geri çağırma sistemi oluşturmasına olanak tanır. [6]
4.3 Hipokampal Bellek İndeksleme (Hippocampal Memory Indexing) Teorisi
Bu çalışmada, geliştirilen yöntemin bilgi getirme (retrieval) yetenekleri üç çok adımlı (multi-hop) soru-cevap (QA) veri kümesi üzerinde değerlendirilmiştir: MuSiQue (answerable), 2WikiMultiHopQA ve HotpotQA. HotpotQA, çok adımlı akıl yürütme açısından daha zayıf olduğu için eklenmiştir. Deneysel maliyeti azaltmak adına, her veri kümesinin doğrulama setinden 1.000 soru seçilmiş ve gerçekçi bir bilgi getirme ortamı oluşturmak için tüm aday pasajlar bir araya getirilerek bir retrieval korpusu oluşturulmuştur. [6]
Karşılaştırmalar güçlü bilgi getirme yöntemleriyle yapılmıştır: BM25, Contriever, GTR, ColBERTv2, Propositionizer ve RAPTOR. Ayrıca IRCoT gibi çok adımlı bilgi getirme yöntemleri de kıyaslamaya dahil edilmiştir. [6]
Değerlendirme metrikleri olarak recall@2 ve recall@5 bilgi getirme başarımını ölçerken, exact match (EM) ve F1 skorları soru-cevap performansını değerlendirmek için kullanılmıştır. [6]
Sonuçlar:
- HippoRAG, MuSiQue ve 2WikiMultiHopQA veri kümelerinde tüm temel yöntemlerden daha iyi performans göstermiştir. [6]
- HotpotQA’da ise performansı benzer seviyede kalmıştır. [6]
- Çok adımlı bilgi getirme testlerinde, HippoRAG + IRCoT kombinasyonu tüm veri kümelerinde en iyi sonuçları vermiştir. [6]
Uygulama Detayları:
HippoRAG, GPT-3.5-turbo-1106 dil modeli ve Contriever veya ColBERTv2 bilgi getirme yöntemleri ile çalıştırılmıştır. Eşik değerler ve rastgele yürüme (random walk) parametreleri optimize edilerek en iyi sonuçlar elde edilmiştir.[6]
4.4 Sonuçlar
Çalışmamızda elde ettiğimiz bilgi erişim (retrieval) ve soru-cevaplama (QA) deneysel sonuçlarını sunuyoruz. Yöntemimizin dolaylı olarak QA performansını etkilediği göz önüne alındığında, QA sonuçlarını en iyi performans gösteren bilgi getirme modeli olan ColBERTv2 üzerinden raporluyoruz. Ancak, farklı güçlü tek adımlı ve çok adımlı bilgi getirme teknikleri için de sonuçlar paylaşıyoruz. [6]
Tek Adımlı Bilgiye Erişim Sonuçları
Tablo 2’de görüldüğü üzere, HippoRAG yöntemi, MuSiQue ve 2WikiMultiHopQA veri kümelerinde Propositionizer ve RAPTOR gibi son LLM destekli yöntemleri geride bırakırken, HotpotQA veri kümesinde de rekabetçi bir performans sergiliyor. Özellikle 2WikiMultiHopQA’da R@2 ve R@5 metriklerinde sırasıyla %11 ve %20'lik, MuSiQue’de ise %3’lük iyileşmeler sağlıyor. Bu başarı, 2WikiMultiHopQA’nın varlık (entity) odaklı yapısına uygunluğuyla açıklanabilir. HotpotQA’da nispeten düşük performansın sebebi, daha az bilgi entegrasyonu gerektirmesi ve HippoRAG’ın konsept-bağlam dengesinin bir trade-off yaratmasıdır; bu, Ek F.2’de açıklanan toplulaştırma (ensembling) tekniğiyle hafifletilmektedir. [6]
Çok Adımlı Bilgi Getirme Sonuçları
Tablo 3’teki deneylerimiz, HippoRAG ve IRCoT yöntemlerinin birbirini tamamlayıcı olduğunu göstermektedir. IRCoT’un bilgi getirici (retriever) olarak HippoRAG’ı kullanması, MuSiQue’de R@5 değerini %4, 2WikiMultiHopQA’da %18 ve HotpotQA’da %1 oranında artırmaktadır.[6]
Soru-Cevaplama Sonuçları
Tablo 4’te HippoRAG, ColBERTv2, IRCoT ve HippoRAG ile güçlendirilmiş IRCoT’un QA performans sonuçlarını sunuyoruz. Tek ve çok adımlı bilgi getirme performansındaki iyileşmeler, QA sonuçlarında da belirgin gelişmelere yol açmaktadır. Aynı QA okuyucusunu kullanarak MuSiQue’de %3, 2WikiMultiHopQA’da %17 ve HotpotQA’da %1 F1 skor artışı elde edilmiştir. Özellikle, tek adımlı HippoRAG, IRCoT ile benzer ya da daha iyi performans sergilerken, çevrimiçi bilgi getirme sırasında 10–30 kat daha ucuz ve 6–13 kat daha hızlı çalışmaktadır (Ek G). [6]
4.5 Tartışmalar
OpenIE Alternatifleri
HippoRAG’ın performansını artırmak için GPT-3.5 gibi kapalı modellerin gerekliliğini test etmek amacıyla, OpenIE tabanlı REBEL modeli ve açık ağırlıklı Llama-3.1’in 8B ve 70B versiyonları kullanıldı. Sonuçlara göre:
- REBEL kullanıldığında performans büyük ölçüde düştü, bu da büyük dil modellerinin (LLM) esnekliğinin önemini gösterdi. GPT-3.5, REBEL’e kıyasla iki kat daha fazla üçlü veri üreterek daha fazla bilgi bağlantısı oluşturdu.[6]
- Llama-3.1–8B, GPT-3.5 ile rekabetçi performans gösterdi ancak 2Wiki veri setinde düşük kaldı.[6]
- Llama-3.1–70B ise üç veri setinin ikisinde GPT-3.5’i geçti ve büyük veri indeksleme için maliyet etkin bir alternatif sundu.[6]
Ayrıca, küçük ölçekli bir değerlendirmede tüm modellerin REBEL’den daha iyi performans gösterdiği ancak Llama-3.1’in GPT-3.5’in biraz gerisinde kaldığı görüldü.[6]
PPR Alternatifleri
HippoRAG’ın PPR yönteminin etkinliğini test etmek için alternatif yöntemler denendi. Sonuçlar, PPR’nin tüm veri setlerinde en etkili yöntem olduğunu gösterdi. Sadece sorgu düğümlerini kullanmak veya komşu düğümleri dahil etmek performansı önemli ölçüde düşürdü.[6]
Ablasyon Testleri
- Düğüm özgüllüğünü kaldırmak, MuSiQue ve HotpotQA veri setlerinde büyük performans düşüşüne neden oldu.[6]
- Eşanlamlılık bağlantılarını kaldırmak ise 2WikiMultiHopQA üzerinde en büyük olumsuz etkiyi yarattı.[6]
HippoRAG’ın Avantajı: Tek Adımlı Multi-Hop Getirme
HippoRAG, çoklu adım gerektiren bilgi getirimi işlemini tek adımda gerçekleştirebilir.[6]
- HippoRAG, ColBERTv2’ye kıyasla tüm destekleyici pasajları getirme konusunda %3’ten %6’ya (MuSiQue), %20’den %38’e (2Wiki) varan iyileşmeler sağladı.[6]
- Örneğin, “Alhandra hangi bölgede doğdu?” sorusunda HippoRAG, Alhandra’nın Vila de Xira ile bağlantısını doğrudan kullanarak doğru yanıtı bulurken, diğer yöntemler başarısız oldu.[6]
- IRCoT modeli de bu tür soruları çözebilse de 10–30 kat daha maliyetli ve 6–13 kat daha yavaş çalışıyor.[6]
HippoRAG’ın Potansiyeli: Yol Bulma Multi-Hop Getirme
Bazı sorular insanlar için kolay ancak mevcut bilgi getirme modelleri için zor olabilir. Örneğin:
- “Hangi Stanford profesörü Alzheimer’ın nörobilimi üzerine çalışıyor?” sorusunda, HippoRAG, farklı bağlantıları değerlendirerek Thomas Südhof’un doğru yanıt olduğunu belirledi.[6]
- ColBERTv2 ve IRCoT ise bu tür çoklu bağlantı gerektiren sorularda başarısız oldu.[6]
Sonuç olarak, HippoRAG, bilgi getirimi için daha hızlı, daha doğru ve çok yönlü bir yöntem sunarak multi-hop sorgularda üstün performans göstermektedir.[6]
4.6 Alakalı Çalışmalar
Bu bölüm, büyük dil modellerinin (LLM) uzun vadeli bellek yeteneklerini ve bilgi entegrasyon yöntemlerini inceliyor.[6]
Parametrik Uzun Vadeli Bellek: Modern LLM’lerin parametrelerinin büyük miktarda dünya bilgisini içerdiği kabul edilse de, bu bilgiyi güncelleme yeteneğimiz hala sınırlıdır. Model ince ayarı, model düzenleme ve insan belleğinden ilham alan harici parametreli bellek modülleri gibi çeşitli yöntemler olsa da, sürekli öğrenme için sağlam bir çözüm henüz bulunamamıştır.[6]
RAG Tabanlı Uzun Vadeli Bellek: RAG yöntemleri, bilgiyi zamanla güncellemenin basit bir yolunu sunar. Daha gelişmiş RAG yöntemleri, çok aşamalı bilgi getirme ve oluşturma süreçleriyle yeni veya güncellenmiş bilgileri entegre edebilir. Ancak, bu tür sistemler karmaşık bilgi entegrasyon görevlerini tamamen çözememektedir. Alternatif yöntemler (RAPTOR, MemWalker, GraphRAG gibi) bilgiyi çevrimdışı indeksleme aşamasında entegre eder, ancak özetleme gerektirdiği için her yeni veri eklendiğinde bu işlemin tekrarlanması gerekir. HippoRAG ise bilgi grafiğine yeni bağlantılar ekleyerek sürekli güncellenebilir bir yapı sunar.[6]
Uzun Bağlam Uzun Vadeli Bellek Olarak: LLM’lerin bağlam pencereleri son yıllarda büyük ölçüde genişlemiştir. Ancak, uzun bağlam kullanarak uzun vadeli bellek sağlama fikri mühendislik engelleri ve mevcut sistemlerin sınırlamaları nedeniyle belirsizliğini korumaktadır.[6]
Çok Adımlı (Multi-Hop) Soru Yanıtlama & Grafik Kullanımı: Çok adımlı soru yanıtlamada grafik yapılarının kullanımı iki ana kategoriye ayrılmaktadır:
- Grafik Destekli Okuma Anlama: Burada dil modelleri, grafik sinyallerini (bağlantılar veya ortak geçen kelimeler) bir grafik sinir ağı (GNN) aracılığıyla işler. Son dönemde ise bilgi grafiği üçlüleri doğrudan model istemine dahil edilmektedir.[6]
- Grafik Destekli Bilgi Getirme: Bu yöntemde, Wikipedia bağlantıları gibi yapılandırılmış verilere dayalı olarak grafik tabanlı bilgi erişimi sağlanır. HippoRAG, Wikipedia gibi mevcut yapıları kullanmak yerine, LLM’ler aracılığıyla sıfırdan bilgi grafikleri oluşturarak çok adımlı bilgi getirme sürecini daha esnek hale getirir.[6]
LLM’ler & Bilgi Grafikleri (KGs): Dil modelleri ile bilgi grafikleri arasındaki entegrasyon uzun süredir araştırılmaktadır. LLM’lerin bilgi grafikleriyle desteklenmesi veya tam tersine, bilgi grafikleri oluşturmak için LLM’lerin kullanılması gibi çeşitli yaklaşımlar bulunmaktadır. HippoRAG, LLM’lerin bilgi grafiklerini oluşturma yeteneğini kullanarak yapılandırılmış bilginin avantajlarını birleştirir ve daha etkili bir RAG sistemi sunar.[6]
4.7 Sonuç ve Kısıtlar
Önerdiğimiz nörobiyolojik açıdan temellendirilmiş yöntem, basit olmasına rağmen, standart RAG sistemlerinin sınırlamalarını aşarken onların parametrik hafızaya göre avantajlarını koruma konusunda umut vaat etmektedir. HippoRAG’in bilgi entegrasyon yetenekleri, yol takip eden çok adımlı soru-cevapta güçlü sonuçlar vermesi, yol bulma süreçlerinde vaat göstermesi, önemli verimlilik artışları ve sürekli güncellenebilir yapısı sayesinde, standart RAG yöntemleri ile parametrik hafıza arasında güçlü bir orta yol sunarak büyük dil modelleri (LLM) için uzun vadeli hafıza konusunda etkili bir çözüm sunmaktadır.[6]
Bununla birlikte, HippoRAG’in bu hedefe daha iyi ulaşabilmesi için gelecekte ele alınması gereken bazı sınırlamalar mevcuttur. Öncelikle, HippoRAG’in tüm bileşenleri şu an ek bir eğitim yapılmadan kullanılmaktadır, dolayısıyla belirli bileşenlerin ince ayar ile geliştirilmesi mümkündür. Ek F’deki hata analizi, sistemin en çok NER ve OpenIE kaynaklı hatalar yaptığını ve bunların doğrudan ince ayar ile iyileştirilebileceğini göstermektedir. Kalan hataların ise grafik arama hataları olduğu göz önüne alındığında, ilişkilerin grafik gezinmeyi doğrudan yönlendirmesine izin vermek gibi çeşitli iyileştirme yolları bulunmaktadır. Ayrıca, Ek F.4’te belirtildiği gibi, OpenIE’nin uzun belgelerdeki tutarlılığını artırmak için daha fazla çalışma gerekmektedir. Son olarak, HippoRAG’in ölçeklenebilirliği hâlâ daha fazla doğrulamaya ihtiyaç duymaktadır. Llama-3.1’in kapalı kaynaklı modellere benzer bir performans sergileyerek maliyetleri önemli ölçüde düşürebileceğini göstermiş olsak da, sentetik hipokampal indeksimizin mevcut kıyaslamaların çok ötesine geçen boyutlarda ne kadar verimli ve etkili çalıştığını henüz ampirik olarak kanıtlamış değiliz. [6]
Kaynaklar
[1] Anthropic, (2024), Contextual Retrieval:
[https://www.anthropic.com/news/contextual-retrieval]
[2] LanceDB, (2024), Hybrid Search:
[3] GeeksforGeeks, (16 January 2025), Top Vector Databases:
[https://www.geeksforgeeks.org/top-vector-databases]
[4] Deeplearning.ai, (2024), Advanced Retrieval for AI:
[https://www.deeplearning.ai/short-courses/advanced-retrieval-for-ai/]
[5] Deeplearning.ai, (2024), Knowledge Graphs:
[https://www.deeplearning.ai/short-courses/knowledge-graphs-rag/]
[6] Bernal Jiménez Gutiérrez, Yiheng Shu, Yu Gu, Michihiro Yasunaga, Yu Su, (23 May 2024), HippoRAG: Neurobiologically Inspired Long-Term Memory for Large Language Models:
[https://arxiv.org/abs/2405.14831]
[7] Chroma Technical Report, (May 29, 2024), Embedding Adapters: