فاز 2 · درس 20

مانیتورینگ مدل‌های یادگیری ماشین در پروداکشن: تغییر توزیع داده و مفهوم

نوع
ساختنی
زمان
حدود 110 دقیقه
زبان‌ها
Python
یک شیار یا منحنی آماری توزیع دوبعدی به همراه دو پنجره‌ی زمانی هم‌پوشان و نمودار تجمعی کولموگروف-اسمیرنوف
فهرست مطالب
  1. اهداف یادگیری
  2. صورت مسئله
  3. مفاهیم پایه
  4. چه چیزی را باید پایش کرد
  5. سه نوع جابه‌جایی توزیع (Drift)
  6. تأخیر برچسب‌ها
  7. پنجره‌های مرجع و جاری
  8. شاخص پایداری جمعیت (PSI)
  9. آماره‌ی کولموگروف-اسمیرنوف
  10. آزمون کای-دو برای ویژگی‌های دسته‌ای
  11. آستانه‌های هشدار و هشدارهای کاذب
  12. محرک‌های بازآموزی
  13. پیاده‌سازی از صفر
  14. گام 1: بازه‌های چارک‌ها و PSI
  15. گام 2: آماره‌ی KS و مقدار p آن
  16. گام 3: آزمون کای-دو
  17. گام 4: پنجره‌ها و مانیتور تغییر توزیع
  18. گام 5: خط‌مشی هشدار
  19. گام 6: جریانی از داده با تغییر توزیع تزریق‌شده
  20. استفاده از ابزارهای واقعی
  21. خروجی این درس
  22. تمرین‌ها
  23. اصطلاحات کلیدی
  24. برای مطالعه‌ی بیشتر

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

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

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

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

  • انتخاب سیگنال‌های مانیتورینگ در چهار لایه: سلامت سیستم، ورودی‌ها، پیش‌بینی‌ها و نتایج نهایی
  • تشخیص تغییر هم‌تغییر (Covariate drift)، تغییر پیشین (Prior drift) و تغییر مفهوم (Concept drift) بر اساس بخشی از توزیع توأم (Joint distribution) که دچار دگرگونی می‌شود
  • پیاده‌سازی شاخص پایداری جمعیت (Population Stability Index یا PSI)، آماره‌ی دو-نمونه‌ای کولموگروف-اسمیرنوف (Kolmogorov-Smirnov) و آزمون کای-دو (Chi-square) از صفر
  • مقایسه‌ی پنجره‌ی مرجع ثابت با پنجره‌های جاری لغزان، و تحلیل اثر اندازه‌ی پنجره بر نوفه و تأخیر
  • طراحی خط‌مشی هشداری که هشدارهای کاذب را با استفاده از اندازه‌ی اثر (Effect size)، تصحیح آزمون‌های چندگانه و قاعده‌ی تداوم مهار کند
  • تصمیم‌گیری درباره‌ی اینکه کدام نوع تغییر نیازمند بازآموزی، کدام مستلزم برچسب‌های جدید و کدام بی‌نیاز از اقدام است

صورت مسئله

یک مدل ریسک پرداخت با دقت 95 درصد به مرحله‌ی عملیاتی می‌رسد. دو ماه بعد، کمپینی بازاریابی کاربرانی را به اپلیکیشن می‌آورد که سبدهای خرید بزرگ‌تری دارند. چهار ماه بعد، کلاهبرداران شیوه‌ی خود را عوض می‌کنند و تراکنش‌هایی که قبلاً امن بودند، به درخواست‌های برگشت وجه (Chargeback) تبدیل می‌شوند.

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

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

مفاهیم پایه

چه چیزی را باید پایش کرد

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

لایه سیگنال‌ها زمان دردسترس بودن سؤالی که پاسخ می‌دهد
سلامت سیستم زمان پاسخ‌دهی در صدک پنجاهم (p50) و نودونهم (p99)، نرخ خطا، توان عملیاتی، حافظه بلافاصله آیا سرویس پاسخ می‌دهد؟
ورودی‌ها توزیع تک‌تک ویژگی‌ها، نرخ مقادیر ناموجود، اسکیمای داده، دسته‌بندی‌های جدید بلافاصله آیا داده‌های زنده شبیه داده‌های آموزشی هستند؟
پیش‌بینی‌ها توزیع امتیازها، نرخ مثبت‌ها، سهم پاسخ‌های پیش‌فرض بلافاصله آیا مدل مثل گذشته رفتار می‌کند؟
نتایج نهایی دقت، صحت، کالیبراسیون، متریک‌های کسب‌وکار پس از تأخیر برچسب آیا مدل همچنان درست عمل می‌کند؟

تمرکز اصلی این درس روی مانیتورینگ ورودی است؛ چون پیش از رسیدن برچسب‌ها کار می‌کند. این لایه محدودیتی هم دارد که پیاده‌سازی عملی نشان خواهد داد: برخی تغییرات اصلاً ورودی‌ها را دستکاری نمی‌کنند.

سه نوع جابه‌جایی توزیع (Drift)

توزیع توأم ورودی‌های x و برچسب‌های y را می‌توان به دو شیوه نوشت:

P(x,y)=P(x)⋅P(y∣x)=P(y)⋅P(x∣y)P(x, y) = P(x) \cdot P(y \mid x) = P(y) \cdot P(x \mid y)

هر نوع تغییر، بخش متفاوتی را دستخوش دگرگونی می‌کند:

نوع تغییر چه چیزی تغییر می‌کند چه چیزی ثابت می‌ماند مثال در درگاه پرداخت کدام مانیتور آن را می‌بیند
تغییر هم‌تغییر P(x) P(y | x) ورود بیشتر کاربران اپلیکیشن با سبدهای خرید سنگین‌تر مانیتورهای ورودی
تغییر پیشین P(y) P(x | y) دوبرابر شدن نرخ کلاهبرداری حین حمله، در حالی که الگوی ظاهری کلاهبرداری یکسان است مانیتورهای پیش‌بینی و نتایج نهایی
تغییر مفهوم P(y | x) می‌تواند ثابت بماند همان سبد خرید اکنون ریسک کلاهبرداری متفاوتی دارد صرفاً مانیتورهای نتایج نهایی

گاما و همکاران تغییر در P(y | x) را تغییر مفهوم واقعی (Real concept drift) می‌نامند. آن‌ها تغییر در P(x) را در شرایطی که P(y | x) دست‌نخورده بماند، تغییر مجازی (Virtual drift) می‌خوانند. پیمایش آن‌ها جابه‌جایی‌ها را از نظر الگوی زمانی نیز دسته‌بندی می‌کند: ناگهانی، افزایشی، تدریجی، یا مفهومی که دوباره تکرار می‌شود.

تغییر هم‌تغییر لزوماً همیشه آسیب‌زننده نیست. اگر مدل P(y | x) را به‌خوبی آموخته باشد، پیش‌بینی‌اش برای هر ورودی درست می‌ماند. با این حال، اگر ترافیک به سمت نواحی کم‌داده در مجموعه‌ی آموزشی یا نزدیک مرز تصمیم‌گیری میل کند، افت دقت رخ خواهد داد.

تأخیر برچسب‌ها

نتیجه‌ی نهایی پس از پیش‌بینی مشخص می‌شود. میزان این تأخیر به ماهیت مسئله بستگی دارد:

مسئله زمان رسیدن برچسب
کلیک روی تبلیغ چند ثانیه
ریزش کاربر در 30 روز آینده 30 روز
کلاهبرداری کارت بانکی با رسیدن درخواست برگشت وجه، معمولاً چند هفته بعد
نکول وام ماه‌ها یا سال‌ها

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

پنجره‌های مرجع و جاری

هر آزمون تغییر توزیع، دو نمونه را مقایسه می‌کند:

  • پنجره‌ی مرجع (Reference window): داده‌های آموزشی، یا دوره‌ای از محیط عملیاتی که به‌صورت دستی بازبینی و تأیید شده است. این پنجره تا زمان بازآموزی بعدی ثابت می‌ماند.
  • پنجره‌ی جاری (Current window): تازه‌ترین رویدادها، که می‌تواند N رویداد اخیر یا T ساعت گذشته باشد.

پنجره‌ی لغزان (Sliding window) با گام‌هایی کوچک‌تر از طول خود به جلو می‌رود، بنابراین پنجره‌های متوالی هم‌پوشانی دارند. پنجره‌ی غلتان (Tumbling window) به اندازه‌ی کل طول خود جابه‌جا می‌شود. اندازه‌ی پنجره همواره یک مصالحه است: پنجره‌ی کوچک واکنشی سریع نشان می‌دهد اما نوفه را به‌اشتباه تغییر توزیع تفسیر می‌کند. پنجره‌ی بزرگ پایدار است ولی تغییرات را با تأخیر اعلام می‌کند.

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

شاخص پایداری جمعیت (PSI)

خاستگاه شاخص PSI اعتبارسنجی بانکی است. مقادیر پنجره‌ی مرجع را به چند بازه (Bin) تقسیم کنید—معمولاً ده بازه روی دهک‌های مرجع، به‌طوری که هر بازه شامل 10 درصد از داده‌های مرجع باشد. سهم داده‌های جاری در هر بازه را حساب کنید، سپس به ازای هر بازه این عبارت را جمع بزنید:

PSI=∑bins(current_share−reference_share)⋅ln⁡(current_sharereference_share)\text{PSI} = \sum_{\text{bins}} (\text{current\_share} - \text{reference\_share}) \cdot \ln\left(\frac{\text{current\_share}}{\text{reference\_share}}\right)

اگر سهم بازه‌ای صفر شود، لگاریتم به بی‌نهایت میل می‌کند؛ پس تمام سهم‌ها را به مقداری ناچیز مثل 0.0001 محدود (Clip) کنید. شاخص PSI متقارن است؛ یعنی برابر است با واگرایی کولبک-لایبلر (KL Divergence) پنجره‌ی جاری نسبت به مرجع به‌علاوه‌ی واگرایی کولبک-لایبلر پنجره‌ی مرجع نسبت به جاری.

متخصصان بازه‌های مشخصی را برای تفسیر PSI به کار می‌برند: کمتر از 0.1 نشانه‌ی پایداری است، بین 0.1 تا 0.25 تغییر متوسط را نشان می‌دهد، و مقادیر بالای 0.25 از تغییری چشمگیر حکایت دارند. این مرزها صرفاً یک توافق عرفی هستند نه آزمون آماری دقیق. مقدار PSI با افزایش تعداد بازه‌ها بزرگ‌تر می‌شود و با افزایش حجم نمونه کاهش می‌یابد. در آزمون این درس، پنجره‌های 500 رویدادی بدون تغییر توزیع، امتیازی بین 0.01 تا 0.04 به دست می‌آورند.

آماره‌ی کولموگروف-اسمیرنوف

آماره‌ی D در آزمون دو-نمونه‌ای کولموگروف-اسمیرنوف، بزرگ‌ترین فاصله‌ی عمودی میان توابع توزیع تجمعی تجربی دو نمونه است:

D=max⁡x∣Freference(x)−Fcurrent(x)∣D = \max_x \left\lvert F_{\text{reference}}(x) - F_{\text{current}}(x) \right\rvert

مقدار D برای دو نمونه‌ی یکسان صفر و برای نمونه‌هایی با دامنه‌های کاملاً مجزا یک است. این روش نیازی به بازه‌بندی ندارد. مقدار p (یا p-value) از توزیع کولموگروف و بر پایه‌ی حجم نمونه‌ی مؤثر n * m / (n + m) محاسبه می‌شود.

وقتی حجم نمونه بزرگ باشد، مقدار p حتی برای اختلاف‌های بی‌اهمیت هم به سمت صفر میل می‌کند. در پنجره‌هایی با یک میلیون رویداد، جابه‌جایی ناچیز به اندازه‌ی 0.02 انحراف معیار هم از نظر آماری «معنادار» شناخته می‌شود. از خود D به‌عنوان اندازه‌ی اثر استفاده کنید، و اعلان هشدار را منوط به عبور هم‌زمان D و مقدار p از آستانه‌هایشان بگذارید.

آزمون کای-دو برای ویژگی‌های دسته‌ای

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

expected[r][c]=row_total[r]⋅column_total[c]/grand_totalchi2=∑cells(observed−expected)2/expected\begin{aligned} \text{expected}[r][c] &= \text{row\_total}[r] \cdot \text{column\_total}[c] / \text{grand\_total} \\ \text{chi2} &= \sum_{\text{cells}} (\text{observed} - \text{expected})^2 / \text{expected} \end{aligned}

این آزمون برای k دسته، k - 1 درجه‌ی آزادی دارد. اگر دسته‌ی جدیدی فقط در پنجره‌ی جاری ظاهر شود، عبارتی بزرگ تولید می‌کند؛ در نتیجه این آزمون نسبت به مقادیر تازه حساس است. معیار کرامر وی (Cramér's V) نقش اندازه‌ی اثر را ایفا می‌کند. برای جدولی با دو سطر، این معیار برابر است با ریشه‌ی دوم حاصل تقسیم chi2 / grand_total.

آستانه‌های هشدار و هشدارهای کاذب

هر آزمون دارای نرخ هشدار کاذب alpha است، حتی وقتی هیچ چیز تغییر نکرده باشد. مانیتورینگ آزمون‌های پرشماری اجرا می‌کند. اگر 50 ویژگی را هر ساعت با alpha = 0.05 بررسی کنید، روزانه انتظار حدود 60 هشدار کاذب را خواهید داشت؛ در حالی که هیچ تغییری رخ نداده است. افراد خیلی زودتر از این ارقام توجه به هشدارها را کنار می‌گذارند.

چهار قاعده نرخ هشدارها را در دامنه‌ای کاربردی نگه می‌دارد:

  1. تصحیح برای آزمون‌های چندگانه: تصحیح بونفرونی (Bonferroni) مقدار alpha را بر تعداد آزمون‌ها تقسیم می‌کند. در پیاده‌سازی این درس، alpha کلی برای دو آزمون برابر 0.01 تعیین شده است؛ یعنی به ازای هر آزمون 0.005 در نظر گرفته می‌شود.
  2. شرط اندازه‌ی اثر: تنها زمانی هشدار دهید که PSI، D یا کرامر وی نیز از آستانه‌ی مجاز فراتر بروند.
  3. شرط تداوم: تنها پس از مشاهده‌ی k پنجره‌ی متوالیِ دارای تغییر هشدار دهید. در این درس k = 2 لحاظ شده است.
  4. تفکیک اخطار از هشدار: اخطار (Warning) در داشبورد ثبت می‌شود؛ اما هشدار (Alert) مستقیماً برای یک فرد ارسال می‌گردد.

پیاده‌سازی ما قاعده‌ی دوم را به‌خوبی نشان می‌دهد. در یکی از پنجره‌های پایدار و تمیز، مقدار p در آزمون کای-دو برابر 0.0009 می‌شود که از alpha آزمون کمتر است؛ اما چون کرامر ویِ آن کمتر از 0.1 است، سیستم صرفاً اخطار ثبت می‌کند نه هشدار.

محرک‌های بازآموزی

سه محرک رایج برای بازآموزی مدل وجود دارد:

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

نوع تغییر توزیع، واکنش مناسب را تعیین می‌کند:

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

پس از بازآموزی، مدل تازه را روی تازه‌ترین داده‌های برچسب‌دار ارزیابی کنید. استقرار را از طریق مراحل سایه (Shadow) و قناری (Canary) که در درس طراحی سیستم آمد پیش ببرید. سپس پنجره‌ی مرجع را بر اساس داده‌های آموزشی جدید به‌روزرسانی کنید.

شاخص پایداری جمعیت (PSI)دریفت را بکشید
POPULATION STABILITY INDEXThe reference window is cut into ten bins that each hold 10 percent of its values. Move the current window and the share in each bin changes. PSI adds up those changes, weighted by the log ratio. A bin that grows from 10 to 30 percent adds much more than one that grows to 12. The 0.1 and 0.25 bands are a credit-scoring rule of thumb, not a statistical test.10٪ به ازای هر باکت در مرجعbins = deciles of the reference window · grey reference, blue currentمیله‌های بالای 80٪ بریده شده‌اند
پنجره مرجع به ده باکت تقسیم شده که هر کدام 10 درصد از مقادیر آن را در بر می‌گیرد. با جابه‌جا کردن پنجره جاری، سهم هر باکت تغییر می‌کند. شاخص PSI این تغییرات را به‌صورت وزن‌دهی‌شده با نسبت لگاریتمی با هم جمع می‌زند. باکتی که از 10 به 30 درصد رشد می‌کند، سهم بسیار بیشتری نسبت به باکتی دارد که به 12 درصد می‌رسد. بازه‌های 0.1 و 0.25 یک قاعده سرانگشتی در اعتبارسنجی هستند، نه یک آزمون آماری.

پیاده‌سازی از صفر

کد موجود در code/main.py تنها از کتابخانه‌ی استاندارد پایتون بهره می‌برد.

گام 1: بازه‌های چارک‌ها و PSI

مرز بازه‌ها همان دهک‌های مرجع هستند. تابع bisect_right مشخص می‌کند هر مقدار در کدام بازه قرار می‌گیرد.

def quantile_edges(reference: list[float], bins: int = 10) -> list[float]:
    ordered = sorted(reference)
    n = len(ordered)
    edges = [ordered[min(n - 1, (n * i) // bins)] for i in range(1, bins)]
    return sorted(set(edges))


def psi_from_fractions(expected: list[float], actual: list[float], eps: float = 1e-4) -> float:
    total = 0.0
    for e, a in zip(expected, actual):
        e, a = max(e, eps), max(a, eps)
        total += (a - e) * math.log(a / e)
    return total

گام 2: آماره‌ی KS و مقدار p آن

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

def ks_statistic(sample_a: list[float], sample_b: list[float]) -> float:
    a, b = sorted(sample_a), sorted(sample_b)
    n, m = len(a), len(b)
    i = j = 0
    d = 0.0
    while i < n and j < m:
        x = min(a[i], b[j])
        while i < n and a[i] <= x:
            i += 1
        while j < m and b[j] <= x:
            j += 1
        d = max(d, abs(i / n - j / m))
    return d

محاسبه‌ی مقدار p از سری توزیع کولموگروف یعنی 2 * sum((-1)^(k-1) * exp(-2 k^2 lambda^2)) به همراه lambda = sqrt(n * m / (n + m)) * D بهره می‌گیرد.

گام 3: آزمون کای-دو

تابع chi2_sf تابع بقای کای-دو را از طریق تابع گامای ناقص بالایی نرمال‌شده محاسبه می‌کند. این تابع برای مقادیر کوچک از بسط سری و برای مقادیر بزرگ از کسر مسلسل بهره می‌برد. تابع chi_square_test جدول دو سطری را می‌سازد، آماره را حساب می‌کند و مقدار p به‌همراه کرامر وی را برمی‌گرداند.

def chi_square_test(reference_counts: dict[str, int], current_counts: dict[str, int]) -> ChiSquareResult:
    categories = [c for c in sorted(set(reference_counts) | set(current_counts))
                  if reference_counts.get(c, 0) + current_counts.get(c, 0) > 0]
    rows = [[reference_counts.get(c, 0) for c in categories], [current_counts.get(c, 0) for c in categories]]
    row_totals = [sum(r) for r in rows]
    grand = sum(row_totals)
    if len(categories) < 2 or 0 in row_totals:
        return ChiSquareResult(0.0, 0, 1.0, 0.0)
    statistic = 0.0
    for col, _ in enumerate(categories):
        col_total = rows[0][col] + rows[1][col]
        for r in range(2):
            expected = row_totals[r] * col_total / grand
            statistic += (rows[r][col] - expected) ** 2 / expected
    dof = len(categories) - 1
    return ChiSquareResult(statistic, dof, chi2_sf(statistic, dof), math.sqrt(statistic / grand))

گام 4: پنجره‌ها و مانیتور تغییر توزیع

کلاس SlidingWindow داده‌های size رویداد اخیر را در یک deque نگه می‌دارد. کلاس DriftMonitor بازه‌ها و شمارش‌های مرجع را یک بار محاسبه می‌کند. متد check آن مقادیر PSI، D و مقدار p آزمون KS را برای تک‌تک ویژگی‌های عددی، و خروجی آزمون کای-دو را برای ویژگی‌های دسته‌ای برمی‌گرداند.

گام 5: خط‌مشی هشدار

این خط‌مشی هر مجموعه‌متریک را به یک سطح وضعیت تبدیل می‌کند و تعداد پنجره‌های متوالی دارای تغییر را برای هر سیگنال می‌شمارد:

def level(self, metrics: dict[str, float]) -> str:
    if "psi" in metrics:
        if metrics["psi"] >= self.psi_drift or (metrics["ks"] >= self.ks_drift and metrics["ks_p"] < self.alpha):
            return "drift"
        return "warn" if metrics["psi"] >= self.psi_warn else "ok"
    if metrics["p"] < self.alpha and metrics["v"] >= self.min_cramers_v:
        return "drift"
    return "warn" if metrics["p"] < self.alpha else "ok"

متد update برای پنجره‌ای که دوره‌ی توالی را تکمیل کند مقدار ALERT، و برای اولین پنجره‌ی سالم پس از وضعیت هشدار مقدار resolved را برمی‌گرداند.

گام 6: جریانی از داده با تغییر توزیع تزریق‌شده

این جریان شامل 9000 رویداد پرداخت با ویژگی‌های amount، channel و برچسب کلاهبرداری label است و در چهار بخش تفکیک می‌شود:

رویدادها Amount ترکیب Channel (اپلیکیشن، فروشگاه، وب) مرز برچسب نوع تغییر
0 تا 2999 میانگین 100، انحراف معیار 20 35، 15، 50 درصد 120 دوره‌ی مرجع
3000 تا 4999 میانگین 118، انحراف معیار 24 60، 10، 30 درصد 120 تغییر هم‌تغییر
5000 تا 6499 میانگین 100، انحراف معیار 20 35، 15، 50 درصد 120 بازگشت به حالت عادی
6500 تا 8999 میانگین 100، انحراف معیار 20 35، 15، 50 درصد 105 تغییر مفهوم

مدل آستانه‌ای ساده روی amount است که با 2000 رویداد اول برازش شده است. پنجره‌ها گنجایش 500 رویداد دارند و با گام‌های 250تایی به جلو می‌روند. برچسب‌ها با 500 رویداد تأخیر دریافت می‌شوند.

python3 code/main.py
  end    PSI   KS D   chi2 p    acc  levels (amount/channel/accuracy)  events
 3000  0.010  0.041  3.0e-01  0.932  ok/ok/ok
 3250  0.152  0.163  7.6e-06  0.924  drift/warn/ok
 3500  0.463  0.298  2.8e-26  0.936  drift/drift/ok                     ALERT amount
 3750  0.609  0.343  3.5e-35  0.942  drift/drift/ok                     ALERT channel
 ...
 5500  0.026  0.044  2.6e-01  0.898  ok/ok/ok                           resolved amount, resolved channel
 ...
 7250  0.040  0.048  7.1e-01  0.848  ok/ok/drift
 7500  0.022  0.039  6.1e-01  0.778  ok/ok/drift                        ALERT accuracy

نتایج در سه بخش قابل تحلیل است:

  • تغییر هم‌تغییر: تغییر از رویداد 3000 آغاز می‌شود و هشدار ویژگی amount در رویداد 3500 به صدا درمی‌آید. با توجه به اینکه قاعده‌ی برچسب‌گذاری دست‌نخورده باقی مانده، دقت بین 0.89 تا 0.95 حفظ می‌شود.
  • بهبود و بازگشت: ورودی‌ها در رویداد 5000 به وضعیت عادی برمی‌گردند و هر دو هشدار در رویداد 5500 برطرف (Resolve) می‌شوند.
  • تغییر مفهوم: تغییر از رویداد 6500 آغاز می‌شود، اما چون داده‌های ورودی تغییری نکرده‌اند، مانیتورهای ورودی واکنشی نشان نمی‌دهند. دقت مدل از 0.946 به بازه‌ی بین 0.76 تا 0.79 سقوط می‌کند. هشدار افت دقت در رویداد 7500 صادر می‌شود؛ یعنی 1000 رویداد پس از وقوع تغییر، که 500 رویدادِ آن ناشی از تأخیر دریافت برچسب‌هاست.

استفاده از ابزارهای واقعی

scipy. ماژول scipy.stats هر دو آزمون را پیاده کرده است. نتایج آماری آن با کدی که از صفر نوشتیم همخوانی دارد. برای اجرای این درس نیازی به scipy ندارید.

from scipy import stats

result = stats.ks_2samp(reference_amounts, current_amounts)
print(result.statistic, result.pvalue)

table = [[1000, 700, 300], [140, 290, 70]]
chi2 = stats.chi2_contingency(table)
print(chi2.statistic, chi2.pvalue, chi2.dof)

ما توابع ks_statistic و chi_square_test را با نمونه‌های تصادفی در برابر scipy 1.18.1 آزمودیم. آماره‌ی KS، آماره‌ی کای-دو و مقدار p آزمون کای-دو تا دقت ممیز شناور کاملاً منطبق بودند. در بین 200 جفت نمونه‌ی تصادفی، مقدار p در آزمون KS تا 18 درصد نسبت به مقدار ارائه‌شده توسط scipy تفاوت داشت. بیشترین اختلاف‌ها در مقادیر بسیار کوچک p رخ داد.

منشأ این اختلاف، تفاوت روش‌هاست. کد ما از حد توزیع کولموگروف استفاده می‌کند. به‌صورت پیش‌فرض، زمانی که هر دو نمونه حداکثر 10,000 مقدار داشته باشند، تابع ks_2samp روش دقیق (Exact) را برمی‌گزیند. حالت مجانبی (Asymptotic) آن از توزیع نمونه‌ی متناهی با حجم نمونه‌ی مؤثر گردشده استفاده می‌کند. در متغیرهای دودسته‌ای، تابع chi2_contingency به‌طور پیش‌فرض تصحیح پیوستگی یتس (Yates) را اعمال می‌کند که باعث تمایز آماره‌ها در این بخش می‌شود.

کتابخانه‌های مانیتورینگ. چند کتابخانه‌ی متن‌باز این آزمون‌ها را همراه با گزارش‌گیری و داشبوردها عرضه می‌کنند:

  • Evidently آزمون تغییر توزیع را بر اساس نوع ستون برمی‌گزیند. برای داده‌های مرجع با 1000 سطر یا کمتر، برای ستون‌های عددی از آزمون KS و برای ستون‌های دسته‌ای از آزمون کای-دو استفاده می‌کند. برای داده‌های بزرگ‌تر، فاصله‌ی واسرشتاین (Wasserstein Distance) و واگرایی جنسن-شنون را با آستانه‌ی 0.1 به کار می‌گیرد. روش PSI نیز جزو گزینه‌های دردسترس آن است.
  • NannyML کارایی مدل را پیش از دریافت برچسب‌ها برآورد می‌کند. الگوریتم تخمین کارایی مبتنی بر اطمینان (CBPE) آن احتمالات پیش‌بینی‌شده‌ی مدل را می‌گیرد که باید به‌خوبی کالیبره شده باشند. مستندات NannyML تصریح می‌کند که CBPE تغییر هم‌تغییر را پوشش می‌دهد اما در برابر تغییر مفهوم ناتوان است. جریان داده‌ی نمایشی ما علت این مسئله را اثبات کرد: تنها برچسب‌ها توانستند تغییر مفهوم را آشکار کنند.
  • TensorFlow Data Validation بازه‌های زمانی متوالی داده‌ها را می‌سنجد. این ابزار فاصله‌ی L-بی‌نهایت را برای متغیرهای دسته‌ای و فرم تقریبی واگرایی جنسن-شنون را برای ویژگی‌های عددی به کار می‌برد. این کتابخانه همچنین داده‌های آموزشی را با داده‌های زمان سرویس‌دهی مقایسه می‌کند که در درس سامانه‌ی ذخیره‌سازی ویژگی‌ها به آن پرداخته‌ایم.

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

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

خروجی این درس

خروجی این درس فایل outputs/skill-drift-monitoring.md است. این مهارت برنامه‌ریزی مانیتورها برای مدلی تازه و بررسی هشدارهای تغییر توزیع را به شما می‌آموزد؛ این‌که کدام لایه فعال شده، شواهد (Evidence) موجود با کدام نوع تغییر توزیع همخوانی دارد، و آیا واکنش درست صبر کردن، کالیبراسیون دوباره، گردآوری برچسب یا بازآموزی مدل است.

تمرین‌ها

  1. جریان را با پنجره‌های 200 و 2000 رویدادی اجرا کنید. تعداد اخطارهای کاذب پیش از رویداد 3000 و میزان تأخیر در هشدار را ثبت کنید.
  2. مقادیر n_tests=1، patience=1 و min_cramers_v=0 را تنظیم کنید. هشدارهای صادرشده در بازه‌ی زمانی پایدار بین 5000 تا 6500 را بشمارید.
  3. ویژگی جدیدی اضافه کنید که میانگین آن با نرخ 0.01 به ازای هر 100 رویداد افزایش یابد. بررسی کنید کدام سیگنال، PSI یا KS، سریع‌تر واکنش نشان می‌دهد.
  4. فاصله‌ی جنسن-شنون را بر پایه‌ی همان بازه‌های دهکی پیاده‌سازی کنید. خروجی آن را در تمام پنجره‌ها با PSI بسنجید.
  5. مانیتوری برای پایش نرخ پیش‌بینی‌های مثبت مدل پیاده کنید. توضیح دهید چرا این مانیتور حین وقوع تغییر مفهوم هیچ هشداری نداد.

اصطلاحات کلیدی

اصطلاح در گفتگوها تعریف دقیق
Data drift (تغییر توزیع داده) «داده‌ها عوض شده» تغییر در توزیع ورودی‌ها، پیش‌بینی‌ها یا برچسب‌ها در مقایسه با پنجره‌ی مرجع
Covariate drift (تغییر هم‌تغییر) «کاربرهای جدید اومدن» تغییر در P(x) در شرایطی که P(y | x) ثابت می‌ماند
Prior drift (تغییر پیشین) «تعداد مثبت‌ها بیشتر شده» تغییر در P(y) در شرایطی که P(x | y) ثابت می‌ماند؛ جابه‌جایی برچسب (Label shift) نیز نامیده می‌شود
Concept drift (تغییر مفهوم) «قواعد بازی عوض شده» تغییر در P(y | x)؛ به‌طوری که همان ورودی قبلی اکنون پاسخ درست متفاوتی دارد
Reference window (پنجره‌ی مرجع) «داده‌های مبنا» نمونه‌ای ثابت، معمولاً از داده‌های آموزشی، که تمام پنجره‌های جاری با آن مقایسه می‌شوند
PSI (شاخص پایداری جمعیت) «امتیاز پایداری» مجموع متقارن در تمام بازه‌ها از: (سهم داده‌های جاری منهای سهم مرجع) ضربدر لگاریتم نسبت آن‌ها
KS statistic (آماره‌ی KS) «آزمون KS» بیشترین فاصله میان دو تابع توزیع تجمعی تجربی
Cramér's V (کرامر وی) «اندازه‌ی اثر برای دسته‌ها» آماره‌ی کای-دو که در دامنه‌ی بین 0 تا 1 نرمال شده است
Label delay (تأخیر برچسب) «برچسب‌ها دیر می‌رسن» فاصله‌ی زمانی میان ثبت پیش‌بینی و دریافت نتیجه‌ی واقعی
Alert fatigue (خستگی از هشدار) «نوتیفیکیشن‌ها زیاده» بی‌توجهی تیم به هشدارها به دلیل اینکه بیشتر آن‌ها کاذب بوده‌اند

برای مطالعه‌ی بیشتر

خودآزمایی

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

  1. پیش از درسیک سرویس مدل به تمام درخواست‌ها با کد وضعیت HTTP 200 و تأخیر عادی پاسخ می‌دهد. این سرویس چه چیزی را همچنان ممکن است پنهان کند؟
  2. حین درسکلاهبرداران تاکتیک‌های خود را تغییر می‌دهند، بنابراین ویژگی‌های پرداختی یکسان حالا ریسک کلاهبرداری متفاوتی دارند. این پدیده چه نوع دریفتی است؟
  3. حین درسیکی از 10 باکت ده‌ک دهک‌های مرجع، حاوی 10 درصد از داده‌های مرجع و 30 درصد از پنجره جاری است. این باکت چه مقداری به PSI اضافه می‌کند؟
  4. حین درسیک سیستم پایش دریفت 50 ویژگی را در هر ساعت با آلفای 0.05 بررسی می‌کند و هیچ تغییری رخ نداده است. این سیستم روزانه تقریباً چند هشدار کاذب ایجاد می‌کند؟
  5. پس از درسبا وجود یک میلیون رویداد در هر پنجره، آزمون KS برای یک ویژگی مقدار p = 2e-7 و D برابر با 0.004 را گزارش می‌دهد. خط‌مشی هشدار چه واکنشی باید نشان دهد؟
  6. پس از درسپایشگرهای ورودی هشداری نمی‌دهند، اما دقت روی رویدادهای به‌تازگی برچسب‌خورده از 0.95 به 0.78 کاهش می‌یابد. اقدام درست بعدی چیست؟

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

تفاوت تغییر هم‌تغییر (Covariate Drift) با تغییر مفهوم (Concept Drift) چیست؟

در تغییر هم‌تغییر، توزیع داده‌های ورودی P(x) دستخوش دگرگونی می‌شود ولی رابطه‌ی آن‌ها با خروجی P(y|x) ثابت می‌ماند؛ در حالی که در تغییر مفهوم، رابطه‌ی بین ورودی و برچسب عوض می‌شود و بدون تغییر در ورودی‌ها نیز عملکرد مدل افت می‌کند.

شاخص پایداری جمعیت (PSI) چگونه محاسبه می‌شود و مقادیر آن چه مفهومی دارند؟

با تقسیم داده‌های مرجع به دهک‌ها، سهم داده‌های جاری در هر بازه مقایسه شده و فرمول متقارن واگرایی KL محاسبه می‌شود. مقدار زیر 0.1 نشان‌دهنده‌ی پایداری، بین 0.1 تا 0.25 تغییر متوسط، و بالاتر از 0.25 نشانه‌ی تغییر چشمگیر است.

چگونه می‌توان هشدارهای کاذب را در مانیتورینگ ویژگی‌ها کاهش داد؟

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

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

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