ساخت سیستمهای جستجو که هم ظاهر یک تصویر و هم محتوای متن آن را درک میکنند، جهشی بزرگ در تجربه کاربری محسوب میشود. برای توسعهدهندگان متوسط تا پیشرفته، چالش اغلب در ترکیب این منابع دادهای ناهمگون در یک خط لوله بازیابی منسجم و یکپارچه نهفته است. اگرچه مدلهای جاسازی چندوجهی تخصصی وجود دارند، اما درک نحوه پیادهسازی جستجوی ترکیبی با استفاده از کتابخانههای سطح پایین مانند FAISS (جستجوی شباهت هوش مصنوعی فیسبوک) ارزش بسیار زیادی دارد. این پست نحوه ساخت سیستمی را بررسی میکند که بر اساس ترکیب وزندار شباهت بصری و متنی نتایج را بازیابی میکند، همراه با معیارهای فاصله سفارشی برای تنظیم دقیق مرتبطی.
چرا جستجوی ترکیبی؟
جستجوی صرفاً متنی، زمینه بصری را از دست میدهد، در حالی که جستجوی صرفاً تصویری (با استفاده از جاسازیهای مدلهایی مانند CLIP) ممکن است ظرایف معنایی موجود در متادیتا یا زیرنویسها را نادیده بگیرد. جستجوی ترکیبی با محاسبه امتیازات شباهت جداگانه برای اجزای متنی و تصویری پرسش و سند، و سپس ترکیب آنها، این مشکل را حل میکند. در FAISS، این کار نیازمند مدیریت دو ایندکس مجزا است—یکی برای جاسازیهای متنی و دیگری برای جاسازیهای تصویری—و پیادهسازی یک لایه منطقی که نتایج آنها را ترکیب میکند.
راهاندازی خط لوله چندوجهی
برای شروع، فرض میکنیم که بردارهای از پیش محاسبهشده برای مجموعه داده خود را دارید. در این مثال، از جاسازیهای ۵۱۲ بعدی استفاده میکنیم که یک اندازه خروجی رایج برای مدلهایی مانند CLIP ViT-B/32 است. ما دو ایندکس FAISS متمایز ایجاد خواهیم کرد:
- ایندکس متنی: با استفاده از
IndexFlatIP(ضرب داخلی) برای شباهت کسینوس. - ایندکس تصویری: همچنین برای سادگی از
IndexFlatIPاستفاده میشود، هرچند برای مجموعههای داده بزرگتر،IndexIVFFlatتوصیه میشود.
import faiss
import numpy as np
# تعریف ابعاد جاسازی
dim = 512
# 1. ایجاد ایندکس متنی (ضرب داخلی)
index_text = faiss.IndexFlatIP(dim)
# 2. ایجاد ایندکس تصویری (ضرب داخلی)
index_image = faiss.IndexFlatIP(dim)
# تولید داده نمونه
num_docs = 1000
np.random.seed(42)
text_embeddings = np.random.rand(num_docs, dim).astype("float32")
image_embeddings = np.random.rand(num_docs, dim).astype("float32")
# نرمالسازی بردارها برای شباهت کسینوس (ضروری برای عملکرد IP به عنوان کسینوس)
faiss.normalize_L2(text_embeddings)
faiss.normalize_L2(image_embeddings)
# افزودن بردارها به ایندکسها
index_text.add(text_embeddings)
index_image.add(image_embeddings)
پیادهسازی معیارهای فاصله سفارشی و ترکیب (Fusion)
هسته جستجوی ترکیبی، استراتژی ترکیب (Fusion) است. یک رویکرد رایج ترکیب خطی است، که در آن امتیاز نهایی مجموع وزندار امتیازات فردی است. با این حال، امتیازات خام از ایندکسهای مختلف ممکن است در مقیاس یکسانی نباشند. ما به یک تابع معیار فاصله سفارشی نیاز داریم که این امتیازات را قبل از ترکیب نرمالسازی کند.
در زیر یک تابع پایتون آورده شده است که جستجوی ترکیبی را انجام میدهد. این تابع وزنهای alpha (برای متن) و beta (برای تصویر) را میپذیرد، جایی که alpha + beta = 1.0 است.
def hybrid_search(index_t, index_i, query_text_vec, query_img_vec, k=10, alpha=0.5):
"""
جستجوی ترکیبی را با ترکیب امتیازات شباهت متن و تصویر انجام میدهد.
:param index_t: ایندکس FAISS برای جاسازیهای متنی
:param index_i: ایندکس FAISS برای جاسازیهای تصویری
:param query_text_vec: بردار پرسش متنی نرمالشده (1, dim)
:param query_img_vec: بردار پرسش تصویری نرمالشده (1, dim)
:param k: تعداد نتایج برای بازگشت
:param alpha: وزن برای شباهت متنی (1-alpha وزن برای تصویر است)
:return: لیستی از تاپلهای (index, combined_score)
"""
# نرمالسازی بردارهای پرسش در صورتی که هنوز انجام نشده باشد
faiss.normalize_L2(query_text_vec)
faiss.normalize_L2(query_img_vec)
# انجام جستجو در هر دو ایندکس
# نکته: FAISS ایندکسها و فاصلهها (امتیازات) را برمیگرداند
D_text, I_text = index_t.search(query_text_vec, k)
D_image, I_image = index_i.search(query_img_vec, k)
# ما باید نتایج را ترکیب کنیم. از آنجا که I_text و I_image ممکن است متفاوت باشند،
# ما top-k از هر دو را بازیابی کرده و آنها را ادغام میکنیم.
candidates = {}
# جمعآوری امتیازات متنی
for idx, score in zip(I_text[0], D_text[0]):
if idx != -1:
candidates[idx] = {'text_score': float(score), 'image_score': 0.0}
# جمعآوری امتیازات تصویری
for idx, score in zip(I_image[0], D_image[0]):
if idx != -1:
if idx in candidates:
candidates[idx]['image_score'] = float(score)
else:
candidates[idx] = {'text_score': 0.0, 'image_score': float(score)}
# اعمال مجموع وزندار سفارشی (ترکیب خطی)
# امتیاز نهایی = alpha * امتیازمتن + (1 - alpha) * امتیازتصویر
weighted_results = []
for doc_id, scores in candidates.items():
combined_score = (alpha * scores['text_score']) + ((1 - alpha) * scores['image_score'])
weighted_results.append((doc_id, combined_score))
# مرتبسازی بر اساس امتیاز ترکیبی نزولی (FAISS امتیازات بالاتر را برای IP بهتر در نظر میگیرد)
weighted_results.sort(key=lambda x: x[1], reverse=True)
return weighted_results[:k]
# مثال استفاده
query_text = np.random.rand(1, dim).astype("float32")
query_img = np.random.rand(1, dim).astype("float32")
results = hybrid_search(index_text, index_image, query_text, query_img, k=5, alpha=0.7)
for doc_id, score in results:
print(f"Doc ID: {doc_id}, Combined Score: {score:.4f}")
بهینهسازی و استراتژیهای پیشرفته
ترکیب خطی فوق ساده است اما قابل بهبود است. برای سیستمهای تولیدی، موارد زیر را در نظر بگیرید:
- ترکیب رتبه متقابل (RRF): به جای جمع زدن امتیازات خام، معکوس رتبهها را جمع بزنید. این روش در برابر تفاوتهای مقیاس بین وجهها (Modalities) مقاوم است.
- انتخاب ایندکس: برای مجموعههای دادهای که بیش از ۱۰۰ هزار بردار دارند،
IndexFlatIPرا باIndexIVFPQ(فایل معکوس با کمّیسازی حاصلضرب) جایگزین کنید تا ردپای حافظه کاهش یابد و سرعت جستجو افزایش پیدا کند. شما نیاز خواهید داشت تا این ایندکسها را بر اساس توزیع دادههای خاص خودتان مجدداً آموزش دهید. - وزندهی پویا: از یک طبقهبند (Classifier) برای تعیین اینکه آیا یک پرسش بیشتر متنی است یا تصویری استفاده کنید و
alphaرا به صورت پویا تنظیم کنید.
نتیجهگیری
پیادهسازی بازیابی چندوجهی با FAISS کنترل ریزبینانهای بر نحوه تفسیر موتور جستجو از نیت کاربر ارائه میدهد. با جداسازی ایندکسهای متنی و تصویری و اعمال منطق ترکیب سفارشی، میتوانید تجربه جستجویی ایجاد کنید که هم از نظر معنایی غنی و هم از نظر بصری شهودی باشد. با وزندهی خطی شروع کنید، نرخهای برخورد (Hit Rates) خود را اندازهگیری کنید و به سمت استراتژیهای ترکیب پیچیدهتر مانند RRF یا مدلهای یادگیری برای رتبهبندی (Learning-to-Rank) حرکت کنید. با سرعت FAISS، حتی جستجوی ترکیبی بلادرنگ روی مجموعههای داده بزرگمقیاس نیز قابل دستیابی است.