Skip to content

ورد۲وک کجا، چطور و چگونه – قسمت اول

در این نوشته میخوام راجع به ورد۲وک (Word2vec) و اینکه چی هست و چطوری کار میکنه و… صبحت کنم. راستش اول که خودم شروع کردم به خواندن ورد۲وک، این قدر مقاله اصلی برام گنگ و نامفهوم بود که فقط ۳۰‌٪‌ درصدشو متوجه شدم و خیلی برام سخت بود که درکش کنم و به جرات میتونم بگم تنها یه دید کلی ازش داشتم و چیزی که فهمیدم این بود که اگر King رو از فلان کم کنیم میشه بسیار… تقریبا هیچ منبع فارسی که توضیح داده باشه پیدا نکردم و هرچی هم مقالات خارجی رو میخوندم که بهتر درکش کنم بازم متوجه نمیشدم. تا اینکه آخر سر یک بلاگ پیدا کردم و بعد از کلی بالا پایین کردن و دستکاری کردن کدش تونستم بفهمم چی هست.
در این پست قصد دارم از مفاهیم ابتدایی ورد۲وک شروع کنم تا به خود الگوریتم برسم و اگر شد چندین مدل توسعه یافته از این الگوریتم هم اشاره میکنم و برخی از کارهایی که میتونید باهاش انجام بدید رو نام ببرم.

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

شبکه عصبی پیشرو (Feedforward neural network): شبکه‌های پیشرو یا به طور خاص شبکه چندلایه پرسپترون (Multi layer perceptron) به ما اجازه می‌دهند تا با یک سایز ثابت از داده‌های ورودی کار کنیم یا اگر سایز داده‌های ورودی یکسان نبود داده‌های ورودی به نحوی است که بتونیم ترتیب آنها را نادیده بگیریم. وقتی یک سری داده (متن، عکس یا…) به این شبکه‌ها داده میشه، شبکه یاد میگیره که چطور به صورت معنادار این داده‌ها را با یکدیگر ترکیب کند و از این روابط معنادار برای classification استفاده کند.
همانطور که از اسمش مشخصه، ایده شبکه عصبی از مغز انسان گرفته شده است و هر کدام از بخش‌های محاسباتی اون رو اصلاحا نورون میگن (نورون‌ها واحدهای محاسباتی هستند که ورودی‌های اسکالر (scalar)  دریافت و خروجی‌های اسکالر تحویل می‌دهند). هر ورودی که به نورون‌ها وارد میشود یک وزن (weight) به آن تخصیص داده میشه (یال‌هایی که نورون‌ها را به یکدیگر وصل میکند دارای یک وزن هستند). وظیفه یک نورون این است که به ازای هر ورودی، آن را در وزن مربوطه ضرب کند و مجموع این ضرب‌ها را در خروجی بفرستد (مانند شکل زیر).

یک شبکه عصبی در شکل بالا وظیفه Neuron این است که x1, x2, x3, x4 را رادر وزن‌شون ضرب کند (وزن آنها در شکل بالا مشخص نشده) و به خروجی بفرستد.

این نورون‌ها وقتی به یکدیگر متصل می‌شوند تشکیل یک شبکه را میدهند و شکل زیر در واقع یک شبکه عصبی پیشرو را نمایش می‌دهد. هر نورون با یک دایره مشخص شده که یک ورودی دریافت و یک خروجی تحویل میدهد و هر یال بین این نورون‌ها یک وزن دارند که میزان اهمیت آن نورون را مشخص می‌کنند. پایین‌ترین لایه ورودی ندارد چون خودشان ورودی شبکه هستند و بالاترین لایه‌ هم خروجی ندارند چون خودشان خروجی هستند و مابقی لایه به عنوان لایه‌های مخفی (Hidden layer) در نظر گرفته می‌شوند. علامت سیگموید که در داخل نورون‌ها مشخص شده است یک تابع غیرخطی است که برروی نتیجه نورون (قبل از اینکه به خروجی فرستاده شود) اعمال می‌شود و به این ترتیب خروجی هر نورون به تمامی نورون‌های مرحله بعد فرستاده میشود.

شبکه پیشرو چندلایه

مقادیر هر لایه در شبکه را می‌توان یک بردار در نظر گرفت. به عنوان مثال در شکل بالا، لایه ورودی (input layer) یک بردار چهاربعدی [x1, x2, x3, x4] و لایه بالاتر از آن یک بردار ۶بعدی و لایه بالاتر یک بردار ۵بعدی است. وقتی بخواهیم شبکه بالا رو در برنامه‌نویسی پیاده‌سازی کنیم به صورت خیلی ساده از ضرب ماتریس وزن‌ها و بردار ورودی میتوانیم لایه اول رو به دست بیاریم:

h = g(x*W)

که در اینجا x بردار ورودی و W ماتریس وزن‌ها و g یک تابع غیر خطی هست که بروی آنها اعمال می‌شود. پس به طور دقیق‌تر شبکه عصبی پیشرو یا چندلایه با پرسپترون یک بردار x به عنوان ورودی میگیرد و یک بردار y به عنوان خروجی میدهد ( فعلا b رو در فرمول پایین نادیده بگیرید):

فرمول MLP

خروجی شبکه (y) معمولا با مقدار صحیح یا اصلاحا ground truth مقایسه می‌شود و متناسب با اینکه چه پیش‌بینی انجام داده است وزن‌های ماتریس (Wها) آپدیت می‌شود. (مثلا در بحث طبقه‌بندی عکس، عکس گربه رو به عنوان ورودی میدهیم و خروجی آن را با مقدار درست مقایسه میکنیم و براساس حدسی که زدیم اندکی وزن‌های W رو تغییر میدهیم تا دفعات بعدی بتوانیم آن را بهتر حدس بزنیم)

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

 

تابع غیرخطی: در بالا گفتیم که g یک تابع غیرخطی است. برای اینکه مفهوم تابع غیرخطی رو درک کنیم بیایید با یک مثال جلو بریم. فرض کنید تابع XOR رو داریم که اگر این تابع را در دو بعد رسم کنیم به صورت زیر خواهد بود:

تابع Xor در دستگاه کارتزین

اگر بخواهیم یک تابع خطی f بنویسیم که دایره و ضربدر‌ها رو به دو قسمت تقسیم کند (یعنی یک خط بکشید که یک طرفش دایره‌ها باشند و یک طرفش ضربدرها)  امکان پذیر نیست. بخاطر همین میایم یک تابع غیرخطی مثل تابع زیر استفاده میکنیم که در این صورت به راحتی میتواند نقاط را متمایز کند.

non-linear function example

برای اینکه به شبکه عصبی قدرت تمایز بین نمونه‌های مختلف (مانند شکل بالا) بدیم مجبوریم از توابع غیرخطی استفاده کنیم (اصلاحا به این توابع غیرخطی، Activation function هم گفته می‌شود). توابع غیرخطی زیادی هست که در شبکه عصبی مورد استفاده می‌شوند از جمله:

  • Sigmoid
  • Hyperbolic tangent (tanh)

  • ReLU

که هر کدام از توابع بالا خواص خودشون رو دارند. اون چیزی که ما لازم داریم اسمش Softmax است که یک حالت کلی از Sigmoid محسوب میشود. Softmax این خاصیت را دارد که وقتی یک بردار به عنوان ورودی به آن داده می‌شود خروجی را به توزیع احتمالاتی تبدیل میکند:

Softmax example

 

اگر دقت کنید در مثال فوق نتیجه بعد از اعمال Softmax به توزیع احتمالاتی تبدیل شده است (جمع مقادیر ۱).

بازنمایی برداری (Vector representation): بازنمایی یک نوشته (سند، جمله، لغت و…) یکی از مهمترین کارهایی است که در پردازش متن صورت میگیرد. به طور کلی ما دو نوع بازنمایی داریم:

  1. بازنمایی محلی (Local representation): مثل One-hot-encoding یا n-gramها
  2. بازنمایی پیوسته (Continuous representation): مثل LSA یا Distributed representationها

در بازنمایی محلی معمولا یک بردار به طول N (سایز لغات) در نظر میگیرند و در صورتی که کلمه مورد نظر  وجود داشت ۱ و در غیر این صورت ۰ قرار میدهند. واضح است که در این صورت طول بردار برابر با N است.

one-hot encoding

در بازنمایی پیوسته طول بردار بسیار کوچکتر از  N در نظر گرفته میشود (معمولا بین ۱۰۰ تا ۱۰۰۰) و بر خلاف قبل تنها با مقادیر صحیح نمایش داده نمیشود. یک ویژگی مهم بازنمایی پیوسته این است که هر درایه از بردار (به نحوی) نشان‌دهنده یک ویژگی است از اون کلمه یا جمله یا… است. مثلا همانطور که در شکل زیر ملاحظه میفرمایید بردارهای woman و queen شبیه یکدیگر هستند چونکه ویژگی‌های feminity و age و masculinity شبیه هم دارند.

dense representation

 

بیشتر بدانید: در مراجع و مقالات گروهی از الگوریتم‌ها وجود دارند که زیر مجموعه Continous representationها قرار میگیرند (بردارهای با ابعاد پایین و دارای روابط معنایی با یکدیگر) که با معمولا شبکه عصبی آموزش داده میشوند (مثل word2vec و glove) که به این دسته‌ الگوریتم‌ها distributed representation یا embedding یا word vector گفته می‌شود.

 

مدل زبانی (Language model): اگر بخواهیم در یک جمله مدل زبانی رو تعریف کنیم میشه گفت:

Assign a probability to a sentence

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

P(علی به مغازه رفت) > P(مغازه رفت علی به)

و ما برای اینکه هریک از احتمالات فوق رو حساب کنیم باید احتمالهای زیر رو حساب کنیم:

P(علی)*P(علی|به)*P(علی به|مغازه)*P(علی به مغازه|رفت)

(واضح است که جمله علی به مغازه رفت محتمل‌تر است)

مدل زبانی در بحث شبکه‌های عصبی معمولا به این صورت به کار میرود که چندتا کلمه قبل رو میدهند مثلا:

علی به مغازه رفت و چند بسته زغال خرید. او با دوستش به …

و شبکه باید به نحوی آموزش داده بشه که بتواند جای خالی را حدس بزند هرچه قدرت درست حدس زدن بیشتر باشه نشان میدهد که شبکه ما بهتر آموزش داده شده است. یک شبکه معروف که توسط آقای Bengio اولین بار در مقاله A Neural Probabilistic Language Model معرفی شد به صورت زیر بود:

A Neural Probabilistic Language Model

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

ورودی این شبکه به صورت بردارهای one-hot است و ماتریس C وزن لایه اول است که قبلا با W نشان دادیم. که تعداد سطرهای ماتریس C به تعداد کلمات دیکشنری هستند (N).
در نتیجه لایه اول مثل یک Hash میمونه که index کلمه w_i تا w_i-n+1 رو میدهیم و سطرهای متناظر با این کلمه رو از ماتریس C واکشی میکنیم.

مرحله دوم خیلی واضح است بردار کلمات به دست آمده را به هم میچسبانیم (concat) و تابع tanh را برروی آن اعمال میکنیم و در نهایت هم این بردار به به Softmax میدهیم و احتمال رخداد کلمه بعدی برای ما حدس زده میشود.

بستگی به اینکه حدس ما درست باشد یا غلط طبق الگوریتم backpropagation وزن‌های شبکه (از جمله ماتریس C) به روزرسانی میشوند و این کار رو به ازای تمام جملاتی که در پیکره ما وجود دارد تکرار میکنیم.

آقای Bengio این قضیه را مطرح کرد و گفتند که بعد از اینکه آموزش شبکه تموم شد (شبکه رو با تمام جملات آموزش دادیم)  ماتریس C دارای خواص جالبی است و در این ماتریس کلماتی که از نظر معنایی شبیه هستند بردارهای شبیه هم خواهند داشت (شباهت الزاما Synonym بودن نیست و ممکن است کلماتی مثل روز و شب بردارهایی شبیه به هم داشته باشند).

 

مدل اصلی: خب حالا به سراغ الگوریتم word2vec میریم. در ساده‌ترین حالت میشه گفت الگوریتم ورد۲وک(word2vec)، یک شبکه عصبی پیشرو (feedforward) است که یک پیکره متنی (مثلا ویکی‌پدیا) به صورت جمله جمله شده میگیرد و بعد از چند ساعت آموزش (train) یک ماتریس در خروجی تحویل میدهد که هر سطر از این ماتریس نمایش‌دهنده یکی از کلماتی است که در پیکره آمده است. سطرهای این ماتریس از نظر معنایی باهم ارتباط دارند و کلماتی که شبیه هم هستند مقادیر شبیه هم دارند. به طور کلی الگوریتم رو میشه در شکل زیر خلاصه کرد:

همانطور که در بالا مشاهده می‌کنید اسناد ویکی‌پدیا رو به جملات تبدیل می‌کنیم و بعد هر جمله را به شبکه عصبی پیشرو می‌دهیم و در نهایت یک ماتریس در خروجی برای ما ذخیره می‌شود که در این ماتریس سطرهاش با یکدیگر روابط معنایی دارند (مثل cat و dog در بالا).

واضح است که این ماتریس به صورت distributed representation است و بُعد هر سطر از این ماتریس (d) نسبت به سایز لغات (N) بسیار کمتر است.

مدل Word2vec مدلی بسیار ساده محسوب میشه ولی متاسفانه در مقاله اصلی بعضی‌ها چیزها رو ذکر نکردن و بخاطر همین یک مقدار فهم اون رو دشوار کرده است. مدل Word2vec از یک کلک استفاده کرده که ممکن است در خیلی از تسک‌های یادگیری ماشین دیده باشید. در الگوریتم Word2vec یک شبکه عصبی آموزش میدهد که کاری که انجام میدهد Language modeling است ولی هدف ما Language Modeling نیست بلکه آن چیزی که برای ما اهمیت دارد وزن‌های لایه اول است (ماتریس C مقاله آقای Bengio). به عبارت دیگر ما یک Fake task تعریف میکنیم (که fake task ما Language modeling است) و شبکه عصبی‌ را روی آن آموزش میدهیم و در پایان خروجی شبکه برای ما اهمیت ندارد بلکه آن چیزی که برای ما اهیمت دارد وزن لایه مخفی (W) است.

اگر جملات بالا رو متوجه نشدید بیایید باهم مدل CBOW در ورد۲وک رو بررسی کنیم. در مقاله دو معماری به نام‌های CBOW و Skip-gram تعریف شده است که به صورت زیر هستند:

word2vec CBOW Skipgram

اگر معماری CBOW رو ملاحظه کنید، میبینید که خیلی شبیه کاری هست که آقای Bengio در Language Modeling انجام دادند با چند تفاوت:

  1. در کار آقای Bengio فقط به کلمات قبل نگاه میکردیم ولی در CBOW علاوه بر کلمات قبل به کلمات بعد هم نگاه میکنیم.
  2. در اینجا هم مثل کار آقای Bengio است و ورودی‌ها به صورت One-hot داده میشوند و ماتریس C وجود دارد ولی جای Concat از Sum استفاده میکند.
  3. لایه tanh که در کار آقای Bengio وجود داشت حذف شده است و همین کار می‌شود سرعت word2vec بسیار بهبود پیدا کند (آموزش مدل آقای Bengio در حدود ۲ماه طول میکشد ولی مدل word2vec در حدود چند ساعت).

خب اگر یک نگاهی به کار Language modeling آقای Bengio بندازید،‌ هدف ما این بود که احتمال کلمه w به شرط داشتن کلمات قبلش را حدس بزنیم و در CBOW هم دقیقا به دنبال همین کار هستیم و شبکه رو با همین ایده آموزش میدهیم. ولی برخلاف کار Bengio هدف ما Language modeling نیست بلکه پیدا کردن یک بازنمایی مناسب (بردار مناسب) برای هر لغت است پس در وقتی که روی تمام جملات Language model را آموزش دادیم، ماتریس C یا W که در لایه مخفی اول وجود داشت را به عنوان Word2vec در نظر میگریم. به عبارت دیگر W لایه اول همان Word2vec است.

شاید الان از نظر ریاضی براتون معقول نیست که چه اتفاقی افتاده است، پس بیایید با یک مثال بررسی کنیم.

معماری CBOW در Word2vec

خب فرض کنید، مانند شکل بالا دو کلمه‌ی قبل و دو کلمه‌ی بعد به ما داده شده است و قرار است حدس بزنیم کلمه وسط چی هست. کلمات را به صورت one-hot به شبکه میدهیم (فرض کنید طول بردار one-hot برابر ۱۰هزار است (N) و میخواهیم بردار نهایی کلمات ما ۳۰۰بعدی (d) باشند).

در شکل بالا W که وزن ما است (ماتریس C در مقاله Bengio) یک ماتریس ۱۰۰۰۰*۳۰۰ است. وقتی یک بردار one-hot با اندازه ۱۰۰۰۰*۱ رو در یک ماتریس ۱۰۰۰۰*۳۰۰ ضرب کنیم نتیجه‌ی آن یک بردار ۳۰۰*۱ میشود. همین کار رو برای ۳ کلمه دیگر انجام میدهیم و بردارهای آنها را با یکدیگر جمع (Sum) میکنیم که باز هم یک بردار ۳۰۰*۱ خواهد بود که یه نحوی بازنمایی کننده ۲کلمه قبل و ۲کلمه بعد است.

خب حالا با استفاده از این بردار ۳۰۰*۱ باید تشخیص بدیم که محتمل‌ترین کلمه که اون وسط قرار میگیرد چی هست. ساده‌ترین راه این است که بردار ۱*۳۰۰ رو در تمام کلمات‌مون ضرب کنیم (تمام کلمات رو ما میتوانیم در یک ماتریس ۳۰۰*۱۰۰۰۰ نشان دهیم که در بالا با d*N نمایش داده شده است).

خوب حالا بردار ۱*۳۰۰ رو در این ماتریس ۱۰۰۰۰*۳۰۰ ضرب کنیم و خروجی یک بردار ۱*۱۰۰۰۰ به ما میدهد که هر درایه از این بردار نشان‌ دهنده شباهت بردار ۳۰۰*۱ با سایر کلمات است. خب اگر بردار خروجی‌مان را که ۱*۱۰۰۰۰ بود به Softmax بدهیم و این خروجی به یک توزیع احتمالاتی تبدیل میشود. پس بعد از اعمال Softmax یک بردار ۱*۱۰۰۰۰ داریم که در درایه از اون یک احتمال هست. ما توی این بردار ۱*۱۰۰۰۰ جستجو میکنیم و کلمه‌ای که بیشترین احتمال رو داره به عنوان کلمه وسط در نظر میگیریم.

خب حالا همین کارو برای تمام کلمات انجام میدهیم و شبکه‌امون رو آموزش میدهیم:

پیکره برای آموزش word2vec

وقتی که تمام متن‌امون رو آموزش دادیم میاییم اون ماتریس W یا C رو در یک فایل ذخیره میکنیم که به این ماتریس ذخیره شده اصلاحا Word2vec گفته میشود :). پس همونطور که ملاحظه کردید خروجی شبکه عملا بدرد ما نمیخورد بلکه اون چیزی که برای ما در نهایت اهمیت دارد وزن‌های لایه اول یا همان W است.

همانطور که ملاحظه فرمودید چیزه سختی نیست و فقط مقاله اصلی اندکی گنگ توضیح داده است. در قسمت بعدی به سراغ الگوریتم Skip-gram میرویم و برخی از ترفند‌هایی که در این الگوریتم استفاده شده است رو باهم بررسی میکنیم.

 

 

منتشر شده درپردازش زبان طبیعییادگیری عمیق

اولین نفری باشید که نظر میدهید

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

All rights reserved by hadifar.net