ساخت موتور جستجوی فارسی؛ مشکلات ی، ک، نیم‌فاصله و اعداد که Retrieval را خراب می‌کنند

نرمال‌سازی متن فارسی چگونه روی Search و RAG اثر می‌گذارد؟ تفاوت ی و ي، ک و ك، نیم‌فاصله، اعداد فارسی و Unicode را با مثال‌های عملی بررسی می‌کنیم.

نوشتهٔ HomAI6 دقیقه مطالعه
  • نرمال سازی متن فارسی
  • پردازش متن فارسی پایتون
  • Persian NLP
  • جستجوی فارسی
  • نیم فاصله
  • Semantic Search فارسی

ساخت موتور جستجوی فارسی؛ مشکلات ی، ک، نیم‌فاصله و اعداد که Retrieval را خراب می‌کنند

دو رشته ممکن است برای چشم انسان کاملاً یکسان باشند اما برای کامپیوتر متفاوت باشند. در زبان فارسی این اتفاق بیشتر از چیزی که در یک dataset تمیز آزمایشی می‌بینیم رخ می‌دهد.

در توسعه موتور Semantic Search خودمان، normalization فارسی یکی از اولین لایه‌هایی بود که مجبور شدیم جدی بگیریم؛ چون مشکل فقط زیبایی متن نبود. تفاوت‌های Unicode مستقیماً روی Exact Search، BM25، tokenization، hashing، duplicate detection و حتی embedding اثر می‌گذاشتند.

ی فارسی و ي عربی

دو حرف زیر ظاهراً نزدیک‌اند:

ی
ي

اما code point متفاوت دارند.

اگر یک سند با کیبورد عربی تولید شده باشد و کاربر با کیبورد فارسی جستجو کند، lexical search می‌تواند match را از دست بدهد.

مثلاً:

مدیریت
مديريت

برای انسان یک واژه‌اند؛ برای matching خام لزوماً نه.

Normalization باید ي را به ی canonical کند.

ک فارسی و ك عربی

همین مسئله برای این دو حرف وجود دارد:

ک
ك

در corpusهایی که از منابع مختلف جمع‌آوری شده‌اند، هر دو نسخه به‌وفور دیده می‌شوند.

اگر canonicalization نداشته باشیم، document frequency در BM25 نیز شکسته می‌شود؛ چون engine آن‌ها را دو term جدا می‌بیند.

اعداد فارسی، عربی و لاتین

یک شناسه ممکن است به سه شکل وارد شود:

7312
۷۳۱۲
٧٣١٢

اگر سیستم شما error code، invoice number یا شماره قرارداد جستجو می‌کند، این تفاوت بسیار مهم است.

برای مثال:

ERR-۷۳۱۲
ERR-7312

باید براساس policy محصول تصمیم بگیرید آیا این‌ها equivalent هستند یا نه. در اکثر سیستم‌های فارسی جواب بله است.

ما canonical digit normalization را در core قرار دادیم تا query و document به representation یکسان برسند.

نیم‌فاصله یا ZWNJ

نیم‌فاصله یکی از پیچیده‌ترین بخش‌های متن فارسی در search است.

مثلاً:

می‌رود
می رود
میرود

ممکن است کاربران یا منابع مختلف هر سه شکل را تولید کنند.

سؤال این است: آیا باید همه را یکسان کنیم؟

پاسخ به use case بستگی دارد. حذف کامل ZWNJ می‌تواند representation را ساده کند، اما اطلاعات orthographic را از بین می‌برد. نگه داشتن آن بدون normalization نیز match را ضعیف می‌کند.

در طراحی ما ZWNJ به‌شکل کنترل‌شده normalize می‌شود تا retrieval پایدارتر باشد.

Unicode Normalization فقط مخصوص فارسی نیست

Unicode یک کاراکتر ظاهری را گاهی به شکل‌های مختلف encode می‌کند. Normalization Formهایی مثل NFC/NFKC می‌توانند برخی اختلاف‌ها را canonical کنند.

در ingestion چندمنبعی این موضوع مهم است، چون متن ممکن است از:

  • PDF
  • Word
  • HTML
  • copy/paste
  • OCR
  • سیستم‌های قدیمی

آمده باشد.

هر source الگوی Unicode متفاوتی دارد.

چرا Embedding همه این مشکلات را حل نمی‌کند؟

ممکن است تصور کنیم مدل multilingual خودش ي و ی را نزدیک می‌بیند. گاهی همین‌طور است، اما تکیه کامل به آن اشتباه است.

اولاً Exact Search هنوز به canonical text نیاز دارد. ثانیاً tokenizer ممکن است شکل‌های مختلف را به tokenهای متفاوت بشکند. ثالثاً embedding modelها در همه edge caseهای فارسی یکسان نیستند.

Normalization باعث می‌شود مدل ورودی پایدارتر و predictableتری ببیند.

تاثیر normalization روی hashing و idempotency

فرض کنید source امروز این متن را داشته باشد:

مديريت کاربران

و فردا بدون تغییر معنایی به این شکل ذخیره شود:

مدیریت کاربران

اگر hash روی raw bytes باشد، سیستم آن را تغییر کامل محتوا می‌بیند و re-index غیرضروری انجام می‌دهد.

اگر hash فقط روی normalized text باشد، ممکن است بعضی تغییرات نمایشی مهم از بین بروند.

بنابراین باید روشن باشد کدام hash برای content identity و کدام representation برای provenance استفاده می‌شود.

در معماری ما متن اصلی و normalized representation هر دو اهمیت دارند.

چرا mapping به متن اصلی مهم است؟

فرض کنید normalization چند character را تغییر دهد. بعد retrieval یک span از متن normalized پیدا می‌کند.

برای نمایش citation در UI باید بدانیم این span در متن اصلی کجا بوده است.

به همین دلیل normalization حرفه‌ای فقط این نیست:

normalized = normalize(text)

بلکه بهتر است mapping نیز تولید شود:

normalized offset -> original offset

این mapping برای highlight کردن پاسخ، citation و audit مفید است.

Whitespace Normalization

PDF extraction معمولاً whitespace عجیبی تولید می‌کند:

PostgreSQL    connection
pool

یا وسط کلمات line break قرار می‌دهد.

Normalization whitespace می‌تواند retrieval را بهتر کند، اما نباید structure مفید را نابود کند. newline میان paragraphها ارزش معنایی دارد و Chunker از آن استفاده می‌کند.

پس بهتر است whitespace normalization context-aware باشد.

OCR و نویز فارسی

در PDF اسکن‌شده OCR ممکن است حروف را اشتباه تشخیص دهد:

۰ / O
۱ / l
ب / پ

Normalization نمی‌تواند همه خطاهای OCR را حل کند؛ چون اینجا مسئله canonicalization نیست، تشخیص اشتباه character است.

اما pipeline باید بداند OCR source کم‌اعتمادتر است و metadata provenance را حفظ کند.

می‌توان source trust یا confidence را در ranking پایین‌دستی لحاظ کرد.

Query Normalization باید با Document Normalization یکسان باشد

یکی از خطاهای رایج این است که ingestion normalization کامل دارد اما Query خام وارد search می‌شود.

هر transformationی که برای index انجام می‌دهید باید نسخه معادل Query-time داشته باشد.

در غیر این صورت representation دو طرف ناسازگار می‌شود.

قاعده ساده:

Index-time normalization و Query-time normalization باید contract مشترک داشته باشند.

Identifierها و normalization بیش از حد

Normalization همیشه باید محافظه‌کار باشد.

مثلاً در code یا identifier، case و punctuation ممکن است اهمیت داشته باشند.

اگر یک normalizer عمومی همه علامت‌ها را حذف کند، این دو identifier ممکن است ناخواسته یکی شوند:

AB-12
AB12

به همین دلیل normalization باید نوع محتوا را در نظر بگیرد و exact referenceها را بی‌دلیل خراب نکند.

یک Pipeline پیشنهادی برای فارسی

Raw text
  -> Unicode normalize
  -> Arabic/Persian char canonicalization
  -> Digit canonicalization
  -> ZWNJ policy
  -> Whitespace cleanup
  -> Original-offset mapping
  -> Structural parsing
  -> Chunking

Query نیز باید از همان canonicalization اصلی عبور کند.

چگونه normalization را تست کنیم؟

برای زبان فارسی unit testهای مشخص بنویسید.

نمونه caseها:

"كتاب" == "کتاب"
"مديريت" == "مدیریت"
"۱۲۳" == "123"
"١٢٣" == "123"

همچنین باید test کنید mapping offset بعد از normalization درست باقی بماند.

در ماژول ما normalization و normalized-to-original offsets جزو بخش‌های تست‌شده core هستند.

تاثیر روی SEO داخلی و Search محصول

گرچه این مقاله درباره RAG است، همان مشکلات در search معمولی سایت فارسی نیز وجود دارند.

اگر کاربر «مدیریت» را جستجو کند و مقاله شما با مديريت ذخیره شده باشد، موتور lexical ساده ممکن است نتیجه را ضعیف کند.

بنابراین Persian normalization فقط موضوع AI نیست؛ یک مسئله پایه Information Retrieval است.

جمع‌بندی

ساخت موتور جستجوی فارسی بدون normalization مثل ساخت index روی داده‌ای است که تعریف ثابتی از «یکسان بودن» ندارد.

تفاوت ی/ي، ک/ك، اعداد و نیم‌فاصله شاید کوچک به نظر برسد، اما در مقیاس corpus واقعی روی matching، scoring، hashing و citation اثر می‌گذارد.

یکی از درس‌های اصلی ما این بود که زبان فارسی را نباید صرفاً به embedding model سپرد. کیفیت retrieval از preprocessing دقیق و قابل تست شروع می‌شود.

به‌روزرسانی:

همهٔ نوشته‌ها

نوشته‌های مرتبط