آشنایی با شبکه های عصبی بازگشتی – RNN

برای درک بهتر این مبحث ابتدا برخی از مساله هایی که با کمک RNN ها قابل حل است را بررسی می کنیم:

  • Music generation : در این نوع از مسائل ما به شبکه یک نت تصادفی (random token) و یا بخشی از یک موسیقی را می دهیم و شبکه ادامه آن موسیقی را برای ما تولید می کند.
  • Sentiment classification : در این مسائل شبکه با داده های افرادی که درباره موارد مختلف هم نظر و هم امتیاز داده اند آموزش می بیند تا بتواند پس از آن با مشاهده نظر هر کاربر با هر تعداد کلمه ای امتیازی که احتمالا وی به آن محصول می داده را مشخص کند.
  • DNA sequence analysis : در این نوع از مسائل معمولا یک رشته DNA نویزی به شبکه داده می شود تا شبکه نویزهای آن را حذف کند و رشته DNA سالم را تحویل بدهد. (کلیک کنید.)
  • Machine Translation : با کمک شبکه های RNN می توان این کار را به صورت دقیق تری از ترجمه لغت به لغت انجام داد. در اصل شبکه پس از آموزش باید بتواند عبارت ها با تعداد کلمات متفاوت را از یک زبان به زبان دیگر ترجمه کند.
  • Video activity recognition : در این نوع از مسائل تلاش می شود تا شبکه طوری آموزش ببیند که پس از مشاهده چندین فریم از یک فیلم بتواند فعالیتی که در آن صحنه اتفاق می افتد تشخیص دهد.
  • Name entity recognition : در تمامی زبان ها بسیاری از کلمات می توانند اسم خاص باشند یا نباشند. (مثلا روحانی یا Harry Potter) در این مسائل هدف آموزش شبکه ای است که بتواند اسامی خاص را در جملات تشخیص دهد.
  • Speech Recognition : در این مساله برای آموزش شبکه ای تلاش می شود که بتواند صدا یا گفتار را به متن یا نوشتار تبدیل کند.

انواع RNN ها

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

  • شبکه های one to one : در این نوع از شبکه ها اندازه ورودی و خروجی ثابت است. تمامی شبکه هایی که قبلا در مورد آن ها خوانده بودیم در این دسته قرار می گیرند. در این دسته از شبکه ها اندازه ورودی لزوما ۱ نیست. به عنوان مثال شبکه می تواند تصویری با ابعاد ۴۸ * ۴۸ به عنوان ورودی دریافت کند اما اندازه ورودی ثابت است. (Vanilla Neural Network)
  • شبکه های one to many : در این نوع از شبکه ها اندازه ورودی ثابت است اما خروجی می تواند طول متغیر داشته باشد. (Music Generation – Image Captioning)
  • شبکه های many to one : در این نوع از شبکه ها اندازه ورودی ثابت نیست و می تواند طول متغیر داشته باشد اما اندازه خروجی مشخص است. (Sentiment Classification)
  • شبکه های many to many : این شبکه ها می توانند دو نوع باشند. در حالت اول (offline) شبکه پس از دریافت کل ورودی و پردازش آن خروجی را که طول متغیر دارد تولید می کند. (Machine Translation) در حالت دوم (online) شبکه هم زمان با دریافت هر ورودی پردازش می کند و خروجی جدید تحویل می دهد. (Video Classification)

به صورت کلی شبکه های RNN دارای یک state داخلی می باشند و در هر مرحله برای تصمیم گیری علاوه بر ورودی جدید به آن state هم نگاه می کند. این state ‌با هر ورودی جدید تغییر می کند. می توان یک RNN را به شکل زیر نمایش داد.

در شکل فوق، Tx بیانگر طول سری زمانی است. Xi بیانگر مقدار ورودی در بازه زمانی i ام و y^i برابر مقدار خروجی در بازه زمانی i ام می باشد. هم چنین ai نشان دهنده مقدار state در بازه زمانی i ام می باشد. مستطیل های سبز را نیز می توان مقدار وزن های شبکه در نظر گرفت. باید توجه کرد که وزن های شبکه در طول سری زمانی تغییر نمی کنند و تنها state تغییر می کند. برای مقداردهی اولیه state یا a0 می توان از یک وکتور با مقدار 0 یا و یک وکتور تصادفی استفاده کرد که روش اول مرسوم تر است. با توجه به شکل می توانیم فرمول زیر را بنویسم.

اگر h را همان state درنظر بگیریم، فرمول فوق بیان می کند که مقدار state جدید تابعی است بر اساس مقدار قبلی state و مقدار ورودی است که دارای پارامترهای W می باشد. باید توجه کرد که مقدار state با مقدار خروجی شبکه متفاوت است. مقدار خروجی شبکه در ساده ترین حالت از فرمولی مانند فرمول زیر محاسبه می شود.

رابطه فوق بیان می کند که مقدار خروجی هر لایه با توجه به مقدار state در آن لایه محاسبه می شود.

استفاده از RNN در مساله Name Entity Recognition

برای بررسی دقیق تر مثال زیر را در نظر بگیرید.

در این مثال هدف Name Entity Recognition است. در مثال فوق Xi کلمه i ام در سری زمانی و yi خروجی i ام شبکه است. Tx اندازه رشته دنباله ورودی و  اندازه رشته خروجی هستند که در مثال فوق هر دو برابر ۹ اند. برای این که بتوانیم کلمه ها را به شبکه بدهیم لازم است برای هر کدام representation ای داشته باشیم. برای این کار روش های مختلفی وجود دارد که ساده ترین حالت آن one hot encoding است و در این مثال از همین روش استفاده می شود. برای استفاده از one hot emcoding نیاز به یک دیکشنری داریم که one hot هر کلمه را به ما بدهد. معمولا این دیکشنری را نامحدود درنظر نمی گیریم بلکه به عنوان مثال به ۱۰۰۰ کلمه پرتکرار one hot خودشان را اختصاص می دهیم و کلمات دیگر را unknown یا <UNK> درنظر می گیریم.

برای درک بهتر موضوع باید به این سوال پاسخ دهیم که چرا برای حل این مساله از RNN ها استفاده می کنیم و از شبکه های عمیقی که قبلا آموخته ایم استفاده نمی کنیم. اگر بخواهیم این مساله را با استفاده از شبکه های عمیق غیر بازگشتی حل کنیم، باید شبکه ای با Tx نورون ورودی و Ty نورون خروجی داشته باشیم. ایراد مهم این شبکه این است که این شبکه تنها برای جملاتی با طول ثابت Tx پاسخگو خواهد بود. به بیان دیگر، اگر بخواهیم شبکه را برای مثال فوق آموزش دهیم، باید تعداد زیادی جمله با ۹ کلمه برای آموزش به آن بدهیم که البته شبکه احتمالا خواهد توانست نام های آن ها را به خوبی تشخیص دهد، اما نمی توانیم از همان شبکه برای تشخیص نام های جمله هایی که تعداد کلماتشان برابر ۹ نیست استفاده کنیم. هم چنین این شبکه نمی تواند در بخش های مختلف جمله از feature هایی که یک بار آموخته استفاده کند. حال ببینیم که چگونه می توانیم این مساله را با RNN ها حل کنیم.

با توجه به موارد ذکر شده متوجه می شویم که در یک شبکه RNN نیاز به سه سری وزن خواهیم داشت. از Wax برای تعیین مقدار state جدید را با توجه به مقدار ورودی، از Waa برای محاسبه مقدار state جدید با توجه به state مرحله قبل و از Wya برای محاسبه مقدار خروجی با توجه به مقدار state استفاده خواهد شد. در عملیات Forward propagation ابتدا a0 را یک وکتور صفر در نظر می گیریم. سپس خواهیم داشت با ضرب Wax در ورودی و Waa در مقدار a مقدار a1 یا state فعلی را محاسبه می کنیم. هم چنین خروجی مرحله اول با حاصل ضرب Wya در مقدار state فعلی محاسبه می شود. (بر روی تمامی عبارات فوق activation function نیز اعمال می شود که آن را با g1 و g2 نشان داده ایم. g1 معمولا tanh یا Relu است و g2 معمولا sigmoid یا softmax.)

برای نوشتن ساده تر رابطه  به جای دو ماتریس Waa و Wax از ماتریس Wa  استفاده می کنیم و آن را در وکتور حاصل از concat دو وکتور a t-1 و Xt ضرب می کنیم. در این صورت اگر a یک وکتور با طول ۱۰۰ و x وکتوری به طول ۱۰۰۰۰ باشد، Wa یک ماترس ۱۰۱۰۰ * ۱۰۰ خواهد بود که با ضرب شدن در در وکتور حاصل از concat دو وکتور a t-1 و Xt که دارای طول ۱۰۱۰۰ خواهد بود، ما را به یک وکتور ۱۰۰ تایی برای state جدید می رساند. در این صورت به جای فرمول های فوق، روابط زیر را خواهیم داشت:

در مرحله بعد باید نحوه پیاده سازی الگوریتم backpropagation را در RNN ها بررسی کنیم. برای این کار ابتدا لازم است کقدار خطای شبکه را محاسبه کنیم. برای محاسبه مقدار خطای کلی شبکه، باید تفاوت خروجی شبکه با label های حقیقی در هر سری زمانی محاسبه شود. این مقادیر در نهایت می توانند با هم جمع شوند و یا میانگین آن ها محاسبه شود تا به مقدار خطای کلی برسیم.

پس از محاسبه خطا این مقدار مانند شبکه های گذشته به عقب برمی گردد. این خطا در زمان نیز به عقب برمی گردد که به آن اصطلاحا backpropagation in time گفته می شود. نکته ای که باید به آن توجه کرد این است که مقدار گرادیان برای هر سری زمانی متفاوت خواهد بود که باعث می شود وزن ها به اعداد متفاوتی تبدیل شوند که این کار ممکن نیست، چون ما در واقع تنها یک ماتریس وزن داریم. برای همین به جای اعمال هر گرادیان در هر سری زمانی بر روی وزن، از میانگین یا مجموع کل گرادیان ها برای آپدیت وزن ها استفاده می شود.

 

استفاده از RNN ها برای ساخت Language Model

حال که با مفاهیم RNN ها آشنا شده ایم، می توانیم از قدرت این شبکه ها برای حل مسائل دیگر استفاده کنیم. به عنوان مثال یک سیستم speech recognition را در نظر بگیرید. دو کلمه pair و pear تلفظ کاملا یکسانی دارند. از این رو این سیستم speech recognition تنها باتوجه به بیان کاربر نمی تواند تصمیم بگیرد که از کدام یک از این دو باید استفاده کند.

The apple and pair salad || The apple and pear salad

برای حل این مشکل از language model ها استفاده می شود. درواقع برای حل این مشکل باید احتمال وقوع هر کدام از جملات محاسبه شود. این تابع احتمالا خروجی ای مانند زیر خواهد داشت:

P(The apple and pair salad) = 0.2 e-10

P(The apple and pear salad) = 0.3 e-5

و با توجه به این مقادیر، چون احتمال وقوع جمله دوم بیشتر است speech recognition این جمله را به عنوان جمله خروجی انتخاب خواهد کرد. برای آموزش این language model نیاز به یک Training set داریم که متشکل تعداد زیادی جمله به زبان مبدا است که هرچقدر این جمله ها متنوع تر باشند، مدل پس از آموزش به صورت general تری عمل خواهد کرد. به عنوان مثال برای آموزش یک مدل برای زبان فارسی، می توان داده ها را از مکالمات تلگرامی، کتاب ها، مقالات علمی، سایت های خبری و ویکی پدیا فارسی جمع آوری کرد.

در مرحله بعد تمامی جملات موجود در این دیتاست را tokenize می کنیم. Tokenizer ها جملات را به واحدهای تشکیل دهنده آن ها می شکنند که با توجه به روشی که اتخاذ می کنند، ممکن است دقت های متفاوتی داشته باشند. سپس به انتهای هر جمله <EOS> یا … را به انتهای جمله اضافه می کنیم. اگر کلمه ای در training set باشد اما در دیکشنری کلمات موجود نباشد (به علت تکرار کم) آن را با <UNK> جایگزین می کنیم.

برای آموزش شبکه لازم است که خروجی شبکه در هر سری زمانی به عنوان ورودی شبکه در سری زمانی بعدی داده شود. برای بررسی دقیق تر شکل زیر را درنظر بگیرید.

در شکل فوق اگر a0 و X1 بردارهای 0 باشند، y^1 کلمه ی آغازین جمله خواهد بود. (X1 را بردار 0 در نظر می گیریم چون می خواهیم مدل بتواند جمله ها را از ابتدا تولید کند. برای این کار لازم است در بازه زمانی اول، شبکه هیچ دیدی نداشته باشد.) در مرحله بعد کلمه اول جمله یا همان X2 را به شبکه می دهیم. با توجه به این که در حالت مطلوب خروجی شبکه در سری زمانی اول باید همان کلمه اول جمله باشد، پس می توان گفت که X2=y1 . در این صورت y^2 برابر است با احتمال این که هر کلمه پس از y1 در جمله بیاید. برای روشن تر شدن موضوع به شکل زیر دقت کنید. این شکل train شبکه برای جمله cats average 15 hours of sleep a day. را نشان می دهد.

پس از اتمام فرآیند training برای تست شبکه، احتمالات خروجی شبکه را در هر سری زمانی در نظر می گیریم و یک کلمه را به عنوان خروجی با توجه به این احتمالات انتخاب می کنیم. (خروجی شبکه به صورت رندم انتخاب می شود اما به کلمه ای که احتمال بالاتری در ^y دارد شانس بیشتری برای انتخاب می دهیم. سپس این خروجی را به عنوان ورودی در سری زمانی بعدی به شبکه می دهیم. هنگامی که به <EOS> برسیم فرآیند generate را متوقف می کنیم. همچنین برای بهتر بودن جملات می توانیم هنگامی که به خروجی های <UNK> رسیدیم، خروجی رندم را مجددا بررسی کنیم.

Language model ها را می توان به جای word level به صورت character level آموزش داد. در این صورت ورودی و خروجی شبکه در هر سری زمانی نه یک کلمه، بلکه یه کاراکتر خواهند بود. این کار فرآیند آموزش را سخت تر می کند چون برای یادگیری هر جمله به سری زمانی های خیلی بیشری نیاز خواهد داشت؛ اما دارای مزایایی نیز هست، مثل این که شبکه قادر به تولید تمامی کلمات خواهد بود و نیازی به <UNK> نداریم.

 

نمونه هایی از خروجی های نهایی RNN ها و visualize کردن state داخلی

شکل های زیر نمونه هایی از خروجی های نهایی شبکه های RNN هستند.

در مثال فوق شاید متن نهایی خیلی بامعنی به نظر نرسد، اما می بینیم که شبکه توانسته کلمات معنادار تولید کند، ” ها را پس از باز کردن ببندد و جمله ها را با نقطه تمام کند.

نمونه کد فوق نیز توسط RNN ها تولید شده که همان طور که می بینید کد از نظر ظاهری و syntax درست به نظر می رسد.

تصاویر زیر نیز visualize برخی نورون های state داخلی RNN ها هستند که برخی از آن ها عملکرد قابل توجهی دارند.

شکل فوق عملکرد یک نورون حساس به دبل کوتیشن را نشان می دهد که مقدار فعالیت نورون قبل و بعد از بسته شدن آن کاملا متفاوت است.

تصویر فوق نیز نشان دهنده عملکرد یک نورون حساس به طول خط است که با افزایش طول جمله سطح فعالیتش بالاتر رفته.