- KFS × Navera
- IoT & AI for Smart Agriculture
- السيشن 16
تطبيق نموذج متقدم + التفسير الزراعي
Advanced Model Application + Agricultural Interpretation
نموذج تصنيف يقرر: هل يحتاج الحقل ري خلال 24 ساعة؟ نتعامل مع البيانات غير المتوازنة، نقيّم بـ Precision و Recall و F1 ومصفوفة الالتباس، نتحقق بـ Cross-validation زمني، نكشف الإفراط في التعلم، ونفسر قرارات النموذج زراعياً بأهمية الميزات — ثم نختار عتبة القرار بمنطق تكلفة الماء مقابل الإجهاد المائي.
نظرة عامة على السيشن
بنهاية السيشن ستكون قادراً على
- تتعامل مع Imbalanced classes (14.5% فقط «ري») بـ
class_weightومقاييس مناسبة. - تقرأ Confusion matrix وتحسب Precision / Recall / F1 وتربطها بتكلفة زراعية.
- تستخدم TimeSeriesSplit cross-validation وتكشف Overfitting بمقارنة التدريب والاختبار.
- تفسر النموذج بـ Permutation importance (و SHAP اختيارياً) وتقيّم منطقيته زراعياً.
- تختار عتبة القرار (Threshold) بناءً على تكلفة FN مقابل FP.
التطبيق في مزرعة الكلية
نموذج «هل نروي خلال 24 ساعة؟» لحقل الخضر، مقارنة بقاعدة المزارع البسيطة، ثم جلسة تفسير مع مشرف المزرعة: هل الميزات المهمة للنموذج منطقية زراعياً؟ وما العتبة المقبولة بين هدر الماء وخطر الإجهاد؟
المُخرج (Deliverable)
نموذج زراعي موثوق وقابل للتفسير (irrigation_model.joblib) يتفوق على القاعدة البسيطة، مع تقرير: مصفوفة الالتباس، Cross-validation، أهمية الميزات، والعتبة المختارة وتبريرها.
خطة السيشن (180 دقيقة)
- 0:00 مراجعة مراجعة: درس الـ Accuracy الخادعة من السيشن 15 10 د
- 0:10 شرح البيانات غير المتوازنة، Precision/Recall/F1، محاكي العتبة 25 د
- 0:35 شرح اختيار الميزات، Overfitting، Cross-validation الزمني، التفسير 25 د
- 1:00 استراحة استراحة 10 د
- 1:10 مشروع المشروع الجاهز: نموذج قرار الري s16 35 د
- 1:45 تطبيق مختبر إضافي: تصنيف الأرز (UCI) وتوصية المحصول (Kaggle) 30 د
- 2:15 ميداني جلسة التفسير الزراعي واختيار العتبة مع مشرف المزرعة 35 د
- 2:50 تقييم اختبار سريع + الواجب 10 د
الأدوات والمكونات
قاموس المصطلحات (Glossary)
فئة نادرة (ري 14.5%) مقابل فئة غالبة.
TP, FP, FN, TN — أين أصاب النموذج وأين أخطأ.
من كل مرات «اروِ» كم كانت صحيحة؟ (تقل مع هدر الماء).
من كل مرات الحاجة الفعلية كم اكتشفنا؟ (تقل مع الإجهاد المائي).
متوسط توافقي للـ Precision والـ Recall.
جعل خطأ الفئة النادرة أغلى أثناء التدريب.
تقييم على عدة تقسيمات للتأكد من ثبات الأداء.
كل طية تتدرب على الماضي وتختبر على ما بعده.
إبقاء الميزات المفيدة وحذف الضارة أو المسربة.
كم يسوء الأداء إذا خلطنا عموداً عشوائياً؟
تفسير مساهمة كل ميزة في قرار واحد بعينه.
احتمال ≥ العتبة ⇒ «اروِ» (الافتراضي 0.5).
البيانات غير المتوازنة والمقاييس
25 دقيقةفي بياناتنا 85.5% من الساعات «لا ري» و 14.5% «ري خلال 24 ساعة». نموذج كسول يقول دائماً «لا» يحقق Accuracy = 85.5% ولا يكتشف حالة ري واحدة! لذلك نقيس بـ F1 و Recall.
| النموذج قال: اروِ | النموذج قال: انتظر | |
|---|---|---|
| فعلاً احتاج ري | TP ✅ رينا في الوقت | FN ❌ إجهاد مائي — نقص إنتاج قد لا يُعوض |
| فعلاً لم يحتج | FP ⚠️ هدر ماء وكهرباء + غسيل أسمدة | TN ✅ وفرنا الماء |
الميزات والإفراط والتحقق والتفسير
25 دقيقة| الميزة | مسموح؟ | السبب |
|---|---|---|
| soil_vwc الآن | ✅ | تقرؤها العقدة الآن |
| vwc_drop_24h | ✅ | معدل الجفاف من الماضي |
| et0_24h، الإشعاع، الرياح | ✅ | محطة الطقس / Open-Meteo |
| ساعات منذ آخر ري | ⚠️ | مفيدة جداً، لكنها تجعل النموذج يحفظ جدول المزرعة بدل التربة |
| رطوبة التربة بعد 6 ساعات | ❌ | تسرب بيانات — من المستقبل! |
| irrigation_mm في نفس الساعة | ❌ | هو الإجابة نفسها |
import sys; sys.path.insert(0, "projects/ai")
from common import load_hourly, simulate_irrigated_field, build_irrigation_dataset
from sklearn.ensemble import RandomForestClassifier
from sklearn.model_selection import TimeSeriesSplit, cross_val_score
from sklearn.metrics import f1_score
X, y = build_irrigation_dataset(simulate_irrigated_field(load_hourly()), 24)
X_tr, y_tr = X.loc[:"2023-12-31"], y.loc[:"2023-12-31"]
X_te, y_te = X.loc["2024-01-01":], y.loc["2024-01-01":]
for depth in (3, 12, None): # None = unlimited depth -> overfitting risk
m = RandomForestClassifier(n_estimators=150, max_depth=depth, class_weight="balanced", n_jobs=-1, random_state=0)
cv = cross_val_score(m, X_tr, y_tr, cv=TimeSeriesSplit(5), scoring="f1", n_jobs=-1)
m.fit(X_tr, y_tr)
print(f"max_depth={str(depth):5s} CV F1={cv.mean():.3f} train F1={f1_score(y_tr, m.predict(X_tr)):.3f}"
f" test F1={f1_score(y_te, m.predict(X_te)):.3f}")
الشجرة بعمق غير محدود تحفظ بيانات التدريب (Train F1 ≈ 1.0) بينما ينخفض أداؤها على 2024 — هذا هو الإفراط في التعلم.
نتيجة تجربة العمق
لاحظ: العمق 3 بسيط جداً (Underfitting: التدريب والاختبار منخفضان معاً)، والعمق غير المحدود يحفظ التدريب (1.000) ويسوء في الاختبار. العمق 12 هو التوازن الذي اخترناه للنموذج.
المخرجات الفعلية عند التشغيل (Real output)
max_depth=3 CV F1=0.763 train F1=0.748 test F1=0.754 max_depth=12 CV F1=0.719 train F1=0.978 test F1=0.863 max_depth=None CV F1=0.708 train F1=1.000 test F1=0.850
المشروع الجاهز: قرار الري
35 دقيقةs16_irrigation_classifier.py — النموذج الذي سيعمل في المنصة
يدرب Random Forest بأوزان فئات متوازنة، يقارنه بقاعدة المزارع، يتحقق زمنياً، يفحص الإفراط، يحسب أهمية الميزات، ويحفظ irrigation_model.joblib الذي تحمّله منصة Smart Farm في السيشن 17. المخرجات الفعلية:
cd projects/ai
python s16_irrigation_classifier.py
المخرجات الفعلية عند التشغيل (Real output)
Class balance (1 = irrigate within 24 h): {0: 0.855, 1: 0.145}
Rule baseline (VWC < 0.300) F1=0.612 balanced acc=0.889
TimeSeriesSplit F1 (train year): [0.832 0.811 0.867 0.857 0.78 ] mean = 0.83
Train F1=0.898 | Test F1=0.865 (a big gap would mean overfitting)
Balanced accuracy: 0.944
Confusion matrix [[TN FP] [FN TP]]:
[[7211 276]
[ 97 1199]]
precision recall f1-score support
wait 0.99 0.96 0.97 7487
irrigate 0.81 0.93 0.87 1296
Permutation importance (drop in F1 when shuffled):
soil_vwc 0.6975
radiation 0.0394
hour 0.0334
et0_24h 0.0300
vwc_drop_24h 0.0159
wind 0.0035
rain_24h 0.0027
air_humidity 0.0026
air_temp 0.0001
soil_temp -0.0004
Irrigation events in 2024 (simulated): 54 | water applied: 2069 mm| المؤشر | القيمة | التفسير الزراعي |
|---|---|---|
| F1 القاعدة البسيطة | 0.612 | قاعدة «VWC < 0.30» تروي مبكراً جداً أو متأخراً |
| F1 النموذج (2024) | 0.865 | تحسن 41% على القاعدة |
| Recall (ري) | 0.93 | اكتشف 93% من حالات الحاجة — 97 ساعة فقط فاتته من 1296 |
| Precision (ري) | 0.81 | من كل 5 تنبيهات «اروِ» واحد مبكر — مقبول لأن الري الفعلي يتم في السادسة صباحاً |
| Train 0.898 / Test 0.865 | فرق صغير | لا يوجد إفراط ملحوظ |
| أهم ميزة | soil_vwc (0.70) | منطقي: الرطوبة الحالية تحدد المتبقي في «الدلو». ثم الإشعاع و ET0 (سرعة الاستهلاك) والساعة (موعد الري الصباحي) |
"""Session 16 - Will this field need irrigation within the next 24 hours? (binary classifier)
Covers: feature selection, imbalanced classes (class_weight), TimeSeriesSplit cross-validation,
overfitting check (train vs test), comparison with a simple agronomic rule, permutation importance.
Optional: SHAP (pip install shap) for per-decision explanations.
Data: real KFS weather 2023-2024 + simulated irrigated field (FAO-56 water balance, common.py)
Output: outputs/irrigation_model.joblib - used by the Smart Farm web app (session 17).
"""
import joblib
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.inspection import permutation_importance
from sklearn.metrics import balanced_accuracy_score, classification_report, confusion_matrix, f1_score
from sklearn.model_selection import TimeSeriesSplit, cross_val_score
from common import (OUT, RAW_MM, REFILL_POINT, ROOT_DEPTH_M, THETA_FC, build_irrigation_dataset, load_hourly,
simulate_irrigated_field)
H = 24
df = simulate_irrigated_field(load_hourly())
X, y = build_irrigation_dataset(df, H) # see common.py: features known NOW + label = irrigation within 24 h
print("Class balance (1 = irrigate within 24 h):", y.value_counts(normalize=True).round(3).to_dict())
X_tr, X_te = X.loc[:"2023-12-31"], X.loc["2024-01-01":]
y_tr, y_te = y.loc[X_tr.index], y.loc[X_te.index]
# ---- Baseline: simple threshold rule ("irrigate when VWC is close to the refill point")
warn_level = REFILL_POINT + 0.25 * RAW_MM / (1000 * ROOT_DEPTH_M)
rule = (X_te["soil_vwc"] < warn_level).astype(int)
print(f"Rule baseline (VWC < {warn_level:.3f}) F1={f1_score(y_te, rule):.3f} "
f"balanced acc={balanced_accuracy_score(y_te, rule):.3f}")
model = RandomForestClassifier(n_estimators=250, max_depth=12, min_samples_leaf=10,
class_weight="balanced", n_jobs=-1, random_state=0)
cv = cross_val_score(model, X_tr, y_tr, cv=TimeSeriesSplit(n_splits=5), scoring="f1", n_jobs=-1)
print("TimeSeriesSplit F1 (train year):", cv.round(3), "mean =", round(cv.mean(), 3))
model.fit(X_tr, y_tr)
pred = model.predict(X_te)
print(f"\nTrain F1={f1_score(y_tr, model.predict(X_tr)):.3f} | Test F1={f1_score(y_te, pred):.3f}"
" (a big gap would mean overfitting)")
print("Balanced accuracy:", round(balanced_accuracy_score(y_te, pred), 3))
print("Confusion matrix [[TN FP] [FN TP]]:\n", confusion_matrix(y_te, pred))
print(classification_report(y_te, pred, target_names=["wait", "irrigate"]))
imp = permutation_importance(model, X_te, y_te, n_repeats=5, random_state=0, n_jobs=-1, scoring="f1")
ranking = pd.Series(imp.importances_mean, index=X.columns).sort_values(ascending=False).round(4)
print("Permutation importance (drop in F1 when shuffled):\n", ranking.to_string())
joblib.dump({"model": model, "features": list(X.columns), "refill_point": REFILL_POINT,
"field_capacity": THETA_FC, "horizon_h": H}, OUT / "irrigation_model.joblib", compress=3)
print("\nSaved ->", OUT / "irrigation_model.joblib")
print("Irrigation events in 2024 (simulated):", int((df.loc['2024', 'irrigation_mm'] > 0).sum()),
"| water applied:", round(df.loc['2024', 'irrigation_mm'].sum()), "mm")
try:
import shap # noqa: F401
print("SHAP available: shap.TreeExplainer(model).shap_values(X_te[:200]) in Jupyter.")
except ImportError:
print("Tip: pip install shap - to explain each individual irrigation decision.")
مختبر إضافي: الأرز والمحاصيل
30 دقيقةRice (Cammeo and Osmancik)
3,810 حبة أرز من صنفين تركيين، لكل حبة 7 خصائص شكلية مستخرجة من الصور (المساحة، المحيط، المحور الأكبر والأصغر، الاستطالة، المساحة المحدبة، الامتداد). مثال كلاسيكي على التصنيف في فحص جودة الحبوب.
- الحجم3,810 × 8
- الترخيصCC BY 4.0 — Cinar & Koklu (2019)
- نوع البياناتحقيقية
Crop Recommendation Dataset
N, P, K، الحرارة، الرطوبة، pH، الأمطار ⇒ أنسب محصول من 22 محصولاً. حمّلها بحساب Kaggle مجاني وضع Crop_recommendation.csv داخل مجلد datasets — السكربت يكتشفها تلقائياً.
- الحجم2,200 × 8
- الترخيصApache 2.0
- نوع البياناتجاهزة للتدريب (مصدرها منشور على Kaggle)
s16b_crop_and_rice_classifiers.py
Logistic Regression (مع StandardScaler داخل Pipeline لمنع التسرب) مقابل Random Forest. المخرجات الفعلية لجزء الأرز:
python s16b_crop_and_rice_classifiers.py
المخرجات الفعلية عند التشغيل (Real output)
[Rice] LogisticRegression test accuracy = 0.921 [Rice] RandomForest test accuracy = 0.922 [[373 35] [ 39 506]] [Crop] Crop_recommendation.csv not found - download it from Kaggle (see docstring).
لماذا Pipeline؟
make_pipeline(StandardScaler(), LogisticRegression()) يحسب متوسط وانحراف التطبيع من بيانات التدريب فقط داخل كل طية — نفس قاعدة منع التسرب من السيشن 14، مطبقة تلقائياً.
جلسة التفسير الزراعي
35 دقيقةimport joblib
bundle = joblib.load("projects/ai/outputs/irrigation_model.joblib")
proba = bundle["model"].predict_proba(X_te[bundle["features"]])[:, 1]
COST_FN = 5.0 # one missed irrigation hour: water stress, yield loss (agree on it with the agronomist)
COST_FP = 1.0 # one unnecessary irrigation hour: water + electricity + fertilizer leaching
for t in (0.3, 0.4, 0.5, 0.6, 0.7):
pred = (proba >= t).astype(int)
fn = int(((pred == 0) & (y_te == 1)).sum()); fp = int(((pred == 1) & (y_te == 0)).sum())
print(f"threshold={t:.1f} FN={fn:4d} FP={fp:4d} cost={COST_FN * fn + COST_FP * fp:7.0f} F1={f1_score(y_te, pred):.3f}")
التكلفة لكل عتبة (FN أغلى 5 مرات من FP)
بهذه التكاليف أقل تكلفة كلية عند العتبة 0.4 رغم أن F1 الأعلى عند 0.6 — القرار الزراعي ≠ أفضل مقياس إحصائي.
المخرجات الفعلية عند التشغيل (Real output)
threshold=0.3 FN= 26 FP= 508 cost= 638 F1=0.826 threshold=0.4 FN= 49 FP= 358 cost= 603 F1=0.860 threshold=0.5 FN= 97 FP= 276 cost= 761 F1=0.865 threshold=0.6 FN= 153 FP= 196 cost= 961 F1=0.868 threshold=0.7 FN= 231 FP= 124 cost= 1279 F1=0.857
هل يفكر النموذج مثل المهندس الزراعي؟
غرفة المزرعة مع مشرف المزرعة / أستاذ المحاصيل — ثم زيارة حقل الخضر
- عرض الأهمية
اعرض ترتيب الميزات على المشرف بدون أرقام واسأله: هل هذا ترتيب منطقي؟ ماذا ينقص؟ (مرحلة نمو النبات؟ نوع التربة؟)
- حالات فردية
اختر 3 ساعات قال فيها النموذج «اروِ» واحدة «انتظر» خطأً (FN). ما ظروفها؟ هل كان المشرف سيخطئ أيضاً؟
- التكلفة
اتفقوا معاً على COST_FN و COST_FP لهذا المحصول وهذا الموسم، وشغّلوا كود العتبة.
- القرار
اختاروا العتبة ووثقوا السبب. ستُستخدم في المنصة (predictor.py) في السيشن 17.
- تحقق ميداني
قارنوا توقع النموذج الآن لحقل الخضر بحالة النبات الفعلية (ذبول الظهيرة؟ لون الأوراق؟).
| الحالة | قرار النموذج | رأي المشرف | السبب | العتبة المقترحة |
|---|---|---|---|---|
اختبر فهمك
6 أسئلة · نتيجة فوريةالواجب والتسليم
الواجب الأسبوعي (Weekly Assignment)
اكتب تقرير النموذج (صفحتان): المقارنة بالقاعدة، مصفوفة الالتباس مفسرة زراعياً، أهمية الميزات، والعتبة المختارة مع التكلفة.
احذف ميزة soil_vwc وأعد التدريب: كم ينخفض F1؟ ماذا يعني ذلك لأهمية الحساس في الحقل؟
اختياري: نفذ جزء Crop Recommendation بعد التحميل من Kaggle وأدخل نتائج تحليل تربة حقيقية من معمل الكلية.
قائمة التسليم (Submission Checklist)
مراجع ومصادر
استعد للسيشن القادمة
السيشن القادمة ندمج النموذج في المنصة: لوحة التحكم ستعرض توقع الري حياً. تأكد من وجود outputs/irrigation_model.joblib.