एम्बेडिंग्स

पहले शब्द को समझें। एक एम्बेडिंग संख्याओं की एक सूची है जो टेक्स्ट के एक टुकड़े की जगह लेती है और उसके अर्थ को कैप्चर करती है, जिससे समान अर्थ वाले टेक्स्ट समान संख्याओं में बदल जाते हैं। यह अध्याय बताता है कि आप शब्दों को कुछ ऐसे में कैसे बदलते हैं जो कंप्यूटर तुलना कर सकता है, और यह वह है जो सर्च को कीवर्ड के बजाय अर्थ को मिलाने देता है।
टेक्स्ट को वेक्टर के रूप में
कंप्यूटर को कैसे पता चलता है कि "कुत्ता" और "पिल्ला" संबंधित हैं, जबकि "कुत्ता" और "स्टेपलर" नहीं हैं? शब्दों में कोई महत्वपूर्ण अक्षर साझा नहीं हैं।
ट्रिक यह है कि टेक्स्ट को संख्याओं में बदलें जो इसके अर्थ को कैप्चर करें, जिससे समान अर्थ समान संख्याओं में आएं। संख्याओं की यह सूची एक एम्बेडिंग है, जिसे वेक्टर भी कहा जाता है। एक मॉडल टेक्स्ट को पढ़ता है और वेक्टर को आउटपुट करता है।
आप संख्याओं को स्वयं व्याख्या नहीं करते। जो मायने रखता है वह उनके बीच का संबंध है।
एक मोटा मानसिक चित्र: कल्पना करें कि टेक्स्ट के हर टुकड़े को अंतरिक्ष में एक बिंदु के रूप में रखा गया है, जहां समान अर्थ वाली चीजें एक दूसरे के पास हैं। "कुत्ता" और "पिल्ला" एक ही पड़ोस में आते हैं। "स्टेपलर" शहर के दूसरी तरफ है। वास्तविक एम्बेडिंग्स सैकड़ों या हजारों आयामों का उपयोग करती हैं बजाय दो या तीन के जो आप चित्रित कर सकते हैं, लेकिन अंतर्ज्ञान बना रहता है: निकटता का अर्थ समानता है।
ये संख्याएं कहां से आती हैं, और वे अर्थ के साथ क्यों संरेखित होती हैं? एक एम्बेडिंग मॉडल लर्निंग एलएलएम्स की भविष्यसूचक मॉडल का एक करीबी चचेरा भाई है, जिसे भारी मात्रा में टेक्स्ट पर प्रशिक्षित किया गया है। अपने कार्य में अच्छा होने के लिए इसे यह सीखना पड़ा कि कौन से शब्द और विचार समान संदर्भों में दिखाई देते हैं, पुरानी टिप्पणी कि आप किसी शब्द को उसकी कंपनी से जानते हैं।
"कुत्ता" और "पिल्ला" एक ही तरह के वाक्यों में दिखाई देते हैं, इसलिए मॉडल ने उन्हें समान रूप से प्रतिनिधित्व करना सीखा। "स्टेपलर" अलग कंपनी रखता है, इसलिए यह कहीं और बैठता है। सैकड़ों संख्याएं अर्थ की सीखी गई दिशाएं हैं, प्रत्येक टेक्स्ट कैसे बदलता है इसके कुछ अक्ष को कैप्चर करती है, कोई भी उन तरीकों से लेबल नहीं किए गए हैं जो एक इंसान पढ़ेगा। आप कभी भी संख्याओं को डिकोड नहीं करते, आप केवल दो वेक्टर कितने करीब हैं यह मापते हैं।
आप सर्च चाहते हैं जो वर्तनी के बजाय अर्थ से मेल खाता हो, इसलिए "अपना पैसा वापस पाएं" रिफंड के बारे में एक पंक्ति खोजता है। कदम यह है कि टेक्स्ट के हर टुकड़े को एक वेक्टर में बदल दें, संख्याओं की एक निश्चित-लंबाई सूची जहां समान अर्थ एक दूसरे के पास बैठते हैं। एक एम्बेडिंग मॉडल टेक्स्ट को पढ़ता है और वह वेक्टर निकालता है, और पूरा बिंदु यह है कि वेक्टर के बीच की दूरी अर्थ की समानता के लिए खड़ी होती है।
वेक्टर की एक निर्धारित लंबाई होती है, इसका आयाम, अक्सर कुछ सौ से कुछ हजार संख्याएं। हर टेक्स्ट जिसे आप एक ही मॉडल के साथ एम्बेड करते हैं वह समान लंबाई में आता है, जो आपको किसी भी दो को तुलना करने देता है। ये आयाम अर्थ की सीखी गई दिशाएं हैं, मानव-लेबल वाली श्रेणियां नहीं, इसलिए आप आयाम 47 को "औपचारिकता" के रूप में नहीं पढ़ते हैं। आप वेक्टर को एक अपारदर्शी बिंदु के रूप में मानते हैं और दूरियों को मापते हैं।
इससे पहले कि आप कुछ वास्तविक एम्बेड करें, एक कदम है जो तय करता है कि क्या पूरी चीज काम करती है: चंकिंग। आप शायद ही कभी एक पूरे दस्तावेज़ को एक वेक्टर के रूप में एम्बेड करते हैं, क्योंकि एक 20-पृष्ठ मैनुअल के लिए एक वेक्टर इसमें हर विषय को एक एकल औसत बिंदु में धुंधला कर देता है जो कुछ भी अच्छी तरह से मेल नहीं खाता। इसके बजाय आप दस्तावेज़ को चंक्स में विभाजित करते हैं, मोटे तौर पर एक पैराग्राफ से एक पृष्ठ तक के मार्ग, और हर चंक को अपने आप से एम्बेड करते हैं। चंक्स एम्बेड करें, पूरे दस्तावेज़ नहीं, इसलिए हर वेक्टर एक केंद्रित विचार लेकर आता है।
चंक का आकार एक वास्तविक ट्रेड-ऑफ है जो आप जानबूझकर सेट करते हैं। जो चंक्स बहुत बड़े हैं वे कई विचारों को औसत करते हैं और ढीली तरह से पुनः प्राप्त करते हैं। चंक्स जो बहुत छोटे हैं आसपास के संदर्भ को खो देते हैं जिसने एक वाक्य को अर्थपूर्ण बनाया। एक सामान्य शुरुआत बिंदु प्रति चंक कुछ सौ टोकन है जिसमें पड़ोसियों के बीच एक छोटा ओवरलैप है, इसलिए एक सीमा के पार विभाजित एक वाक्य अभी भी उनमें से एक में पूरा दिखाई देता है, फिर आप अपने पुनः प्राप्ति के आधार पर समायोजन करते हैं।
जो सर्च अर्थ पर मेल खाता है वह एक विचार पर निर्भर करता है: वेक्टर स्पेस में टेक्स्ट को मैप करें (एक निश्चित-आयामी समन्वय स्पेस जहां हर बिंदु संख्याओं की एक सूची है) जिससे सिमेंटिक समानता ज्यामितीय निकटता बन जाती है। एक एम्बेडिंग मॉडल वह नक्शा है: यह टेक्स्ट को पढ़ता है और एक वेक्टर देता है।
हर चीज जो आप शीर्ष पर बनाते हैं, इंडेक्सिंग, पुनः प्राप्ति, रैंकिंग, वह सब उन वेक्टर पर ज्यामिति है। मैपिंग गलत करें और नीचे की कोई भी चीज इसे पुनः प्राप्त नहीं कर सकती।
पहला डिजाइन लीवर यह है कि आप क्या एम्बेड करते हैं, और वह चंकिंग है: एम्बेड करने से पहले पूरी फाइलों को एम्बेड करने के बजाय स्रोत दस्तावेज़ों को मार्गों में विभाजित करना। कारण यह है कि एक एम्बेडिंग इसके इनपुट का एक हानिकारक संपीड़न है एक बिंदु में, इसलिए एक चंक जितनी अधिक अलग विचारें रखता है, उतना वे एक अस्पष्ट केंद्रद्रव्य में औसत होते हैं जो तेजी से कुछ भी पुनः प्राप्त नहीं करता है। चंक को सही आकार दें जिसके बारे में एक उपयोगकर्ता पूछेगा, आमतौर पर कुछ सौ टोकन का एक मार्ग, एक छोटा ओवरलैप के साथ ताकि एक तथ्य एक सीमा के पार जीवित रहे कम से कम एक चंक में।
चंकिंग रणनीति वह जगह है जहां पुनः प्राप्ति की गुणवत्ता जीती या खोई जाती है, मॉडल विकल्प से अधिक:
- निश्चित आकार की खिड़कियां सस्ती हैं, लेकिन वे वाक्यों को बीच में काट देती हैं।
- संरचना-जागरूक विभाजन (शीर्षकों, पैराग्राफों या कोड ब्लॉक पर) हर चंक को सुसंगत रखता है और बेहतर पुनः प्राप्ति करता है, अधिक जटिल पाइपलाइन की कीमत पर।
जो भी आप चुनें, चंक के स्रोत और स्थिति को इसके वेक्टर के साथ संग्रहीत करें, क्योंकि पुनः प्राप्ति समय पर आपको यह बताना होगा कि एक तथ्य कहां से आया है, और एक नंगा वेक्टर आपको नहीं बता सकता।
एक और संपत्ति को आंतरिक करें: एम्बेडिंग आयाम व्याख्या योग्य नहीं हैं अक्षों को, इसलिए आप एक वेक्टर पढ़कर पुनः प्राप्ति को डीबग नहीं कर सकते। आप एम्बेडिंग्स को निरीक्षण करके डीबग करते हैं कि वे क्या पुनः प्राप्त करते हैं, कभी भी संख्याओं को पढ़कर नहीं। जब कोई क्वेरी गलत चंक खींचती है, तो सुधार चंकिंग, मॉडल, या क्वेरी में है, पुनः प्राप्त परिणामों के माध्यम से परीक्षा की जाती है, निर्देशांक के माध्यम से नहीं।
एम्बेडिंग जेनरेट करना
आप एम्बेडिंग मॉडल से वेक्टर के लिए पूछते हैं, बिल्कुल सामान्य मॉडल कॉल जैसे लेकिन एक अलग एंडपॉइंट और एक अलग मॉडल के साथ।
from openai import OpenAI
client = OpenAI()
EMBED_MODEL = "text-embedding-3-small" # एम्बेडिंग मॉडल के लिए स्वैप बिंदु
def embed(text):
response = client.embeddings.create(model=EMBED_MODEL, input=text)
return response.data[0].embedding # संख्याओं की एक सूची
vector = embed("एक छोटा खेलने वाला कुत्ता")
print(len(vector)) # उदा। 1536 संख्याएंपरिणाम संख्याओं की एक सूची है। एक नियम बाकी सब से अधिक मायने रखता है: हमेशा एक ही मॉडल से बनाई गई एम्बेडिंग्स की तुलना करें। विभिन्न मॉडलों से वेक्टर विभिन्न स्पेस में रहते हैं और तुलना योग्य नहीं हैं, विभिन्न इकाइयों में माप की तरह। मॉडल मिलाना हर बार बकवास स्कोर देता है।
और हर मॉडल कॉल की तरह, आपको टोकन के आधार पर बिल किया जाता है, इसलिए एक बड़े दस्तावेज़ संग्रह को एम्बेड करने की एक लागत होती है जो आप हजारों दस्तावेज़ों के पार चलाने से पहले अनुमान लगाने लायक होते हैं।
कॉल स्वयं एक अलग एंडपॉइंट है जिसमें एक चैट कॉल जैसा ही आकार है: आप टेक्स्ट भेजते हैं, आप एक वेक्टर वापस पाते हैं।
from openai import OpenAI
client = OpenAI()
EMBED_MODEL = "text-embedding-3-small"
def embed_batch(texts):
# एक अनुरोध में कई टेक्स्ट भेजें, हर टेक्स्ट के लिए एक वेक्टर वापस पाएं
response = client.embeddings.create(model=EMBED_MODEL, input=texts)
return [item.embedding for item in response.data]
chunks = ["एक छोटा खेलने वाला कुत्ता", "एक जवान कुत्ता", "एक कार्यालय सामग्री"]
vectors = embed_batch(chunks) # एक वेक्टर प्रति चंक, इनपुट में एक ही क्रम मेंआकार प्रदाता द्वारा भिन्न होता है, लेकिन अनुरोध-और-प्रतिक्रिया विचार हर जगह एक ही है। मूल कॉल से परे दो व्यावहारिक बिंदु। पहला, बैच में एम्बेड करें: अधिकांश एंडपॉइंट एक अनुरोध में टेक्स्ट की एक सूची लेते हैं और एक वेक्टर हर एक के लिए देते हैं, जो एक कॉल प्रति चंक से तेज और सस्ता है क्योंकि आप कम अनुरोध ओवरहेड का भुगतान करते हैं और दर सीमा को कम हिट करते हैं। जब आप एक बड़े कॉर्पस को इंडेक्स करते हैं, तो बैचिंग वह है जो लागत और दीवार-घड़ी समय को समझदारी भरा रखता है।
दूसरा, एक ही-मॉडल नियम का एक साथी है: मॉडल और आयाम एक विकल्प हैं, एक डिफ़ॉल्ट नहीं। बड़े एम्बेडिंग मॉडल अधिक आयामों के साथ बेहतर अंतर कैप्चर करते हैं लेकिन गणना, स्टोरेज और तुलना करने में अधिक खर्च होता है। सबसे छोटा मॉडल और आयाम चुनें जो अभी भी आपके डेटा को अलग करता है।
कुछ मॉडल आपको एक छोटा वेक्टर अनुरोध करने देते हैं भी सटीकता के लिए सस्ता स्टोरेज और तेज सर्च का व्यापार करने के लिए। जो भी आप चुनते हैं वह एक अनुबंध बन जाता है: मॉडल को बाद में बदलें और हर संग्रहीत वेक्टर को फिर से जेनरेट करना पड़ता है।
एपीआई सतह छोटी है। आप टेक्स्ट भेजते हैं, आप एक फ्लोट वेक्टर पाते हैं, और दिलचस्प निर्णय उस कॉल के चारों ओर हैं न कि इसमें।
from openai import OpenAI
client = OpenAI()
EMBED_MODEL = "text-embedding-3-small"
def embed_batch(texts):
# एक अनुरोध, कई वेक्टर वापस, इनपुट क्रम में
response = client.embeddings.create(model=EMBED_MODEL, input=texts)
return [item.embedding for item in response.data]अनुरोध और प्रतिक्रिया आकार प्रदाता द्वारा भिन्न होता है, लेकिन हर एम्बेडिंग एंडपॉइंट यह एक ही बैच-इन, वेक्टर-आउट अनुबंध है। बैचिंग स्केल पर एक सुविधा नहीं है, यह उस अंतर है जो एक इंडेक्स जॉब को खत्म करता है और जो एक को पूरे दोपहर थ्रॉटल करता है: हर अनुरोध निश्चित ओवरहेड रखता है और एक दर सीमा के विरुद्ध गिनता है, इसलिए दोनों को बड़े बैच भेजकर मोचा गया करें, फिर पीछे हटें और बड़े जॉब को मारने वाली आंशिक विफलताओं पर फिर से प्रयास करें।
मॉडल और आयाम एक लंबी पूंछ के साथ एक आर्किटेक्चरल प्रतिबद्धता हैं। उच्च-आयामी वेक्टर अधिक अंतर संग्रहीत करते हैं लेकिन उत्पादन में मायने रखने वाले हर अक्ष पर अधिक लागत आती है: वेक्टर प्रति स्टोरेज, इंडेक्स में मेमोरी, और तुलना प्रति समय, सब कुछ आयाम के साथ स्केल करता है। कुछ मॉडल मात्रेयोष्का एम्बेडिंग्स का समर्थन करते हैं (वेक्टर जिन्हें आप एक छोटी लंबाई तक छंट सकते हैं और अभी भी उपयोग कर सकते हैं), जो आपको सटीकता के लिए पूर्ण वेक्टर रखने देते हैं और सस्ता पहली-पास सर्च के लिए एक छंटा हुआ उपसर्ग सेवा करते हैं। यह लायक है जब आपका कॉर्पस पर्याप्त बड़ा है कि स्टोरेज और क्वेरी लेटेंसी, मॉडल गुणवत्ता नहीं, बाध्यकारी बाधा हैं।
वह प्रतिबद्धता जो बाद में काटती है वह संस्करण है। एक संग्रहीत एम्बेडिंग हमेशा के लिए एक सटीक मॉडल संस्करण से जुड़ी होती है।
दो मॉडल संस्करणों से वेक्टर तुलना योग्य नहीं हैं, इसलिए आप एक नई क्वेरी को एक नए मॉडल के साथ एम्बेड नहीं कर सकते और इसकी तुलना पुरानी वाली पर बने एक इंडेक्स के विरुद्ध नहीं कर सकते। परिणाम मौन है: स्कोर प्रशंसनीय दिखते हैं और रैंकिंग गलत हैं। मॉडल संस्करण को अपने इंडेक्स के स्कीमा के हिस्से के रूप में मानें, इसे वेक्टर के साथ संग्रहीत करें, और इस अध्याय में बाद में ताजगी चर्चा में कवर किए गए एक माइग्रेशन के रूप में फिर से एम्बेड करने की योजना बनाएं।
समानता को मापना
यह जानने के लिए कि दो टेक्स्ट कितने संबंधित हैं, आप यह मापते हैं कि उनके वेक्टर कितने करीब हैं। मानक माप है कोसाइन समानता, जो -1 से 1 तक की संख्या देता है: 1 के करीब का मतलब अधिक समान है, 0 के पास का मतलब असंबंधित है। इसे उपयोग करने के लिए आपको इसके पीछे का गणित जानने की आवश्यकता नहीं है। इसे दो वेक्टर जिस दिशा में इंगित करते हैं उसकी तुलना करने के रूप में सोचें।
दिशा वह है जो यहाँ अर्थ को ले जाती है, यही कारण है कि कोसाइन कच्ची दूरी के बजाय कोण की तुलना करता है। रिफंड के बारे में दो टेक्स्ट एक ही तरह से इंगित करते हैं चाहे एक छोटा वाक्य हो या लंबा पैराग्राफ।
import math
def cosine_similarity(a, b):
dot = sum(x * y for x, y in zip(a, b))
mag_a = math.sqrt(sum(x * x for x in a))
mag_b = math.sqrt(sum(x * x for x in b))
return dot / (mag_a * mag_b)
dog = embed("एक छोटा खेलने वाला कुत्ता")
puppy = embed("एक जवान कुत्ता")
stapler = embed("कागज़ बाइंडिंग के लिए एक कार्यालय सामग्री")
print(cosine_similarity(dog, puppy)) # उच्चतर, दोनों अर्थ में करीब हैं
print(cosine_similarity(dog, stapler)) # निचला, दोनों असंबंधित हैंसंख्याएं अंतर्ज्ञान की पुष्टि करती हैं: कुत्ता और पिल्ला उच्च स्कोर करते हैं, कुत्ता और स्टेपलर निचला स्कोर करते हैं। एक क्वेरी के वेक्टर की तुलना संग्रहीत वेक्टर के विरुद्ध करना सिमेंटिक सर्च का दिल है। वह एक फंक्शन वह कदम है जो अध्याय के बाकी हिस्से को बनाता है।
आपके पास वेक्टर हैं, अब आपको एक संख्या चाहिए जो कहे कि दो कितने करीब हैं। मानक है कोसाइन समानता: यह दो वेक्टर के बीच के कोण को मापता है, उनकी लंबाई को नजरअंदाज करता है, और -1 से 1 तक की मान देता है जहां उच्च का मतलब अधिक समान है। कोण, दूरी नहीं, वह है जो आप चाहते हैं, क्योंकि एक ही विषय पर एक लंबा मार्ग और एक छोटा वाक्य समान स्कोर करना चाहिए भले ही उनके वेक्टर की अलग-अलग परिमाण हो।
def dot(a, b):
return sum(x * y for x, y in zip(a, b))
# अगर वेक्टर को लंबाई 1 तक सामान्य किया जाता है, तो कोसाइन समानता उनके डॉट प्रोडक्ट के बराबर है
score = dot(query_vector, doc_vector) # कोई विभाजन की जरूरत नहींयहाँ वह कदम है जो स्केल पर भुगतान करता है: जब आप संग्रहीत करते हैं तो अपने वेक्टर को लंबाई 1 तक सामान्य करें। एक बार हर वेक्टर की एक ही लंबाई है, कोसाइन समानता में विभाजन गायब हो जाता है और स्कोर एक साधारण डॉट प्रोडक्ट बन जाता है, जो एक आयाम प्रति गुणा-और-जोड़ है और एक बड़े सेट में चलाने के लिए बहुत तेज़ है। एक बार स्टोरेज पर सामान्य करें, फिर एक डॉट प्रोडक्ट के साथ तुलना करें। कई एम्बेडिंग मॉडल पहले से सामान्य वेक्टर देते हैं, लेकिन यह न मानें, जांचें।
दूसरी बदलाव खिलौने के उदाहरण से: आप शायद ही कभी एक एकल निकटतम मेल चाहते हैं। आप शीर्ष-के पुनः प्राप्ति चाहते हैं, k उच्चतम-स्कोरिंग परिणाम, आमतौर पर तीन से दस। एक वेक्टर एक निकट-मिस हो सकता है जबकि दूसरा और तीसरा मिलकर उत्तर रखते हैं, और एक छोटा सेट देने से अगले चरण को काम करने के लिए कमरा मिलता है। केवल शीर्ष वाला लौटाना संदर्भ को फेंक देता है जिसे आप रखना चाहते।
आपको एक समानता मेट्रिक चाहिए, और कोसाइन समानता (दो वेक्टर के बीच कोण की कोसाइन, -1 से 1 तक) डिफ़ॉल्ट है क्योंकि यह परिमाण को नजरअंदाज करता है, इसलिए दस्तावेज़ की लंबाई विषय को डुबो नहीं देती। उस विकल्प का एक प्रदर्शन परिणाम है जो डिज़ाइन करने लायक है: कच्चे वेक्टर पर कोसाइन एक डॉट प्रोडक्ट प्लस दो परिमाण संगणना प्रति तुलना करता है, और आप हर क्वेरी पर इंडेक्स में हर वेक्टर के लिए इसे भुगतान करते हैं।
import numpy as np
# लिखने के समय सामान्य करें, वेक्टर प्रति एक बार
def normalize(v):
v = np.asarray(v, dtype=np.float32)
return v / np.linalg.norm(v)
# क्वेरी समय पर, सामान्य वेक्टर पर कोसाइन एक एकल मैट्रिक्स-वेक्टर प्रोडक्ट है
def search(query_vec, matrix):
return matrix @ query_vec # एक स्कोर प्रति पंक्ति, वेक्टरीकृतअनुकूलन सामान्य करना है हर वेक्टर को लिखने के समय यूनिट लंबाई तक, जो कोसाइन समानता को एक डॉट प्रोडक्ट के समान बनाता है और आपको पूरे कॉर्पस को एक मैट्रिक्स-वेक्टर गुणा के बजाय एक पायथन लूप के रूप में स्कोर करने देता है। लिखने के समय सामान्य करें और समानता एक डॉट प्रोडक्ट है पूरे इंडेक्स के पार एक बार। यह ब्रूट-फोर्स बेसलाइन है: सटीक, प्रत्यक्ष, और कॉर्पस आकार में रैखिक, जो कम सौ हजार वेक्टर तक ठीक है और उससे ऊपर गिरता है, बाद में कवर किए गए एक अनुमानित इंडेक्स में जाने का संकेत।
दो उत्पादन वास्तविकताएं जो खिलौने की मेट्रिक को छुपाती हैं। पहला, आप लगभग कभी भी argmax नहीं चाहते, आप एक स्कोर थ्रेशोल्ड के साथ शीर्ष-के चाहते हैं, क्योंकि पहले और दूसरे परिणाम के बीच की खाई आपको बताती है कि मेल कितना आत्मविश्वास से है, और एक शीर्ष परिणाम जो अभी भी कम स्कोर करता है आपके कॉर्पस में कुछ भी नहीं का मतलब है वास्तव में क्वेरी का उत्तर दिया। इसे "कोई अच्छा मेल नहीं" के रूप में लौटाएं बजाय सबसे खराब पंक्ति को हाथ देने के। दूसरा, कोसाइन समानता सापेक्ष है, निरपेक्ष नहीं: 0.4 का स्कोर केवल आपके कॉर्पस और मॉडल जो स्कोर उत्पादित करते हैं उसके वितरण के विरुद्ध अर्थपूर्ण है, इसलिए थ्रेशोल्ड को अनुभवजन्य रूप से इंडेक्स प्रति कैलिब्रेट करें बजाय एक ब्लॉग पोस्ट से जादुई संख्या की नकल करने के।
सिमेंटिक सर्च
टुकड़ों को एक साथ रखें और आप कीवर्ड के बजाय अर्थ से सर्च कर सकते हैं। अपने दस्तावेज़ों को एक बार एम्बेड करें और वेक्टर संग्रहीत करें। जब एक क्वेरी आती है, तो इसे भी एम्बेड करें, फिर उन दस्तावेज़ों को लौटाएं जिनके वेक्टर सबसे करीब हैं।
docs = [
"अपने खाते की सेटिंग्स पृष्ठ से अपना पासवर्ड रीसेट करें।",
"हमारी रिफंड नीति 30 दिनों के भीतर रिटर्न की अनुमति देती है।",
"बिलिंग प्रश्नों के लिए ईमेल द्वारा सहायता से संपर्क करें।",
]
# हर दस्तावेज़ को एक बार एम्बेड करें, आगे
index = [{"text": text, "vector": embed(text)} for text in docs]
def search(query):
query_vector = embed(query)
scored = [{"text": item["text"], "score": cosine_similarity(query_vector, item["vector"])} for item in index]
return sorted(scored, key=lambda x: x["score"], reverse=True)[0] # सबसे करीबी मेल
print(search("मुझे अपना पैसा कैसे वापस मिलेगा?"))
# रिफंड-नीति पंक्ति लौटाता है, भले ही इसमें क्वेरी के साथ कोई महत्वपूर्ण शब्द साझा न होक्वेरी "मुझे अपना पैसा कैसे वापस मिलेगा?" रिफंड पंक्ति को खोजता है भले ही यह इससे कोई महत्वपूर्ण शब्द साझा न करता हो। सिमेंटिक सर्च वर्तनी नहीं, अर्थ पर मेल खाता है। कीवर्ड सर्च इसे पूरी तरह से मिस कर देता, क्योंकि क्वेरी और दस्तावेज़ के पास कोई महत्वपूर्ण शब्द सामान्य नहीं हैं।
आकार हमेशा समान होता है: एक बार एम्बेड करें और वेक्टर रखें, अनुरोध समय पर क्वेरी एम्बेड करें, स्कोर करें, सॉर्ट करें, सबसे करीब वाला लौटाएं। वह सिमेंटिक सर्च लूप है, और आप इस हैंडबुक के बाकी हिस्से के लिए इसे फिर से उपयोग करेंगे।
खिलौने का संस्करण वेक्टर को एक पायथन सूची में संग्रहीत करता है और हर क्वेरी पर हर एक को स्कोर करता है। वह एक डेमो के लिए काम करता है और इसके लिए टूट जाता है क्षण आपका कॉर्पस बढ़ता है, क्योंकि एक रैखिक स्कैन हर क्वेरी पर हर वेक्टर पर पूरी तुलना फिर से चलाता है, और आप हर पुनरारंभ पर मेमोरी में इंडेक्स फिर से बनाते हैं।
सुधार एक वेक्टर डेटाबेस है: वेक्टर रखने और "इस क्वेरी वेक्टर के लिए निकटतम के को खोजो" तेजी से जवाब देने के लिए बनाया गया एक स्टोर। अपनी सूची और लूप के बजाय, आप एक बार वेक्टर लिखते हैं और डेटाबेस सर्च, दृढ़ता, और अलग प्रविष्टियों को अपडेट करने को संभालता है।
# आकार वेक्टर डीबी द्वारा भिन्न होता है, लेकिन पैटर्न हर जगह समान है
index.upsert([
{"id": "doc-1", "vector": embed(chunk), "metadata": {"source": "faq.md", "section": "refunds"}}
for chunk in chunks
])
results = index.query(vector=embed(user_question), top_k=5) # निकटतम 5, केवल 1 नहींदो चीजें जो डेटाबेस आपको सूची नहीं देता है:
- यह शीर्ष-के परिणाम एक कॉल में लौटाता है, निकटतम कई बजाय एक एकल सबसे अच्छा, जो वह है जो आप वास्तव में अगले चरण को खिलाते हैं।
- यह हर वेक्टर के साथ मेटाडेटा संग्रहीत करता है, स्रोत फाइल, अनुभाग, और टाइमस्टैम्प, इसलिए एक परिणाम इसके स्रोत की ओर संकेत कर सकता है और आप इसके आधार पर फ़िल्टर कर सकते हैं।
वेक्टर को एक वेक्टर डीबी में संग्रहीत करें, एक पायथन सूची में नहीं, इसके बाद आपके पास वास्तविक डेटा है। सूची तंत्र सिखाता है, डेटाबेस इसे चलाता है।
संदर्भ लूप, एक बार एम्बेड करें, स्टोर करें, क्वेरी एम्बेड करें, स्कोर करें, सॉर्ट करें, लौटाएं, सही है और पैमाने के साथ संपर्क से नहीं बचता। एक ब्रूट-फोर्स स्कैन कॉर्पस आकार प्रति क्वेरी में रैखिक है, इसलिए एक मिलियन वेक्टर पर और कोई भी वास्तविक क्वेरी दर आप एक CPU-सेकंड प्रति सर्च खर्च कर रहे हैं जो आपके पास नहीं है। उत्पादन सर्च में सब कुछ हर वेक्टर की तुलना क्वेरी के विरुद्ध करने के बारे में नहीं है।
वह है जो अनुमानित निकटतम पड़ोसी सर्च आपको देता है (एएनएन: एल्गोरिदम जो सही निकटतम को याद करने का एक छोटा मौका स्वीकार करके तेजी से सबसे निकट वेक्टर खोजता है)। दो परिवार हावी हैं:
- एचएनएसडब्ल्यू (पदानुक्रमित नेविगेबल छोटी दुनिया) वेक्टर का एक स्तरीय ग्राफ बनाता है और इसे मोटे से महीन तक चलाता है, उच्च स्मृति की कीमत पर कम क्वेरी लेटेंसी देता है।
- आईवीएफ (उल्टी फाइल) वेक्टर को क्लस्टर करता है और केवल निकटतम क्लस्टर को सर्च करता है, कम स्मृति का उपयोग करता है लेकिन डेटा बदलने के साथ अपने क्लस्टर केंद्रों को फिर से प्रशिक्षित करने की जरूरत है।
कुछ सौ हजार वेक्टर से ऊपर, एएनएन वैकल्पिक नहीं है, यह इंडेक्स है। ट्रेड-ऑफ जो आप ट्यून कर रहे हैं वह याद रखना है (सच्चे निकटतम पड़ोसियों का अंश जो इंडेक्स वास्तव में लौटाता है) लेटेंसी और स्मृति के विरुद्ध: याद रखना को 1.0 की ओर दबाएं और क्वेरी धीमी हो जाती हैं, इसे आराम दें और आप चुपचाप प्रासंगिक चंक्स को याद करते हैं।
वह विफलता मोड जो खिलौने का लूप पूरी तरह से छुपाता है वह ताजगी है। एक इंडेक्स एक स्नैपशॉट है, इसलिए जब एक स्रोत दस्तावेज़ बदलता है, तो इसका संग्रहीत वेक्टर अब पुराना है और वर्तमान के रूप में पुनः प्राप्त होता रहता है। आपको एक पुनः एम्बेड पाइपलाइन की जरूरत है जो दस्तावेज़ परिवर्तन पर कुंजीबद्ध है: संपादन का पता लगाएं, उस दस्तावेज़ को फिर से चंक करें और फिर से एम्बेड करें, और नए वेक्टर को अपसर्ट करें जबकि पुरानी को हटा दें, आदर्श रूप से एक सामग्री हैश द्वारा ट्रैक किया गया जिससे आप पूरे कॉर्पस पर हर संपादन के बजाय केवल वास्तव में बदली हुई चीज़ों को फिर से एम्बेड करते हैं। पुराने वेक्टर वह शांत तरीका है जिससे एक पुनः प्राप्ति प्रणाली झूठ बोलना शुरू करती है।
आखिरी खाई सटीक मेल है। वेक्टर सर्च इसे याद करने के लिए बनाया गया है: एक क्वेरी ऑर्डर A-10293 या एक त्रुटि कोड के लिए एक अस्पष्ट क्षेत्र में एम्बेड करता है और उस एक दस्तावेज़ को सतह पर नहीं ला सकता जो उस स्ट्रिंग में है। हाइब्रिड सर्च कीवर्ड सर्च (जो सटीक टोकन को नेल करता है) और वेक्टर सर्च (जो अर्थ को नेल करता है) को एक साथ चलाता है और रैंकिंग को मर्ज करता है, इसलिए पहचानकर्ता, नाम, और दुर्लभ शर्तें सिमेंटिक धुंधले में खो नहीं जाती हैं। इसे तब पहुंचें जब आपके कॉर्पस में चीजें हों जिन्हें उपयोगकर्ता सटीक रूप से टाइप करेंगे।
एम्बेडिंग्स किसके लिए नहीं हैं
एम्बेडिंग्स एक समानता उपकरण हैं, एक सोचने का उपकरण नहीं। अपने आप को याद दिलाने के लिए कुछ सीमाएं ताकि आप सही उपकरण तक पहुंचें:
- वे सटीक मिलान नहीं करते हैं। एक सटीक ऑर्डर नंबर या एक त्रुटि कोड खोजने के लिए, सादा कीवर्ड या डेटाबेस सर्च बेहतर है।
- वे कारण नहीं करते। वे आपको बताते हैं कि दो चीजें संबंधित हैं, वजह नहीं, और न ही यह कि एक तार्किक रूप से दूसरे का अनुसरण करता है।
- वे पुराने हो जाते हैं। अगर आपके दस्तावेज़ बदलते हैं, तो आपको उन्हें फिर से एम्बेड करना होगा, या आपका सर्च पुरानी सामग्री की ओर इशारा करेगा।
एम्बेडिंग्स पाते हैं कि क्या संबंधित है, वे निर्णय नहीं लेते कि क्या सच है या वर्तमान है। इनमें से प्रत्येक खाई का एक बेहतर उपकरण है, और कौन सा उपयोग करना है यह जानना आधा कौशल है।
एक उपकरण के साथ जो इतनी अच्छी तरह से काम करता है वह कहीं भी इसे पहुंचाने का जाल है। तीन सीमाएं चिह्नित करती हैं जहां रुकें और कुछ और चुनें।
सटीक मिलान पहला है। एम्बेडिंग्स एक ऑर्डर आईडी या एक त्रुटि कोड को एक ही अस्पष्ट सिमेंटिक स्पेस में मैप करते हैं जैसे सब कुछ, इसलिए A-10293 अन्य ऑर्डर-आकार वाली स्ट्रिंग्स के पास आता है, बिल्कुल अपने आप पर नहीं। पहचानकर्ता, कोड, और कोई भी मान जिसे उपयोगकर्ता शाब्दिक रूप से टाइप करता है के लिए, कीवर्ड या डेटाबेस सर्च सही उपकरण है, और हाइब्रिड सर्च (कीवर्ड और वेक्टर सर्च एक साथ चलाता है) कैसे आप एक बार दोनों को पाते हैं।
कारण दूसरा है। समानता अनुमान नहीं है: दो चंक्स उच्च स्कोरिंग का मतलब वे संबंधित चीजों के बारे में हैं, न कि एक प्रश्न का उत्तर देता है या तार्किक रूप से दूसरे से अनुसरण करता है। कारण वह मॉडल में होता है जिसे आप पुनः प्राप्त चंक्स खिलाते हैं, पुनः प्राप्ति में नहीं। पुनः प्राप्ति उम्मीदवार खोजता है, मॉडल सोचता है।
ताजगी तीसरी और जो चुपचाप काटती है। आपका इंडेक्स एक स्नैपशॉट है जब भी आपने इसे एम्बेड किया, इसलिए संपादित दस्तावेज़ अपनी पुरानी शब्दावली को वापस लौटाते रहते हैं जब तक आप फिर से एम्बेड न करें। परिवर्तन पर पुनः एम्बेड करने को सिस्टम के हिस्से के रूप में मानें, न कि एक चीज जो आप तब करते हैं जब कोई गलत उत्तर मिलता है।
जानना कि एम्बेडिंग्स कहां रुकती हैं वह है जो एक पुनः प्राप्ति प्रणाली को विश्वसनीय रखता है, और तीन सीमाएं उत्पादन में महत्वपूर्ण हैं।
सटीक मेल एक संरचनात्मक अंधा स्थान है, ट्यूनिंग समस्या नहीं। एम्बेडिंग्स इनपुट को सिमेंटिक पड़ोस की ओर संपीड़ित करते हैं, इसलिए पहचानकर्ता, कोड, एसकेयू, और दुर्लभ उचित नाम समान-दिखने वाली स्ट्रिंग्स की ओर स्मीयर हो जाते हैं बजाय स्वयं पर आते हैं। शुद्ध वेक्टर सर्च सटीक हिट से ऊपर एक निकट-मिस रैंक करेगा। सुधार हाइब्रिड पुनः प्राप्ति है जो एक शब्दार्थ इंडेक्स (कीवर्ड या बीएम 25, एक क्लासिक शब्द-आवृत्ति रैंकिंग) को वेक्टर इंडेक्स के साथ फ्यूज करता है, इसलिए सटीक टोकन सटीक से मेल खाते हैं जबकि अर्थ सिमेंटिक रूप से मेल खाते हैं, और आप एक ऑर्डर लुकअप को ज्यामिति पर जुआ नहीं खेलते।
ड्रिफ्ट वह सीमा है जो चुपचाप क्षीण हो जाती है। एम्बेडिंग ड्रिफ्ट वह असमानता है जो आप प्राप्त करते हैं जब आपके इंडेक्स के पीछे का मॉडल संस्करण बदलता है: दो संस्करणों से वेक्टर विभिन्न स्पेस पर कब्जा करते हैं और तुलना योग्य नहीं हैं, इसलिए पुराने वाली के विरुद्ध एक नए मॉडल के साथ एम्बेड किया गया एक क्वेरी प्रशंसनीय दिखने वाली बकवास देता है। अपने इंडेक्स स्कीमा में मॉडल संस्करण को पिन करें, और किसी भी मॉडल अपग्रेड को एक पूर्ण पुनः एम्बेड माइग्रेशन के रूप में मानें, ड्रॉप-इन नहीं, क्योंकि कोई त्रुटि नहीं फेंकी जाती, केवल चुपचाप खराब परिणाम।
मूल्यांकन वह सीमा है जिसे लोग पुनः प्राप्ति पहले से ही विफल होने तक छोड़ देते हैं। आप आँख से यह नहीं बता सकते कि क्या पुनः प्राप्ति अच्छी है, आप इसे मापते हैं: ज्ञात-प्रासंगिक चंक्स के साथ क्वेरी का एक सेट रखें और याद रखना@के (क्या सही चंक शीर्ष के में आया) और सटीकता (आपके द्वारा लौटाई गई कितनी प्रासंगिक थी) स्कोर करें। अगर आप याद रखना@के को मापते नहीं हैं, तो आप नहीं जानते कि क्या पुनः प्राप्ति काम करती है।
वह मूल्यांकन सेट भी वह है जो आपको बताता है कि एक नई चंकिंग रणनीति, मॉडल, या हाइब्रिड वेटिंग वास्तव में मदद करी, बजाय एक विफलता को दूसरे से बदलने के जिसे आप अभी तक नहीं देख पाए हैं। पुनः प्राप्ति गुणवत्ता वह नींव है जिस पर आरएजी खड़ा होता है, इसलिए यह एक वास्तविक परीक्षा अर्जित करता है, एक वाइब जांच नहीं।
अभ्यास में
पूर्ण प्रवाह ऊपर search फंक्शन में रहता है: एफएक्यू प्रविष्टियों के एक छोटे सेट को एक बार एम्बेड करें, फिर अर्थ द्वारा क्वेरी का जवाब दें। पैटर्न किसी भी आकार में समान होता है, केवल स्टोरेज बदलता है जब आप बढ़ते हैं।
अगले अध्याय में आप सबसे करीबी मेल लेते हैं और उन्हें एक मॉडल को खिलाते हैं, इसलिए यह आपके दस्तावेज़ों से मेमोरी के बजाय उत्तर देता है। वह तकनीक आरएजी है, और यह सीधे यहाँ लिखे गए बिल्कुल पर बनी है।
तंत्र तय है: चंक, बैच-एम्बेड, वेक्टर डीबी में स्टोर करें, समानता से शीर्ष-के पुनः प्राप्त करें। जो बदलता है जब आप उत्पादन में जाते हैं वह चारों ओर का प्लंबिंग है, परिवर्तन पर पुनः एम्बेड, आप जिस मेटाडेटा को फ़िल्टर करते हैं, मॉडल संस्करण जिसे आप पिन करते हैं।
पुनः प्राप्त चंक्स अंत नहीं हैं। अगले अध्याय में आप उन्हें एक मॉडल को संदर्भ के रूप में खिलाते हैं ताकि यह आपके दस्तावेज़ों से उत्तर दे सके। वह आरएजी है, और इसके उत्तर की गुणवत्ता यहाँ आप जो पुनः प्राप्ति बनाते हैं उससे सीमित है।
सब कुछ यहाँ, चंकिंग, सामान्यीकरण, एएनएन इंडेक्सिंग, हाइब्रिड पुनः प्राप्ति, ताजगी, और याद रखना@के मूल्यांकन, पुनः प्राप्ति परत है, और यह वह भाग है जो एक एआई प्रणाली का चुपचाप निर्णय लेता है कि क्या उत्तर किसी भी अच्छे हैं। मॉडल शीर्ष पर केवल पुनः प्राप्ति को हाथ सकता है, इसलिए एक आत्मविश्वास से गलत उत्तर आमतौर पर पुनः प्राप्ति विफलता है एक पीढ़ी पोशाक पहने हुए।
वह अगले अध्याय के लिए पुल है। आरएजी यह पुनः प्राप्ति परत है जो एक मॉडल में आधारित संदर्भ खिलाता है, और यहाँ हर विफलता मोड, पुराने वेक्टर, छूटे सटीक मेल, कम याद रखना, वहाँ एक गलत उत्तर के रूप में सतह पर आता है। पुनः प्राप्ति को मापा जा रहा है और ताजा बनाएं, और आरएजी के पास खड़े होने के लिए कुछ ठोस है।

