فاز 2 · درس 16

تشخیص ناهنجاری: از مبانی آماری تا جنگل انزوا (Isolation Forest)

نوع
ساختنی
زمان
حدود 75 دقیقه
زبان‌ها
Python
پیش‌نیاز
یادگیری ماشین چیست؟ مفاهیم پایه، انواع و پیاده‌سازی از صفر، رگرسیون خطی از صفر تا پیاده‌سازی با scikit-learn، رگرسیون لجستیک و دسته‌بندی از صفر، درخت تصمیم و جنگل تصادفی: پیاده‌سازی از صفر و تئوری ریاضی، ماشین‌های بردار پشتیبان (SVM)؛ از فرمول‌بندی ریاضی تا پیاده‌سازی، الگوریتم K-نزدیک‌ترین همسایه (KNN) و معیارهای فاصله، یادگیری نظارت‌نشده و الگوریتم‌های خوشه‌بندی، مهندسی و انتخاب ویژگی در یادگیری ماشین، ارزیابی مدل در یادگیری ماشین: از صفر تا پیاده‌سازی و تحلیل
یک قطب‌نمای ناوبری که تمام عقربه‌های متعدد آن به یک سمت نشانه رفته‌اند به جز یک عقربه‌ی فلزی متمایز که در جهت متفاوتی قرار دارد
فهرست مطالب
  1. اهداف یادگیری
  2. صورت مسئله
  3. مفاهیم پایه
  4. انواع ناهنجاری‌ها
  5. فرموله‌سازی مسئله به صورت بدون نظارت
  6. موازنه‌ی روش بانظارت در برابر بدون نظارت
  7. روش Z-Score
  8. روش دامنه بین‌چارکی (IQR)
  9. جنگل انزوا (Isolation Forest)
  10. فاکتور محلی داده‌ی پرت (Local Outlier Factor یا LOF)
  11. مقایسه‌ی روش‌ها
  12. چالش‌های ارزیابی
  13. خط لوله‌ی تشخیص ناهنجاری
  14. پیاده‌سازی از صفر
  15. شناساگر Z-Score
  16. شناساگر IQR
  17. ساخت جنگل انزوا از صفر
  18. سناریوهای نمایشی
  19. استفاده از ابزارهای آماده
  20. پارامتر Contamination در sklearn
  21. مدل One-Class SVM
  22. پیش‌نمایش روش اتوانکودر (Autoencoder)
  23. تشخیص ناهنجاری گروهی (Ensemble)
  24. ملاحظات محیط عملیاتی
  25. خروجی این درس
  26. انتخاب آستانه
  27. مقیاس‌پذیری در محیط پروداکشن
  28. تمرین‌ها
  29. اصطلاحات کلیدی
  30. برای مطالعه‌ی بیشتر

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

تشخیص ناهنجاری صورت مسئله را دگرگون می‌کند: به جای تلاش برای آموختن تمام شکل‌های ممکن خرابی و تقلب، مدل صرفاً توزیع داده‌های «عادی» را فرامی‌گیرد و هر انحراف معناداری را زنگ خطر می‌داند. در این درس، تفاوت میان ناهنجاری‌های نقطه‌ای، زمینه‌ای و تجمعی را بررسی می‌کنید و می‌بینید که چگونه مدل‌سازی وضعیت عادی به سیستم اجازه می‌دهد حتی ناهنجاری‌های کاملاً نوظهور را هم کشف کند.

در این بخش، علاوه بر مبانی آماری مانند Z-score و دامنه بین‌چارکی (IQR)، الگوریتم‌های پرکاربردی مثل جنگل انزوا (Isolation Forest) و فاکتور محلی داده‌ی پرت (LOF) را از صفر در Python پیاده‌سازی کرده و در نهایت عملکرد آن‌ها را با ابزارهای آماده‌ی کتابخانه‌ی scikit-learn و معیارهای سنجش داده‌های نامتوازن مانند AUPRC مقایسه می‌کنیم.

تعریف حالت عادی ساده است. هر چیزی که در این تعریف نگنجد، ناهنجار است.

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

  • پیاده‌سازی روش‌های تشخیص ناهنجاری Z-score، دامنه بین‌چارکی (IQR) و جنگل انزوا (Isolation Forest) از صفر
  • تفکیک ناهنجاری‌های نقطه‌ای، زمینه‌ای و تجمعی از یکدیگر و انتخاب روش مناسب برای هر کدام
  • توضیح اینکه چرا تشخیص ناهنجاری به جای دسته‌بندی ناهنجاری‌ها، در قالب مدل‌سازی داده‌های عادی فرموله‌بندی می‌شود
  • مقایسه‌ی تشخیص ناهنجاری بدون نظارت با دسته‌بندی با‌نظارت و ارزیابی موازنه‌ی میان پوشش ناهنجاری‌های جدید در برابر دقت (precision)

صورت مسئله

یک کارت اعتباری ساعت 14:00 در نیویورک استفاده می‌شود و سپس ساعت 14:05 در توکیو تراکنش می‌زند. سنسور یک کارخانه دمای 150 درجه را نشان می‌دهد، در حالی که بازه‌ی عادی بین 80 تا 120 است. سروری 50,000 درخواست در ثانیه می‌فرستد، در حالی که میانگین روزانه‌اش 200 درخواست است.

این‌ها ناهنجاری هستند و شناسایی‌شان حیاتی است. کلاهبرداری میلیاردها دلار خسارت می‌زند، خرابی تجهیزات به توقف کار منجر می‌شود و نفوذ به شبکه داده‌ها را بر باد می‌دهد.

چالش اصلی این است: به‌ندرت نمونه‌های برچسب‌دار از ناهنجاری‌ها در دست دارید. کلاهبرداری تنها 0.1% از کل تراکنش‌ها را تشکیل می‌دهد. خرابی تجهیزات هم شاید سالی چند بار بیشتر رخ ندهد. نمی‌توانید یک دسته‌بند استاندارد آموزش دهید؛ چون کلاسی به نام «ناهنجاری» عملاً نمونه‌ای برای یادگیری ندارد. حتی اگر چندتایی برچسب داشته باشید، ناهنجاری‌هایی که تابه‌حال دیده‌اید تنها الگوهایی نیستند که با آن‌ها روبه‌رو خواهید شد؛ شگرد کلاهبرداری فردا با امروز فرق دارد.

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

مفاهیم پایه

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

همه‌ی ناهنجاری‌ها از یک جنس نیستند:

  • ناهنجاری‌های نقطه‌ای (Point anomalies): داده‌ی تکی که صرف‌نظر از بستر خود غیرعادی است؛ مثل ثبت دمای 500 درجه یا تراکنش 50,000 دلاری از حسابی که معمولاً خریدهای 50 دلاری دارد.
  • ناهنجاری‌های زمینه‌ای (Contextual anomalies): داده‌ای که با توجه به بستر و زمینه‌اش غیرعادی محسوب می‌شود. دمای 90 درجه در تابستان عادی است، اما در زمستان ناهنجاری به شمار می‌آید؛ همان مقدار، اما در زمینه‌ای متفاوت.
  • ناهنجاری‌های تجمعی (Collective anomalies): توالی‌ای از داده‌ها که در قالب یک گروه غیرعادی هستند، هرچند تک‌تک داده‌ها به تنهایی عادی باشند. پنج بار تلاش ناموفق برای ورود به سیستم عادی است، اما پنجاه بار پشت‌سرهم یعنی حمله‌ی جستجوی فراگیر (brute-force).

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

انواع ناهنجاریناهنجاری نقطه‌ایناهنجاری زمینه‌ایناهنجاری جمعییک مقدار غیرعادی منفرددما: 500Fغیرعادی در زمینه90F در ژانویهتوالی غیرعادی50 ورود ناموفق

فرموله‌سازی مسئله به صورت بدون نظارت

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

  1. کاملاً بدون نظارت (Fully unsupervised): هیچ برچسبی ندارید. شناساگر را روی کل داده‌ها برازش می‌دهید و امیدوارید ناهنجاری‌ها آن‌قدر کم‌تعداد باشند که مدل داده‌های «عادی» را تخریب نکنند.
  2. نیمه‌نظارتی (Semi-supervised): مجموعه‌داده‌ای تمیز دارید که فقط شامل داده‌های عادی است. مدل را روی همین داده‌های تمیز برازش می‌دهید و سپس به بقیه‌ی داده‌ها امتیاز می‌دهید. در صورت امکان، این روش قدرتمندترین پیکربندی ممکن است.
  3. نظارت ضعیف (Weakly supervised): تعداد انگشت‌شماری ناهنجاریِ برچسب‌دار دارید. از آن‌ها برای ارزیابی استفاده کنید، نه آموزش. مدل را بدون نظارت آموزش دهید و سپس سنجه‌های دقت/بازخوانی (precision/recall) را روی همان زیرمجموعه‌ی برچسب‌دار بسنجید.

نکته‌ی کلیدی: تشخیص ناهنجاری اساساً با مسئله‌ی دسته‌بندی فرق دارد. شما توزیع داده‌های عادی را مدل‌سازی می‌کنید، نه مرز تصمیم‌گیری بین دو کلاس را.

موازنه‌ی روش بانظارت در برابر بدون نظارت

اگر ناهنجاری‌های برچسب‌دار داشته باشید، باید از آن‌ها برای آموزش استفاده کنید (دسته‌بندی با‌نظارت) یا فقط برای ارزیابی (تشخیص بدون نظارت)؟

روش بانظارت (نگاه به چشم مسئله‌ی دسته‌بندی):

  • دقیقاً همان ناهنجاری‌هایی را می‌گیرد که قبلاً دیده‌اید
  • دقت بالاتر روی ناهنجاری‌های شناخته‌شده
  • نادیده‌گرفتن کامل ناهنجاری‌های با الگوی جدید
  • نیاز به بازآموزی با پیدایش انواع جدید ناهنجاری
  • نیازمند نمونه‌های ناهنجار کافی (که اغلب بسیار کم است)

روش بدون نظارت (مدل‌سازی وضعیت عادی، نشانه‌گذاری انحرافات):

  • هرگونه انحراف از وضعیت عادی، از جمله انواع کاملاً جدید را شناسایی می‌کند
  • نیازی به ناهنجاری‌های برچسب‌دار ندارد
  • نرخ مثبت کاذب (false positive) بالاتر (چون هر چیز غیرعادی لزوماً بد نیست)
  • تاب‌آوری بیشتر در برابر جابه‌جایی توزیع (distribution shift)

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

روش Z-Score

ساده‌ترین روش ممکن. میانگین و انحراف معیار را برای هر ویژگی حساب کنید. هر داده‌ای را که بیش از kk انحراف معیار از میانگین فاصله دارد، نشانه‌گذاری کنید.

z_score = (x - mean) / std
anomaly if |z_score| > threshold

آستانه‌ی پیش‌فرض 3.0 است (در توزیع گاوسی، 99.7% داده‌های عادی در فاصله‌ی 3 انحراف معیار قرار می‌گیرند).

نقاط قوت: ساده، سریع و تفسیرپذیر («این مقدار 4.5 انحراف معیار از حالت عادی فاصله دارد»).

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

کاربرد ایده‌آل: پایش تک‌ویژگی‌هایی که داده‌هایشان تقریباً زنگوله‌ای است؛ مثل زمان پاسخ‌دهی سرور، تلرانس‌های ساخت و تولید یا داده‌های سنسورهایی با خط مبنای پایدار.

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

روش دامنه بین‌چارکی (IQR)

این روش نسبت به Z-score مقاوم‌تر است و به جای میانگین و انحراف معیار، از دامنه بین‌چارکی استفاده می‌کند.

Q1 = 25th percentile
Q3 = 75th percentile
IQR = Q3 - Q1
lower_bound = Q1 - factor * IQR
upper_bound = Q3 + factor * IQR
anomaly if x < lower_bound or x > upper_bound

ضریب پیش‌فرض برابر با 1.5 است.

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

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

نکته‌ی کاربردی: ضریب 1.5 در IQR معادل سبیل‌ها (whiskers) در نمودار جعبه‌ای است. نقاط بیرون از سبیل‌ها، داده‌های پرت احتمالی هستند. استفاده از ضریب 3.0 به جای 1.5 شناساگر را محافظه‌کارتر می‌کند (نشانه‌گذاری‌های کمتر و خطاهای مثبت کاذب پایین‌تر). انتخاب ضریب مناسب به آستانه‌ی تحمل شما در برابر هشدارهای اشتباه بستگی دارد.

جنگل انزوا (Isolation Forest)

ایده‌ی اصلی این است: ناهنجاری‌ها هم کم‌تعدادند و هم متفاوت. در یک افرازبندی تصادفی از داده‌ها، جداسازی ناهنجاری‌ها بسیار ساده‌تر است؛ یعنی با تعداد برش‌های تصادفی کمتری از بقیه‌ی داده‌ها جدا می‌شوند.

همه نقاط دادهویژگی تصادفی + انشعاب تصادفیافراز چپافراز راستویژگی تصادفی + انشعاب تصادفینقطه عادی - در عمق درختنیاز به انشعاب‌های بیشتر...ناهنجاری - جداسازی سریع (مسیر کوتاه)

نحوه‌ی کارکرد:

  1. ساخت چندین درخت تصادفی (یک جنگل انزوا)
  2. در هر گره، انتخاب تصادفی یک ویژگی و یک مقدار برشِ تصادفی بین کمینه و بیشینه‌ی آن ویژگی
  3. ادامه‌ی انشعاب‌ها تا جایی که هر داده منزوی شود (در برگ مخصوص به خود قرار گیرد)
  4. طول مسیر کوتاه‌تر ناهنجاری‌ها به‌طور میانگین در تمام درخت‌ها

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

امتیاز ناهنجاری بر اساس میانگین طول مسیر در تمام درخت‌ها محاسبه می‌شود که نسبت به طول مسیر مورد انتظار در یک درخت جستجوی دودویی تصادفی نرمال‌سازی شده است:

score(x)=2−average_path_length(x)/c(n)\text{score}(x) = 2^{-\text{average\_path\_length}(x) / c(n)}

که در آن c(n) طول مسیر مورد انتظار برای nn نمونه است. امتیاز نزدیک به 1 یعنی ناهنجاری، امتیاز نزدیک به 0.5 یعنی داده‌ی عادی و امتیاز نزدیک به 0 یعنی داده‌ی کاملاً عادی (در عمق خوشه‌های متراکم).

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

نقاط ضعف: ضعف در شناسایی ناهنجاری‌های درون نواحی متراکم (اثر پوشش یا masking) و کارایی کمتر برش تصادفی در صورت وجود ویژگی‌های نامربوط پرتعداد.

ابرپارامترهای کلیدی:

  • n_estimators: تعداد درخت‌ها. معمولاً 100 درخت کافی است. درخت‌های بیشتر پایداری امتیازها را بالا می‌برند اما سرعت پردازش را کم می‌کنند.
  • max_samples: تعداد نمونه‌ها در هر درخت. مقدار پیش‌فرض مقاله 256 است. مقادیر کوچک‌تر دقت تک‌تک درخت‌ها را کاهش می‌دهند ولی تنوع میان آن‌ها را بیشتر می‌کنند. اصلاً همین زیرنمونه‌گیری سرعت بالای Isolation Forest را رقم می‌زند؛ چرا که هر درخت بخش کوچکی از کل داده‌ها را می‌بیند.
  • contamination: نسبت تخمینی ناهنجاری‌ها. این پارامتر فقط برای تعیین آستانه استفاده می‌شود و اثری روی خود امتیازها ندارد.

فاکتور محلی داده‌ی پرت (Local Outlier Factor یا LOF)

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

نحوه‌ی کارکرد:

  1. پیدا کردن kk نزدیک‌ترین همسایه برای هر داده
  2. محاسبه‌ی چگالی دسترسی‌پذیری محلی (نشان‌دهنده‌ی میزان تراکم در همسایگی)
  3. مقایسه‌ی چگالی هر نقطه با چگالی همسایگانش
  4. در صورت پایین‌تر بودن چشمگیر چگالی یک نقطه نسبت به همسایگانش، آن نقطه داده‌ی پرت اعلام می‌شود

امتیاز LOF:

  • نزدیک به 1.0 یعنی چگالی مشابه همسایگان (عادی)
  • بزرگ‌تر از 1.0 یعنی چگالی کمتر از همسایگان (احتمالاً ناهنجار)
  • بسیار بزرگ‌تر از 1.0 (مثلاً 2.0 به بالا) یعنی چگالی به‌مراتب کمتر (به احتمال زیاد ناهنجاری)

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

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

نقاط ضعف: کندی روی مجموعه‌داده‌های بزرگ (پیچیدگی O(n2)O(n^2) در پیاده‌سازی ساده)، حساسیت به انتخاب پارامتر kk و ضعف در ابعاد بسیار بالا (نفرین ابعاد محاسبات فاصله را مختل می‌کند).

مقایسه‌ی روش‌ها

روش مفروضات سرعت پشتیبانی از ابعاد بالا تشخیص ناهنجاری محلی
Z-score توزیع نرمال بسیار سریع بله (به‌ازای هر ویژگی) خیر
IQR بدون فرض (برای هر ویژگی) بسیار سریع بله (به‌ازای هر ویژگی) خیر
Isolation Forest بدون فرض سریع بله تا حدی
LOF معنادار بودن فاصله کند ضعیف بله

چالش‌های ارزیابی

ارزیابی شناساگرهای ناهنجاری دشوارتر از دسته‌بندها است:

  • ناهمگونی شدید کلاس‌ها (Extreme class imbalance): وقتی 0.1% داده‌ها ناهنجارند، اگر برای همه پیش‌بینی «عادی» ثبت کنید، به صحت (accuracy) معادل 99.9% می‌رسید. در اینجا accuracy کاملاً بی‌فایده است.
  • گمراه‌کنندگی AUROC: در ناترازی‌های شدید، سنجه‌ی AUROC ممکن است خوب به نظر برسد، حتی اگر مدل در آستانه‌های عملیاتی بیشتر ناهنجاری‌ها را از دست بدهد.
  • سنجه‌های بهتر: Precision@k (از میان kk داده‌ی با بالاترین اولویت هشدار، چندتایشان ناهنجاری واقعی‌اند)، AUPRC (سطح زیر منحنی دقت-بازخوانی) و بازخوانی در یک نرخ مثبت کاذب ثابت.
داده‌های خامآموزش فقط روی داده‌های عادیامتیازدهی به همه داده‌های آزمونرتبه‌بندی بر اساس امتیاز ناهنجاریارزیابی K مورد علامت‌گذاری‌شده برترPrecision at K / AUPRC

خط لوله‌ی تشخیص ناهنجاری

تشخیص ناهنجاری در عمل این مراحل را طی می‌کند:

  1. جمع‌آوری داده‌های خط مبنا: در حالت ایده‌آل، بازه‌ای که مطمئنید هیچ ناهنجاری در آن رخ نداده (یا بسیار اندک بوده) است.
  2. مهندسی ویژگی‌ها: ویژگی‌های خام به‌اضافه‌ی ویژگی‌های مشتق‌شده (آماره‌های متحرک، ویژگی‌های زمانی، نسبت‌ها).
  3. آموزش شناساگر: برازش روی داده‌های خط مبنا تا مدل یاد بگیرد وضعیت «عادی» چه شکلی است.
  4. امتیازدهی به داده‌های جدید: محاسبه‌ی امتیاز ناهنجاری برای هر مشاهده‌ی جدید.
  5. انتخاب آستانه: تعیین حد قطع امتیاز. این یک تصمیم کسب‌وکاری است؛ آستانه‌ی بالاتر یعنی هشدار کاذب کمتر، اما ناهنجاری‌های ازدست‌رفته‌ی بیشتر.
  6. هشدار و بررسی: ارسال موارد نشانه‌گذاری‌شده برای بازبینی انسانی یا اقدام خودکار.
  7. جمع‌آوری بازخوردها: ثبت این نکته که موارد نشانه‌گذاری‌شده ناهنجاری واقعی بوده‌اند یا هشدار اشتباه. از این داده‌ها برای ارزیابی شناساگر و تنظیم تدریجی آستانه استفاده کنید.

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

تشخیص ناهنجاریمدل‌سازی داده‌های عادی، نشانه‌گذاری بقیه
ANOMALY DETECTIONThere are too few labelled anomalies to learn what abnormal looks like, so the detector learns the shape of normal instead: the dense region where ordinary points live. Anything that lands outside that fence is flagged, no matter what kind of anomaly it is. That is why the method needs no anomaly labels and still catches failure modes it has never seen.مدل این ناحیه را یاد می‌گیرد: عادیناهنجاری:خارج از محدودهٔ عادی
داده‌های ناهنجار برچسب‌دار بسیار کم‌تر از آن‌اند که بتوان ظاهر ناهنجاری را آموخت؛ بنابراین آشکارساز در عوض مرزهای حالت عادی را یاد می‌گیرد: ناحیهٔ متراکمی که نقاط معمولی در آن قرار دارند. هر داده‌ای که بیرون از این حصار بیفتد، صرف‌نظر از نوع ناهنجاری‌اش، پرچم‌گذاری می‌شود. به همین دلیل، این روش نیازی به برچسب‌های ناهنجاری ندارد و با این حال حالت‌های خرابی دیده‌نشده را نیز شناسایی می‌کند.

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

کد موجود در فایل code/anomaly_detection.py روش‌های Z-score، IQR و Isolation Forest را از صفر پیاده‌سازی می‌کند.

شناساگر Z-Score

def zscore_detect(X, threshold=3.0):
    mean = X.mean(axis=0)
    std = X.std(axis=0)
    std[std == 0] = 1.0
    z = np.abs((X - mean) / std)
    return z.max(axis=1) > threshold

ساده و برداری‌شده. در صورتی که هر کدام از ویژگی‌ها از حد آستانه فراتر بروند، نقطه نشانه‌گذاری می‌شود.

شناساگر IQR

def iqr_detect(X, factor=1.5):
    q1 = np.percentile(X, 25, axis=0)
    q3 = np.percentile(X, 75, axis=0)
    iqr = q3 - q1
    iqr[iqr == 0] = 1.0
    lower = q1 - factor * iqr
    upper = q3 + factor * iqr
    outside = (X < lower) | (X > upper)
    return outside.any(axis=1)

ساخت جنگل انزوا از صفر

پیاده‌سازی از صفر، درخت‌های انزوایی می‌سازد که فضای ویژگی‌ها را به شکل تصادفی افراز می‌کنند:

def _c_factor(n):
    if n <= 1:
        return 0.0
    if n == 2:
        return 1.0
    return 2.0 * (np.log(n - 1) + 0.5772156649) - 2.0 * (n - 1.0) / n


class IsolationTree:
    def __init__(self, max_depth):
        self.max_depth = max_depth

    def fit(self, X, depth=0):
        n, p = X.shape
        if depth >= self.max_depth or n <= 1:
            self.is_leaf = True
            self.size = n
            return self
        self.is_leaf = False
        self.feature = np.random.randint(p)
        x_min = X[:, self.feature].min()
        x_max = X[:, self.feature].max()
        if x_min == x_max:
            self.is_leaf = True
            self.size = n
            return self
        self.threshold = np.random.uniform(x_min, x_max)
        left_mask = X[:, self.feature] < self.threshold
        self.left = IsolationTree(self.max_depth).fit(X[left_mask], depth + 1)
        self.right = IsolationTree(self.max_depth).fit(X[~left_mask], depth + 1)
        return self

    def path_length(self, x, depth=0):
        if self.is_leaf:
            return depth + _c_factor(self.size)
        if x[self.feature] < self.threshold:
            return self.left.path_length(x, depth + 1)
        return self.right.path_length(x, depth + 1)

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

کلاس IsolationForest مجموعه‌ای از درخت‌ها را در بر می‌گیرد:

class IsolationForest:
    def __init__(self, n_estimators=100, max_samples=256, seed=42):
        self.n_estimators = n_estimators
        self.max_samples = max_samples
        self.seed = seed
        self.trees = []
        self.n_train = 0

    def fit(self, X):
        self.n_train = X.shape[0]
        rng = np.random.RandomState(self.seed)
        self.trees = []
        sample_size = min(self.max_samples, X.shape[0])
        max_depth = int(np.ceil(np.log2(sample_size)))
        for _ in range(self.n_estimators):
            idx = rng.choice(X.shape[0], size=sample_size, replace=False)
            tree = IsolationTree(max_depth=max_depth)
            tree.fit(X[idx])
            self.trees.append(tree)
        return self

    def anomaly_score(self, X):
        avg_path = np.zeros(X.shape[0])
        for tree in self.trees:
            for i in range(X.shape[0]):
                avg_path[i] += tree.path_length(X[i])
        avg_path /= self.n_estimators
        c = _c_factor(min(self.max_samples, self.n_train))
        return 2.0 ** (-avg_path / c) if c > 0 else np.zeros(X.shape[0])

ضریب نرمال‌سازی c(n) (همان _c_factor در کد) طول مسیر مورد انتظار در جستجوی ناموفق یک درخت جستجوی دودویی با nn عضو است. مقدار آن برابر با 2 * H(n-1) - 2*(n-1)/n است که در آن H عدد هارمونیک است. این کد بر اساس مقاله‌ی اصلی Isolation Forest، از تقریب استاندارد H(i) ≈ ln(i) + 0.5772 (ثابت اویلر) استفاده می‌کند. این نرمال‌سازی امکان مقایسه‌ی امتیازها را در مجموعه‌داده‌هایی با ابعاد متفاوت فراهم می‌آورد.

سناریوهای نمایشی

این کد چندین سناریوی آزمایشی ایجاد می‌کند:

  1. تک‌خوشه همراه با داده‌های پرت: یک خوشه‌ی گاوسی 2D با ناهنجاری‌هایی که دور از مرکز تزریق شده‌اند. همه‌ی روش‌ها باید از پس این سناریو برآیند.
  2. داده‌های چندحالته: سه خوشه با اندازه‌ها و تراکم‌های مختلف. داده‌های بین خوشه‌ها ناهنجارند. روش Z-score در اینجا افت می‌کند؛ چون بازه‌ی هر ویژگی بسیار عریض می‌شود.
  3. داده‌های ابعاد بالا: مجموعه‌ای با 50 ویژگی که ناهنجاری‌ها فقط در 5 ویژگی نمود پیدا می‌کنند. این سناریو می‌سنجد که آیا روش‌ها می‌توانند ناهنجاری‌ها را در زیرمجموعه‌ای از ویژگی‌ها شناسایی کنند یا خیر.

هر دمو همه‌ی روش‌ها را با معیارهای دقت، بازخوانی، F1 و Precision@k مقایسه می‌کند.

استفاده از ابزارهای آماده

استفاده در sklearn (با تکیه بر توابع آماده‌ی کتابخانه، نه نسخه‌های دست‌ساز):

from sklearn.ensemble import IsolationForest
from sklearn.neighbors import LocalOutlierFactor

iso = IsolationForest(n_estimators=100, contamination=0.05, random_state=42)
iso.fit(X_train)
predictions = iso.predict(X_test)

lof = LocalOutlierFactor(n_neighbors=20, contamination=0.05, novelty=True)
lof.fit(X_train)
predictions = lof.predict(X_test)

توجه داشته باشید که پارامتر contamination نسبت تخمینی ناهنجاری‌ها را تعیین می‌کند. تنظیم درست آن اهمیت زیادی دارد؛ مقدار بیش از حد پایین باعث جا افتادن ناهنجاری‌ها و مقدار بسیار بالا باعث ایجاد هشدارهای نادرست می‌شود.

کد موجود در anomaly_detection.py پیاده‌سازی‌های از صفر را روی داده‌هایی یکسان در مقابل sklearn مقایسه می‌کند.

پارامتر Contamination در sklearn

پارامتر contamination در sklearn صرفاً آستانه‌ی تبدیل امتیازهای پیوسته به پیش‌بینی‌های باینری را تعیین می‌کند و تغییری در خود امتیازها نمی‌دهد.

iso_5 = IsolationForest(contamination=0.05)
iso_10 = IsolationForest(contamination=0.10)

هر دو مدل امتیازهای ناهنجاری کاملاً یکسانی تولید می‌کنند. اما iso_5 تنها 5% داده‌های اول را نشانه‌گذاری می‌کند و iso_10 به سراغ 10% اول می‌رود. اگر نرخ واقعی ناهنجاری‌ها را نمی‌دانید (که معمولاً هم نمی‌دانید)، پارامتر را روی "auto" بگذارید و مستقیماً با خود امتیازهای خام کار کنید. سپس آستانه را بر اساس هزینه‌ی موازنه‌ی میان خطاهای مثبت کاذب و منفی کاذب تنظیم کنید.

مدل One-Class SVM

یکی دیگر از شناساگرهای ناهنجاری بدون نظارت که ارزش بررسی دارد One-Class SVM است. این الگوریتم مرزی دور داده‌های عادی در فضای ویژگی ابعاد بالا ترسیم می‌کند (با استفاده از ترفند کرنل).

from sklearn.svm import OneClassSVM

oc_svm = OneClassSVM(kernel="rbf", gamma="auto", nu=0.05)
oc_svm.fit(X_train)
predictions = oc_svm.predict(X_test)

پارامتر nu تخمینی از نسبت ناهنجاری‌ها ارائه می‌دهد. One-Class SVM روی داده‌های کوچک تا متوسط خوب جواب می‌دهد، اما برای داده‌های واقعاً حجیم مقیاس‌پذیر نیست (ماتریس کرنل رشد درجه‌دو دارد).

پیش‌نمایش روش اتوانکودر (Autoencoder)

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

این رویکرد را در فاز 3 (یادگیری عمیق) پوشش می‌دهیم، اما اصول کار یکسان است: مدل‌سازی وضعیت عادی و نشانه‌گذاری هر چیزی که انحراف دارد.

تشخیص ناهنجاری گروهی (Ensemble)

همان‌طور که روش‌های گروهی باعث ارتقای دسته‌بندی می‌شوند (درس 11)، ترکیب چند شناساگر ناهنجاری هم دقت تشخیص را بالا می‌برد. ساده‌ترین رویکرد:

  1. اجرای چند شناساگر (Z-score، IQR، Isolation Forest و LOF)
  2. نرمال‌سازی امتیاز هر شناساگر در بازه‌ی [0, 1]
  3. میانگین‌گیری از امتیازهای نرمال‌شده
  4. نشانه‌گذاری داده‌هایی که میانگین امتیازشان از آستانه بالاتر است

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

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

ملاحظات محیط عملیاتی

  1. جابه‌جایی آستانه (Threshold drift): با تغییر توزیع داده‌ها، آستانه‌ی ثابت قدیمی می‌شود. توزیع امتیازهای ناهنجاری را رصد و آن را دوره‌ای به‌روزرسانی کنید.
  2. فرسودگی ناشی از هشدار (Alert fatigue): اگر هشدارهای اشتباه بیش از حد باشد، اپراتورها دیگر به آن توجهی نمی‌کنند. کار را با آستانه‌ای بالا آغاز کنید (هشدارهای کمتر اما مطمئن‌تر) و با شکل‌گیری اعتماد، آن را کاهش دهید.
  3. رویکرد گروهی: در محیط پروداکشن، چند شناساگر را ترکیب کنید. تنها در شرایطی یک نقطه را ناهنجار اعلام کنید که چندین روش مختلف بر سر آن توافق داشته باشند؛ این کار نرخ مثبت کاذب را به‌شدت کاهش می‌دهد.
  4. مهندسی ویژگی‌ها: تکیه بر ویژگی‌های خام به‌ندرت کافی است. آماره‌های متحرک، نسبت‌ها، زمان سپری‌شده از آخرین رخداد و ویژگی‌های حوزه‌ی تخصصی کسب‌وکار را اضافه کنید. داشتن مجموعه‌ای از ویژگی‌های خوب اهمیت بیشتری از انتخاب خود الگوریتم دارد.
  5. حلقه‌ی بازخورد: هر زمان اپراتورها موارد هشدار داده‌شده را بررسی کرده و آن‌ها را تأیید یا رد می‌کنند، نتیجه را به سیستم برگردانید. داده‌های برچسب‌دار را به مرور زمان جمع‌آوری کنید تا شناساگر ارزیابی و بهبود یابد.

خروجی این درس

از این درس خروجی‌های زیر به دست می‌آید:

  • outputs/skill-anomaly-detector.md -- مهارت تصمیم‌گیری برای انتخاب شناساگر مناسب
  • code/anomaly_detection.py -- پیاده‌سازی Z-score، IQR و Isolation Forest از صفر به همراه مقایسه با sklearn

انتخاب آستانه

امتیاز ناهنجاری مقداری پیوسته است. برای رسیدن به یک تصمیم صفر و یکی، نیاز به آستانه دارید. این انتخاب، تصمیمی تجاری و مرتبط با کسب‌وکار است، نه یک مسئله‌ی صرفاً فنی.

این دو سناریو را مقایسه کنید:

  • تشخیص تقلب: از دست دادن کلاهبرداری هزینه‌ی سنگینی دارد (تراکنش‌های برگشتی، سلب اعتماد مشتری). در مقابل، هشدارهای اشتباه فقط 5 دقیقه از زمان تحلیل‌گر را برای بررسی می‌گیرد. آستانه را پایین بگذارید تا تقلب‌های بیشتری کشف شوند و هشدارهای اشتباه بیشتری را به جان بخرید.
  • تعمیر و نگهداری تجهیزات: یک هشدار کاذب به توقف بی‌مورد دستگاه و هزینه‌ی 50,000 دلاری منجر می‌شود، اما از دست رفتن خرابی، خسارت تعمیراتی 500,000 دلاری به بار می‌آورد. آستانه را طوری تعیین کنید که موازنه‌ی این دو هزینه بهینه شود.

در هر دو حالت، بهترین آستانه به نسبت هزینه‌ی خطای مثبت کاذب به منفی کاذب بستگی دارد. نمودار دقت و بازخوانی را در آستانه‌های مختلف رسم کنید، تابع هزینه را روی آن بیندازید و نقطه‌ای را که کمترین هزینه را دارد انتخاب کنید.

مقیاس‌پذیری در محیط پروداکشن

برای تشخیص ناهنجاری بلادرنگ در محیط پروداکشن:

  1. آموزش دسته‌ای، امتیازدهی آنلاین: مدل را در بازه‌های منظم (روزانه، هفتگی) روی داده‌های عادی اخیر آموزش دهید. هر رخداد تازه را به محض ثبت، امتیازدهی کنید.
  2. تطابق دقیق محاسبات ویژگی‌ها: اگر برای آموزش از آماره‌های متحرک 30 روز گذشته استفاده کرده‌اید، برای محاسبه‌ی ویژگی‌های یک مشاهده‌ی جدید هم به تاریخچه‌ی 30 روزه نیاز دارید. داده‌های تاریخی لازم را در بافر نگه دارید.
  3. پایش توزیع امتیازها: توزیع امتیازهای ناهنجاری را در طول زمان زیر نظر بگیرید. اگر میانه‌ی امتیازها به مرور به سمت بالا رفت، یا داده‌ها دستخوش تغییر شده‌اند یا مدل قدیمی شده است.
  4. تفسیرپذیری: وقتی داده‌ای را ناهنجار می‌نامید، دلیلش را توضیح دهید. در روش Z-score: «ویژگی X به اندازه‌ی 4.2 انحراف معیار بالاتر از حد نرمال است.» در Isolation Forest: «این نقطه به‌طور میانگین تنها با 3.1 انشعاب منزوی شد (نقاط عادی 8.5 انشعاب زمان می‌برند).»

تمرین‌ها

  1. تنظیم آستانه. شناساگر Z-score را با آستانه‌هایی از 1.0 تا 5.0 با گام‌های 0.5 اجرا کنید. دقت و بازخوانی را در هر آستانه رسم کنید. نقطه‌ی ایده‌آل (sweet spot) داده‌های شما کجاست؟

  2. ناهنجاری‌های چندمتغیره. داده‌ای دوبعدی بسازید که هر یک از ویژگی‌های آن به تنهایی کاملاً عادی به نظر برسند، اما ترکیب آن‌ها در کنار هم ناهنجار باشد (مثلاً نقاطی که از قطر اصلی خوشه فاصله دارند). نشان دهید که چطور روش Z-score در سطح هر ویژگی آن‌ها را جا می‌اندازد، اما الگوریتم Isolation Forest آن‌ها را شناسایی می‌کند.

  3. پیاده‌سازی LOF از صفر. فاکتور محلی داده‌ی پرت (LOF) را با الگوریتم k-نزدیک‌ترین همسایه پیاده‌سازی کنید. خروجی را با LocalOutlierFactor در کتابخانه‌ی sklearn روی همان داده‌ها مقایسه کنید. تست را با مقادیر k=10k=10 و k=50k=50 پیش ببرید؛ تغییر پارامتر kk چه تأثیری بر نتایج می‌گذارد؟

  4. تشخیص ناهنجاری استریمینگ. شناساگر Z-score را طوری تغییر دهید که در بستر استریمینگ کار کند: میانگین و واریانس جاری را با ورود داده‌های جدید به‌روزرسانی کنید (الگوریتم برخط ولفورد - Welford). آن را با روش Z-score دسته‌ای روی داده‌های یکسان مقایسه کنید.

  5. ارزیابی در دنیای واقعی. مجموعه‌داده‌ای با ناهنجاری‌های مشخص انتخاب کنید (مانند داده‌های کلاهبرداری کارت اعتباری در Kaggle). هر چهار روش را با سنجه‌های precision@100، precision@500 و AUPRC بسنجید. کدام روش بهترین عملکرد را دارد؟ چرا؟

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

اصطلاح چیزی که بقیه می‌گویند معنای دقیق فنی
ناهنجاری (Anomaly) «داده‌ی پرت، مقدار غیرعادی» نقطه‌ای از داده‌ها که به‌طرز چشمگیری از الگوی مورد انتظار داده‌های عادی فاصله دارد
ناهنجاری نقطه‌ای (Point anomaly) «یک مقدار عجیب‌وغریب» مشاهده‌ای منفرد که صرف‌نظر از بستر خود، غیرعادی است
ناهنجاری زمینه‌ای (Contextual anomaly) «مقدار عادی، بستر اشتباه» مشاهده‌ای که با توجه به بستر خود (زمان، مکان و غیره) غیرعادی است، هرچند ممکن است در بستری دیگر عادی باشد
جنگل انزوا (Isolation Forest) «برش‌های تصادفی برای یافتن داده‌های پرت» مجموعه‌ای از درخت‌های تصادفی که ناهنجاری‌ها را با تعداد برش‌های کمتری نسبت به نقاط عادی منزوی می‌کند
فاکتور محلی داده‌ی پرت (Local Outlier Factor) «مقایسه‌ی چگالی با همسایه‌ها» روشی که نقاطی را با چگالی محلی به‌مراتب کمتر از چگالی همسایگانشان، به عنوان داده‌ی پرت علامت می‌زند
Z-score «انحراف معیار از میانگین» رابطه‌ی (x−mean)/std(x - \text{mean}) / \text{std}، نشان می‌دهد یک نقطه بر حسب واحد انحراف معیار چقدر از مرکز توزیع فاصله دارد
دامنه بین‌چارکی (IQR) «فاصله‌ی بین چارک اول و سوم» رابطه‌ی Q3−Q1Q3 - Q1، سنجه‌ای برای پراکندگی 50% از داده‌های میانی که برای تشخیص مقاوم داده‌های پرت به کار می‌رود
Contamination «نسبت تقریبی ناهنجاری‌ها» ابرپارامتری که به شناساگر اعلام می‌کند چه نسبتی از داده‌ها را باید ناهنجار علامت‌گذاری کند
Precision@k «در میان k گزینه‌ی اول، چندتایشان واقعی است» دقت محاسبه‌شده تنها روی kk داده‌ای که بالاترین میزان شک و تردید را دارند؛ کاربردی برای ارزیابی ناهنجاری‌های نامتوازن
AUPRC «سطح زیر منحنی دقت-بازخوانی» معیاری که عملکرد دقت و بازخوانی را در تمامی آستانه‌ها خلاصه می‌کند و برای داده‌های نامتوازن گزینه‌ای بهتر از AUROC است

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

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

چرا در تشخیص ناهنجاری نمی‌توان همیشه از مدل‌های دسته‌بندی بانظارت استفاده کرد؟

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

الگوریتم Isolation Forest چگونه داده‌های پرت را تشخیص می‌دهد؟

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

چرا سنجه‌ی Accuracy یا AUROC برای ارزیابی تشخیص ناهنجاری گمراه‌کننده است؟

وقتی تنها 0.1% داده‌ها ناهنجار باشند، پیش‌بینی همه‌ی داده‌ها به عنوان «عادی» به دقت 99.9% می‌رسد در حالی که حتی یک ناهنجاری هم کشف نشده است؛ برای همین سنجه‌هایی مانند Precision@k و AUPRC مناسب‌ترند.

پارامتر contamination در scikit-learn چه کاری انجام می‌دهد؟

این پارامتر صرفاً آستانه‌ی تبدیل امتیازهای پیوسته‌ی ناهنجاری به خروجی باینری (1 و 1-) را مشخص می‌کند و هیچ اثری روی مقدار امتیازهای خام تولیدشده توسط مدل ندارد.

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

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