چالش‌های نرمال‌سازی نوشته‌های فارسی

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

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

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

متن ورودی به دلایل متعددی می‌تواند اشکال متنوعی داشته باشد. گاهی این موضوع به‌خاطر پیش‌فرض‌های غلطِ برخی از سیستم‌عامل‌ها و دستگاه‌ها است؛ مثلاً به‌جای اینکه صفحه‌کلیدِ پیش‌فرض (به طور دقیق‌تر keyboard Layout) فارسی باشد، عربی است، یا فارسی است ولی فارسیِ استاندارد نیست.

گاهی نیز کاربر تعمداً (؟) شڪل نوشــــــــــتاري غیرمرسومے را در پیش میگیردددددد. به‌هرروی متن ورودی صرفنظر از چرایی آن می‌تواند حالات مختلفی داشته باشد. این تنوع و ناهمگنی باید از میان برداشته شود یا دست‌کم کاهش یابد تا متن برای مراحل بعدی پردازش آماده شود.

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

تهیه مجموعه‌ای از متن‌های غیراستاندارد

اولین کار تهیه دیتاستی از متون غیرنرمال، با تنوع و حجم مناسب بود تا بعد از اعمال هر تغییر در نرمالایزر، از میزان موفقیت و اثرات جانبی آن مطلع شویم. مشکل این بود که بیشترِ دیتاست‌ها از قبل تا حدِ مطلوبی نرمال‌سازی شده‌اند. بنابراین تصمیم گرفتیم از دیتاستی از چت‌های کاربران در گروه‌های تلگرامی استفاده کنیم که معمولاً تنوع نوشتاری خوبی دارد.

شنبهها > شنبه‌ها

اولین مشکل، چسبیدن دو حرف «ه» در کلماتی مثل «شنبهها» بود که خوشبختانه با یک الگوی ساده ریجکس حل شد. (بین 1\ و 2\، کاراکتر نیم‌فاصله با کد u200c\ هست که در تصویر دیده نمی‌شود).

وجود نیم‌فاصله قبل یا بعد از فاصله

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

۱۰تا۱۵کیلومتر > ۱۰ تا ۱۵ کیلومتر

مشکل بعدی، نرمال‌سازی ترکیب اعداد و حروف بود. راه‌حل اول این بود که با یک ریجکس ساده، هرجایی عدد دیدیم، دو طرفش فاصله بگذاریم؛ ولی این روش همیشه کارساز نیست؛ مثلاً در 5x+2 کسی انتظارِ وجود فاصله بین ۵ و x را ندارد! بنابراین گفتیم فقط جایی فاصله بگذارد که قبل یا بعد از عدد، یک حرف فارسی وجود دارد؛ مثلاً ۱۰تا۱۵ کیلومتر.

میخواهم > می‌خواهم

مورد بعدی، نیم‌جداکردن پیشوند «می» در افعال است. مسلماً این کار را فقط باید برای افعال انجام داد؛ چون در کلماتی مثل «میهن»، «میراث»، نباید این اتفاق بیفتد. بنابراین به این راه‌حل رسیدیم که ابتدا در لیست صورت‌های صرفی افعال جستجو کند و افعال دارای «می» را پیدا کند. حالا یک دیکشنری از شکل نیم‌جدا و سرهمِ «می» در این افعال تهیه کند و درنهایت با کمک این دیکشنری، اقدام به جایگزینی شکل صحیح کند.

نرمالسازے ڪاراڪترهاي خاص یونیکد و حذف اعراب و اموجی‌ها

تنوع صفحه‌کلیدها و تعدد کاراکترهای یونیکد آنقدر زیاد است که این مشکل را با یک نگاشت ساده نمی‌توان حل کرد؛ مثلاً برای همین حرف «ی»، در رنج کاراکترهای یونیکد عربی، چندین «ی» وجود دارد. تنها کار امنی که توانستیم بکنیم بررسی و استخراج تک‌تک کاراکترهای عربی در فهرست کاراکترهای یونیکد و اضافه‌کردن شکل استاندارد آن به عنوان نسخه جایگزین بود. برای حذف اموجی‌ها و اعراب هم تقریباً باید تمام ۱۴۰ هزار و اندی کاراکترهای یونیکد بررسی می‌شد که در حال حاضر تا حدی این کار جلو رفته است. این کار از این جهت ضروری است که احتمال استفاده از هر یک از کاراکترها در متن وجود دارد.

سلامممم > سلام

شاید حذف کاراکترهای تکراری، سخت‌ترین چالشی بود که با آن روبرو شدیم. مسلماً وجود کلماتی مثل «ببر» و «ضرر» اجازه نمی‌دهد بی‌مهابا دست به حذف تمام تکرارها بزنیم؛ ولی یک کار را با خیال راحت می‌توانستیم بکنیم: اینکه بیشتر از دو تکرار را به دو تکرار کاهش دهیم (سلامممم ⮜ سلامم)؛ چون در فارسی کلماتی با سه تکرارِ حروف و بیشتر نداریم.

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

اما این راهبرد هم جواب نداد:

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

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

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

این تنها بخشی از چالش‌هایی بود که فقط در بخشی از هضم با آن روبرو شدیم. در سایر بخش‌های هضم نیز مانند ریشه‌یابی، جداسازی جملات و کلمات، تحلیل نحوی و غیره چالش‌های از همین جنس و شاید حتی پیچیده‌تر وجود دارد که ان‌شاءالله در فرصتی دیگر درباره آن‌ها نیز صحبت خواهیم کرد.

اگر شما نیز ایده و پیشنهادی دارید ذیل همین پست بنویسید. گاهی مسائل پیچیده، راه‌حل‌های ساده‌ای دارند! همچنین اگر دوست دارید در توسعه این پروژه سهیم باشید.

گیتهاب هضم