فاز 19 · درس 30

پیاده‌سازی توکنایزر BPE از صفر با پایتون

نوع
ساختنی
زمان
حدود 90 دقیقه
زبان‌ها
Python
پیش‌نیاز
درس‌های فاز 04 و درس‌های ترنسفورمر در فاز 07
بلوک‌های چوبی حروف کوچک که در مجاورت هم قرار گرفته و با گیره‌های فلزی به نمادهای بزرگ‌تر متصل می‌شوند
فهرست مطالب
  1. اهداف یادگیری
  2. صورت مسئله
  3. پایپ‌لاین
  4. الفبای بایتی
  5. حلقه‌ی آموزش
  6. کُدگذاری متن تازه
  7. توکن‌های ویژه
  8. تضمین بازگشت‌پذیری (Round-trip)
  9. مواردی که در این درس بررسی نمی‌شوند
  10. راهنمای مطالعه‌ی کدها

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

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

در این درس، الگوریتم BPE را از پایه با پایتون پیاده‌سازی می‌کنید؛ از تفکیک اولیه و شمارش تکرار جفت‌ها گرفته تا ساخت جدول ادغام، مدیریت توکن‌های ویژه و بررسی ویژگی فشرده‌سازی متن. تمام بخش‌های کد روی پردازنده‌ی معمولی سیستم بدون نیاز به منابع پردازشی خاص قابل اجرا هستند.

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

اهداف یادگیری

  • آموزش واژگان کُدگذاری جفت‌بایت (Byte-Pair Encoding یا BPE) روی یک پیکره‌ی متنی خام از طریق ادغام مکرر پرتکرارترین جفت نمادهای مجاور.
  • پیاده‌سازی یک جدول ادغام قطعی (deterministic) و اعمال آن روی متن جدید برای تولید جریانی از شناسه‌های زیرکلمه (subword).
  • تبدیل رفت‌وبرگشتی بدون افت اطلاعات برای هر ورودی اختیاری UTF-8: تبدیل به شناسه و بازگشت دقیق به همان بایت‌ها.
  • رزرو و محافظت از توکن‌های ویژه (<|endoftext|> و <|pad|>) به‌گونه‌ای که در طول آموزش و رمزگشایی (Decode) دست‌نخورده باقی بمانند.
  • تحلیل این‌که چرا الفبای مبتنی بر بایت، بهترین سطح پایه برای یک توکنایزر چندمنظوره است.
حلقه ادغام BPEپویش · ادغام · تکرار
BPE MERGE LOOPByte-Pair Encoding starts from raw bytes and repeatedly finds the most frequent adjacent pair, merges it into one new symbol, and grows the vocabulary by one id. The scan box sweeps every neighboring pair; the winner collapses into a single token.بایت‌هاlowestmergeest→ شناسه جدیدواژگان + 1
الگوریتم کدگذاری جفت‌بایت (BPE) از بایت‌های خام آغاز می‌شود و به‌صورت مکرر پرتکرارترین جفت مجاور را می‌یابد، آن را به یک نماد جدید merge می‌کند و اندازه واژگان را با یک id افزایش می‌دهد. کادر پویش تمام جفت‌های همسایه را بررسی می‌کند؛ جفت برنده به یک توکن ادغام می‌شود.

صورت مسئله

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

رایج‌ترین خانواده‌ی توکنایزرهای زیرکلمه برای مدل‌های متنی عمومی، کدگذاری جفت‌بایت است. ایده‌ی کار بسیار ساده است: از یک الفبای مشخص شروع کنید. پرتکرارترین جفت نماد مجاور را در پیکره‌ی متنی آموزشی بیابید. آن دو را در قالب یک نماد جدید merge کنید. این کار را آن‌قدر تکرار کنید تا اندازه‌ی واژگان (vocabulary size) به مقدار هدف برسد. برای کُدگذاری متن جدید نیز از همان فهرست ادغام‌ها و با همان ترتیب استفاده می‌شود.

ما گونه‌ی بایت‌محور (byte-level) آن را پیاده خواهیم کرد. الفبای پایه در اینجا 256 بایت خام است، نه کدپوینت‌های یونیکد (Unicode code points). همین انتخاب باعث می‌شود توکنایزر بتواند هر ورودی UTF-8 را بدون نیاز به توکن ناشناخته (unknown token) پردازش کند.

پایپ‌لاین

خیربلهمتن خام پیکرهتفکیک بایت به ازای هر کلمهشمارش بسامد جفت‌های مجاورجدول جفت‌ها خالی است؟merge پرتکرارترین جفتافزودن merge به جدول ادغامافزایش واژگان با یک شناسهواژگان نهایی + ادغام‌هاکدگذاری متن جدیدرمزگشایی (Decode) شناسه به بایت

بخش آموزش و بخش استنتاج (inference) از یک جدول ادغام مشترک استفاده می‌کنند. این اشتراک‌گذاری در واقع قرارداد اصلی سیستم است. اگر ترتیب ادغام‌ها را هنگام استنتاج تغییر دهید، جریان متفاوتی از شناسه‌ها را رمزگشایی خواهید کرد.

الفبای بایتی

256 شناسه‌ی اول به بایت‌های خام 0x00 تا 0xFF اختصاص دارند. این ویژگی تضمین می‌کند که هر رشته‌ی ورودی، حتی پیش از انجام کوچک‌ترین ادغامی، درون واژگان قابل نمایش باشد. پس از بلوک بایت‌ها، بازه‌ی کوچکی را به توکن‌های ویژه اختصاص می‌دهیم. حلقه‌ی آموزش هرگز این شناسه‌ها را به‌عنوان هدف ادغام پیشنهاد نمی‌دهد، زیرا ما آن‌ها را کاملاً از جریان پیش‌توکنایزشده بیرون نگه می‌داریم.

پیش‌توکنایزر (pretokenizer) پیش از آنکه متن وارد حلقه‌ی آموزش شود، پیکره را بر اساس فاصله‌ها و علائم نگارشی تفکیک می‌کند. بدون این تفکیک، گام ادغام BPE ادغام‌هایی فراتر از مرز کلمات یاد می‌گیرد و واژگان با عبارت‌های رایج پر می‌شود. با اعمال این جداسازی، ادغام‌ها داخل چارچوب هر کلمه باقی می‌مانند و خروجی تعمیم‌پذیر می‌شود.

حلقه‌ی آموزش

در هر گام آموزش، حلقه سه کار انجام می‌دهد: تک‌تک کلمات پیکره‌ی متنی را پیمایش می‌کند و تعداد دفعات تکرار هر جفت نماد مجاور را می‌شمارد (با وزنی برابر با تعداد دفعات تکرار خود کلمه). سپس جفتی را که بالاترین شمارش را دارد انتخاب می‌کند. بعد از آن، تمام رخدادهای آن جفت را به یک نماد جدید با شناسه‌ای معادل اولین خانه‌ی خالی واژگان بازنویسی می‌کند. در پایان نیز این ادغام را ثبت می‌کند.

VocabMergeTablePairCountCorpusVocabMergeTablePairCountCorpusشمارش جفت‌های مجاور"انتخاب جفت برتر (a,b)"تخصیص شناسه جدید = a+b"بازنویسی هر (a,b) به شناسه جدید"شمارش مجدد برای گام بعد

هزینه‌ی محاسباتی هر گام با اندازه‌ی پیکره‌ی متنی (در قالب دنباله‌ای از نمادها) رابطه‌ی خطی دارد. برای یک پیکره‌ی یک میلیون کلمه‌ای و اندازه‌ی واژگان هدف 10000 شناسه، اجرای این حلقه ظرف چند ثانیه به پایان می‌رسد؛ زیرا دنباله‌ی نمادها با انجام هر ادغام کوتاه‌تر می‌شود.

کُدگذاری متن تازه

در زمان استنتاج، نیازی به شمارش جفت‌ها نیست؛ بلکه جدول ادغام دقیقاً با همان ترتیبی که یاد گرفته شده اعمال می‌شود. انکودر برای کلمه‌ای تازه، کار را با تفکیک بایت‌ها آغاز می‌کند. سپس دنباله‌ی فعلی را بررسی می‌کند تا ادغامی با کمترین رتبه (Rank) — یعنی اولین ادغامی که قابل اعمال است — را بیابد. آن ادغام را اجرا می‌کند و دوباره دنباله را می‌کاود. این حلقه زمانی پایان می‌یابد که هیچ‌یک از ادغام‌های جدول روی دنباله‌ی فعلی قابل اعمال نباشد.

ترتیب‌بندی بر اساس رتبه، همان ویژگی مهمی است که کُدگذاری را قطعی می‌کند و رفتار آن را روی ورودی یکسان، دقیقاً با رفتار زمان آموزش هماهنگ می‌سازد. ادغامی که زودتر یاد گرفته شده در بالای جدول می‌نشیند و اولویت اعمال بالاتری دارد. اگر دو ادغام مختلف در یک موقعیت قابل اجرا باشند، گزینه‌ای که رتبه‌ی پایین‌تری دارد برنده می‌شود.

توکن‌های ویژه

توکن‌های ویژه شناسه‌هایی هستند که جریان بایت‌ها هرگز نمی‌تواند آن‌ها را تولید کند. این شناسه‌ها را به‌صورت دستی رزرو می‌کنیم. برای این درس، دو مورد کفایت می‌کند:

  • <|endoftext|> در طول پیش‌آموزش، مرز بین اسناد را مشخص می‌کند. این توکن به مدل می‌گوید: «سند جدیدی از اینجا آغاز شده است؛ اجازه نده زمینه‌ی سند قبلی به اینجا نفوذ کند.»
  • <|pad|> دنباله‌های کوتاه را پر می‌کند تا بتوان دسته‌ها (batches) را در قالب یک تنسور مستطیلی چید. ماسکِ تابع زیان (loss) این توکن را در طول آموزش پنهان می‌کند.

انکودر پرچمی (flag) می‌گیرد که مشخص می‌کند آیا توکن‌های ویژه در ورودی مجاز هستند یا نه. اگر پرچم غیرفعال باشد، رشته‌های متنی <|endoftext|> و <|pad|> درست مانند بایت‌های معمولیِ سازنده‌شان توکنایز می‌شوند. اما اگر پرچم فعال باشد، این عبارت‌های متنی به شناسه‌های رزروشده‌شان نگاشت می‌شوند و مشمول هیچ‌گونه ادغامی نخواهند شد.

تضمین بازگشت‌پذیری (Round-trip)

عملیات کُدگذاری و سپس رمزگشایی باید دقیقاً بایت‌های ورودی اولیه را بازگرداند. دیکودر، بسط بایتی هر شناسه را به ترتیب کنار هم قرار می‌دهد. از آنجا که هر شناسه یا یک بایت خام است یا حاصل اتصال دو شناسه‌ی قبلی، این بسط بازگشتی همیشه به بایت‌های خام ختم می‌شود. در نهایت، مرحله‌ی رمزگشایی همان رشته‌ی UTF-8 حاصل از آن بایت‌ها را بازمی‌گرداند.

مجموعه تست‌های این درس، این ویژگی را روی یک جمله‌ی نادیده، روی جمله‌ای شامل اموجی‌های یونیکد، و روی جمله‌ای حاوی توکن مستقیم <|endoftext|> بررسی و تأیید می‌کنند.

مواردی که در این درس بررسی نمی‌شوند

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

همچنین فرایند شمارش جفت‌ها به‌صورت موازی انجام نمی‌شود. یک حلقه‌ی پایتونی روی پیکره‌ای با چند هزار کلمه در زمانی بسیار کمتر از یک ثانیه اجرا می‌شود. برای پیکره‌های متنی بزرگ‌تر، راهکار منطقی شمارش موازی جفت‌ها در هر کلمه و تجمیع نهایی (reduce) نتایج است.

راهنمای مطالعه‌ی کدها

فایل main.py چهار جزء اصلی را تعریف می‌کند: BPETokenizer که واژگان، جدول ادغام و جدول توکن‌های ویژه را در خود نگه می‌دارد؛ train که همان حلقه‌ی آموزش است؛ encode که مسیر استنتاج را شکل می‌دهد؛ و decode که بایت‌ها را به هم پیوند می‌زند. دموی انتهای فایل، یک توکنایزر کوچک را روی پیکره‌ی پیش‌فرض آموزش می‌دهد، جمله‌ای خارج از داده‌ها را کُدگذاری و شناسه‌های آن را دوباره رمزگشایی می‌کند و خروجی هر دو را نمایش می‌دهد. تست‌های موجود در code/tests/test_bpe.py ویژگی رفت‌وبرگشتی، رزرو توکن‌های ویژه و ترتیب اعمال ادغام‌ها را به شکل دقیق تثبیت می‌کنند.

دمو را اجرا کنید. سپس اندازه‌ی واژگان هدف را در دمو از 300 به 600 تغییر دهید و ببینید طول دنباله‌ی کُدگذاری‌شده برای جمله‌ی نمونه چقدر کمتر می‌شود. این منحنی، همان منحنی فشرده‌سازی BPE است.

خودآزمایی

به سؤال‌ها جواب بدهید تا ببینید چقدر از درس در ذهنتان مانده است.

  1. پیش از درسچرا یک توکنایزر BPE در سطح بایت با یک الفبای 256 نمادی شروع به کار می‌کند؟
  2. حین درسهر گام آموزش BPE چه کاری انجام می‌دهد؟
  3. حین درسهنگام انکود کردن متن جدید، ادغام‌ها با چه ترتیبی اعمال می‌شوند؟
  4. حین درسرفتار انکودر روی رشته توکن ویژه وقتی `allow_special` مقدار `False` دارد چیست؟
  5. پس از درسچرا دیکودر هیچ‌گاه به شناسه توکن ناشناخته (unknown-token) نیاز ندارد؟
  6. پس از درسبا افزایش اندازه واژگان هدف، چه اتفاقی برای طول کدگذاری‌شده یک جمله ثابت می‌افتد؟

سؤالات متداول

چرا در توکنایزر مدرن BPE از سطح بایت استفاده می‌شود؟

شروع از 256 بایت خام (0x00 تا 0xFF) تضمین می‌کند که هر کاراکتر و رشته‌ی متنی در استاندارد UTF-8 حتی پیش از انجام هرگونه ادغامی قابل نمایش باشد و نیازی به توکن ناشناخته نباشد.

ویژگی بازگشت‌پذیری (Round-trip) در توکنایزر چیست؟

این ویژگی تضمین می‌کند که اگر یک رشته‌ی ورودی کُدگذاری و سپس رمزگشایی شود، بایت‌های خروجی دقیقاً و بدون کم‌وکاست برابر با بایت‌های رشته‌ی اولیه باشند.

توکن‌های ویژه مثل endoftext چگونه مدیریت می‌شوند تا دچار ادغام نشوند؟

این شناسه‌ها به‌صورت دستی خارج از بازه‌ی بایت‌ها رزرو می‌شوند و پیش‌توکنایزر آن‌ها را از جریان داده‌های آموزشی حذف می‌کند تا حلقه‌ی آموزش هیچ ادغامی روی آن‌ها اعمال نکند.

ما رو توی گوگل بیشتر ببین

با افزودن نکست ادمین به «منابع ترجیحی» گوگل، درس‌ها و مطالب ما را در بخش اخبار برتر جستجو بیشتر می‌بینید.