مهندس نرم افزار | متخصص علوم داده
داستان نامداران: یافتن اسامی خاص!
سلام!
پیدا کردن اسامی خاص توی متون یکی از تسکهای پیشپردازشی توی پردازش زبان طبیعی محسوب میشه. توی این تسک دنبال این هستیم تا اسامی خاص مثل اسم افراد یا اسم سازمانها و یا اسم مکانها رو بتونیم پیدا کنیم و تگ بزنیم. تگ هر یک از این کلمات میتونه به عنوان فیچر برای تسکهای بعدی پردازش زبان طبیعی محسوب بشه. البته دیده شده که همین تسک به تنهایی هم میتونه کاربردهایی داشته باشه. مثلا قابلیت X-ray توی کیندلهای آمازون دقیقا از همین استفاده میکنه. اگه از کتابخوانهای آمازون استفاده کرده باشید میدونید که موقع مطالعه کتابتون میتونید از این قابلیت استفاده کنید و اطلاعات مختصری درباره هر یک از اسامی خاص توی متن رو ببینید. تو این پست قصد داریم درباره این تسک و روند پیشرفتش و مدلهای موجود صحبت کنیم.
نامداران وارد میشوند!: معرفی مساله
برای اینکه بتونیم این تسک رو انجام بدیم روشهای متفاوتی در طول زمان استفاده شده. روشهای اولیه مبتنی بر دانش بودند. به این صورت که یه دیکشنری از اسامی خاص موجود بوده و توی متن باید بر اساس اون اسامی خاص برچسب میخوردن. بعد از اون که یادگیری ماشینی سری توی سرا در آورد سراغ روشهای بدون نظارت رفتن که این مدلها پیچیدگی زیادی داشتن و کیفیت کار در حد مطلوب نبود. البته علت اینکه ملت سراغ این روش رفتن این بوده که اون موقع داده برچسب خورده مثل امروز فراوون! نبوده ولی امروزه که دادههای برچسب خورده زیاد داریم بیشتر سراغ روشهای با نظارت رفتن و از این روشها جواب گرفتن. تا قبل از ظهور یادگیری عمیق از روشهای آماری استفاده میشده که در اون روشهای نیاز به feature engineering شدید بوده و همین کار رو سخت میکرده. علتش هم اینه که توی این حوزه بدست آوردن ویژگیهایی برای کلمات که بشه با اونا کلمات رو برچسب اسامی خاص زد خیلی سخته. اما حالا که یادگیری عمیق داریم دیگه این فاز رو به عهده شبکههای عصبی گذاشتیم و از شرش خلاص شدیم. البته لازمه که دیتاستهای قدر قدرتی! داشته باشیم که اونها رو هم معرفی میکنیم.
یک سیستم مبتنی بر یادگیری عمیق برای تشخیص اسامی خاص سه قسمت اصلی داره که توی تصویر زیر مشخصه. در ادامه درباره هر یک از این قسمتا توضیحاتی میدیم.
بازنمایی دیتای ورودی
توی هر مساله پردازش زبان طبیعی یکی از مهمترین قسمتها نحوه ورودی دادن دیتا ست. ورودی مساله ما از جنس کلمات و جملهها هستن اما باید تبدیل به اعداد بشن. اینجا ست که مساله word embedding وارد بازی میشه. شاید جا داشته باشه که توی یه پست جدا درباره انواع و اقسام روشهاش توضیح بدیم ولی اینجا هم یه سری توضیحات میدیم که در کار خود وا نمانیم!!
یکی از روشهای رایج، بازنمایی در سطح کلمه ست. برای این کار چندین روش وجود داره که یکی از معروفترین اونها Word2Vec هست که همین روش رو به دو صورت continues bag of words یا CBOW و skip-gram میشه انجام داد. تصویر کلی این دو روش رو در زیر میبینید. از معایب بزرگ این روش اینه که محتوا رو بازتاب نمیدن. ینی اینکه برای کلمه مثل "شیر" کلا یه بردار ایجاد میکنن. اما پر واضحه که این کلمه در context های مختلف میتونه معانی متفاوت داشته باشه. از طرفی به برخی ساختارهای زیرکلمهای توی زبان هم توجه نمیکنن. اما یه خوبی بزرگی که دارن اینه که معانی رو حفظ میکنن. ینی کلمات هممعنی برداراشون هم فاصله کمی با هم دارن.
از روشهای بازنمایی سطح کلمات که بگذریم میتونیم بریم سراغ بازنمایی کلمات در سطح حروف! توی این روشهای به ویژگیهای زیرکلمهای یه کلمه توجه میشه. از طرفی یکی دیگه از مشکلات بازنمایی سطح کلمه رو که در بالا نگفتیم رو هم نداره و اون کلمات خارج از واژگان هست. توی تصویر زیر نشون دادیم که میشه با استفاده از بازنمایی سطح حروف بردارهای حساس به محتوا تولید کرد. برای این کار از شبکههای LSTM یا CNN استفاده میشه. البته این روش در مقایسه با روش قبلی که گفتیم طبیعتا به محاسبات بیشتر و همچنین به منابع پردازشی بیشتر احتیاج داره چرا که روشهای قبلی حتی بعضا جزو یادگیری عمیق هم محسوب نمیشن اما توی این روش شما باید یه شبکه عمیق رو آموزش بدید.
روشهایی هم هستن که اومدن بردار کلمه حاصل از دو روش بالا رو بهم پیوست میدن و همزمان استفاده میکنن. اما یه تکنیک دیگه ای هم سوار میکنن. همونطور که اول پست گفتیم قبل از یادگیری عمیق مجبور بودیم فاز مهندسی ویژگی داشته باشیم. اما الان که شبکه عمیق داریم هم دلیل نمیشه اصلا از ویژگیهایی که میشه به صورت دستی برای این تسک درآورد استفاده نکنیم! چراااا؟ دلیلش واضحه. چون میتونه فضای جستوجو رو برای شبکه عصبی کوچیک کنه و در نتیجه هم سریعتر به کیفیت مطلوب برسه و هم داده کمتری رو مصرف کنه. اما یه مقدار باید باهوش باشیم که بدونیم چهطوری اینا رو کنار هم قرار بدیم. توی تصویر زیر معماری این سیستم نشون داده شده. مثلا فرض کنین چند تا فیچر دستی در آوردیم یکی برچسب POS هر کلمه و یکی هم اینکه آیا حرف اولش بزرگ هست یا نه ( توی زبان انگلیسی ) و یکی هم مثلا وجود کسره اضافه بعد از کلمه ( توی زبان فارسی میتونه به تشخیص مرزهای یه کلمه خاص کمک کنه ) . میایم بردار مربوط به این ویژگیهای دستی رو با بردار حاصل از بازنمایی سطح کلمه و بردار حاصل از بازنمایی سطح حروف هر کلمه پیوست میکنیم. بعد این بردار رو به شبکه دوطرفه LSTM میدیم و بعد حالات پنهان خروجی LSTM به طور همزمان به یه شبکه خودرمزنگار و برچسب زننده میدیم. خروجی شبکه خودرمزنگار باید با بردارهای حاصل از ویژگیهای دستی برابر باشه در نتیجه میزان loss به صورت ترکیبی میتونه حساب بشه و loss هر دو شبکه به صورت همزمان سعی میکنه کمینه بشه. معماری این روش رو هم میتونین توی تصویر زیر ببینین.
بازنمایی وابستگی کلمات
همون طور که توی قسمت اول گفتیم بعد از بخش word embeddings نوبت به بخش Generate Representation using Dependencies میرسه. توی بخش قبل سعی کردیم هر کلمه رو به یه بازنمایی عددی تبدیل کنیم. اما برای اینکه بتونیم مساله رو کامل حل کنیم به یه سری اطلاعات دیگه هم نیاز داریم. جملات توی زبان طبیعی دنبالهای از کلمات هستن و به عبارت دیگه هر دنبالهای از کلمات توی زبان طبیعی نمیتونه معنیدار باشه. همین موضوع ینی اینکه توی وابستگی بین کلمات هم اطلاعاتی نهفته ست. پس باید بتونیم از این وابستگیها هم استفاده کنیم. طبعا اولین گزینهای که به ذهن میرسه استفاده از RNN هست اما میدونیم که با مشکل فراموشی بردار گرادیان مواجه میشیم. به خاطر همین از LSTM استفاده میشه. در واقع خروجیهای فاز قبلی که بازنمایی کلمات هستن رو میشه به شبکههای LSTM داد و با استفاده از اونها وابستگی بین کلمات رو کپچر کرد. اما طبیعتا استفاده از این نوع شبکهها امکان موازی سازی رو از ما میگیره که همین میتونه نقطه ضعف این روش باشه. به خاطر همین سعی شده تا از شبکههای کانولوشنی هم استفاده بشه. در این روش با استفاده از بردار N بعدی برای هر کلمه و جمله متناظر اون وابستگی بین دادهای بازنمایی میشه. البته این مدلها توی پردازش جملات طولانی به مشکل میخورن که برای مقابله با این مشکل مدل Iterated Dilated Convolutions معرفی شده. ID-CNN ها به صورت عمق ثابت به طوری که طول موثر ورودی به صورت نمایی با این عمق رشد کنه طراحی شدن. بر خلاف LSTM که پیچیدگی محاسباتی اون به صورت خطی همراه با طول ورودی رشد میکنه این شبکه میتونه به صورت موازی عملیات پردازش روی جملهها رو انجام بده. در عمل نشون داده شده که سرعت این شبکه در حدود ۱۴ الی ۲۰ برابر شبکههای LSTM هست در حالیکه دقت این شبکه فقط کمی بدتر از LSTM هست. در زیر میتونید معماری این نوع شبکهها رو ببینید.
اما در نهایت با ظهور و بروز ترنسفورمرها کلا بازی عوض شد! این شبکهها تنها با درنظر گرفتن مکانیزم توجه میتونن وابستگیهای دادهای رو منظور کنن. بردار توجهی که از داده ورودی به دست میآد رو میشه به صورت موازی به دست آورد و بعد این بردار رو به یه شبکه fully connected داد تا فضای ورودی رو انکود کنه. بردار توجه یه کلمه به صورت زیر به دست میاد:
برای اینکه ترنسفورمرها رو بهتر بفهمید توصیه میکنم حتما این سری پستها رو مطالعه کنین. برای اینکه تشخیص اسامی خاص انجام بشه با استفاده از بخش انکودر توی ترنسفورها فضای ورودی رو انکود میکنن و بعد با استفاده از یه شبکه برچسبزن کار برچسب زدن رو انجام میدن.
یه پیاده سازی خوب از ترنسفورمرها، BERT هست. برت یه معماری عمومی با استفاده از ترنسفورمر هست که میشه به تسکهای مختلفی توی حوزه پردازش زبان طبیعی اون رو اپلای کرد. برای این کار باید فرمت ورودی و خروجی برت رو بشناسین و همچنین روی دیتای موردنظرتون اون رو fine-tune بکنین. توی تصویر زیر نحوه ورودی دادن به شبکه برت توی یه تسک NER رو میتونین ببینین.
شبکه برچسب زن میتونه یه لایه Dense روی برت باشه. برای fine-tune کردن برت روی تسک NER اول باید دادههایی رو که توی دیتاست استاندارد NER دارین، یه مرحله پردازش کنین. پردازش میتونه شامل موارد زیر باشه :
- اضافه کردن توکن 'CLS' به ابتدای جملات و توکن 'SEP' به انتهای جملات
- همسان سازی طول جملات با مقدار max_seq_length ( عملیات padding )
- تبدیل توکنها به آیدی با استفاده از BERT Tokenizer
- عملیات masking : در این مرحله باید مشخص کنین که توی هر جمله کدوم توکن مربوط به padding هست و کدوم نیست
- آیدی segment : توی این قسمت توکنهای مربوط به جمله اول آیدی ۰ و مربوط به جمله دوم آیدی ۱ میگیرن اما چون تسک NER یه تسک یه جملهای هست طبیعتا همه توکنهای ما اینجا مقدار ۰ میگیرن.
- آیدی برچسب : یه عدد صحیح که به هر یک از انواع برچسبها زده میشه
- عملیات masking برای برچسبها: به هر برچسب مقدار True زده میشه و برای اون قسمتهایی که مربوط به padding هستن باید برچسب False بزنین.
- مقادیر valid ids : برت برای توکنایزر از word-piece tokenizer استفاده میکنه که ممکنه برخی کلمات هم شکسته بشن. به همین دلیل برای اون توکنهایی که شکسته نشن این مقدار برابر با ۱ و برای اونایی که شکسته بشن برای قسمت اول اون کلمه مقدار ۱ و برای قسمت دوم اون کلمه مقدار ۰ گذاشته میشه.همچنین توکنهای مربوط به paddings هم مقدار ۱ دریافت میکنن.
به فرض مثال برای جمله ['.' ,‘EU’, ‘rejects’, ‘German’, ‘call’, ‘to’, ‘boycott’, ‘British’, ‘lamb’] و برای دنباله برچسب [‘B-ORG’, ‘O’, ‘B-MISC’, ‘O’, ‘O’, ‘O’, ‘B-MISC’, ‘O’, ‘O’] مقادیر بالا به صورت زیر خواهند بود :
در این حالت لازم داریم تا حالتهای پنهان میانی رو به عنوان بازنمایی کلمات درنظر بگیریم. برای برچسب زدن هم که از یه لایه Dense بر روی برت استفاده میکنیم که در طول عملیات fine-tuning آموزش داده میشه. شبکه برت از ۱۲ لایه ترنسفورمر استفاده میکنه که هر یک از این لایههای یا تعدادی از اونها میتونه به عنوان بازنمایی کلمات در نظر گرفته بشه. توی این پست میتونین جزییات عملیات fine-tuning رو ببینید همچنین بررسی شده که کدوم لایههای میانی به عنوان بازنمایی ورودی در نظر گرفته بشن تا بتونیم نتیجه بهتری بگیریم. توی تصویر زیر نتیجه این بررسی رو میبینید:
اگه دوست دارید کار کردن با برت براتون راحت باشه و بتونین عملیاتهای fine-tune رو هم راحت انجام بدید میتونین از کتابخونه Huggingface استفاده کنین که اینترفیسهای راحتی رو هم براتون فراهم میکنه. با این کتابخونه خیلی از عملیاتهای پیشپردازش برای tokenization هم راحت میشه و مقادیر بالا رو که توضیح دادیم راحت میتونین به دست بیارین.
برچسب زننده
همونطور که تو قسمت اول توضیح دادیم بعد از تبدیل فضای بردار ورودی به فضایی که وابستگیهای دادهای رو در نظر گرفته حالا باید کلمات رو برچسب اسامی خاص بزنیم. معمولا اسامی اشخاص، اسامی سازمانها، اسامی مکان، اسم رویدادها و اسامی گروهها به عنوان اسامی خاص در نظر گرفته میشن. در برخی کاربردها تاریخها و یا واحدهای ارزی نیز به عنوان اسامی خاص در نظر گرفته میشن.
معروفترین روشی که تو این بخش استفاده میشه روش CRF هست که نسبت به روشهای رایج مثل مدل مخفی مارکوف مزیتهایی داره از جمله سادهسازی فرضهای سخت استقلال که توی مدل مارکف ایجاد میشن. این روش امروزه بیشترین استفاده رو توی برچسب زدن اسامی خاص داره. روش دیگه هم استفاده از پرسپترونهای چندلایه ست که توی سیستمهای قدیمیتر استفاده میشده. اگه بخوایم از شبکه چندلایه پرسپترون استفاده کنیم باید خروجی قسمتهای قبل رو به این شبکه بدیم و در انتها یه لایه softmax بذاریم. آموزش این شبکه آسون تر از شبکههای CRF هست اما یه مشکل بزرگ داره و اونم اینه که وابستگی بین دنبالهای از برچسبها رو در نظر نمیگیره. به عبارت دیگه خود برچسب اسامی خاص هم یه دنباله هستن ینی هر دنبالهای از برچسبها لزوما نمیتونه توی زبان معنیدار باشه پس این وابستگی بین برچسبها هم حاوی اطلاعات هستن که میتونیم اونا رو منظور کنیم.
نحوه ارزیابی
با توجه به اینکه مساله NER یه مساله دستهبندی هست بهترین معیاری که برای ارزیابی این مدلها استفاده میشه معیار f-score هست. معیار f-score سعی میکنه همزمان هم به اسامی که درست برچسب خوردن توجه کنه و هم به اسامی عادی که درست برچسب خوردن. برای آشنایی با این معیار باید با precision و recall هم آشنا باشید. تصویر زیر میتونه به درک بهتر این دو معیار کمک کنه:
معیار f-score با ترکیب دو معیار بالا بر اساس هر نوع دسته یه امتیاز ارایه میده. به عبارت دیگه کیفیت دستهبندی روی هر دسته رو میتونه بهمون بده. در نهایت برای اینکه بتونیم کیفیت کلی مدل رو بهدست بیاریم میتونیم روی امتیازهای هر دسته مثلا میانگین وزندار بگیریم. نحوه محاسبه f-score رو هم در تصویر زیر میتونین ببینیم :
معرفی چند دادگان
توی این بخش هم میخوایم چندتا دیتاست که برای این مساله استاندارد سازی شدن رو بهتون معرفی کنیم. اولین مجموعه، دیتاست Onto-Notes5.0 هست که به زبان انگلیسی، چینی و عربی و به صورت عمومی در دسترسه. بخش انگلیسی این دیتاست شامل ۱۰۸۸۵۰۳ کلمه و بخش چینی شامل ۷۵۶۰۶۳ کلمه هست. بالاترین F-score روی این مجموعه حدود ۸۹.۷۱ هستش. این پیکره از ۵ ژانر محتلف دیتا جمعآوری کرده: مکالمات پخش شده، اخبار، مجلات، روزنامهها و دادههای روی وب. همچنین ۱۸ نوع برچسب موجودیت داره که نشون میده خیلی مفصل هست. همچنین قسمت انگلیسی زبان این مجموعه شامل ۱۴۷۷۲۴ کلمه برای اعتبارسنجی و ۱۵۲۷۲۸ کلمه برای تست هست.
پیکره بعدی هم پیکره CoNLL2003 هست که به زبانهای انگلیسی و آلمانی هست. مجموعه آموزشی این پیکره شامل ۲۰۳۶۲۱ کلمه و مجموعه آلمانی شامل ۲۰۶۹۳۱ کلمه هست. همچنین برای اعتبارسنجی حدود ۵۱۳۶۲ و ۵۱۴۴۴ کلمه به ترتیب برای انگلیسی و آلمانی در نظر گرفته. برای تست هم به ترتیب ۴۶۴۳۵ و ۵۱۹۴۳ کلمه در نظر گرفته شده. توی این دیتاست کلا ۴ نوع اسم خاص در نظر گرفته شده : اسم اشخاص، اسم مکان، اسم سازمان و سایر در نظر گرفته شده است. دادههای زبان انگلیسی مربوط به روزنامه رویترز و دادههای زبان آلمانی برای روزنامه Frankfurter Rundschau است. همچنین بالاترین F-score برای این دیتاست حدود ۹۳.۵ درصده.
برای کار تو حوزه فارسی دو تا پیکره اصلی وجود داره که روی اونها کارهای زیادی انجام شده. پیکره اول پیکره آرمان هست. این پیکره شامل ۲۵۰۰۱۵ کلمه هست که حدود ۷۶۸۲ جمله میشه. اسامی خاصی که توی این پیکره اومدن ۶ نوع هستن. نوع اول سازمان هست که شامل وزاتخانه، سفارتخانه، تیم های فوتبال، انتشارات و ... میشه. نوع بعدی اسم مکان هست که شامل شهرها، روستاها، کشورها، رودخانه، مناظر طبیعی و ... ست. نوع سوم facility هستش که شامل مدارس، دانشگاهها، فرودگاهها، مراکز توسعه و تحقیق و ... ست. انواع دیگر هم محصولات و رویدادها هستن. در این پیکره یه نوع دیگه هم درنظر گرفته شده و اون MISC هست که مربوط به توکنهای اسامی خاصی ست که توی انواع بالا نمیگنجن. دادههای این پیکره از اخبار و روزنامههای رسمی گردآوری شده و به زبان فارسی معیار هست. بالاترین مدلی که بر روی این مجموعه آموزش داده شده مربوط به همون ParsBert جناب فراهانی هستش که f-score حدود ۹۹.۸۴ به دست آورده.
پیکره بعدی پیکره PEYMA هست که توسط آزمایشگاهی در دانشگاه تهران ایجاد شده. این پیکره با استناد به دو شیوهنامه استاندارد MUC , CONLL تهیه شده و با فرمت IOB برچسب خورده. این پیکره انواع اسامی خاص بیشتری رو پشتیبانی میکنه و البته همین هم باعث شده که برچسب زدن بر روی اون سخت تر بشه. انواعی که پشتیبانی میکنه شامل اسامی اشخاص، سازمان، مکان، ساختمانها، زمان، تاریخ، مبالغ مالی و درصدها ست. این مجموعه شامل ۳۰۲۵۳۰ توکن و ۷۱۴۵ جمله ست که از مجموعه دادههای روزنامههای رسمی و خبرنامهها برداشت شده در نتیجه این مجموعه هم به زبان فارسی معیار هست. بهترین مدلی که روی این مجموعه آموزش داده شده باز مربوط به مدل ParsBert هست با f-score حدود ۹۳.۴۰ هست. اگه قصد دارید دیتاست تهیه کنین حتما توصیه میشه که این دیتاست ها رو یه بررسی بکنین و شیوهنامههای استاندارد رو هم یه نگاه بندازین. حتما هم سعی کنین برچسبهاتون توی فرمت IOB باشه که با مدلهای دیگه راحت بتونین تستش بکنین.
و در آخر: معرفی مدلهای ناب!
تا اینجا مساله رو معرفی کردیم و هر یک از قسمتهاش رو توضیح دادیم. اما الان وقت عمله. توی این بخش یه چند تا مدل خوب معرفی میکنیم. اولین مدل مربوط به خود مقاله برت هست. این مدل به f-score حدود ۹۲.۸ رسیده اونم توی دیتای Co-NLL 2003 که دقت بالایی هم هست اما بعد از اون دو سه تا مدل خیلی خوب اومدن مثل این مدل و این مدل که به ترتیب f-score حدود ۹۴.۳ و ۹۳.۵ به دست آوردن. هر دوی این ها هم از ترنسفورمرها استفاده کردن. اما توی فارسی شاید بهترین مدلی که الان وجود داره مدل ParsBert هست. این مدل روی دادگان Arman به f-score هیولای ۹۹.۸۴ رسیده! و بر روی دیتای PEYMA هم به f-score حدود ۹۳.۴۰ دست پیدا کرده که در نوع خودش بینظیره. کاری که این دوستان انجام دادن در واقع pre-train کردن شبکه برت برای مجموعه زبان فارسی بوده و تونستن دقتهای بسیار خوبی به دست بیارن. اگه هم دوست دارین که از پیشرفتهای این حوزه و حتی حوزههای دیگه توی NLP با خبر بشین حتما با برادر Ruder مانوس باشید که به گمراهی کشیده نشید!
مطلبی دیگر از این انتشارات
بازسازی عکسهای قدیمی با هوشمصنوعی
مطلبی دیگر از این انتشارات
مدل MEND؛ ادیت سریع، فوری و انقلابی مدلهای زبانی
مطلبی دیگر از این انتشارات
هوش مصنوعی با فیدبکهای واقعی!