آموزش پردازش زبان طبیعی با اکوسیستم هاگینگ فیس ؛ توکن کننده ها (قسمت چهارم فصل دوم)
توکن کننده ها یکی از اجزای اصلی پایپلاین «پردازش زبان طبیعی» هستند. هدف آنها تبدیل متن به دادههای قابل پردازش است. مدلها فقط قادر به پردازش اعداد میباشند؛ اما توکن کننده ها ورودیهای متنی را به دادههای عددی تبدیل میکنند. جزئیات پایپلاین توکنسازی در این بخش بیان میشود. دادههایی که در فرایند پردازش زبان طبیعی مورد پردازش قرار میگیرند، متن خام هستند. به نمونهی زیر توجه کنید:
Jim Henson was a puppeteer
از آنجا که مدلها فقط اعداد را پردازش میکنند، باید روشی برای تبدیل متن خام به اعداد پیدا کنیم. توکن کننده ها برای این کار طراحی شدهاند و راههای بسیاری برای پیادهسازیِ این هدف وجود دارد. بنابراین، باید معنادارترین بازنمایی Representation را یافت؛ به تعبیری، این بازنمایی باید برای مدل منطقی باشد. در صورت امکان، باید کوچکترین بازنمایی را در نظر گرفت.
اینک، نوبت به بررسی چند نمونه الگوریتم توکنسازی Tokenization algorithms رسیده است. فصل جاری قصد دارد به برخی از پرسشهای علاقمندان درباره توکنسازی پاسخ دهد.
واژهمحور
Word-based اولین توکنکنندهای است به ذهن خطور میکند. این توکن کننده به راحتی نصب میشود، با چند قانون ساده به اجرا در میآید و معمولاً نتایج خوبی به دست میدهد. برای نمونه، هدف اصلی در تصویر زیر این است که متن خام به واژگان تبدیل شود و زمینه برای یافتنِ بازنمایی عددیِ هر یک از آنها فراهم گردد:
روشهای مختلفی برای تجزیه متن وجود دارد. برای مثال، میتوان از whitespace برای توکنسازی متن به واژگان استفاده کرد. تابع Split پایتون به حصول این هدف کمک میکند:
tokenized_text = "Jim Henson was a puppeteer".split() print(tokenized_text)
.
['Jim', 'Henson', 'was', 'a', 'puppeteer']
توکن کننده های واژه نیز انواع مختلفی دارند. همچنین، قوانین دیگری برای نشانهگذاری در آنها در نظر گرفته شده است. این نوع توکنکننده میتواند نقش موثری در ایجاد دایره واژگانی بزرگ داشته باشد؛ واژگان با تعداد کلِ توکنهای مستقل در پیکره تعریف میشوند. یک شناسه ID منحصربهفرد برای هر واژه اختصاص مییابد؛ این شناسه میتواند از صفر آغاز شود. مدل از این شناسهها برای شناسایی هر واژه استفاده میکند.
اگر میخواهید زبان خاصی را به طور کامل با توکن کننده واژهمحور پوشش دهید، باید شناسهی خاصی برای تکتک واژگان آن زبان اختصاص دهید. ماحصل این کار، تولید حجم عظیمی از توکن است. برای نمونه، بیش از ۵۰۰/۰۰۰ واژه در زبان انگلیسی وجود دارد. افزون بر این، واژههایی مثل «سگ» به روش بازنمایی متفاوتی در مقایسه با واژههایی نظیر «سگها» دارند.
مدل در ابتدا قادر به تشخیص این موضوع نیست که «سگ» و «سگها» شبیه یکدیگرند. بنابراین، مدل اینطور نتیجهگیری خواهد کرد که هیچ ارتباطی بین این دو واژه نیست. این اتفاق در سایر واژههای مشابه از قبیل «دو» و «دویدن» نیز رخ میدهد. مدل در ابتدا این دو واژه را به صورت جدا از هم پردازش خواهد کرد.
یک توکن اختصاصی برای نمایش واژههایی که در دایره واژِگانی وجود ندارد، مورد نیاز است. اینجا با توکن ناشناختهای سر و کار داریم که اغلب به صورت ”[UNK]” یا ”” نشان داده میشود. اگر حجم توکنهای تولید شده به دست این توکن کننده بیش از حد باشد، باید تدبیر مناسبی بیندیشید چرا که امکان بازنمایی معقولِ واژه فراهم نشده است.
بنابراین، بخشی از اطلاعات را در طول فرایند از دست میدهید. باید هنگام کار با واژگان به نحوی عمل کنید که توکن کننده بتواند کمترین واژه ممکن را در توکن نامعلوم توکنسازی کند. اگر خواستار کاهش میزان توکنهای نامعلوم هستید، بهتر است یک گام فراتر برداشته و از توکن کننده کاراکتر محور Character-based استفاده کنید.
کاراکتر محور
توکن کننده کاراکتر محور میتواند متن را به کاراکتر تبدیل کند (نَه واژگان). این اقدام دو مزیت عمده به همراه دارد:
- دایره واژگان بسیار کوچکتر است.
- تعداد توکنهای نامعلوم بسیار ناچیز است زیرا هر واژه میتواند از کاراکتر ساخته شده باشد.
حال، پرسشهایی درباره فضا و نشانهگذاری مطرح میشود:
این روش نیز کم و کاستیهای خاص خود را دارد. حال که بازنمایی بر پایهی کاراکتر است (نَه واژه)، میتوان گفت که سطح معناداری کمتری دارد. هر کاراکتر به تنهایی معنای چندانی ندارد، اما شرایط برای واژهها فرق میکند. البته هر زبان با خصوصیات خاص خودش شناخته میشود. برای مثال، هر کاراکتر زبان چینی میتواند حامل اطلاعات بیشتری از کاراکترهای زبان لاتین باشد.
نکته دیگری که باید در نظر گرفت این است که توکنهای بسیار زیادی در اختیار مدل قرار میگیرد تا عمل پردازش بر روی آنها انجام شود. هر واژه میتواند فقط یک توکن با یک توکن کننده واژهمحور باشد، اما اگر به کاراکتر تبدیل گردد، میتواند به آسانی به 10 توکن یا بیشتر نیز تبدیل شود. اگر میخواهید بهترین خروجی را به دست آورید، روش سوم را به شما توصیه میکنیم که دو روش پیشین را با یکدیگر ادغام میکند: این روش «توکنسازی زیرواژه Subword tokenization» نام دارد.
توکنسازی زیرواژه
الگوریتمهای توکنسازی زیرواژه بر پایه این اصل عمل میکنند که واژههای پرکاربرد نباید به زیرواژههای کوچکتر تبدیل شوند، اما نیاز است واژههای نادر به زیرواژههای معنادار تجزیه شوند. برای نمونه، ممکن است واژهی «annoyingly» در زمرهی واژههای کمیاب باشد و البته امکان تجزیه آن به «annoying» و «ly» وجود داشته باشد. این احتمال وجود دارد که هر دوی آنها در قالب زیرواژه به صورت وسیع به کار برده شوند. در عین حال، معنای «annoyingly» با معنای ترکیبی «annoying» و «ly» حفظ میشود.
در مثال زیر خواهید دید که الگوریتم توکنسازی زیرواژه چگونه زنجیرهی «Let’s do tokenization!» را توکنسازی میکند.
در مثال فوق، «tokenization» به «token» و «ization» تجزیه شد؛ این دو توکن دارای یک کلاس معنایی هستند، اما به لحاظ فضا کارآمد تلقی میشوند ( زیرا تنها دو توکن برای نمایش این واژه طولانی مورد نیاز است). بنابراین، میتوان حجم کار بیشتری را با دایره واژگان کوچک پوشش داد. این روش در زبانهای پیوندی از قبیل ترکی بسیار مفید واقع میشود چرا که میتوان واژههای پیچیده و و طولانیتری با کنار هم قرار دادنِ زیرواژهها ایجاد کرد. بیتردید، روشهای دیگری هم وجود دارد که میتوانید از آنها استفاده کنید. به تعدادی از این روشها در بخش زیر اشاره شده است:
- Byte-level BPE که در GPT-2 استفاده شده است.
- WordPiece که در بِرت استفاده شده است.
- SentencePiece یا Unigram که در چندین مدل چندزبانی استفاده شده است.
به نظر میرسد تا بدینجا اطلاعات خوبی درباره نحوه کارکرد توکن کننده ها به دست آورده باشید.
بارگذاری و ذخیرهسازی
بارگذاری و ذخیرهسازی توکن کننده ها به آسانی قابل انجام است و بر پایه دو روش from_pretrained و save_pretrained قرار دارد. این روشها میتوانند الگوریتمِ مورد استفادهی توکن کننده و واژگان آن را بارگذاری و ذخیره کنند. بارگذاریِ توکن کننده بِرت با چکپوینت یکسان با همان روش بارگذاری مدل صورت میگیرد؛ با این تفاوت که از کلاس BertTokenizer استفاده میشود:
from transformers import BertTokenizer tokenizer = BertTokenizer.from_pretrained("bert-base-cased")
شبیه به آنچه در TFAutoModel عمل شد، دستهی AutoTokenizer میتواند کلاس توکن کننده مناسب در کتابخانه را بر پایهی نام چکپوینت تهیه کند. همچنین، امکان استفاده مستقیم از آن با هر چکپوینتی وجود دارد.
from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("bert-base-cased")
اکنون، آنطور که در بخش پیشین ملاحظه کردید، میتوان از توکن کننده استفاده کرد:
tokenizer("Using a Transformer network is simple")
خروجی به شکل زیر میباشد.
{'input_ids': [101, 7993, 170, 11303, 1200, 2443, 1110, 3014, 102], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]}
ذخیرهسازی توکن کننده مثل ذخیرهسازی مدل است:
tokenizer.save_pretrained("directory_on_my_computer")
در فصل 3، بیشتر به token_type_ids خواهیم پرداخت. attention_mask نیز در بخشهای بعدی توضیح داده خواهد شد. بیایید در ابتدا نحوهی ایجاد input_ids را بررسی کنیم.
رمزگذاری
تبدیل متن به اعداد «رمزگذاری» نامیده میشود. رمزگذاری در فرایندی دومرحلهای انجام میشود: توکنسازی و تبدیل به شناسههای ورودی input IDs. آنطور که پیشتر ملاحظه کردید، در مرحله اول باید متن را به واژه (یا بخشی از واژگان، نمادهای نشانهگذاری و غیره) که معمولاً توکن نامیده میشوند تجزیه کرد . قوانین بسیاری بر این فرایند حاکم است؛ به همین منظور، باید توکن کننده را با استفاده از نام مدل توضیح داد تا از این موضوع اطمینان حاصل شود که قوانین یکسانی در هنگام پیشآموزشِ مدل استفاده شده است.
مرحله دوم به تبدیل آن توکنها به اعداد اختصاص دارد؛ بنابراین، میتوان تنسوری از دل آنها ایجاد کرد و در اختیار مدل قرار داد. برای انجام این کار، توکن کننده واژگان مخصوص خود را دارد که باید آن را دانلود کرد. باید از همان واژگانی استفاده کرد که در زمان پیشآموزش مدل استفاده شد. برای اینکه درک عمیقتری از این دو مرحله به دست آورید، باید آنها را به طور جداگانه بررسی کرد. توجه داشته باشید که ما از روشهایی استفاده خواهیم کرد که بخشهایی از فرایند توکنسازی را به طور جداگانه انجام میدهند. در عمل باید توکن کننده را به طور مستقیم در ورودیهایتان فراخوانی کنید.
توکنسازی
فرایند توکنسازی توسط ماژول ()tokenize انجام میگیرد:
from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained("bert-base-cased") sequence = "Using a Transformer network is simple" tokens = tokenizer.tokenize(sequence) print(tokens)
خروجیِ این روش فهرستی از رشته یا توکن است:
['Using', 'a', 'transform', '##er', 'network', 'is', 'simple']
این توکن کننده یک توکنکننده زیرواژه به حساب میآید. واژهها تا جایی تجزیه میشوند که توکن به دست آمده از تجریه را بتوان در دایره واژگانشان نشان داد. شرایط یکسانی نیز در خصوص transformer وجود دارد که به دو توکن transform و ##er تجزیه میشود.
از توکن به شناسه ورودی
روش توکن کننده convert_tokens_to_ids مسئولیت این تبدیل را به عهده دارد:
ids = tokenizer.convert_tokens_to_ids(tokens) print(ids)
.
[7993, 170, 11303, 1200, 2443, 1110, 3014]
این خروجیها به محض تبدیل به تنسور چارچوب مناسب میتوانند به عنوان ورودی مدل استفاده شوند. این فرایند در ابتدای فصل جاری توضیح داده شد.
خودتان امتحان کنید: دو مرحله پیشین (توکنسازی و تبدیل به شناسه ورودی) را در جملات ورودی استفاده شده در بخش 2 اجرا کنید. توجه داشته باشید که باید شناسه ورودی با آنچه در بخش پیشین به دست آمد، یکسان باشد.
رمزگشایی
رمزگشایی Decoding برعکس رمزگذاری عمل میکند. باید از استرینگ String به شاخصهای واژگانی Vocabulary indices دست یافت.
decoded_string = tokenizer.decode([7993, 170, 11303, 1200, 2443, 1110, 3014]) print(decoded_string)
.
‘Using a Transformer network is simple’
این موضوع را به خاطر داشته باشید که روش رمزگشایی نه تنها شاخصها را به توکن تبدیل میکند، بلکه توکنهایی را که بخشی از واژگان یکسان بودهاند را نیز با هم گروهبندی میکند تا جمله خوانایی تولید شود. این روش زمانی بیشترین فایده را به همراه خواهد داشت که از مدلها برای پیشبینی متن بعدی استفاده کنیم.
تا به این جای کار احتمالاً میدانید که توکن کننده چه عملیاتی را میتواند پشت سر بگذارد.
از طریق لینک زیر میتوانید به سایر قسمتهای این دوره آموزشی دسترسی داشته باشید:
[button href=”https://hooshio.com/%D8%B1%D8%B3%D8%A7%D9%86%D9%87-%D9%87%D8%A7/%D8%A2%D9%85%D9%88%D8%B2%D8%B4-%D9%BE%D8%B1%D8%AF%D8%A7%D8%B2%D8%B4-%D8%B2%D8%A8%D8%A7%D9%86-%D8%B7%D8%A8%DB%8C%D8%B9%DB%8C/” type=”btn-default” size=”btn-lg”]آموزش پردازش زبان طبیعی[/button]