آموزش مدلهای زبانی روی پیکرههای متنی عظیم (Corpus) آغاز میشود؛ جایی که دانلود صدها گیگابایت داده از سرورهای دوردست اولین چالش زیرساختی است. ناپایداری ارتباط شبکه، قطع اتصال روی درصدهای بالا و خطاهای غیرمنتظره دانلود معمولاً باعث شکست فرآیند میشود. یک فراخوانی ساده با ابزارهای دانلودی معمولی بدون پشتیبانی درست از Range و اعتبارسنجی بایتها، در عمل یا به دانلود دوبارهی چندصد گیگابایت داده ختم میشود یا به خرابی پنهانی که تا مرحلهی توکنسازی خود را نشان نمیدهد.
علاوه بر پایداری در دانلود، مدیریت حافظهی رم و پالایش داده نیز ضرورت دارد. باز کردن شاردها (Shards) و نگهداری فایلهای چند گیگابایتی روی رم ناممکن است؛ بنابراین خواندن و باز کردن فشردهسازی باید بهشکل استریمینگ (Streaming) و تکخطی انجام شود. همچنین وجود دادههای تقریباً تکراری (Near-duplicates) بهینگی یادگیری را کاهش داده و منابع پردازشی گرانبها را هدر میدهد.
در این درس یاد میگیرید چطور با استفاده از کتابخانهی استاندارد پایتون، سازوکار ادامهی دانلود (Resume) ضدخرابی مبتنی بر Range و چکپوینتهای هش پیاده کنید. سپس شاردها را با Zstandard بیدرنگ فشردهزدایی کرده و با تکنیکهای MinHash و LSH دادههای تکراری را پیش از توکنسازی فیلتر نمایید.
آموزش مدل زبانی خیلی پیش از نخستین گذر رو به جلو (forward pass) آغاز میشود. پیکرهی متنی (corpus) باید روی دیسک بنشیند، از حالت فشرده خارج شود، دادههای تکراریاش حذف گردد و آدرسپذیر باشد؛ آن هم در شرایطی که سازوکار ادامهی دانلود (resume) از قبل آماده شده باشد، نه بعد از آنکه اتصال شبکه روی 4 درصد قطع شد. در این درس یک دانلودکنندهی استریمینگ میسازیم که شاردها (shard)ی فشرده را دریافت میکند، آنها را در لحظه با Zstandard از حالت فشرده خارج میسازد، نمونههای تقریباً تکراری (near-duplicates) را با MinHash بههمراه درهمسازی حساس به مکان (LSH) انگشتنگاری و شناسایی میکند و در نهایت مانیفستی از شاردها مینویسد که ادامهی پایپلاین بتواند با خیال راحت به آن اعتماد کند.
اهداف یادگیری
- استریم شاردهای راهدور با
urllibو خروج از حالت فشرده باzstandardبدون نگه داشتن کل فایل در حافظه. - ادامهی دانلودهای نیمهکاره با ارسال درخواستهای
Rangeدر پروتکل HTTP بر مبنای آفست بایتیِ اعتبارسنجیشده. - ساخت یک امضای MinHash برای هر سند و دستهبندی آن با LSH تا اسنادِ تقریباً تکراری برخورد (collision) پیدا کنند.
- صدور مانیفست شاردها شامل هش محتوا، حجم بر حسب بایت، تعداد اسناد و وضعیت حذف تکرارها (dedup).
صورت مسئله
نخستین باری که مدلی را روی پیکرهای 200 گیگابایتی آموزش میدهید، اتصال شبکه روی 41 درصد قطع میشود و اسکریپت با خطای urllib بیرون میپرد. دفعهی دوم روی 78 درصد قطع میشود. تا رسیدن به 99 درصد هم احتمالاً حلقهی دانلود را سه بار از نو نوشتهاید. دو مشکلی که باید از همان دقیقهی اول برایشان چارهاندیشی کنید، ادامهی دانلودهای نیمهکاره و حذف اسناد تکراری است. هر دو راهحلهای شناختهشدهای دارند؛ اما معمولاً نادیده گرفته میشوند، چون پایپلاین کارش را با یک فراخوانی تکخطیِ requests.get شروع کرده و بهمرور دستوپاگیر و دردسرساز شده است.
ادامهی دانلود، مسئلهای در سطح پروتکل HTTP است. سرور باید از Range پشتیبانی کند، کلاینت باید آفست تاییدشده را بر مبنای سوابق ذخیرهشده روی دیسک رهگیری کند و این آفست تاییدشده باید حتی پس از بستهشدن ناگهانی فرایند هم معتبر بماند. اگر آفست و فایل حتی یک بایت با هم مغایرت داشته باشند، ادامهی دانلود دادههای نامعتبر روی دیسک مینویسد و پیکرهی متنی دچار خرابی خاموشی میشود که خودش را فقط هنگام توکنسازی نشان خواهد داد.
حذف تکرارها نیز مسئلهای از جنس امضاست. حذف تکرار با هش دقیق (Exact-hash) مواردِ تقریباً تکراری را از دست میدهد: یک مقالهی ویکیپدیا با سه پانویس متنی متفاوت ظاهر میشود، یک فایل کد با سرآیند متفاوتی از مجوز انتشار میآید، یا یک پست وبلاگ با پارامترهای رهگیری متفاوتی روی هر پیوند تکرار میشود. ترکیب MinHash و LSH این موارد را با هزینهای زیرخطی (sub-linear) به دام میاندازد. این هزینه برابر است با یک امضا برای هر سند و یک جستوجوی سطل (bucket) بهازای هر امضا.
مفاهیم پایه
استریمینگ با urllib
تابع urllib.request.urlopen از کتابخانهی استاندارد پایتون، شیئی شبیه به فایل (file-like) برمیگرداند. اگر آن را داخل zstandard.ZstdDecompressor().stream_reader قرار دهید، بایتها بدون اینکه کل شارد فشرده یا بازشده روی رم ذخیره شوند، از شبکه مستقیماً وارد موتور فشردهزدایی و سپس تکرارکنندهی اسناد (document iterator) میشوند. تنها هزینهی رم در این حالت، بافر خط، امضای MinHash برای سندِ جاری و نمایهی LSH است.
ادامهی دانلود با Range
دانلودکننده برای هر شارد دو فایل روی دیسک مینویسد: خودِ شارد و یک چکپوینت با پسوند .partial.json. این چکپوینت مقادیر verified_bytes، expected_size، sha256_prefix (محاسبهشده روی نخستین بایتهای معادل verified_bytes) و آدرس URL مبدا را ذخیره میکند. دانلودکننده هنگام شروع به کار، چکپوینت را میخواند، sha256_prefix را روی بایتهای ذخیرهشده روی دیسک بازآزمایی میکند و تنها در صورتی دانلود را پی میگیرد که هشِ بازتولیدشده کاملاً همخوانی داشته باشد. اگر هش مغایرت داشته باشد، فایل ناقص دور ریخته میشود و دانلود از بایت صفر از سر گرفته خواهد شد. با این رویکرد خرابی خاموش ناممکن میشود؛ چون بایتهای تاییدشده سنجیده میشوند، نه اینکه صرفاً درست فرض شوند.
ترکیب MinHash و LSH
الگوریتم MinHash شباهت جاکارد (Jaccard similarity) دو مجموعه را در فضایی ثابت تخمین میزند. مجموعهی مربوط به هر سند، همان شینگلها (shingles - n-gramهای همپوشانِ) متن آن هستند. امضای نهایی شامل k کمینهی مقدار هش است که هر کدام از یک تابع هش مستقل به دست میآیند. دو سند با شباهت جاکارد s، با احتمال s روی هر کدام از مولفههای امضا مقدار یکسانی دارند.
سپس LSH این k مولفه را در b باند که هر یک شامل r سطر است دستهبندی میکند، بهطوری که k = b * r. دو سند با احتمال 1 - (1 - s^r)^b در دستکم یک باند با هم برخورد دارند؛ این رابطه یک آستانهی قطعی و تیز حول مقدار s میسازد که میتوانید با تنظیم (b, r) آن را کنترل کنید. آستانهی متداول برای حذف تکرارها در پیکرههای متنی s = 0.8 است که در مقالات پژوهشی LSH با مقادیر k = 128، b = 32 و r = 4 حاصل میشود.
مانیفست شاردها بهعنوان یک قرارداد
تنها خروجی پایدارِ دانلودکننده، مانیفست است. مانیفست بهازای هر شارد شامل آدرس URL، حجم بایتهای بازشده، تعداد اسناد، تعداد اسناد یکتا پس از حذف تکرارها و هش sha256 فایل نهایی شارد است. بخشهای بعدی توکنسازی، مانیفست را مبنای کار قرار میدهند نه فهرست فایلهای دایرکتوری را. اگر شاردی موجود نباشد یا sha256 آن اشکال داشته باشد، مانیفست به مرحلهی بعد اعلام میکند که اجرا نشود. این مانیفست همان مرز تعیینکنندهای است که «داده دانلود شده» را از «داده دانلود و اعتبارسنجی شده» جدا میسازد.
پیادهسازی از صفر
فایل code/main.py این بخشها را پیادهسازی میکند:
ShardPlanner- فهرستی از URLهای شاردها را میخواند و ورودیهای برنامهریزیشدهی مانیفست را میسازد.StreamingDownloader- استریمurllibرا با قابلیت اختیاریRangeباز میکند، در فایلی موقت مینویسد، چکپوینت.partial.jsonرا با دریافت هر چانک بهروزرسانی میکند و پیشوند sha256 را هنگام ادامهی دانلود اعتبارسنجی مینماید.ZstdDocIterator- استریم فایلی را درونzstandard.ZstdDecompressorمیپیچد و در هر خط، یک سند برمیگرداند.MinHasher- امضایی باkمولفه برای یک رشته با استفاده از خانوادهای ثابت از مقادیر سید هش (hash seeds) تولید میکند.LSHIndex- امضاها را بر اساس باند دستهبندی کرده و موارد همپوشان را گزارش میدهد.Dedup- ترکیب هشساز و نمایه برای برچسبگذاری هر سند با عنوانkeep(نگهداری) یاnear_duplicate(تقریباً تکراری) به همراه شناسهی شاردِ همپوشان.ManifestWriter- آمارهای مربوط به هر شارد را گردآوری کرده و فایلmanifest.jsonرا روی دیسک مینویسد.
یک دموی نمونه در انتهای فایل، پیکرهای ساختگی و کوچک روی دیسک میسازد، آن را با zstandard فشرده میکند، از طریق یک آدرس file:// دانلودش مینماید، تکرارهایش را حذف میکند و مانیفست را به نمایش درمیآورد.
برای اجرا:
python3 code/main.py
اسکریپت با کد خروج صفر به پایان میرسد و خلاصهای از مانیفست را نمایش میدهد.
الگوهای محیط عملیاتی
چهار الگو این درس را برای کار با پیکرههای متنی بزرگ در محیط عملیاتی (پروداکشن) آماده میسازد.
ذخیرهی چکپوینت پیش از نوشتن داده. فایل .partial.json باید پیش از الحاق بایتها به شارد، حتماً fsync شود. در غیر این صورت، قطع ناگهانی برق ترتیب را معکوس میکند: بایتهای شارد روی دیسک نوشته شدهاند اما چکپوینت ثبتشان نکرده است؛ در نتیجه، اجرای بعدی تصور میکند بایتهای تاییدشدهی کمتری در اختیار دارد و بایتهای تکراریِ ناشی از ادامهی دانلود، فایل را خراب میکنند. اول چکپوینت، سپس نوشتن داده؛ این قاعده درست مانند اصول کار با لاگ پیشنویس (write-ahead log) است.
نمایهی LSH شاردبندیشده. یک نمایهی LSH یکپارچه برای کل پیکره، در مقیاس 200 گیگابایت در رم جا نمیشود. نمایهی LSH را بر اساس هش اولین باند افراز (partition) کنید، افرازها را روی دیسک نگه دارید و تنها افرازی را بخوانید که امضای جدید قرار است در آن بنشیند. هزینهی این کار یکبار خواندن اضافی دیسک بهازای هر سند است؛ مزیتش این است که نمایهی LSH دیگر سقف حافظهی رم را پر نمیکند.
سنگ مزار به جای حذف فیزیکی (Tombstone). اسناد تکراریِ کنارگذاشتهشده، در مانیفست با وضعیت near_duplicate و با ذکر شناسهی شارد سندی که با آن برخورد داشتهاند ثبت میشوند. پاک کردن کامل آنها پیوند میان نسخهی تکراری و نسخهی نگهداریشده را از بین میبرد. استفاده از الگوی tombstone امکان حسابرسی (audit trail) را حفظ میکند و به مراحل بعدی اجازه میدهد در صورت نیاز آستانهی شباهت را تغییر دهند.
هش sha256 بهازای هر شارد، بههمراه هش خودِ مانیفست. خودِ مانیفست نیز یک هش محتوا دریافت میکند. مراحل بعدی پایپلاین پیش از اعتماد به دادههای هر شارد، هش مانیفست را ارزیابی میکنند. بدون این کار، مانیفست به یک سطح حمله (attack surface) خاموش تبدیل میشود: نفوذگری که بتواند یک فایل را دستکاری کند، کل پایپلاین را آلوده خواهد کرد.
استفاده از ابزارهای واقعی
الگوهای محیط عملیاتی:
- امکان ادامهی دانلود در هر اجرای CI. رانرهای CI موقتی هستند. دانلودکننده باید فرض کند در هر بار اجرا با دیسکی تازه روبهروست و دادهها را از کش یا مخزن راهدور بازگرداند. فلگ
--cache-dirنقشی کلیدی دارد. - حذف تکرارها پیش از توکنسازی. توکنسازی پرهزینه است. دو بار اجرا کردن آن روی یک سند، بدون هیچ تغییری در نمودار تابع زیان (loss)، هزینهی پردازش را دو برابر میکند. حذف تکرارها در بالادستِ پایپلاین توکنسازی قرار دارد، نه در پاییندست آن.
- مانیفست بهمثابه دروازهی merge. فرایند آموزش، sha256 مانیفست را از روی یک commit مشخص و ثابت میخواند. تغییر نسخهی دیتاست نیازمند یک commit جدید برای مانیفست است. پیوند میان کد و داده از طریق git برقرار میشود، نه توافقات شفاهی.
انتشار در محیط عملیاتی
فایل outputs/skill-corpus-downloader.md در یک پروژهی واقعی مشخص میکند کدام URLها به دانلودکننده متصل میشوند، دایرکتوری چکپوینت چگونه چیده شده است، عرض شینگل و سهگانهی (k, b, r) برای حذف تکرارها چه مقادیری دارند و مانیفست در کجای سیستم کنترل نسخه نگهداری میشود. این درس موتور اصلی آن را آماده میکند.
تمرینها
- فلگ
--shingle-widthرا اضافه کنید و تغییرات وضعیت حذف تکرارها را در مقادیر 3، 5 و 9 بسنجید. از مقدار پیشفرضی که برمیگزینید دفاع کنید. - با بررسی بایتهای جادویی (magic bytes)، پشتیبانی از gzip را در کنار zstd پیاده کنید. دانلودکننده نباید نیازی به تعیین دستی نوع کدک توسط کاربر داشته باشد.
- حالتی تحت عنوان
--resume-onlyپیاده کنید که اگر چکپوینتی نیافت، از شروع یک دانلود تازه سر باز زند. این قابلیت در CI مفید است تا مانع شود که یک اجرا تصادفاً 200 گیگابایت داده را از نو دریافت کند. - نمایهی LSH را به یک فایل shelf یا sqlite انتقال دهید و نرخ پردازش (throughput) آن را در مقایسه با نسخهی درونحافظهای اندازه بگیرید.
- اعتبارسنجی sha256 مانیفست را هنگام شروع به کار اضافه کنید. اگر مانیفست موجود روی دیسک با هش ثبتشده در
manifest.lockیکسان نبود، دانلودکننده باید متوقف شود و کار را پیش نبرد (fail closed).
اصطلاحات کلیدی
| اصطلاح | در گفتوگوهای عامیانه | معنای دقیق فنی |
|---|---|---|
| Shard (شارد) | «یک فایل» | بخشی مستقل از پیکرهی متنی با sha256 اختصاصی که مبنای ادامهی دانلود و حذف تکرارها قرار میگیرد |
| MinHash signature (امضای مینهش) | «اثرانگشت» | طرحی فشرده (sketch) از یک مجموعه با k مولفه، که در آن هر مولفه برابر با کمینهی یک تابع هش مستقل روی مجموعه است |
| LSH band (باند LSH) | «سطل» | گروهی متشکل از r مولفهی امضا که بهعنوان کلید یک سطل واحد برای شناسایی برخوردها استفاده میشود |
| Verified bytes (بایتهای تاییدشده) | «آفست ادامهی دانلود» | بایتهایی روی دیسک که پیشوند sha256 آنها با چکپوینت بخواند؛ تنها آفست امن برای ادامهی فرایند دانلود |
| Manifest (مانیفست) | «ایندکس» | تنها سند پایدار از خروجیهای دانلودکننده که هش محتوای بخشها را نیز در بر دارد |
برای مطالعهی بیشتر
- RFC 7233 - درخواستهای Range در HTTP، پروتکل ادامهی دانلود
- مشخصات قالب Zstandard - قالب فریم که خروج امن از حالت فشرده بهصورت استریمینگ را ممکن میسازد
- MinHash - خانوادهی امضایی که در این درس استفاده شده است
- درهمسازی حساس به مکان (LSH) - طرح دستهبندی باندی پشت آستانهی حذف تکرارها
- بخش 19 · 43 - پیکرهی توکنسازیشدهی HDF5 که ورودیاش را از این دانلودکننده میگیرد
- بخش 19 · 44 - برنامهی زمانبندی کسینوسی (cosine schedule) که روی این پیکره آموزش میبیند
- بخش 19 · 45 - حلقهی AMP که از این برنامهی زمانبندی استفاده میکند
