آموزش پردازش زبان طبیعی در پایتورچ
در این پست به آموزش پردازش زبان طبیعی در پایتورچ خواهیم پرداخت. پردازش زبان طبیعی (NLP) شاخهای از هوش مصنوعی است که به کامپیوترها در درک و تفسیر زبان انسان کمک می کند. در این پست میخواهیم پردازش زبان طبیعی را به صورت پروژهمحور در پایتورچ آموزش دهیم. پروژهای که انجام خواهیم داد یک پروژه دسته بندی متن است. یک مدل NLP آموزش خواهیم داد که خبرها را دسته بندی کند. با هوسم همراه باشید …
کتابخانه torchtext در pytorch
اگر پروژهای در زمینه پردازش زبان طبیعی انجام داده باشید، میدانید که مراحل پیشپردازش چقدر خستهکننده است. قبل از شروع آموزش مدل باید:
- دادهها را از دیسک بخوانید.
- جملات را Tokenize کنید.
- یک نگاشت از هر کلمه به یک عدد صحیح و منحصربهفرد ایجاد کنید.
- متن را به لیستی از اعداد صحیح تبدیل کنید.
- دادهها را به هر شکلی که فریمورک یادگیری عمیق شما نیاز دارد load کنید.
- متن را pad کنید تا تمامی دنبالهها به یک اندازه باشند، تا بتوانید آنها را به صورت یک batch پردازش کنید.
Torchtext در پایتورچ، کتابخانهای است که پروسههای بالا را بسیار سادهتر میکند. اگرچه این کتابخانه نسبتا جدید است. اما عملکرد آسان آن، خصوصاً در Batching و Loading، آن را به کتابخانهای ارزشمند تبدیل کردهاست. در این پست ما میخواهیم با استفاده از torchtext و ماژول text_classification اخبار را دستهبندی کنیم. قبل از شروع آموزش دو مفهوم N-grams و Vocabulary را توضیح خواهیم داد. بعد میرویم سراغ شروع آموزش …
ngrams در پردازش زبان طبیعی
در این بخش از پس « آموزش پردازش زبان طبیعی در پایتورچ » میخواهیم با Ngrams آشنا شویم. همه ما میدانیم که معنای هر کلمه در جمله، وابسته به کلمات قبل و بعد از آن است. مثلا دو عبارت «.We need to book our ticket soon» و «.We need to read this book soon» را در نظر بگیرید. کلمه book در جمله اول به معنای رزرو کردن و یک فعل است. کلمه book در جمله دوم به معنای کتاب و یک اسم است. در مورد کلمه Ice Cream هم همین شرایط وجود دارد. ما میخواهیم مدل هم علاوه بر Ice و Cream ، کلمه Ice Cream را هم یک کلمه بداند.
انسانها از بدو تولد میتوانند با توجه به نشانهها در چنین جملههایی معنای آن جمله را درک کنند. اما ماشینها برای پیدا کردن این نشانهها باید به کلمات قبل و بعد از کلمه هدف نگاه کند. مثلا در دو جملهای که گفتیم، ماشین باید به کلمات قبل و بعد از book نگاه کند. به این ترکیبات N-grams گفته میشود.
برای تشکیل N-grams، درواقع یک پنجرهای به طول N روی جمله میلغزد. این پنجره ترکیبات Nتایی از کلمات تشکیل میدهد. مثلا Bi-grams به معنای ترکیبات دوتایی و Tri-gram به معنای ترکیبات سهتایی از کلمات هستند. مثلا برای عبارت «READ THIS BOOK SOON» اگر بخواهیم Bi-grams را بنویسیم:
Tri-grams برای همان جمله به صورت زیر است:
Vocabulary در NLP
گفته شد که قدم اول در پیشپردازش دادهها در NLP تبدیل جملات به مجموعهای از token-ها است. به این کار tokenization گفته میشود. به مجموعهای از tokenهای معتبر و یکتا که در corpus وجود دارند، vocabulary گفته میشود. در NLP ، همه چیز بر اساس vocabulary انجام میشود.
فراخوانی کتابخانه torchtext
تا اینجای « آموزش پردازش زبان طبیعی در پایتورچ »، مقدمات را گفتیم. در این بخش کدنویسی را شروع میکنیم. ابتدا باید کتابخانههای ضروری برای پروژه « پردازش زبان طبیعی با پایتورچ » را نصب و فراخوانی کنیم. ما به کتابخانههای torch، torchtext و ماژول text_classification از torchtext نیار داریم. همگی این کتابخانهها به صورت پیشفرض در کولب نصب هستند. پس فقط باید فراخوانی شوند:
import torch import torchtext from torchtext.datasets import text_classification
ممکن است در فراخوانی torchtext به خطا بربخوریم. در این صورت ورژن 0.4 از این کتابخانه را با کد زیر نصب کنید و سپس کد بالا را اجرا کنید:
! pip install torchtext==0.4
دریافت پایگاه داده AGnews از torchtext
در بخش قبل از « آموزش پردازش زبان طبیعی در پایتورچ » کتابخانههای لازم را فراخوانی کردیم. برای آموزش یک مدل NLP، نیاز به یک پایگاه داده داریم. پایگاه دادهای که برای این کار در نظر گرفتهایم، پایگاه داده AGnews است. ابتدا یک پوشه به نام data میسازیم و دادهها را در این پوشه میریزیم:
if not os.path.isdir('/content/data'): os.mkdir('/content/data')
کد بالا میگوید اگر پوشه data وجود دارد که هیچ. اما اگر وجود ندارد یک پوشه به نام data بساز. حالا میتوانیم دادهها را به کمک text-classification خوانده و در پوشه data ذخیره کنیم:
NGRAMS = 2 train_dataset, test_dataset = text_classification.DATASETS['AG_NEWS']( root='/content/data', ngrams=NGRAMS, vocab=None)
مشاهده میکنید که مسیر ذخیره دادهها با عبارت root مشخص شده است. مقدار ngrams را برابر با 2 درنظر گرفتیم. در نهایت مقدار vocab را نیز None گذاشتیم. با این کار میگوییم که ما vocabulary نداریم و بر اساس داده آموزش برایمان یک vocabulary بساز. با اجرای کد بالا، دیتاست AGnews در دو متغیر train_dataset و test_dataset ذخیره خواهد شد.
- نمایش داده های آموزش و تست
بیایید یک نمونه از این متغیرها را نمایش دهیم:
print('train sample: ', next(iter(train_dataset))) print('test sample: ', next(iter(test_dataset)))
با اجرای کد بالا داریم:
train sample: (2, tensor([ 572, 564, 2, 2326, 49106, 150, 88, 3, 1143, 14, 32, 15, 32, 16, 443749, 4, 572, 499, 17, 10, 741769, 7, 468770, 4, 52, 7019, 1050, 442, 2, 14341, 673, 141447, 326092, 55044, 7887, 411, 9870, 628642, 43, 44, 144, 145, 299709, 443750, 51274, 703, 14312, 23, 1111134, 741770, 411508, 468771, 3779, 86384, 135944, 371666, 4052])) test sample: (2, tensor([ 1169, 12, 111, 187, 2115, 40, 233, 2575, 7136, 535, 22, 13915, 262719, 315, 90, 52, 17, 8110, 17, 40, 233, 20, 31666, 3797, 436, 260, 25589, 2, 34651, 0, 0, 0, 0, 38519, 0, 61363, 432460, 10954, 62400, 0, 0, 3701, 1976, 319981, 485989, 728324, 94121, 38519, 1416, 0, 0, 159317, 0, 774722, 0]))
مشاهده میکنید که هر نمونه در دادههای آموزش و تست، یک tuple است که دو مقدار دارد. مقدار اول برچسب است. مقدار دوم یک تنسور است که یکسری عدد در خودش دارد. این تنسور درواقع همان جمله ماست. هر عددی که در این تنسور وجود دارد، نماینده یک کلمه است. این بلایی است که tokenization بر سر یک جمله میآورد.
-
اضافه کردن داده اعتبارسنجی در پایتورچ با استفاده از random_split
در این بخش میخواهیم تعدادی از دادهها را برای اعتبارسنجی جدا کنیم. برای این کار از دستور random_split در پایتورچ استفاده میکنیم. برای اینکه 5 درصد از دادههای آموزش را برای اعتبارسنجی و مابقی را به آموزش اختصاص دهیم مینویسیم:
from torch.utils.data.dataset import random_split train_len = int(len(train_dataset) * 0.95) sub_train_, sub_valid_ = random_split(train_dataset, [train_len, len(train_dataset) - train_len])
- batch کردن داده ها در پایتورچ
برای آموزش یک شبکه نیاز به یک batch از دادهها داریم. در بخش قبل دیدیم که دادهها بچ نیستند. پس نیاز داریم که دادهها را بچ کنیم. در پایتورچ از DataLoader استفاده میکنیم. انداز بچ (batch size) را برابر با 16 در نظر میگیریم:
from torch.utils.data import DataLoader BATCH_SIZE = 16 data = DataLoader(sub_train_, batch_size=BATCH_SIZE)
خب حالا میخواهیم یک بچ را بخوانیم:
next(iter(data))
in default_collate(batch) 53 storage = elem.storage()._new_shared(numel) 54 out = elem.new(storage) 56 elif elem_type.__module__ == 'numpy' and elem_type.__name__ != 'str_' \ ---> 55 return torch.stack(batch, 0, out=out) 57 and elem_type.__name__ != 'string_': RuntimeError: stack expects each tensor to be equal size, but got [53] at entry 0 and [87] at entry 2
با اجرای کد بالا با خطا مواجه میشویم. مشاهده میکنید که هنگام چسباندن دادهها برای ساختن یک بچ مشکلی وجود دارد. خطا میگوید که ابعاد دادهها یکسان نیستند. اما دادههای ما که همان متنها هستند، طول یکسانی ندارند. لزومی ندارد طول خبر «الف» با طول خبر «ب» برابر باشد.
- استفاده از collate_fn() در پایتورچ
راه حل این مشکل چیست؟ بهتر نیست همه token-های دادههای یک بچ را در یک تنسور قرار دهیم. یعنی خبرها را به هم بچسبانیم. یعنی مطابق شکل زیر این کار را انجام دهیم:
برای این کار از callback استفاده میکنیم. ابتدا یک تابع به نام generate_batch مینویسیم و در آن جملات را به هم میچسبانیم. ابتدای هر متن را مشخص میکنیم. همچنین برچسبها را نیز از دادهها جدا میکنیم.
def generate_batch(batch): #1 label = torch.tensor([entry[0] for entry in batch]) #2 text = [entry[1] for entry in batch] #3 offsets = [0] + [len(entry) for entry in text] #4 offsets = torch.tensor(offsets[:-1]).cumsum(dim=0) #5 text = torch.cat(text) #6 return text, offsets, label #7
توضیح کد بالا:
- در خظ 1، تابعی به نام generate_batch ساختیم.
- در خط 2 در یک batch حلقه میزنیم و اولین المان که برچسب است را برمیداریم و در متغیر label ذخیره میکنیم.
- در خط 3 نیز در batch حلقه میزنیم و دومین المان که متن خبر است را برمیداریم و در متغیر text ذخیره میکنیم.
- در خط 4 یک حلقه در متنهای موجود در بچ میزنیم. در هر مرحله طول هر متن را گرفته و در لیستی به نام offsets ذخیره میکنیم. اولین المان این لیست را صفر در نظر میگیریم.
- در خط 5 جمع انباشته (cumulative sum) را برای لیست offsets محاسبه میکنیم. به این صورت مشخص میشود که بعد از چسباندن textها به هم، ابتدای هر جمله کجاست.
- در خط 6 تمامی textها را در یک بچ به هم میچسبانیم.
- در خط 7 هم متغیرهای text، offsets و label را برمیگردانیم.
خب حالا با استفاده از collate_fn، تابعی که نوشتیم را وارد میکنیم:
from torch.utils.data import DataLoader BATCH_SIZE = 16 train_data = DataLoader(sub_train_, batch_size=BATCH_SIZE, shuffle=True, collate_fn=generate_batch) valid_data = DataLoader(sub_valid_, batch_size=BATCH_SIZE, collate_fn=generate_batch)
این کار را برای دادههای تست و اعتبارسنجی انجام دادیم. دادههای بچ شده را به صورت زیر نمایش میدهیم:
next(iter(train_data))
(tensor([ 101, 1880, 2663, ..., 177, 162, 508]), tensor([ 0, 73, 146, 245, 304, 373, 426, 475, 554, 619, 708, 801, 880, 953, 996, 1097]), tensor([2, 1, 2, 0, 0, 1, 3, 0, 1, 2, 0, 3, 2, 3, 0, 2]))
خروجی یک tuple است که سه المان دارد. المان اول، 16 تا text است که به هم چسبانده شده است. المان دوم آفستها هستند که نشان میدهند اندیس شروع هرکدام از این 16 تا text کجاست. المان سوم هم برچسبهای مربوط به این 16 تا text است.
حالا با دستور torch.device یک device تعریف میکنیم که اگر GPU وجود داشت با GPU محاسبات انجام شود. در غیر اینصورت محاسبات روی CPU انجام شود:
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
تعریف مدل NLP در پایتورچ
پس از اینکه دادهها را خواندیم، نوبت به تعریف مدل میرسد. در این بخش از پست « آموزش پردازش زبان طبیعی در پایتورچ » میخواهیم با کمک پایتورچ یک مدل NLP تعریف کنیم. این مدل باید بتواند یک خبر را گرفته و بگوید این خبر، چه نوع خبری است. چهار حالت برای خروجی وجود دارد: بینالمللی ، ورزشی ، بیزینس و علمی/تکنولوژی.
- تعریف کلاس TextSentiment
ما در این پست قصد آموزش دادن کدنویسی پایتورچ را نداریم. اما به صورت خلاصه مدلی که نوشتیم را کمی توضیح خواهیم داد. اگر با پایتورچ آشنایی ندارید اینجا و اینجا نگاهی بیندازید. تمپلیت ساختن مدل در پایتورچ به صورت زیر است:
import torch.nn as nn class ClassName(nn.Module): def __init__(self, ...): super().__init__() def forward(self, ...): return
کلاسی با نام TextSentiment به شکل زیر تعریف میکنیم:
import torch.nn as nn #1 class TextSentiment(nn.Module): #2 def __init__(self, vocab_size, embed_dim, num_class): #3 super().__init__() #4 self.embedding = nn.EmbeddingBag(vocab_size, embed_dim, sparse=True) #5 self.fc = nn.Linear(embed_dim, num_class) #6 self.init_weights() #7 def init_weights(self): #8 initrange = 0.5 #9 self.embedding.weight.data.uniform_(-initrange, initrange) #10 self.fc.weight.data.uniform_(-initrange, initrange) #11 self.fc.bias.data.zero_() #12 def forward(self, text, offsets): #13 embedded = self.embedding(text, offsets) #14 return self.fc(embedded) #15
- در خط اول torch.nn را فراخوانی میکنیم.
- در خط دوم یک کلاس به نام TextSentiment تعریف میکنیم.
- در خط سوم تابع __init__() را تعریف میکنیم. ورودیهای این تابع، طول vocabulary یا vocab_size، طول embeddingها یا embed_dim و تعداد کلاسها یا num_class است.
- خط چهارم تابع init مربوط به nn.Module فراخوانی میشود.
- در خط پنجم یک لایه EmbeddingBag تعریف میکنیم. در ورودی لایه EmbeddingBag باید ابتدا ابعاد دیکشنری امبدینگها مشخص شود. سپس ابعاد هر embedding مشخص میشوند. این دستور به صورت پیشفرض میانگین امبدینگها را محاسبه میکند. در این مسئله خروجی این دستور 16 آرایه (به تعداد batch-size) به طول embed_dim خواهد بود.
- در خط ششم یک لایه Linear تعریف میکنیم. ابعاد ورودی این لایه embed_dim و خروجی آن به تعداد کلاسها (num_class) خواهد بود.
- در خط هفتم وزنهای دو لایهای که تعریف کردیم با تابع init_weights، مقداردهی اولیه شدند. این تابع در خط بعد تعریف شده است.
- در خط هشتم تابعی به نام init_weights تعریف میکنیم.
- در خط نهم مقدار initrange را 0.5 درنظر میگیریم. این متغیر در دو خط بعدی برای تعیین محدوده مقادیر وزنها استفاده میشوند.
- در خط دهم مقدار وزنهای لایه Embedding بین 0.5- و 0.5 به صورت uniform مقداردهی اولیه میشوند.
- در خط یازدهم نیز وزنهای لایه Linear بین 0.5- و 0.5 به صورت uniform مقداردهی اولیه میشوند.
- در خط دوازدهم مقدار اولیه بایاسها صفر در نظر گرفته میشود.
- در خط سیزدهم تابع forward شبکه را مینویسیم. ورودی این تابع text و offsets هستند.
- در خط چهاردهم لایه Embedding با ورودی text و offsets تعریف میشود.
- در خط پانزدهم نتیجه لایه embedding را به لایه Linear میدهیم و نتیجه را برمیگردانیم.
خب حالا باید از کدی که نوشتیم استفاده کرده و مدل را تعریف کنیم. در بخش بعدی این کار انجام شده است …
- تعریف مدل NLP از روی کلاس TextSentiment
قبل از تعریف مدل ابتدا باید یکسری مقادیر را تعریف کنیم. متغیر اول، ابعاد vocabulary است که باید تعیین شود. با استفاده از get_vocab() میتوانیم این کار را انجام دهیم. متغیر دوم اندازه Embedding است که آن را برابر با 32 در نظر میگیریم. در نهایت تعداد کلاسها باید مشخص شوند که میتوانیم از get_labels() استفاده کنیم. همه کارهای که گفتیم به شکل زیر انجام میشود:
VOCAB_SIZE = len(train_dataset.get_vocab()) EMBED_DIM = 32 NUN_CLASS = len(train_dataset.get_labels())
حالا میتوانیم مدل را به شکل زیر تعریف کنیم:
model = TextSentiment(VOCAB_SIZE, EMBED_DIM, NUN_CLASS).to(device)
در کد بالا ما مدل را به device فرستادیم. که اگر GPU داشتیم، محاسباتش روی GPU انجام شود که سرعت بالاتری دارد. تا اینجا دادهها را ابتدا دانلود و سپس بچ کردیم. مدل را نیز تعریف کردیم.
- تست مدل NLP
تا اینجا مدل را تعریف کردیم. در این بخش میخواهیم ببینیم که آیا مدلی که تعریف کردیم درست کار میکند یا نه. برای این کار یک نمونه از دادهها را جدا کرده و به مدل میدهیم. سپس خروجی مدل به این نمونه را نمایش میدهیم. با این کار میتوانیم تا حدودی اطمینان حاصل کنیم که مدل درست کار میکند یا نه. برای این کار مینویسیم:
text_t, offset_t, labels_t = next(iter(data)) print(model(text_t, offset_t))
با اجرای کد بالا خواهیم داشت:
tensor([[ 0.1758, 0.0003, 0.0977, -0.1066], [ 0.0553, 0.0239, -0.0171, 0.0324], [-0.0364, 0.0130, 0.0166, 0.1018],
[ 0.1437, 0.0205, 0.0403, -0.0071],
[-0.0570, -0.1275, -0.0338, -0.0177],
[ 0.0854, -0.0022, -0.0149, -0.0552],
[ 0.0116, 0.0164, -0.0027, 0.0458],
[-0.1004, -0.0185, 0.0517, 0.0468],
[ 0.0668, -0.0052, -0.0108, -0.0837],
[ 0.1754, 0.0033, -0.0594, -0.0775],
[-0.0191, -0.0020, -0.0711, 0.0083],
[ 0.0785, 0.0209, 0.0260, -0.1053],
[-0.0330, -0.0632, 0.0434, 0.0004],
[-0.0481, 0.0209, -0.0615, 0.0394],
[ 0.1330, -0.0336, -0.1395, -0.1051]], grad_fn=<AddmmBackward>) [ 0.0235, -0.0394, -0.0709, 0.0129],
خوشبختانه کد بدون خطا اجرا شد. مشاهده میکنید که خروجی تنسوری با 16 سطر و 4 ستون است. که هرکدام از 16 سطر مربوط به یک نمونه در batch است. هر ستون هم متناظر با یکی از 4 کلاس است. حالا باید مدل را آموزش دهیم. بخش بعدی به آموزش مدل اختصاص دارد. برویم سراغ بخش بعدی …
آموزش مدل NLP
این بخش اختصاص به آموزش مدل دارد. قبل از آموزش مدل نیاز است که مدل به اصطلاح کانفیگ شود. یعنی تابع اتلاف، الگوریتم بهینهسازی و پارامترهای مربوط به آن مشخص شود.
- کانفیگ مدل در پایتورچ
برای این پروژه تابع اتلاف CrossEntropy و بهینه ساز SGD انتخاب شده است. مقدار نرخ یادگیری 0.4 در نظر گرفته شده است. همچنین نرخ یادگیری را متغیر در نظر میگیریم:
N_EPOCHS = 5 criterion = torch.nn.CrossEntropyLoss().to(device) optimizer = torch.optim.SGD(model.parameters(), lr=4.0) scheduler = torch.optim.lr_scheduler.StepLR(optimizer, 1, gamma=0.9)
تعداد epochها را 5 در نظر گرفتیم. همچنین با کمک lr_scheduler.StepLR، نرخ یادگیری در هر epoch به اندازه gamma کم خواهد شد.
- آموزش مدل در پایتورچ
در بخشهای قبل مدل را تعریف و کانفیگ کردیم. حالا دیگر میتوانیم از مدلی که ساختیم، استفاده کنیم:
import time #1 for epoch in range(N_EPOCHS): #2 start_time = time.time() #3 train_loss, train_acc = train_func(sub_train_) #4 valid_loss, valid_acc = test(sub_valid_) #5 secs = int(time.time() - start_time) #6 mins = secs / 60 #7 secs = secs % 60 #8 print('Epoch: %d' %(epoch + 1), " | time in %d minutes, %d seconds" %(mins, secs)) #9 print(f'\tLoss: {train_loss:.4f}(train)\t|\tAcc: {train_acc * 100:.1f}%(train)') #10 print(f'\tLoss: {valid_loss:.4f}(valid)\t|\tAcc: {valid_acc * 100:.1f}%(valid)') #11
توضیحات خط به خط این کد به شکل زیر است:
- در خط 1 کتابخانه time فراخوانی شده است. از این کتابخانه برای محاسبه زمان انجام محاسبات استفاده خواهیم کرد.
- در خط 2 یک حلقه for مینویسیم که به تعداد epoch2ها تکرار داشته باشد.
- در خط 3 زمان شروع epoch را با استفاده از time.time() دریافت کرده و در متغیر start_time ذخیره میکنیم.
- در خط 4 با استفاده از تابع train_func و دادههای sub_train_، شبکه آموزش داده شده است. سپس اتلاف و accuracy آموزش ذخیره شدهاند.
- در خط 5 با استفاده از مدل آموزش دیده، نتیجه را برای دادههای اعتبارسنجی پیشبینی میکنیم. برای این دادهها نیز اتلاف و accuracy را ذخیره میکنیم.
- در خط 6 تا 8 زمان را دوباره دریافت کرده و از start_time کم میکنیم. با این کار زمان اجرای محاسبات در یک epoch محاسبه میشود. سپس محاسبه میکنیم که چند دقیقه و چند ثانیه طول کشیده است.
- در خط 9 تا 11 مقادیر اتلاف، accuracy و زمان اجرای برنامه را نمایش میدهیم.
اما دو تابع train_func و test چه هستند؟ در ادامه هرکدام از این توابع تشریح میشوند.
-
تابع آموزش در پایتورچ
def train_func(train_data): #1 # Train the model train_loss = 0 #2 train_acc = 0 #3 for i, (text, offsets, cls) in enumerate(data): #4 optimizer.zero_grad() #5 text, offsets, cls = text.to(device), offsets.to(device), cls.to(device)#6 output = model(text, offsets) #7 loss = criterion(output, cls) #8 train_loss += (loss.item() / len(cls)) #9 loss.backward() #10 optimizer.step() #11 train_acc += ((output.argmax(1) == cls).sum().item() / len(cls)) #12 # Adjust the learning rate scheduler.step() #13 return train_loss / len(train_data), train_acc / len(train_data) #14
- خط 1 تا 3 نکته خاصی وجود ندارد و همه چیز واضح است.
- در خط 4 در دادههای آموزش حلقه میزنیم.
- در خط 5 گرادیانها را صفر میکنیم.
- در خط 6 متغیرها را به GPU میبریم تا با سرعت بیشتری محاسباتشان انجام شود.
- در خط 7، پیشبینی شبکه را دریافت کرده و در متغیر output ذخیره میکنیم.
- در خط 8 اتلاف محاسبه میشود.
- در خط 9 متغیر train_loss وجود دارد. در این متغیر اتلاف همه batch-ها با هم جمع میشود. چرا که برای محاسبه اتلاف در یک epoch باید میانگین اتلاف همه batch-ها محاسبه شود.
- در خط 10، گرادیانها محاسبه میشوند.
- در خط 11، الگوریتم بهینهسازی پارامترها را با توجه به گرادیانهای محاسبه شده آپدیت میشوند.
- در خط 12 مقادیر accuracy برای هر batch در متغیر train_acc ذخیره میشود. مشاهده میکنید که کلاسی که بزرگترین مقدار را دارد به عنوان پیشبینی شبکه انتخاب شده است. سپس پیشبینی شبکه با مقدار برچسب مقایسه شده است.
- بعد از پایان epoch، در خط 13 مقدار نرخ یادگیری تغییر میکند.
- در نهایت هم مقدار میانگین اتلاف و accuracy برگردانده میشود.
در بخش بعدی تابع ارزیابی آورده شده است.
-
تابع ارزیابی در پایتورچ
def test(data_): #1 loss = 0 #2 acc = 0 #3 for text, offsets, cls in data: #4 text, offsets, cls = text.to(device), offsets.to(device), cls.to(device)#5 with torch.no_grad(): #6 output = model(text, offsets) #7 loss = criterion(output, cls) #8 loss += (loss.item() / len(cls)) #9 acc += ((output.argmax(1) == cls).sum().item() / len(cls)) #10 return loss / len(data_), acc / len(data_) #11
- خط 1 تا 5 مشابه تابع آموزش است، پس توضیح داده نمیشوند.
- در خط 6 با دستور no_grad به بهینهساز میگوییم که به وزنها دست نزن و آنها را آپدیت نکن. در فاز تست همیشه این کار را انجام میدهیم.
- خط 7 تا 11 نیز مشابه با تایع train هستند.
در پایان، این تابع مقدار اتلاف و accuracy را برمیگرداند.
-
نتیجه آموزش مدل NLP
با اجرای کدهای مربوط به آموزش شبکه خواهیم داشت:
Epoch: 1 | time in 0 minutes, 24 seconds Loss: 0.0263(train) | Acc: 84.6%(train) Epoch: 2 | time in 0 minutes, 24 seconds Loss: 0.0008(valid) | Acc: 90.0%(valid) Loss: 0.0119(train) | Acc: 93.7%(train) Epoch: 3 | time in 0 minutes, 24 seconds Loss: 0.0006(valid) | Acc: 90.9%(valid) Loss: 0.0069(train) | Acc: 96.4%(train) Loss: 0.0003(valid) | Acc: 90.7%(valid) Loss: 0.0002(valid) | Acc: 91.0%(valid) Epoch: 4 | time in 0 minutes, 24 seconds Loss: 0.0039(train) | Acc: 98.1%(train) Epoch: 5 | time in 0 minutes, 25 seconds Loss: 0.0003(valid) | Acc: 91.2%(valid) Loss: 0.0022(train) | Acc: 99.0%(train)
مشاهده میکنید که accuracy برای دادههای آموزش برابر با 99 درصد و برای دادههای اعتبارسنجی برابر با 91.2 درصد به دست آمد. اما برای ما نتیجه شبکه برای دادههای ارزیابی مهم است. در بخش بعدی نتیجه شبکه برای دادههای ارزیابی را بررسی خواهیم کرد.
نتایج شبکه برای دادههای ارزیابی
گفتیم برای ما پیشبینی شبکه بر دادههای ارزیابی مهم است. برای اینکه ببینیم شبکه چه عملکردی بر دادههای ارزیابی دارد، ابتدا باید یک DataLoader برای دادههای ارزیابی بسازیم. سپس دادهها را به شبکه بدهیم و نتیجه را ببینیم:
test_data = DataLoader(test_dataset, batch_size=BATCH_SIZE, collate_fn=generate_batch) print('Checking the results of test dataset...') test_loss, test_acc = test(test_data) print(f'\tLoss: {test_loss:.4f}(test)\t|\tAcc: {test_acc * 100:.1f}%(test)')
با اجرای کد بالا داریم:
Checking the results of test dataset…
Loss: 0.0023(test) | Acc: 89.4%(test)
مشاهده میکنید که مقدار accuracy برای دادههای ارزیابی 89.4 درصد به دست آمده است. خب نتیجه بدی نیست. اما اگر بخواهیم یک خبر جدید که در AGnews نیست را به این شبکه بدهیم تا پیشبینی کند چه کنیم؟ در بخش بعدی به این سوال پاسخ داده شده است …
طبقه بندی خبر با شبکه NLP
تا اینجا ما یک شبکه NLP را با استفاده از دادههای AGnews آموزش دادیم. این شبکه خبرها را به 4 دسته تقسیم میکند: بینالمللی ، ورزشی ، بیزینس و علمی/تکنولوژی.
ag_news_label = {0 : "World", 1 : "Sports", 2 : "Business", 3 : "Sci/Tec"}
حالا ما میخواهیم یک خبر که خارج از دادههای AGnews است را با این شبکه، طبقه بندی کنیم. برای اینکه پیشبینی شبکه روی یک خبر جدید را ببینیم ابتدا باید متن خبر را tokenize کنیم. برای tokenization در پایتورچ از دستور get_tokenizer استفاده میکنیم. سپس این token-ها را به شبکه میدهیم تا پیشبینی کند. همچنین برای ngrams-ها در پایتورچ باید از ngrams_iterator استفاده کنیم.
from torchtext.data.utils import ngrams_iterator #1 from torchtext.data.utils import get_tokenizer #2 def predict(text, model, vocab, ngrams): #3 tokenizer = get_tokenizer("basic_english") #4 with torch.no_grad(): #5 text = torch.tensor([vocab[token] for token in ngrams_iterator(tokenizer(text), ngrams)]) #6 output = model(text, torch.tensor([0])) #7 return output.argmax(1).item() #8
- در خط 1 و 2، دو دستور ngrams_iterator و get_tokenizer را فراخوانی میکنیم.
- در خط 3 تابع predict را تعریف کردیم. ورودیهای این تابع، متن خبر، vocabulary و ngrams هستند.
- در خط 4 با کمک get_tokenizer یک tokenizer میسازیم. زبانی که انتخاب شده، basic_english است.
- در خط 5 محاسبه گرادیان را غیرفعال کردیم. چون در فاز آموزش نیستیم و میخواهیم پیشبینی شبکه را ببینیم.
- در خط 6 متن را با کمک tokenizer و ngrams_iterator تبدیل به مجموعهای از token-ها میکنیم. این token-ها را ابتدا در یک لیست ذخیره کرده و سپس تبدیل به تنسور میکنیم.
- در خط 7، token-هایی که در خط قبلی ساختیم را به شبکه میدهیم. مقدار offset را هم صفر در نظر میگیریم. زیرا ما میخواهیم یک خبر را طبقه بندی کنیم و ابتدای این خبر اندیس صفر خواهد داشت.
- در خط 8 هم کلاسی که بیشترین مقدار را دارد انتخاب خواهد شد.
خبری که انتخاب کردیم به صورت زیر است:
ex_text_str = "MEMPHIS, Tenn. – Four days ago, Jon Rahm was \ enduring the season’s worst weather conditions on Sunday at The \ Open on his way to a closing 75 at Royal Portrush, which \ considering the wind and the rain was a respectable showing. \ Thursday’s first round at the WGC-FedEx St. Jude Invitational \ was another story. With temperatures in the mid-80s and hardly any \ wind, the Spaniard was 13 strokes better in a flawless round. \ Thanks to his best putting performance on the PGA Tour, Rahm \ finished with an 8-under 62 for a three-stroke lead, which \ was even more impressive considering he’d never played the \ front nine at TPC Southwind."
حالا از تابعی که نوشتیم استفاده میکنیم:
vocab = train_dataset.get_vocab() #1 model = model.to("cpu") #2 model_prediction = predict(ex_text_str, model, vocab, 2) #3
- خط 1 برای استخراج vocabulary است.
- خط 2 مدل را به cpu میبرد. برای یک داده نیازی به GPU نیست!
- در خط 3 نیز پیشبینی را با کمک تابع predict انجام میشود.
خروجی این کد، کلاسی را مشخص میکند که شبکه پیشبینی کرده است. برای نمایش بهترِ نتیجه مینویسیم:
print("This is a %s news" %ag_news_label[model_prediction])
با اجرای کد بالا داریم:
This is a Sports news
پیشبینی شبکه این است که خبر موردنظر ما یک خبر ورزشی است. با نگاهی به متن خبر میبینیم که پیشبینی شبکه درست است.
در پست « آموزش پردازش زبان طبیعی در پایتورچ » یک مدل NLP آموزش دادیم که میتواند خبر را دسته بندی کند. نظرات و سوالات خود را پایین 👇 برایمان کامنت کنید. حتما سوالات شما پاسخ داده خواهد شد.
مطالب زیر را حتما مطالعه کنید
مدل nanoGPT
شگفتانگیزترین ایده هوش مصنوعی از نظر Andrej Karpathy
شبکه ویژن ترنسفورمر
شبکه اتوانکدر
شبکه عصبی mlp
شبکه ترنسفورمر
6 دیدگاه
به گفتگوی ما بپیوندید و دیدگاه خود را با ما در میان بگذارید.
سلام. خدا قوت
مثل همیشه عالییییییی
ببخشید امکانش هست منبعی رو معرفی کنید که مراحل پیش پردازش و روشهای embedding , …. رو در فرایند متن کاوی کامل توضیح داده باشه.
ممنون از تیم هوسم…
سلام،
سپاس🙏🌹
کتابهای:
Natural Language Processing in Action
Natural Language Processing with PyTorch
Speech and Language Processing
و دورههای:
nlp اندرو انگ
دوره NLP دانشگاه Stanford
امیدوارم که مفید باشه براتون.
سلام خیلی ممنون عالی بود
من یه فایل متنی با فرمت txt دارم میخوام اونو به کمک اسپارک پردازش کنم توی گوگل کولب و تعدادکلماتش رو بشمارم و اینکه ده کلمه ای که بیشترین تکرار رو دارند تشخیص بدم و تعداد تکرارشون رو پیدا کنم
ممنون میشم راهنماییم کنید که چیکار باید کنم
سلام،
سپاس🌹
لینک زیر رو چک کنید، احتمالا بهتون کمک میکنه:
لینک
دمتون گرم عالی بود. لطفن از nlp بیشتر پروژه و پست بذارین.
ممنون،
بله حتما 🌹