كشف الشذوذ العملي: إنذار ذكي عندما تتصرف الآلة بغرابة
ما هو كشف الشذوذ ولماذا يهم الصناعة؟
كشف الشذوذ هو تحديد الأنماط التي تختلف جوهرياً عن السلوك الطبيعي. في البيئة الصناعية، الشذوذ قد يعني:
- بداية تلف في محمل أو مضخة
- تسرب في نظام هيدروليكي
- خلل في مستشعر يعطي قراءات خاطئة
- تغير في جودة المواد الخام
لماذا لا نستخدم التصنيف العادي؟
بيانات الأعطال نادرة جداً (أقل من 1% عادةً). لا تتوفر أمثلة كافية لتدريب مصنف تقليدي. كشف الشذوذ يتعلم فقط من البيانات الطبيعية ويكتشف أي انحراف عنها.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# محاكاة بيانات مستشعر اهتزاز محرك صناعي
np.random.seed(42)
n_normal = 5000
n_anomaly = 50
# بيانات طبيعية: اهتزاز مستقر
normal_data = pd.DataFrame({
'vibration_x': np.random.normal(2.0, 0.3, n_normal),
'vibration_y': np.random.normal(1.8, 0.25, n_normal),
'temperature': np.random.normal(65, 3, n_normal)
})
# بيانات شاذة: اهتزاز مرتفع وحرارة عالية
anomaly_data = pd.DataFrame({
'vibration_x': np.random.normal(4.5, 0.8, n_anomaly),
'vibration_y': np.random.normal(4.0, 0.7, n_anomaly),
'temperature': np.random.normal(85, 5, n_anomaly)
})
all_data = pd.concat([normal_data, anomaly_data], ignore_index=True)
labels = np.array([0] * n_normal + [1] * n_anomaly) # 0=طبيعي، 1=شاذ
print(f"البيانات: {len(all_data)} سجل ({n_anomaly} شاذ = {n_anomaly/len(all_data)*100:.1f}%)")
الحدود الإحصائية: أبسط طريقة
الطريقة الأبسط: أي قراءة تبتعد أكثر من 3 انحرافات معيارية عن المتوسط تُعد شاذة:
def statistical_anomaly(data, column, n_sigma=3):
mean = data[column].mean()
std = data[column].std()
lower = mean - n_sigma * std
upper = mean + n_sigma * std
is_anomaly = (data[column] < lower) | (data[column] > upper)
return is_anomaly, lower, upper
# تطبيق على بيانات الاهتزاز
anomalies_stat, lower, upper = statistical_anomaly(all_data, 'vibration_x')
print(f"حدود الاهتزاز الطبيعي: [{lower:.2f}, {upper:.2f}]")
print(f"عدد الشذوذ المكتشف: {anomalies_stat.sum()}")
# رسم الحدود
plt.figure(figsize=(12, 4))
plt.plot(all_data['vibration_x'].values, linewidth=0.5, alpha=0.7)
plt.axhline(y=upper, color='r', linestyle='--', label=f'الحد العلوي ({upper:.2f})')
plt.axhline(y=lower, color='r', linestyle='--', label=f'الحد السفلي ({lower:.2f})')
plt.ylabel('الاهتزاز (mm/s)')
plt.title('كشف الشذوذ بالحدود الإحصائية')
plt.legend()
plt.tight_layout()
plt.savefig('statistical_bounds.png', dpi=150)
plt.show()
Isolation Forest: عزل الشاذ عن الطبيعي
Isolation Forest يعمل على مبدأ بسيط: النقاط الشاذة أسهل في العزل من النقاط الطبيعية:
from sklearn.ensemble import IsolationForest
from sklearn.metrics import classification_report
# تدريب على البيانات الطبيعية فقط
X_train = normal_data.values
X_all = all_data.values
iso_forest = IsolationForest(
n_estimators=100,
contamination=0.02, # نسبة الشذوذ المتوقعة
random_state=42
)
iso_forest.fit(X_train)
# التنبؤ: -1 شاذ، 1 طبيعي
predictions = iso_forest.predict(X_all)
anomaly_pred = (predictions == -1).astype(int)
print("نتائج Isolation Forest:")
print(classification_report(labels, anomaly_pred, target_names=['طبيعي', 'شاذ']))
# درجة الشذوذ: كلما كانت أصغر كان أكثر شذوذاً
scores = iso_forest.decision_function(X_all)
all_data['anomaly_score'] = scores
plt.figure(figsize=(10, 5))
plt.scatter(range(len(scores)), scores, c=labels, cmap='coolwarm', s=5, alpha=0.5)
plt.axhline(y=0, color='black', linestyle='--', label='حد القرار')
plt.ylabel('درجة الشذوذ')
plt.xlabel('رقم القراءة')
plt.title('درجات الشذوذ - Isolation Forest')
plt.legend()
plt.tight_layout()
plt.savefig('isolation_forest.png', dpi=150)
plt.show()
Autoencoder: التعلم العميق لكشف الشذوذ
Autoencoder يتعلم ضغط البيانات الطبيعية وإعادة بنائها. عندما يواجه بيانات شاذة، يفشل في إعادة البناء:
from sklearn.preprocessing import StandardScaler
from sklearn.neural_network import MLPRegressor
# تطبيع البيانات
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(normal_data.values)
X_all_scaled = scaler.transform(all_data.values)
# Autoencoder باستخدام MLPRegressor (بديل بسيط عن Keras)
autoencoder = MLPRegressor(
hidden_layer_sizes=(16, 4, 16), # ضغط: 3 -> 16 -> 4 -> 16 -> 3
activation='relu',
max_iter=200,
random_state=42
)
# التدريب: المدخل = المخرج (البيانات الطبيعية)
autoencoder.fit(X_train_scaled, X_train_scaled)
# حساب خطأ إعادة البناء
X_reconstructed = autoencoder.predict(X_all_scaled)
reconstruction_error = np.mean((X_all_scaled - X_reconstructed) ** 2, axis=1)
# تحديد الحد بناءً على البيانات الطبيعية
normal_errors = reconstruction_error[:n_normal]
threshold = np.percentile(normal_errors, 99)
anomaly_ae = (reconstruction_error > threshold).astype(int)
print(f"حد خطأ إعادة البناء: {threshold:.4f}")
print(f"الشذوذ المكتشف: {anomaly_ae.sum()}")
plt.figure(figsize=(12, 4))
plt.plot(reconstruction_error, linewidth=0.5, alpha=0.7)
plt.axhline(y=threshold, color='r', linestyle='--', label=f'الحد ({threshold:.4f})')
plt.ylabel('خطأ إعادة البناء')
plt.title('كشف الشذوذ بـ Autoencoder')
plt.legend()
plt.tight_layout()
plt.savefig('autoencoder_anomaly.png', dpi=150)
plt.show()
ضبط حساسية الإنذار: موازنة الدقة
التحدي الحقيقي هو ضبط الحساسية: إنذارات كثيرة كاذبة تفقد ثقة المشغلين، وقليلة جداً تفوّت الأعطال:
from sklearn.metrics import precision_score, recall_score
# اختبار حدود مختلفة
thresholds = np.percentile(normal_errors, [90, 95, 97, 99, 99.5])
print("تأثير اختيار حد الإنذار:")
print(f"{'الحد المئوي':<15} {'الدقة':<10} {'الاستدعاء':<10} {'إنذارات كاذبة'}")
print("-" * 55)
for pct, thresh in zip([90, 95, 97, 99, 99.5], thresholds):
pred = (reconstruction_error > thresh).astype(int)
if pred.sum() > 0:
prec = precision_score(labels, pred, zero_division=0)
rec = recall_score(labels, pred, zero_division=0)
false_alarms = ((pred == 1) & (labels == 0)).sum()
print(f"{pct}%{'':<11} {prec:.3f}{'':<5} {rec:.3f}{'':<5} {false_alarms}")
القاعدة العملية
- أنظمة السلامة الحرجة: حساسية عالية (استدعاء > 95%)، نتقبل إنذارات كاذبة
- مراقبة روتينية: توازن (دقة واستدعاء > 80%)
- تنبيهات إعلامية: دقة عالية (> 95%)، تفويت بعض الأحداث مقبول
مثال عملي: نظام إنذار ذكي لمحرك صناعي
import pandas as pd
import numpy as np
from sklearn.ensemble import IsolationForest
from sklearn.preprocessing import StandardScaler
# بيانات محرك أسبوع كامل (قراءة كل 30 ثانية)
np.random.seed(42)
n = 20160
timestamps = pd.date_range('2025-03-01', periods=n, freq='30s')
motor = pd.DataFrame({
'timestamp': timestamps,
'vibration': np.random.normal(2.5, 0.3, n),
'temperature': np.random.normal(68, 2, n),
'current': np.random.normal(12.5, 0.8, n),
'sound_db': np.random.normal(75, 2, n)
})
# حقن أحداث شاذة واقعية
# حدث 1: تآكل محمل (اهتزاز تدريجي)
ramp = np.linspace(0, 4, 200)
motor.loc[8000:8199, 'vibration'] += ramp
motor.loc[8000:8199, 'sound_db'] += ramp * 1.5
# حدث 2: ارتفاع حرارة مفاجئ
motor.loc[15000:15050, 'temperature'] += 20
# بناء النظام
features = ['vibration', 'temperature', 'current', 'sound_db']
scaler = StandardScaler()
# تدريب على أول 5000 قراءة (فترة طبيعية مؤكدة)
train_data = scaler.fit_transform(motor.loc[:4999, features])
iso_model = IsolationForest(n_estimators=200, contamination=0.01, random_state=42)
iso_model.fit(train_data)
# مراقبة كامل البيانات
all_scaled = scaler.transform(motor[features])
motor['anomaly_score'] = iso_model.decision_function(all_scaled)
motor['is_anomaly'] = iso_model.predict(all_scaled) == -1
# تقرير الإنذارات
alerts = motor[motor['is_anomaly']].copy()
print(f"إجمالي الإنذارات: {len(alerts)} من {len(motor)} قراءة")
print(f"نسبة الإنذار: {len(alerts)/len(motor)*100:.2f}%")
if len(alerts) > 0:
print(f"\nأول إنذار: {alerts['timestamp'].iloc[0]}")
print(f"آخر إنذار: {alerts['timestamp'].iloc[-1]}")
الخلاصة
كشف الشذوذ هو من أكثر تطبيقات التعلم الآلي قيمة في الصناعة. تعلمنا ثلاث طرق متدرجة في التعقيد: الحدود الإحصائية للحالات البسيطة، وIsolation Forest كحل متوازن وعملي، وAutoencoder للأنماط المعقدة. المفتاح هو ضبط حساسية النظام حسب أهمية المعدة وتكلفة التوقف. في الدرس القادم سنتعلم تحليل السلاسل الزمنية والتنبؤ.