فاز 11 · درس 4

امبدینگ و بازنمایی برداری: راهنمای جامع جستجوی معنایی

نوع
ساختنی
زمان
حدود 75 دقیقه
زبان‌ها
Python
چند نقطه و شکل نورانی در یک فضای هندسی چندبعدی تاریک که با خطوط درخشان به یکدیگر متصل شده‌اند
فهرست مطالب
  1. اهداف یادگیری
  2. صورت مسئله
  3. مفاهیم پایه
  4. امبدینگ چیست؟
  5. جهش بزرگ با Word2Vec
  6. از کلمات تا جمله‌ها
  7. مدل‌های امبدینگ امروزی
  8. معیارهای شباهت
  9. پایگاه‌های داده برداری و HNSW
  10. استراتژی‌های قطعه‌بندی متن
  11. بای‌ان‌کودر در برابر کراس‌ان‌کودر
  12. امبدینگ‌های ماتریوشکا
  13. کوانتیزاسیون باینری
  14. پیاده‌سازی عملی
  15. گام 1: قطعه‌بندی متن
  16. گام 2: ساخت امبدینگ‌ها از صفر
  17. گام 3: توابع شباهت
  18. گام 4: نمایه‌ی برداری همراه با جست‌وجوی جامع (Brute-Force)
  19. گام 5: موتور جست‌وجوی معنایی
  20. گام 6: مقایسه‌ی معیارهای شباهت
  21. استفاده از ابزارهای آماده
  22. خروجی این درس
  23. تمرین‌ها
  24. اصطلاحات کلیدی
  25. منابع برای مطالعه‌ی بیشتر

جست‌وجوی سنتی کلمات کلیدی، با وجود سادگی، در فهم معنای واقعی ناتوان است. اگر کاربری به جای «پرداخت انجام نشد» بنویسد «تراکنش ناموفق بود»، سیستم‌های متکی بر کلیدواژه این دو متن کاملاً هم‌معنا را تطبیق نمی‌دهند. امبدینگ‌ها (Embeddings) پل میان دنیای متن و ریاضیات پیوسته هستند که با تبدیل مفاهیم به بردارهای متراکم، امکان درک و مقایسه‌ی معنا را فراهم می‌سازند.

درک سازوکار امبدینگ‌ها، پایگاه‌های داده برداری و معیارهای سنجش شباهت، سنگ‌بنای ساخت سیستم‌های بازیابی اطلاعات مدرن (RAG) و چت‌بات‌های پیشرفته است. بدون تسلط بر شیوه‌های بهینه‌سازی ابعاد مانند ماتریوشکا یا کوانتیزاسیون، هزینه‌های ذخیره‌سازی و زمان پاسخ‌دهی در پروژه‌های واقعی به‌سرعت بحرانی می‌شوند.

برای پیاده‌سازی و آزمایش کدهای این درس، علاوه بر استفاده از APIهایی نظیر OpenAI و Cohere، می‌توانید از مدل‌های وزن‌باز مانند BGE-M3 یا Qwen به‌صورت محلی بهره ببرید. همچنین اگر در استفاده از APIهای خارجی با چالش‌های پرداخت ارزی یا محدودیت دسترسی بر اساس IP روبه‌رو هستید، گیت‌وی‌های داخلی سازگار با OpenAI می‌توانند دسترسی به این مدل‌ها را آسان‌تر کنند.

متن گسسته است؛ ریاضی پیوسته. هر بار که از یک مدل زبانی بزرگ (LLM) می‌خواهید اسناد «مشابه» را پیدا کند، معانی را بسنجد یا فراتر از کلیدواژه‌ها جست‌وجو کند، به پلی میان این دو جهان تکیه می‌کنید. این پل همان امبدینگ (embedding) است. اگر امبدینگ‌ها را درک نکنید، هوش مصنوعی نوین را نفهمیده‌اید؛ فقط کاربر آن هستید.

مرتبط: فاز 5 · درس 22 (بررسی عمیق مدل‌های امبدینگ) مباحث مربوط به متراکم در برابر تنک در برابر چندبرداری، هرس ماتریوشکا و انتخاب مدل بر اساس معیارهای گوناگون را پوشش می‌دهد. این درس روی پایپ‌لاین محیط تولید (پایگاه‌های داده برداری، HNSW و ریاضیات شباهت) تمرکز دارد. پیش از انتخاب مدل، فاز 5 · درس 22 را مطالعه کنید.

اهداف یادگیری

  • تولید امبدینگ‌های متنی با ارائه‌دهندگان API و مدل‌های متن‌باز، و محاسبه‌ی شباهت کسینوسی میان آن‌ها
  • توضیح علت حل مشکل عدم تطابق واژگان به کمک امبدینگ‌ها، جایی که جست‌وجوی کلیدواژه‌ای ناتوان است
  • ساخت نمایه‌ی (index) جست‌وجوی معنایی برای بازیابی اسناد بر اساس معنا به‌جای انطباق دقیق کلیدواژه‌ها
  • ارزیابی کیفیت امبدینگ با معیارهای محک بازیابی (precision@k و بازخوانی) و انتخاب مدل امبدینگ مناسب برای سناریوی کاری خود

صورت مسئله

فرض کنید 10,000 تیکت پشتیبانی دارید. مشتری می‌نویسد: «پرداخت من انجام نشد». می‌خواهید تیکت‌های مشابه قبلی را پیدا کنید. جست‌وجوی کلیدواژه‌ای فقط تیکت‌هایی را پیدا می‌کند که کلمات «پرداخت» و «انجام نشد» را دارند. این روش مواردی مثل «تراکنش ناموفق بود»، «کسر وجه ناموفق بود» و «خطای صورت‌حساب» را کاملاً جا می‌اندازد؛ در حالی که این تیکت‌ها دقیقاً همان مشکل را با کلماتی کاملاً متفاوت توصیف می‌کنند.

این همان مسئله‌ی عدم تطابق واژگان (vocabulary mismatch) است. زبان انسان ده‌ها روش برای گفتن یک حرف واحد دارد. جست‌وجوی کلیدواژه‌ای با هر کلمه مثل نمادی مستقل و بی‌معنا برخورد می‌کند و نمی‌فهمد «ناموفق بود» و «انجام نشد» به یک مفهوم اشاره دارند.

شما به بازنمایی خاصی از متن نیاز دارید که در آن معنا، نه املا، میزان شباهت را تعیین کند؛ یعنی روشی که «پرداخت من انجام نشد» و «تراکنش ناموفق بود» را در یک فضای ریاضی کنار هم بنشاند، اما «پرداخت من به‌موقع رسید» را با وجود کلمه‌ی مشترک «پرداخت»، در فاصله‌ای دورتر قرار دهد.

این بازنمایی همان امبدینگ است.

مفاهیم پایه

امبدینگ چیست؟

امبدینگ یک بردار متراکم (dense vector) از اعداد ممیز شناور است که معنای متن را بازنمایی می‌کند. کلمه‌ی «متراکم» کلیدی است؛ برعکس بازنمایی‌های تنک (مثل bag-of-words و TF-IDF) که بیشتر ابعادشان صفر است، در بردار متراکم تک‌تک ابعاد بار معنایی و اطلاعات دارند.

جمله‌ی «The cat sat on the mat» به مقداری مثل [0.023, -0.041, 0.087, ..., 0.012] تبدیل می‌شود؛ فهرستی از 768 تا 3072 عدد، بسته به مدلی که انتخاب می‌کنید. این اعداد معنا را کدگذاری می‌کنند. شما هیچ‌وقت مستقیماً این اعداد را بررسی نمی‌کنید، بلکه آن‌ها را با هم می‌سنجید.

جهش بزرگ با Word2Vec

در سال 2013، توماس میکولوف و همکارانش در گوگل Word2Vec را معرفی کردند. ایده‌ی اصلی این بود: شبکه‌ی عصبی را برای پیش‌بینی کلمه از روی کلمات همسایه‌اش (یا برعکس) آموزش دهید؛ آن‌وقت وزن‌های لایه‌ی پنهان تبدیل به بردارهای معنادار می‌شوند.

نتیجه‌ی معروف آن:

king - man + woman = queen

حساب برداری روی امبدینگ‌های کلمه، روابط معنایی را بازنمایی می‌کند. جهت بردار از «man» به «woman» تقریباً همان جهت بردار از «king» به «queen» است. این همان لحظه‌ای بود که این حوزه فهمید هندسه می‌تواند معنا را کدگذاری کند.

مدل Word2Vec بردارهایی 300 بعدی تولید می‌کرد. هر کلمه بدون در نظر گرفتن بافتار (context) فقط یک بردار می‌گرفت؛ مثلاً «bank» در دو عبارت «river bank» (ساحل رود) و «bank account» (حساب بانکی) امبدینگ یکسانی داشت. همین محدودیت، موتور محرک پژوهش‌های دهه‌ی بعد شد.

از کلمات تا جمله‌ها

امبدینگ کلمه فقط یک توکن منفرد را نشان می‌دهد، اما سیستم‌های محیط تولید باید کل یک جمله، پاراگراف یا سند را امبد کنند. برای این کار چهار رویکرد شکل گرفت:

میانگین‌گیری (Averaging): محاسبه‌ی میانگین همه‌ی بردارهای کلمات جمله. ارزان، همراه با اتلاف اطلاعات، اما برای متن‌های کوتاه در کمال شگفتی خوب کار می‌کند. عیب بزرگش حذف کامل ترتیب کلمات است؛ جملات «dog bites man» و «man bites dog» امبدینگ‌های یکسانی دریافت می‌کنند.

توکن CLS: مدل‌های ترنسفورمر (مثل BERT، سال 2018) امبدینگ ویژه‌ای برای توکن [CLS] تولید می‌کنند که کل ورودی را بازنمایی می‌کند. این روش از میانگین‌گیری بهتر است، اما توکن [CLS] برای پیش‌بینی جمله‌ی بعدی آموزش دیده بود، نه سنجش شباهت.

یادگیری تقابلی (Contrastive learning): آموزش مستقیم مدل برای نزدیک کردن جفت‌های مشابه به هم و دور کردن جفت‌های نامشابه از یکدیگر. مدل Sentence-BERT (رایمرز و گورویش، 2019) این رویکرد را پیاده کرد و پایه‌ی مدل‌های امبدینگ امروزی شد. مدل با دیدن دو عبارت «?How do I reset my password» و «I need to change my password» یاد می‌گیرد که بردارهای آن‌ها باید تقریباً یکی باشند.

امبدینگ‌های دستورپذیر (Instruction-tuned embeddings): تازه‌ترین رویکرد. مدل‌هایی مانند E5 و GTE پیشوند وظیفه (مثل search_query: یا search_document:) می‌گیرند که مشخص می‌کند مدل چه نوع امبدینگی بسازد. به این ترتیب یک مدل واحد می‌تواند برای چندین کار مختلف به کار برود.

2024: Instruction-Tuned2019: Sentence-BERT2013: Word2Vecپادشاه[0.2, -0.1, ...]ملکه[0.3, -0.2, ...]چگونه رمز عبورم را بازنشانی کنم؟[0.04, 0.12, ...]باید رمز عبورم را تغییر دهم[0.05, 0.11, ...]search_query: بازیابی رمز عبور[0.08, 0.09, ...]search_document: برای بازنشانی رمز عبور،کلیک کنید...[0.07, 0.10, ...]

مدل‌های امبدینگ امروزی

بازار روی چند گزینه‌ی آماده برای محیط تولید تثبیت شده است (امتیازهای MTEB مربوط به اوایل 2026، نسخه MTEB v2):

مدل ارائه‌دهنده ابعاد MTEB بافتار هزینه / 1 میلیون توکن
Gemini Embedding 2 Google 3072 (ماتریوشکا) 67.7 (بازیابی) 8192 $0.15
embed-v4 Cohere 1024 (ماتریوشکا) 65.2 128K $0.12
voyage-4 Voyage AI 1024/2048 (ماتریوشکا) 66.8 32K $0.12
text-embedding-3-large OpenAI 3072 (ماتریوشکا) 64.6 8192 $0.13
text-embedding-3-small OpenAI 1536 (ماتریوشکا) 62.3 8192 $0.02
BGE-M3 BAAI 1024 (متراکم+تنک+ColBERT) 63.0 چندزبانه 8192 وزن‌باز
Qwen3-Embedding Alibaba 4096 (ماتریوشکا) 66.9 32K وزن‌باز
Nomic-embed-v2 Nomic 768 (ماتریوشکا) 63.1 8192 وزن‌باز

معیار MTEB (Massive Text Embedding Benchmark) نسخه 2 بیش از 100 وظیفه را در حوزه‌های بازیابی، دسته‌بندی، خوشه‌بندی، بازرتبه‌بندی و خلاصه‌سازی ارزیابی می‌کند؛ هرچه عدد بالاتر باشد، عملکرد بهتر است. تا سال 2026، مدل‌های با وزن باز (مثل Qwen3-Embedding و BGE-M3) در بیشتر سناریوها به مدل‌های تجاری ابری رسیده‌اند یا حتی از آن‌ها جلو زده‌اند. مدل Gemini Embedding 2 در بازیابی خالص پیشتاز است و Voyage و Cohere در حوزه‌های تخصصی (مالی، حقوقی، کد) برتری دارند. همیشه قبل از قطعی کردن انتخاب، مدل‌ها را روی پرامپت‌ها و پرسش‌های واقعی خودتان بسنجید.

معیارهای شباهت

برای سنجش شباهت دو بردار امبدینگ، سه روش اصلی وجود دارد:

شباهت کسینوسی (Cosine similarity): کسینوس زاویه‌ی بین دو بردار که دامنه‌ی آن از 1- (کاملاً مخالف) تا 1 (جهت یکسان) تغییر می‌کند. این معیار به اندازه یا طول بردار کاری ندارد؛ یعنی یک جمله‌ی 10 کلمه‌ای و یک سند 500 کلمه‌ای اگر هم‌جهت باشند، امتیاز 1.0 می‌گیرند. این گزینه انتخاب پیش‌فرض 90٪ سناریوهاست.

cosine_sim(a,b)=dot(a,b)∥a∥⋅∥b∥\mathop{\mathrm{cosine\_sim}}(a, b) = \frac{\mathop{\mathrm{dot}}(a, b)}{\lVert a \rVert \cdot \lVert b \rVert}

ضرب داخلی (Dot product): ضرب نقطه‌ای خام دو بردار. اگر بردارها نرمال‌سازی شده باشند (طول واحد داشته باشند)، این مقدار دقیقاً با شباهت کسینوسی برابر است، اما سریع‌تر محاسبه می‌شود. امبدینگ‌های OpenAI نرمال شده‌اند؛ بنابراین ضرب داخلی و کسینوسی رتبه‌بندی یکسانی تولید می‌کنند.

dot(a,b)=∑iaibi\mathop{\mathrm{dot}}(a, b) = \sum_i a_i b_i

فاصله‌ی اقلیدسی (L2): فاصله‌ی مستقیم خطی در فضای برداری. عدد کمتر یعنی شباهت بیشتر. این معیار به تفاوت طول بردارها حساس است. زمانی از آن استفاده کنید که موقعیت مطلق در فضا مهم باشد، نه فقط جهت بردار.

L2(a,b)=∑i(ai−bi)2\mathop{\mathrm{L2}}(a, b) = \sqrt{\sum_i (a_i - b_i)^2}

راهنمای انتخاب معیار:

معیار زمان استفاده زمان پرهیز
شباهت کسینوسی مقایسه‌ی متن‌هایی با طول‌های متفاوت؛ بیشتر وظایف بازیابی زمانی که اندازه و طول بردار حامل اطلاعات است
ضرب داخلی وقتی امبدینگ‌ها از پیش نرمال شده‌اند؛ اولویت حداکثر سرعت وقتی بردارها طول‌های متفاوتی دارند
فاصله‌ی اقلیدسی خوشه‌بندی؛ مسائل مربوط به همسایگی مکانی مقایسه‌ی اسنادی با طول‌های بسیار متفاوت

پایگاه‌های داده برداری و HNSW

در جست‌وجوی جامع و ساده (brute-force)، پرسش کاربر با تک‌تک بردارهای ذخیره‌شده مقایسه می‌شود. در مقیاس 1 میلیون بردار 1536 بعدی، یعنی 1.5 میلیارد عملیات ضرب و جمع به ازای هر پرسش که بسیار کند است.

پایگاه‌های داده برداری این مشکل را با الگوریتم‌های نزدیک‌ترین همسایه‌ی تقریبی (Approximate Nearest Neighbor یا ANN) برطرف می‌کنند. پرکاربردترین الگوریتم در این زمینه HNSW (گراف دنیای کوچک ناوبری‌پذیر سلسله‌مراتبی) است:

  1. گرافی چندلایه از بردارها می‌سازد.
  2. لایه‌های بالایی تنک هستند و خوشه‌های دوردست را با یال‌های بلند متصل می‌کنند.
  3. لایه‌های پایینی متراکم‌اند و بردارهای نزدیک را با جزئیات به هم وصل می‌کنند.
  4. جست‌وجو از لایه‌ی بالا آغاز شده و با رویکرد حریصانه به لایه‌های پایین‌تر می‌رود تا دقت بالا برود.
  5. نتایج تقریبی برتر (top-k) را در زمان O(log⁡n)O(\log n) به‌جای O(n)O(n) برمی‌گرداند.

الگوریتم HNSW کاهش اندک دقت (معمولاً بازخوانی بین 95 تا 99 درصد) را می‌پذیرد تا سرعت را جهش دهد. برای 10 میلیون بردار، روش brute-force چند ثانیه زمان می‌برد؛ اما HNSW در چند میلی‌ثانیه پاسخ می‌دهد.

لایه‌های HNSWپرش‌های بلندپرش‌های کوتاه‌ترورود از بالاترین لایهنزدیک‌ترین همسایه‌هالایه 2 (تنک)لایه 1 (متوسط)لایه 0 (متراکم، تمام بردارها)بردار پرس‌وجوk نتیجه برتر

گزینه‌های عملیاتی برای محیط تولید:

پایگاه داده نوع مناسب برای سقف مقیاس‌پذیری
Pinecone سرویس مدیریت‌شده (SaaS) محیط تولید بدون دغدغه‌ی نگهداری زیرساخت میلیاردها
Weaviate متن‌باز میزبانی شخصی، جست‌وجوی ترکیبی +100M
Qdrant متن‌باز کارایی بالا، فیلترسازی پیشرفته +100M
ChromaDB توکار (Embedded) ساخت پروتوتایپ، توسعه‌ی محلی 1M
pgvector اکستنشن Postgres تیم‌هایی که از قبل از Postgres استفاده می‌کنند 10M
FAISS کتابخانه اجرای درون‌فرآیندی، کارهای پژوهشی +1B

استراتژی‌های قطعه‌بندی متن

اسناد بلندتر از آن هستند که بتوان آن‌ها را در قالب یک بردار منفرد ذخیره کرد. یک فایل PDF پنجاه‌صفحه‌ای ده‌ها موضوع مختلف را شامل می‌شود؛ در نتیجه امبدینگ آن به میانگینی از همه‌چیز تبدیل شده و به هیچ چیز خاصی شباهت دقیق نخواهد داشت. راهکار این است که اسناد را به قطعه‌ها (chunks) بشکنید و هر قطعه را جداگانه امبد کنید.

قطعه‌بندی با اندازه‌ی ثابت (Fixed-size): تقسیم متن در هر N توکن با M توکن همپوشانی (overlap). ساده و پیش‌بینی‌پذیر است و وقتی اسناد ساختار مشخصی ندارند به‌خوبی کار می‌کند. برای مثال در قطعه‌های 512 توکنی با 50 توکن همپوشانی، قطعه‌ی 1 شامل توکن‌های 0 تا 511 و قطعه‌ی 2 شامل توکن‌های 462 تا 973 خواهد بود.

قطعه‌بندی بر اساس جمله (Sentence-based): شکستن متن در مرز جمله‌ها و کنار هم قرار دادن آن‌ها تا رسیدن به سقف توکن‌ها. هر قطعه دست‌کم شامل یک جمله‌ی کامل است. این روش از اندازه‌ی ثابت بهتر است؛ چون هرگز جریان یک فکر را از وسط نصف نمی‌کنید.

قطعه‌بندی بازگشتی (Recursive): اولویت شکستن با بزرگ‌ترین مرز ساختاری (تیتر بخش‌ها) است. اگر قطعه باز هم بزرگ بود، سراغ مرز پاراگراف‌ها می‌رود؛ سپس مرز جمله‌ها و در نهایت محدودیت کاراکتری. ابزار RecursiveCharacterTextSplitter در LangChain از همین روش استفاده می‌کند و برای مجموعه‌های متنی با فرمت‌های گوناگون خروجی بسیار خوبی دارد.

قطعه‌بندی معنایی (Semantic): ابتدا هر جمله به‌صورت جداگانه امبد می‌شود، سپس جملات متوالی که امبدینگ‌های مشابهی دارند در یک دسته قرار می‌گیرند. وقتی شباهت امبدینگ‌ها از یک آستانه پایین‌تر آمد، قطعه‌ی جدیدی شروع می‌شود. این روش هزینه‌بر است (چون باید تک‌تک جملات را جداگانه امبد کنید)، اما منسجم‌ترین قطعات را می‌سازد.

استراتژی پیچیدگی کیفیت مناسب برای
اندازه‌ی ثابت پایین معمولی متن‌های بدون ساختار، لاگ‌ها
بر اساس جمله پایین خوب مقالات، ایمیل‌ها
بازگشتی متوسط خوب اسناد Markdown، HTML و متون ترکیبی
معنایی بالا بهترین حساس‌ترین کاربردهای بازیابی اطلاعات

نقطه‌ی تعادل تجربی برای بیشتر سیستم‌ها: قطعات 256 تا 512 توکنی با همپوشانی 50 توکن.

بای‌ان‌کودر در برابر کراس‌ان‌کودر

بای‌ان‌کودر (Bi-encoder) پرسش و اسناد را جداگانه به بردار تبدیل می‌کند و سپس بردارها را می‌سنجد. این روش بسیار سریع است؛ پرسش را یک بار امبد کرده و آن را با بردارهای از پیش محاسبه‌شده‌ی اسناد مقایسه می‌کنید. برای مرحله‌ی بازیابی اسناد از همین مدل استفاده می‌شود.

کراس‌ان‌کودر (Cross-encoder) پرسش و سند را در قالب یک ورودی مشترک دریافت کرده و یک نمره‌ی ارتباط تولید می‌کند. این رویکرد کند است، چون هر جفت «پرسش-سند» باید کل لایه‌های مدل را طی کند. با این حال دقت بسیار بالاتری دارد، زیرا می‌تواند هم‌زمان به توکن‌های پرسش و سند توجه کند.

الگوی استاندارد محیط تولید: بای‌ان‌کودر 100 گزینه‌ی برتر را بازیابی می‌کند و سپس کراس‌ان‌کودر آن‌ها را بازرتبه‌بندی (rerank) کرده و 10 گزینه‌ی اول را انتخاب می‌کند. این الگو را پایپ‌لاین بازیابی و بازرتبه‌بندی می‌نامند.

پرس‌وجوبای‌ان‌کودر: جاسازی پرس‌وجوجستجوی برداری: 100 مورد برترکراس‌ان‌کودر: بازرتبه‌بندی10 نتیجه برتر

مدل‌های بازرتبه‌بندی: Cohere Rerank 3.5 (هزینه‌ی 2 دلار برای هر 1000 پرسش)، BGE-reranker-v2 (رایگان، متن‌باز) و Jina Reranker v2 (رایگان، متن‌باز).

امبدینگ‌های ماتریوشکا

امبدینگ‌های سنتی وضعیت همه یا هیچ دارند؛ یعنی یک بردار 1536 بعدی از 1536 عدد ممیز شناور تشکیل شده و نمی‌توانید بدون آموزش مجدد، آن را به 256 بعد هرس (truncate) کنید.

یادگیری بازنمایی ماتریوشکا (Matryoshka Representation Learning - کوسوپاتی و همکاران، 2022) این مشکل را حل کرد. مدل به‌گونه‌ای آموزش می‌بیند که N بُعد ابتدایی آن، درست مانند عروسک‌های تودرتوی روسی، مهم‌ترین اطلاعات را نگه‌داری کنند. هرس کردن یک امبدینگ ماتریوشکای 1536 بعدی به 256 بعد، بخش اندکی از دقت را کم می‌کند اما بردار همچنان کارایی خود را حفظ می‌کند.

مدل‌های text-embedding-3-small و text-embedding-3-large در OpenAI از هرس ماتریوشکا با پارامتر dimensions پشتیبانی می‌کنند. انتخاب 256 بعد به‌جای 1536 بعد، حجم فضای ذخیره‌سازی را 6 برابر کاهش می‌دهد، در حالی که در بنچمارک‌های MTEB افت دقت تنها حدود 3 تا 5 درصد خواهد بود.

کوانتیزاسیون باینری

یک بردار امبدینگ 1536 بعدی با فرمت float32 حدود 6,144 بایت حجم دارد. این رقم را در 10 میلیون سند ضرب کنید: 61 گیگابایت حافظه فقط برای ذخیره‌ی بردارها نیاز است.

کوانتیزاسیون باینری (Binary quantization) هر عدد ممیز شناور را به یک بیت تبدیل می‌کند: مقادیر مثبت 1 و مقادیر منفی 0 می‌شوند. حجم ذخیره‌سازی از 6,144 بایت به 192 بایت سقوط می‌کند؛ یعنی 32 برابر فشرده‌تر! در این حالت، شباهت با استفاده از فاصله‌ی همینگ (شمارش بیت‌های متفاوت) محاسبه می‌شود که پردازنده‌ها می‌توانند آن را در یک دستورالعمل سخت‌افزاری اجرا کنند.

افت دقت بازخوانی در بازیابی اطلاعات حدود 5 تا 10 درصد است. الگوی پرکاربرد: جست‌وجوی سریع اولیه با کوانتیزاسیون باینری روی میلیون‌ها بردار، و سپس امتیازدهی مجدد به 1000 گزینه‌ی برتر با استفاده از بردارهای با دقت اعشاری کامل. با این کار و با صرف 32 برابر حافظه‌ی کمتر، بیش از 95٪ دقت اصلی حفظ می‌شود.

شباهت کسینوسیزاویه را بکشید
COSINE SIMILARITYEmbeddings compare by angle, not distance. Cosine is 1 when two vectors point the same way, 0 when orthogonal (unrelated), and negative when opposed. Magnitude drops out, so a long document and a short query can still match.
امبدینگ‌ها بر اساس زاویه مقایسه می‌شوند، نه فاصله. وقتی دو بردار هم‌جهت باشند کسینوس 1 است، وقتی عمود بر هم (نامرتبط) باشند 0، و وقتی در جهت مخالف باشند منفی است. اندازه بردار تأثیری ندارد، بنابراین یک سند طولانی و یک پرس‌وجوی کوتاه همچنان می‌توانند با یکدیگر تطابق پیدا کنند.

پیاده‌سازی عملی

یک موتور جست‌وجوی معنایی را از صفر پیاده‌سازی می‌کنیم؛ بدون هیچ پایگاه داده برداری یا API امبدینگ خارجی، فقط با پایتون خالص و کتابخانه‌ی numpy برای محاسبات ریاضی.

گام 1: قطعه‌بندی متن

def chunk_text(text, chunk_size=200, overlap=50):
    words = text.split()
    chunks = []
    start = 0
    while start < len(words):
        end = start + chunk_size
        chunk = " ".join(words[start:end])
        chunks.append(chunk)
        start += chunk_size - overlap
    return chunks


def chunk_by_sentences(text, max_chunk_tokens=200):
    sentences = text.replace("\n", " ").split(".")
    sentences = [s.strip() + "." for s in sentences if s.strip()]
    chunks = []
    current_chunk = []
    current_length = 0
    for sentence in sentences:
        sentence_length = len(sentence.split())
        if current_length + sentence_length > max_chunk_tokens and current_chunk:
            chunks.append(" ".join(current_chunk))
            current_chunk = []
            current_length = 0
        current_chunk.append(sentence)
        current_length += sentence_length
    if current_chunk:
        chunks.append(" ".join(current_chunk))
    return chunks

گام 2: ساخت امبدینگ‌ها از صفر

یک امبدینگ متراکم ساده را با TF-IDF و نرمال‌سازی L2 پیاده‌سازی می‌کنیم. این یک امبدینگ شبکه‌ی عصبی نیست، اما از همان منطق پیروی می‌کند: دریافت متن، بازگرداندن برداری با طول ثابت، و تولید بردارهای نزدیک به هم برای متن‌های مشابه.

import math
import numpy as np
from collections import Counter

class SimpleEmbedder:
    def __init__(self):
        self.vocab = []
        self.idf = []
        self.word_to_idx = {}

    def fit(self, documents):
        vocab_set = set()
        for doc in documents:
            vocab_set.update(doc.lower().split())
        self.vocab = sorted(vocab_set)
        self.word_to_idx = {w: i for i, w in enumerate(self.vocab)}
        n = len(documents)
        self.idf = np.zeros(len(self.vocab))
        for i, word in enumerate(self.vocab):
            doc_count = sum(1 for doc in documents if word in doc.lower().split())
            self.idf[i] = math.log((n + 1) / (doc_count + 1)) + 1

    def embed(self, text):
        words = text.lower().split()
        count = Counter(words)
        total = len(words) if words else 1
        vec = np.zeros(len(self.vocab))
        for word, freq in count.items():
            if word in self.word_to_idx:
                tf = freq / total
                vec[self.word_to_idx[word]] = tf * self.idf[self.word_to_idx[word]]
        norm = np.linalg.norm(vec)
        if norm > 0:
            vec = vec / norm
        return vec

گام 3: توابع شباهت

def cosine_similarity(a, b):
    dot = np.dot(a, b)
    norm_a = np.linalg.norm(a)
    norm_b = np.linalg.norm(b)
    if norm_a == 0 or norm_b == 0:
        return 0.0
    return float(dot / (norm_a * norm_b))


def dot_product(a, b):
    return float(np.dot(a, b))


def euclidean_distance(a, b):
    return float(np.linalg.norm(a - b))
class VectorIndex:
    def __init__(self):
        self.vectors = []
        self.texts = []
        self.metadata = []

    def add(self, vector, text, meta=None):
        self.vectors.append(vector)
        self.texts.append(text)
        self.metadata.append(meta or {})

    def search(self, query_vector, top_k=5, metric="cosine"):
        scores = []
        for i, vec in enumerate(self.vectors):
            if metric == "cosine":
                score = cosine_similarity(query_vector, vec)
            elif metric == "dot":
                score = dot_product(query_vector, vec)
            elif metric == "euclidean":
                score = -euclidean_distance(query_vector, vec)
            else:
                raise ValueError(f"Unknown metric: {metric}")
            scores.append((i, score))
        scores.sort(key=lambda x: x[1], reverse=True)
        results = []
        for idx, score in scores[:top_k]:
            results.append({
                "text": self.texts[idx],
                "score": score,
                "metadata": self.metadata[idx],
                "index": idx
            })
        return results

    def size(self):
        return len(self.vectors)

گام 5: موتور جست‌وجوی معنایی

class SemanticSearchEngine:
    def __init__(self, chunk_size=200, overlap=50):
        self.embedder = SimpleEmbedder()
        self.index = VectorIndex()
        self.chunk_size = chunk_size
        self.overlap = overlap

    def index_documents(self, documents, source_names=None):
        all_chunks = []
        all_sources = []
        for i, doc in enumerate(documents):
            chunks = chunk_text(doc, self.chunk_size, self.overlap)
            all_chunks.extend(chunks)
            name = source_names[i] if source_names else f"doc_{i}"
            all_sources.extend([name] * len(chunks))
        self.embedder.fit(all_chunks)
        for chunk, source in zip(all_chunks, all_sources):
            vec = self.embedder.embed(chunk)
            self.index.add(vec, chunk, {"source": source})
        return len(all_chunks)

    def search(self, query, top_k=5, metric="cosine"):
        query_vec = self.embedder.embed(query)
        return self.index.search(query_vec, top_k, metric)

    def search_with_scores(self, query, top_k=5):
        results = self.search(query, top_k)
        return [
            {
                "text": r["text"][:200],
                "source": r["metadata"].get("source", "unknown"),
                "score": round(r["score"], 4)
            }
            for r in results
        ]

گام 6: مقایسه‌ی معیارهای شباهت

def compare_metrics(engine, query, top_k=3):
    results = {}
    for metric in ["cosine", "dot", "euclidean"]:
        hits = engine.search(query, top_k=top_k, metric=metric)
        results[metric] = [
            {"score": round(h["score"], 4), "preview": h["text"][:80]}
            for h in hits
        ]
    return results

استفاده از ابزارهای آماده

اگر از یک API امبدینگ تجاری استفاده کنید، ساختار اصلی هیچ تفاوتی نخواهد کرد؛ فقط تابع تولیدکننده‌ی امبدینگ عوض می‌شود:

from openai import OpenAI

client = OpenAI()

def openai_embed(texts, model="text-embedding-3-small", dimensions=None):
    kwargs = {"model": model, "input": texts}
    if dimensions:
        kwargs["dimensions"] = dimensions
    response = client.embeddings.create(**kwargs)
    return [item.embedding for item in response.data]

هرس ماتریوشکا با OpenAI؛ همان مدل، ابعاد کمتر، هزینه‌ی ذخیره‌سازی پایین‌تر:

full = openai_embed(["semantic search query"], dimensions=1536)
compact = openai_embed(["semantic search query"], dimensions=256)

بردار 256 بعدی 6 برابر فضای کمتری مصرف می‌کند. برای 10 میلیون سند، این یعنی 10 گیگابایت به‌جای 61 گیگابایت. افت دقت روی بنچمارک‌های استاندارد هم تنها در حدود 3 تا 5 درصد است.

برای بازرتبه‌بندی با Cohere:

import cohere

co = cohere.ClientV2()

results = co.rerank(
    model="rerank-v3.5",
    query="What is the refund policy?",
    documents=["Full refund within 30 days...", "No refunds after 90 days..."],
    top_n=3
)

برای تولید محلی امبدینگ بدون وابستگی به API:

from sentence_transformers import SentenceTransformer

model = SentenceTransformer("BAAI/bge-small-en-v1.5")
embeddings = model.encode(["semantic search query", "another document"])

کلاس VectorIndex که ساختیم با همه‌ی این‌ها کار می‌کند. کافی است تابع امبدینگ را جابه‌جا کنید و منطق جست‌وجو را دست‌نخورده نگه دارید.

خروجی این درس

از این درس موارد زیر حاصل می‌شود:

  • outputs/prompt-embedding-advisor.md -- پرامپتی تخصصی برای انتخاب مدل‌های امبدینگ و استراتژی‌های متناسب با نیازهای خاص
  • outputs/skill-embedding-patterns.md -- مهارتی که به ایجنت‌ها یاد می‌دهد چگونه از امبدینگ‌ها در محیط تولید استفاده کنند

تمرین‌ها

  1. مقایسه‌ی معیارها: 5 پرسش یکسان را با شباهت کسینوسی، ضرب داخلی و فاصله‌ی اقلیدسی روی اسناد نمونه اجرا کنید. 3 نتیجه‌ی برتر هر کدام را یادداشت کنید. معیارها در کدام پرسش‌ها هم‌نظر نیستند؟ چرا؟

  2. آزمایش اندازه‌ی قطعات: اسناد نمونه را با اندازه‌ی قطعه‌های 50، 100، 200 و 500 کلمه‌ای نمایه کنید. برای هر حالت، 5 پرسش اجرا کرده و امتیاز شباهت نتیجه‌ی اول را ثبت کنید. نمودار رابطه‌ی بین اندازه‌ی قطعه و کیفیت بازیابی را بکشید. نقطه‌ای را پیدا کنید که در آن افزایش اندازه‌ی قطعه به کیفیت آسیب می‌زند.

  3. شبیه‌سازی ماتریوشکا: کلاسی به‌نام SimpleEmbedder بسازید که بردارهای 500 بعدی تولید کند. سپس آن‌ها را به ابعاد 50، 100، 200 و 500 هرس کنید. افت بازخوانی بازیابی را در هر مرحله از هرس اندازه بگیرید. این تمرین رفتار ماتریوشکا را بدون نیاز به آموزش شبکه‌ی عصبی واقعی شبیه‌سازی می‌کند.

  4. کوانتیزاسیون باینری: امبدینگ‌های موتور جست‌وجو را به مقادیر باینری تبدیل کنید (1 برای مقادیر مثبت، 0 برای منفی) و جست‌وجوی فاصله‌ی همینگ را پیاده‌سازی کنید. 10 نتیجه‌ی برتر را با شباهت کسینوسی در دقت کامل مقایسه کرده و درصد شباهت نتایج را محاسبه کنید.

  5. قطعه‌بندی بر اساس جمله: قطعه‌بندی با اندازه‌ی ثابت را با chunk_by_sentences جایگزین کنید. همان پرسش‌ها را دوباره اجرا کرده و امتیازهای بازیابی را بسنجید. آیا حفظ مرز جملات نتایج را بهبود می‌دهد؟

اصطلاحات کلیدی

اصطلاح در باور عمومی مفهوم واقعی و دقیق
امبدینگ «تبدیل متن به عدد» یک بردار متراکم که در آن نزدیکی هندسی نشان‌دهنده‌ی شباهت معنایی است
Word2Vec «امبدینگ قدیمی و اولیه» مدلی ارائه‌شده در سال 2013 که با پیش‌بینی کلمات همسایه، بردارهای کلمه را یاد می‌گرفت؛ و ثابت کرد محاسبات برداری بازتاب‌دهنده‌ی معنا هستند
شباهت کسینوسی «میزان شباهت دو بردار» کسینوس زاویه‌ی بین دو بردار؛ 1 = جهت یکسان، 0 = عمود بر هم، 1- = جهت کاملاً معکوس
HNSW «جست‌وجوی سریع برداری» گراف دنیای کوچک ناوبری‌پذیر سلسله‌مراتبی؛ ساختاری چندلایه که جست‌وجوی نزدیک‌ترین همسایه‌ی تقریبی را در زمان O(log⁡n)O(\log n) ممکن می‌کند
بای‌ان‌کودر «امبدینگ جداگانه، مقایسه‌ی سریع» پرسش و سند را جداگانه به بردار تبدیل می‌کند؛ مناسب برای پیش‌محاسبه و بازیابی سریع
کراس‌ان‌کودر «بازرتبه‌بند کند اما دقیق» جفت پرسش و سند را هم‌زمان پردازش می‌کند؛ دقت بسیار بالاتر بدون امکان پیش‌محاسبه
امبدینگ‌های ماتریوشکا «بردارهای هرس‌پذیر» امبدینگ‌هایی که طوری آموزش دیده‌اند تا N بعد اولیه‌شان مهم‌ترین داده‌ها را نگه دارد و امکان استفاده از طول متغیر را بدهد
کوانتیزاسیون باینری «امبدینگ‌های 1 بیتی» تبدیل بردارهای اعشاری به باینری (فقط بیت علامت) برای کاهش 32 برابری حجم همراه با جست‌وجوی فاصله‌ی همینگ
قطعه‌بندی متن «تکه‌تکه کردن اسناد برای امبدینگ» خرد کردن اسناد به قطعات 256 تا 512 توکنی تا هر قطعه جداگانه امبد و بازیابی شود
پایگاه داده برداری «موتور جست‌وجوی امبدینگ‌ها» پایگاه داده‌ای بهینه‌سازی‌شده برای ذخیره‌ی بردارها و اجرای سریع الگوریتم‌های نزدیک‌ترین همسایه‌ی تقریبی در مقیاس وسیع
یادگیری تقابلی «آموزش بر پایه‌ی مقایسه» شیوه‌ای از آموزش که در آن امبدینگ‌های جفت‌های مشابه به هم نزدیک و جفت‌های نامشابه از هم دور می‌شوند
MTEB «معیار سنجش امبدینگ» بنچمارک Massive Text Embedding Benchmark شامل 56 مجموعه‌داده در 8 وظیفه؛ معیار استاندارد ارزیابی مدل‌های امبدینگ

منابع برای مطالعه‌ی بیشتر

  • مقاله‌ی Mikolov et al. با عنوان «Efficient Estimation of Word Representations in Vector Space» (سال 2013) -- مقاله‌ی Word2Vec که انقلاب امبدینگ‌ها را با قیاس مرد-زن و شاه-ملکه آغاز کرد.
  • مقاله‌ی Reimers & Gurevych با عنوان «Sentence-BERT: Sentence Embeddings using Siamese BERT-Networks» (سال 2019) -- آموزش بای‌ان‌کودرها برای شباهت در سطح جمله که اساس مدل‌های امبدینگ امروزی شد.
  • مقاله‌ی Kusupati et al. با عنوان «Matryoshka Representation Learning» (سال 2022) -- تکنیک امبدینگ با ابعاد متغیر که OpenAI در مدل‌های text-embedding-3 به کار برده است.
  • مقاله‌ی Malkov & Yashunin با عنوان «Efficient and Robust Approximate Nearest Neighbor using Hierarchical Navigable Small World Graphs» (سال 2018) -- مقاله‌ی اصلی الگوریتم HNSW، موتور محرک بیشتر ابزارهای جست‌وجوی برداری در محیط تولید.
  • راهنمای امبدینگ‌های OpenAI در (platform.openai.com/docs/guides/embeddings) -- مرجع کاربردی برای مدل‌های text-embedding-3 و شیوه‌ی کاهش ابعاد با ماتریوشکا.
  • تابلوی امتیازات MTEB در (huggingface.co/spaces/mteb/leaderboard) -- بنچمارک زنده برای مقایسه‌ی مدل‌های امبدینگ در وظایف و زبان‌های گوناگون.
  • مقاله‌ی Muennighoff et al., "MTEB: Massive Text Embedding Benchmark" (EACL 2023) -- مقاله‌ی معرفی 8 دسته‌بندی وظایف (دسته‌بندی، خوشه‌بندی، دسته‌بندی جفت‌ها، بازرتبه‌بندی، بازیابی، STS، خلاصه‌سازی و داده‌کاوی دوزبانه)؛ پیش از استناد به امتیازهای MTEB حتماً این مقاله را مطالعه کنید.
  • مستندات Sentence Transformers -- مرجع اصلی مقایسه‌ی بای‌ان‌کودر و کراس‌ان‌کودر، استراتژی‌های تجمیع (pooling) و پایپ‌لاین RAG (شامل مراحل دریافت، قطعه‌بندی، امبدینگ و ذخیره‌سازی) که در این درس پیاده شد.

سؤالات متداول

امبدینگ (Embedding) چیست و چه فرقی با جست‌وجوی کلمات کلیدی دارد؟

امبدینگ یک بردار متراکم از اعداد اعشاری است که معنای متن را کدگذاری می‌کند؛ در نتیجه بر خلاف جست‌وجوی کلیدواژه‌ای که فقط املا را مقایسه می‌کند، امبدینگ می‌تواند عبارت‌های هم‌معنا با واژگان کاملاً متفاوت را بازیابی کند.

تفاوت بای‌ان‌کودر (Bi-encoder) با کراس‌ان‌کودر (Cross-encoder) چیست؟

بای‌ان‌کودر پرسش و سند را جداگانه به بردار تبدیل می‌کند و سرعت بالایی برای بازیابی اولیه دارد؛ اما کراس‌ان‌کودر جفت پرسش و سند را هم‌زمان تحلیل کرده و دقت بسیار بالاتری دارد و برای بازرتبه‌بندی نتایج نهایی به کار می‌رود.

امبدینگ ماتریوشکا (Matryoshka) چیست و چه مزیتی دارد؟

روشی است که طی آن مدل طوری آموزش می‌بیند که مهم‌ترین اطلاعات در ابعاد ابتدایی بردار ذخیره شوند؛ به این ترتیب می‌توان بدون آموزش مجدد، طول بردار را هرس کرد و فضای ذخیره‌سازی را تا چند برابر با کمترین افت دقت کاهش داد.

چه تفاوتی میان شباهت کسینوسی و ضرب داخلی وجود دارد؟

شباهت کسینوسی فقط زاویه‌ی بین دو بردار را می‌سنجد و کاری به طول بردار ندارد؛ اما اگر بردارها از پیش نرمال‌سازی شده باشند، ضرب داخلی مقداری دقیقاً برابر با شباهت کسینوسی خواهد داشت و با سرعت بالاتری محاسبه می‌شود.

ما رو توی گوگل بیشتر ببین

با افزودن نکست ادمین به «منابع ترجیحی» گوگل، درس‌ها و مطالب ما را در بخش اخبار برتر جستجو بیشتر می‌بینید.