Semantic Chunking چیست؟ تشخیص مرز موضوع در متن برای RAG

Semantic Chunking با استفاده از embedding و شباهت معنایی، تغییر موضوع در متن را تشخیص می‌دهد. در این مقاله منطق، مزایا، محدودیت‌ها و پیاده‌سازی عملی آن را بررسی می‌کنیم.

نوشتهٔ HomAI7 دقیقه مطالعه
  • Semantic Chunking
  • Semantic Chunking چیست
  • Semantic Chunking Python
  • RAG Chunking
  • Embedding Similarity

Semantic Chunking چیست؟ تشخیص مرز موضوع در متن برای RAG

تقسیم متن براساس تعداد کاراکتر یا token ساده است، اما متن طبیعی همیشه مطابق اندازه‌های ثابت موضوع عوض نمی‌کند. ممکن است یک موضوع در ۸۰ کلمه تمام شود و موضوع بعدی ۷۰۰ کلمه ادامه داشته باشد. Semantic Chunking تلاش می‌کند به جای اندازه ثابت، تغییر معنی را به‌عنوان سیگنال مرز Chunk استفاده کند.

در توسعه Semantic Chunk Search، این روش یکی از strategyهای مهم بود؛ اما تجربه ما نشان داد Semantic Chunking زمانی ارزشمند است که بدانیم دقیقاً چه مسئله‌ای را حل می‌کند و کجا نباید از آن استفاده کنیم.

ایده اصلی Semantic Chunking

فرض کنید متن از جمله‌های زیر تشکیل شده باشد:

1. PostgreSQL یک دیتابیس رابطه‌ای است.
2. Connection Pool تعداد اتصال‌های هم‌زمان را مدیریت می‌کند.
3. تنظیم نامناسب Pool می‌تواند latency را افزایش دهد.
4. Kubernetes از Pod برای اجرای workload استفاده می‌کند.
5. Deployment تعداد replicaها را مدیریت می‌کند.

برای انسان واضح است که میان جمله ۳ و ۴ تغییر موضوع رخ داده است.

Semantic Chunker می‌تواند برای هر جمله embedding بسازد:

E1, E2, E3, E4, E5

سپس similarity میان جملات متوالی را محاسبه کند:

sim(E1,E2) = 0.84
sim(E2,E3) = 0.79
sim(E3,E4) = 0.31   <- افت قابل توجه
sim(E4,E5) = 0.82

افت شدید similarity می‌تواند candidate boundary باشد.

چرا این روش جذاب است؟

زیرا مرزهای موضوعی همیشه با ساختار ظاهری متن هم‌راستا نیستند.

بعضی فایل‌ها heading درست ندارند. بعضی PDFها هنگام extraction ساختار heading را از دست می‌دهند. در متن‌های طولانی ممکن است موضوع بدون newline یا separator مشخص عوض شود.

Semantic Chunking در این شرایط می‌تواند boundaryهایی پیدا کند که Recursive Chunking نمی‌بیند.

واحد اولیه تقسیم چه باشد؟

قبل از محاسبه similarity باید متن را به واحدهای کوچک تقسیم کنیم. این واحد می‌تواند:

  • جمله
  • پاراگراف کوتاه
  • window چند جمله‌ای

باشد.

جمله معمولاً granular است، اما برای متن فنی ممکن است یک جمله بسیار کوتاه یا fragment باشد. پاراگراف context بیشتری دارد ولی boundary detection را کم‌دقت‌تر می‌کند.

در عمل باید این انتخاب با نوع corpus هماهنگ شود.

Threshold ثابت یا Adaptive؟

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

اگر similarity < 0.55 بود، boundary بساز.

اما این threshold روی همه اسناد خوب عمل نمی‌کند.

در یک متن تخصصی ممکن است similarity کلی پایین باشد، چون واژگان بخش‌ها متنوع‌اند. در متن دیگری ممکن است همه بخش‌ها درباره یک حوزه نزدیک باشند و similarity حتی هنگام تغییر موضوع بالا بماند.

به همین دلیل adaptive threshold مفید است. به جای یک مقدار جهانی، distribution شباهت‌های همان بخش یا سند را بررسی می‌کنیم و افت‌های غیرعادی را پیدا می‌کنیم.

این ایده در نسخه ما نیز وارد شد.

مشکل Chunkهای بسیار کوچک

فرض کنید یک جمله متفاوت بین دو بخش وجود داشته باشد. اگر هر افت similarity را boundary کنیم، ممکن است Chunkهای ۲۰ یا ۳۰ توکنی تولید شوند.

این Chunkها معمولاً برای retrieval ایده‌آل نیستند. بنابراین Semantic Chunker باید حداقل اندازه داشته باشد.

یک policy رایج:

اگر chunk فعلی هنوز از min_tokens کوچک‌تر است، boundary را فعلاً نپذیر.

در سمت دیگر نیز max_tokens لازم است تا یک بخش بسیار همگن به Chunk چند هزار توکنی تبدیل نشود.

Semantic coherence نباید محدودیت مدل embedding را نقض کند.

Overlap در Semantic Chunking

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

Overlap می‌تواند چند جمله یا token از انتهای Chunk قبلی را وارد Chunk بعدی کند.

اما overlap باید کنترل شود. overlap زیاد باعث می‌شود candidateهای بسیار مشابه تولید شوند و ranking را اشغال کنند.

در سیستم ما diversity و source caps در مراحل بعدی کمک می‌کنند، اما بهتر است از ابتدا duplicate بیش از حد ایجاد نشود.

Embedding Model روی Chunking اثر می‌گذارد

Semantic Chunking به embedding وابسته است. اگر مدل در زبان فارسی یا domain شما representation ضعیفی داشته باشد، boundaryها نیز ضعیف خواهند بود.

برای مثال یک مدل عمومی ممکن است تفاوت دقیق میان دو زیرموضوع حقوقی یا مخابراتی را کمتر از یک مدل domain-aware تشخیص دهد.

بنابراین انتخاب embedding model فقط روی retrieval اثر ندارد؛ اگر همان model برای semantic boundary استفاده شود، روی chunking نیز اثر مستقیم دارد.

Semantic Chunking و متن فارسی

برای فارسی normalization قبل از embedding اهمیت دارد.

نمونه‌های زیر از نظر نمایش ممکن است نزدیک باشند اما در سطح Unicode متفاوت باشند:

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

همچنین ي/ی، ك/ک و اعداد فارسی/عربی می‌توانند representation را بی‌دلیل پراکنده کنند.

ما normalization را قبل از مراحل semantic اجرا می‌کنیم و هم‌زمان mapping به متن اصلی را نگه می‌داریم تا citation قابل بازیابی باشد.

چرا Semantic Chunking برای کد مناسب نیست؟

کد ساختار syntax مشخص دارد. ممکن است embedding دو خط مجاور را از نظر معنایی متفاوت ببیند، اما شکستن وسط function اشتباه باشد.

برای Python، AST boundary بسیار قابل اعتمادتر است:

class PaymentService:
def charge(...):
    ...

بهتر است function یا class واحد Chunk باشد، نه اینکه similarity بین lineها boundary تعیین کند.

در سند ترکیبی نیز باید Semantic Chunking فقط روی prose اعمال شود؛ همین منطق یکی از دلایل ساخت Composite Chunker بود.

Semantic Chunking برای جدول هم انتخاب خوبی نیست

در جدول رابطه معنایی row و header اهمیت دارد. similarity خطی میان rowها لزوماً ساختار واقعی جدول را نشان نمی‌دهد.

برای جدول باید schema و header حفظ شود.

پس Semantic Chunking بیشتر برای متن پیوسته و prose مناسب است.

تفاوت Semantic و Recursive Chunking

Recursive Chunking ساختار ظاهری را دنبال می‌کند؛ Semantic Chunking ساختار معنایی را حدس می‌زند.

ویژگیRecursiveSemantic
سرعتبالاترپایین‌تر
نیاز به embeddingخیربله
قابل پیش‌بینی بودنبالامتوسط
تشخیص تغییر موضوع بدون headingضعیف‌تربهتر
هزینه پردازشپایینبیشتر
مناسب برای سند ساختاریبسیار خوبوابسته

در عمل این دو رقیب مطلق نیستند. می‌توان ابتدا structure را جدا کرد و داخل بخش‌های prose از Semantic Chunking استفاده کرد.

چگونه کیفیت Semantic Chunking را ارزیابی کنیم؟

صرفاً نگاه کردن به چند Chunk کافی نیست.

دو نوع metric مهم داریم.

۱. کیفیت boundary

اگر ground truth یا ساختار انسانی داشته باشیم، می‌توان Boundary F1 را اندازه گرفت.

۲. کیفیت retrieval پایین‌دستی

مهم‌تر از زیبایی Chunkها این است که آیا retrieval بهتر شده یا نه.

مثلاً:

  • Recall@5
  • MRR
  • NDCG
  • Candidate Recall
  • Citation Coverage

ممکن است Chunkها از نظر بصری عالی باشند اما retrieval بهتر نشود.

یک الگوی عملی برای پیاده‌سازی

منطق ساده‌شده می‌تواند چنین باشد:

units = split_into_sentences(text)
vectors = embed(units)

current = []
chunks = []

for i, unit in enumerate(units):
current.append(unit)

if i == len(units) - 1:
    break

similarity = cosine(vectors[i], vectors[i + 1])

if similarity < adaptive_threshold and token_count(current) >= min_tokens:
    chunks.append(join(current))
    current = overlap_tail(current)

if current:
chunks.append(join(current))

نسخه production طبیعتاً باید max token، metadata، offsetها، duplicate-safe IDs و error handling را نیز مدیریت کند.

چه زمانی از Semantic Chunking استفاده کنیم؟

مناسب است اگر:

  • متن طولانی و prose باشد.
  • headingها ضعیف یا از بین رفته باشند.
  • تغییر موضوع درون paragraphهای بزرگ رخ دهد.
  • embedding model مناسب زبان/domain داشته باشید.

کمتر مناسب است اگر:

  • سند کد است.
  • جدول غالب است.
  • Markdown ساختار بسیار خوب دارد.
  • ingestion throughput بسیار حساس است.
  • embedding مناسب زبان ندارید.

جمع‌بندی

Semantic Chunking راهی است برای تبدیل «تغییر معنی» به boundary. این روش می‌تواند retrieval را در متن‌های آزاد بهتر کند، اما هزینه و dependency بیشتری دارد و نباید جایگزین ساختار صریح سند شود.

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

ابتدا ساختار قطعی سند را حفظ کن؛ سپس در بخش‌هایی که ساختار کافی ندارند از semantics کمک بگیر.

همین دیدگاه پایه Composite Chunking و معماری چنداستراتژی ما شد.

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

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

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