الفصل 3: إصدار النماذج
المقدمة
إصدار النماذج أمر حيوي لأنظمة تعلم الآلة في الإنتاج. فهو يمكّن من تتبع النماذج ومقارنتها والتراجع عنها مع الحفاظ على سلالة واضحة من البيانات إلى النشر.
لماذا إصدار النماذج؟
تحدي إدارة النماذج
فوائد إصدار النماذج
| الفائدة | الوصف |
|---|---|
| إمكانية التكرار | إعادة إنشاء أي إصدار من النموذج |
| السلالة | تتبع البيانات → النموذج → التنبؤات |
| المقارنة | مقارنة الأداء عبر الإصدارات |
| التراجع | التراجع بسرعة إلى الإصدارات السابقة |
| الامتثال | سجل تدقيق للصناعات المنظمة |
مفاهيم سجل النماذج
المكونات الرئيسية
مراحل دورة حياة النموذج
| المرحلة | الوصف |
|---|---|
| لا شيء | الحالة الأولية، مسجل حديثًا |
| مرحلة الاختبار | يتم اختبار/التحقق من النموذج |
| الإنتاج | النموذج يخدم حركة المرور الحية |
| مؤرشف | النموذج مهمل، يحتفظ به للرجوع إليه |
سجل نماذج MLflow
إعداد MLflow
# تثبيت MLflow
pip install mlflow
# بدء خادم MLflow مع مخزن الخلفية
mlflow server \
--backend-store-uri postgresql://user:pass@localhost/mlflow \
--default-artifact-root s3://mlflow-artifacts/ \
--host 0.0.0.0 \
--port 5000
تسجيل النماذج
# register_model.py
import mlflow
import mlflow.sklearn
from sklearn.ensemble import RandomForestClassifier
# تعيين URI التتبع
mlflow.set_tracking_uri("http://localhost:5000")
# تدريب وتسجيل النموذج
with mlflow.start_run() as run:
# تدريب النموذج
model = RandomForestClassifier(n_estimators=100)
model.fit(X_train, y_train)
# تسجيل المعاملات
mlflow.log_param("n_estimators", 100)
mlflow.log_param("max_depth", None)
# تسجيل المقاييس
accuracy = model.score(X_test, y_test)
mlflow.log_metric("accuracy", accuracy)
# تسجيل النموذج
mlflow.sklearn.log_model(
model,
"model",
registered_model_name="customer-churn-model"
)
print(f"Model registered with run_id: {run.info.run_id}")
إدارة إصدارات النماذج
# model_management.py
from mlflow.tracking import MlflowClient
client = MlflowClient()
# الحصول على جميع إصدارات النموذج
versions = client.search_model_versions("name='customer-churn-model'")
for v in versions:
print(f"Version {v.version}: {v.current_stage}")
# نقل النموذج إلى مرحلة الاختبار
client.transition_model_version_stage(
name="customer-churn-model",
version=2,
stage="Staging"
)
# نقل النموذج إلى الإنتاج
client.transition_model_version_stage(
name="customer-churn-model",
version=2,
stage="Production",
archive_existing_versions=True # أرشفة الإصدار الحالي في الإنتاج
)
# إضافة وصف لإصدار النموذج
client.update_model_version(
name="customer-churn-model",
version=2,
description="تحسين هندسة الميزات، زيادة AUC بنسبة 5%"
)
# إضافة علامات
client.set_model_version_tag(
name="customer-churn-model",
version=2,
key="validation_status",
value="approved"
)
تحميل النماذج من السجل
# load_model.py
import mlflow
# تحميل إصدار محدد
model_v1 = mlflow.sklearn.load_model(
"models:/customer-churn-model/1"
)
# تحميل حسب المرحلة
production_model = mlflow.sklearn.load_model(
"models:/customer-churn-model/Production"
)
# تحميل نموذج مرحلة الاختبار
staging_model = mlflow.sklearn.load_model(
"models:/customer-churn-model/Staging"
)
# إجراء التنبؤات
predictions = production_model.predict(X_new)
البيانات الوصفية للنموذج والسلالة
بطاقة نموذج شاملة
# model_card.py
import json
from dataclasses import dataclass, asdict
from typing import List, Dict, Optional
from datetime import datetime
@dataclass
class ModelCard:
"""توثيق النموذج المنظم وفقًا للمعايير الصناعية."""
# معلومات أساسية
model_name: str
version: str
description: str
owner: str
created_at: str
# معلومات التدريب
training_data: Dict[str, str]
features: List[str]
target: str
algorithm: str
hyperparameters: Dict[str, any]
# الأداء
metrics: Dict[str, float]
evaluation_data: str
# الاستخدام المقصود
intended_use: str
out_of_scope_use: str
# القيود والتحيزات
limitations: List[str]
ethical_considerations: List[str]
# السلالة
parent_model: Optional[str] = None
training_run_id: Optional[str] = None
data_version: Optional[str] = None
code_version: Optional[str] = None
# إنشاء بطاقة النموذج
model_card = ModelCard(
model_name="customer-churn-model",
version="2.1.0",
description="يتنبأ باحتمالية مغادرة العميل خلال 30 يومًا",
owner="ml-team@company.com",
created_at=datetime.now().isoformat(),
training_data={
"source": "s3://data-lake/customer-features/",
"date_range": "2023-01-01 to 2024-01-01",
"records": "1,500,000"
},
features=[
"account_age_days",
"total_purchases",
"days_since_last_login",
"support_tickets_90d",
"nps_score"
],
target="churned_within_30_days",
algorithm="GradientBoostingClassifier",
hyperparameters={
"n_estimators": 200,
"max_depth": 7,
"learning_rate": 0.1,
"min_samples_split": 10
},
metrics={
"auc_roc": 0.89,
"precision": 0.82,
"recall": 0.75,
"f1_score": 0.78
},
evaluation_data="s3://data-lake/test-set-2024-01/",
intended_use="تحديد العملاء المعرضين للخطر لحملات الاحتفاظ",
out_of_scope_use="تقييم الائتمان، قرارات التوظيف",
limitations=[
"قد يتدهور الأداء للعملاء الذين تقل أعمارهم عن 30 يومًا",
"لم يتم التحقق من صحة النموذج للعملاء من فئة المؤسسات",
"يتطلب حد أدنى من 6 أشهر من تاريخ المعاملات"
],
ethical_considerations=[
"لا ينبغي استخدام النموذج لرفض الخدمة",
"يجب مراجعة التنبؤات من قبل الإنسان قبل اتخاذ الإجراءات"
],
parent_model="customer-churn-model/1.0.0",
training_run_id="abc123def456",
data_version="data-v2.1.0",
code_version="git-sha-789xyz"
)
# حفظ بطاقة النموذج
with open("model_card.json", "w") as f:
json.dump(asdict(model_card), f, indent=2)
تسجيل سلالة النموذج
# lineage_tracking.py
import mlflow
import git
import hashlib
def get_code_version():
"""الحصول على تجزئة الالتزام الحالية في git."""
repo = git.Repo(search_parent_directories=True)
return repo.head.object.hexsha
def get_data_hash(data_path: str) -> str:
"""حساب تجزئة ملف البيانات."""
with open(data_path, 'rb') as f:
return hashlib.md5(f.read()).hexdigest()
def train_with_lineage(X_train, y_train, X_test, y_test, data_path: str):
"""تدريب النموذج مع تتبع السلالة الكاملة."""
with mlflow.start_run() as run:
# تتبع إصدار الكود
code_version = get_code_version()
mlflow.set_tag("code_version", code_version)
mlflow.set_tag("git_repo", "https://github.com/company/ml-project")
# تتبع إصدار البيانات
data_hash = get_data_hash(data_path)
mlflow.set_tag("data_hash", data_hash)
mlflow.set_tag("data_path", data_path)
# تتبع البيئة
mlflow.set_tag("python_version", "3.10.0")
mlflow.set_tag("sklearn_version", "1.3.0")
# تتبع تكوين التدريب
mlflow.log_param("training_samples", len(X_train))
mlflow.log_param("test_samples", len(X_test))
mlflow.log_param("features", list(X_train.columns))
# تدريب النموذج
model = GradientBoostingClassifier(
n_estimators=200,
max_depth=7,
learning_rate=0.1
)
model.fit(X_train, y_train)
# تسجيل المقاييس
train_accuracy = model.score(X_train, y_train)
test_accuracy = model.score(X_test, y_test)
mlflow.log_metric("train_accuracy", train_accuracy)
mlflow.log_metric("test_accuracy", test_accuracy)
# تسجيل النموذج مع التوقيع
from mlflow.models.signature import infer_signature
signature = infer_signature(X_train, model.predict(X_train))
mlflow.sklearn.log_model(
model,
"model",
signature=signature,
registered_model_name="customer-churn-model"
)
return run.info.run_id
استراتيجيات إصدار النماذج
الإصدار الدلالي للنماذج
MAJOR.MINOR.PATCH
أمثلة:
- 1.0.0 → 1.0.1 # تصحيح: إصلاح خطأ، بدون إعادة تدريب
- 1.0.0 → 1.1.0 # ثانوي: إعادة تدريب، نفس الهيكل
- 1.0.0 → 2.0.0 # رئيسي: هيكل جديد أو تغييرات جذرية
زيادة الإصدار التلقائية
# version_manager.py
import re
from enum import Enum
from mlflow.tracking import MlflowClient
class VersionBump(Enum):
MAJOR = "major"
MINOR = "minor"
PATCH = "patch"
def get_next_version(current_version: str, bump: VersionBump) -> str:
"""حساب الإصدار الدلالي التالي."""
match = re.match(r"(\d+)\.(\d+)\.(\d+)", current_version)
if not match:
return "1.0.0"
major, minor, patch = map(int, match.groups())
if bump == VersionBump.MAJOR:
return f"{major + 1}.0.0"
elif bump == VersionBump.MINOR:
return f"{major}.{minor + 1}.0"
else:
return f"{major}.{minor}.{patch + 1}"
def register_new_version(
model_name: str,
run_id: str,
bump: VersionBump,
description: str
):
"""تسجيل النموذج مع الإصدار المناسب."""
client = MlflowClient()
# الحصول على أحدث إصدار
versions = client.search_model_versions(f"name='{model_name}'")
if versions:
latest_version = max(v.version for v in versions)
latest_tags = client.get_model_version(model_name, latest_version).tags
current_semantic = latest_tags.get("semantic_version", "0.0.0")
else:
current_semantic = "0.0.0"
# حساب الإصدار الدلالي الجديد
new_semantic = get_next_version(current_semantic, bump)
# تسجيل النموذج
result = client.create_model_version(
name=model_name,
source=f"runs:/{run_id}/model",
run_id=run_id,
description=description
)
# تعيين علامة الإصدار الدلالي
client.set_model_version_tag(
name=model_name,
version=result.version,
key="semantic_version",
value=new_semantic
)
print(f"Registered {model_name} v{result.version} (semantic: {new_semantic})")
return result
سير عمل الموافقة على النموذج
خط أنابيب التحقق التلقائي
# model_validation.py
from typing import Dict, List, Tuple
import mlflow
from mlflow.tracking import MlflowClient
class ModelValidator:
"""التحقق من النماذج قبل النشر في الإنتاج."""
def __init__(self, model_name: str, version: int):
self.client = MlflowClient()
self.model_name = model_name
self.version = version
self.model_uri = f"models:/{model_name}/{version}"
def validate_performance(
self,
X_test,
y_test,
thresholds: Dict[str, float]
) -> Tuple[bool, Dict[str, float]]:
"""التحقق مما إذا كان النموذج يفي بعتبات الأداء."""
model = mlflow.sklearn.load_model(self.model_uri)
from sklearn.metrics import accuracy_score, roc_auc_score, f1_score
predictions = model.predict(X_test)
probabilities = model.predict_proba(X_test)[:, 1]
metrics = {
"accuracy": accuracy_score(y_test, predictions),
"auc_roc": roc_auc_score(y_test, probabilities),
"f1_score": f1_score(y_test, predictions)
}
# التحقق من العتبات
passed = all(
metrics.get(metric, 0) >= threshold
for metric, threshold in thresholds.items()
)
return passed, metrics
def validate_latency(
self,
X_sample,
max_latency_ms: float = 100
) -> Tuple[bool, float]:
"""التحقق مما إذا كان النموذج يفي بمتطلبات التأخير."""
import time
model = mlflow.sklearn.load_model(self.model_uri)
# تسخين
_ = model.predict(X_sample[:1])
# قياس التأخير
times = []
for _ in range(100):
start = time.time()
_ = model.predict(X_sample[:1])
times.append((time.time() - start) * 1000)
p99_latency = sorted(times)[98]
passed = p99_latency <= max_latency_ms
return passed, p99_latency
def validate_consistency(
self,
X_sample,
production_model_uri: str
) -> Tuple[bool, float]:
"""التحقق من تطابق التنبؤات مع نموذج الإنتاج."""
new_model = mlflow.sklearn.load_model(self.model_uri)
prod_model = mlflow.sklearn.load_model(production_model_uri)
new_predictions = new_model.predict(X_sample)
prod_predictions = prod_model.predict(X_sample)
agreement = (new_predictions == prod_predictions).mean()
passed = agreement >= 0.95 # على الأقل 95% توافق
return passed, agreement
def run_all_validations(
self,
X_test,
y_test,
thresholds: Dict[str, float]
) -> Dict[str, any]:
"""تشغيل جميع عمليات التحقق وإرجاع النتائج."""
results = {
"model_name": self.model_name,
"version": self.version,
"validations": {}
}
# التحقق من الأداء
perf_passed, perf_metrics = self.validate_performance(
X_test, y_test, thresholds
)
results["validations"]["performance"] = {
"passed": perf_passed,
"metrics": perf_metrics
}
# التحقق من التأخير
latency_passed, p99_latency = self.validate_latency(X_test[:100])
results["validations"]["latency"] = {
"passed": latency_passed,
"p99_ms": p99_latency
}
# النتيجة العامة
results["approved"] = all(
v["passed"] for v in results["validations"].values()
)
# تحديث علامات النموذج
status = "approved" if results["approved"] else "rejected"
self.client.set_model_version_tag(
name=self.model_name,
version=self.version,
key="validation_status",
value=status
)
return results
النقاط الرئيسية
ملخص
- استخدام سجل النماذج لإدارة النماذج المركزية
- تتبع السلالة الكاملة - البيانات، الكود، المعاملات، البيئة
- تنفيذ الإصدار الدلالي لتقدم الإصدارات الواضح
- إنشاء بطاقات النماذج للتوثيق والامتثال
- أتمتة التحقق قبل النشر في الإنتاج
- الحفاظ على سجلات التدقيق للصناعات المنظمة
تمرين عملي
- إعداد خادم تتبع MLflow
- تدريب وتسجيل إصدارات متعددة من النماذج
- إنشاء بطاقة نموذج لأفضل نموذج لديك
- تنفيذ خط أنابيب التحقق من النموذج
- ممارسة نقل النماذج بين المراحل
التالي: اختبار الوحدة 2