MLOps وDevOps لأنظمة الذكاء الاصطناعي

قنوات البيانات والميزات

18 دقيقة الدرس 2 من 28

قنوات البيانات والميزات

في البرمجيات التقليدية، يُتراجع عن النشر المعطوب في غضون دقائق. أما في التعلم الآلي، فالكارثة المقابلة صامتة تماماً: تنجرف قناة الميزات من المصدر، وتتدفق قيم قديمة إلى نموذج الخدمة، وتتراجع جودة التنبؤات دون أن يُسجَّل خطأ واحد. قنوات البيانات والميزات إذن ليست مجرد سباكة — إنها الهيكل الحامل لأي منصة تعلم آلي. يتناول هذا الدرس الركائز الثلاث التي تجعلها جاهزة للإنتاج: إصدار البيانات، ومخازن الميزات، وتنسيق القنوات.

إصدار البيانات

قابلية الاستنساخ هي الأساس غير القابل للتفاوض. إذا عجزت عن الإجابة عن سؤال "ما البيانات الدقيقة التي أنتجت هذا النموذج؟"، فلن تستطيع تصحيح الأخطاء أو التدقيق أو إعادة التدريب بأمان. على النطاق الكبير، تعيش البيانات الخام في تخزين الكائنات (S3، GCS) أو بيت بيانات (Delta Lake، Iceberg). تنقسم استراتيجيات الإصدار إلى عائلتين:

  • لقطات النسخ عند الكتابة — تحتفظ Delta Lake وApache Iceberg بسجل معاملات يوضح الملفات التي تنتمي إلى كل لقطة. لا أعباء نسخ للقراءات؛ والانتقال الزمني مجرد شرط SQL واحد. هذا هو النهج المفضل للجداول بحجم بيتابايت.
  • أشجار قابلة للعنونة بالمحتوى — تتعامل DVC (التحكم في إصدار البيانات) مع مجموعات البيانات كأشياء Git: كل إصدار عبارة عن تجزئة لمحتوياته، مع مؤشر .dvc رفيع يُودَع في مستودع الكود. يعمل مع أي نوع ملف وأي مخزن بعيد (S3، GCS، Azure، SSH).
# تهيئة DVC جانب مستودع Git موجود dvc init git add .dvc .dvcignore && git commit -m "Initialize DVC" # تتبع دليل مجموعة بيانات خام؛ البيانات الفعلية تذهب إلى التخزين البعيد dvc add data/raw/clicks_2025_06.parquet git add data/raw/clicks_2025_06.parquet.dvc .gitignore git commit -m "Track June 2025 click dataset v1" # رفع البيانات إلى المخزن البعيد على S3 (مُهيَّأ مسبقاً في .dvc/config) dvc push # على أي جهاز: استعادة البيانات الدقيقة لحزمة git محددة git checkout v1.3.0 dvc pull # يجلب الملفات المطابقة للتجزئة من المخزن البعيد # Delta Lake: الانتقال الزمني بدون DVC — SQL أصلي spark.read \ .format("delta") \ .option("versionAsOf", 42) \ .load("s3a://ml-lake/events/clicks")
في Uber وLinkedIn وMeta، يتتبع نظام النسب الأصلي إصدار مجموعة البيانات الذي أطعم كل جلسة تدريب. تكتب مهمة التدريب dataset_version=<hash> كبيانات وصفية في إدخال سجل النماذج. هذا يُغلق حلقة النسب وهو أول ما يفحصه مهندسو الإنجاز عند حوادث تراجع النماذج.

مخازن الميزات

يحل مخزن الميزات مشكلة الانحراف بين التدريب والخدمة — أكثر قاتل صامت شيوعاً في إنتاج التعلم الآلي. بدون مخزن ميزات، يحسب عالم البيانات rolling_7d_revenue بشكل مختلف في دفتر Jupyter (السجل الكامل) عما يفعله المهندس في خدمة مصغرة للخدمة (نافذة بث). يتدرب النموذج على توزيع ويستنتج على آخر.

يتكون مخزن الميزات من مستويين:

  • المخزن غير المتصل — عمودي (Parquet على S3، BigQuery، Snowflake). يُستخدم لوصلات صحيحة بحسب الوقت أثناء التدريب. "ما كانت عائدات المستخدم لمدة 7 أيام وقت طابع التدريب التسمية؟" هذا يمنع التسرب المستقبلي.
  • المخزن المتصل — قيمة مفتاح منخفض الكمون (Redis، DynamoDB، Cassandra). يقرأ مسار الخدمة متجهات الميزات المادية مسبقاً في أقل من 5 مللي ثانية. تزامن مهمة التحقيق من غير المتصل إلى المتصل بجدول زمني أو مشغل حدث.
Feature Store Architecture Data Sources Feature Store Consumers Event Stream (Kafka) Data Warehouse Operational DB (CDC) Batch Files (S3/GCS) Offline Store Parquet / BigQuery Online Store Redis / DynamoDB Feature Registry Schema + Lineage Training Jobs Model Serving Analytics / BI Materialization: Offline → Online
بنية مخزن الميزات: تتدفق البيانات من مصادر متعددة عبر المخازن غير المتصلة والمتصلة إلى مستهلكي التدريب والخدمة.

Feast هو الخيار الأكثر انتشاراً في المصادر المفتوحة. في الإنتاج، تُعرِّف الفرق الميزات ككائنات Python وتنشرها بأمر CLI واحد:

# feature_repo/features.py (تعريفات ميزات Feast) from feast import Entity, FeatureView, Field, FileSource from feast.types import Float32, Int64 from datetime import timedelta user = Entity(name="user_id", join_keys=["user_id"]) user_stats_source = FileSource( path="s3://ml-lake/features/user_stats/", timestamp_field="event_timestamp", ) user_stats_fv = FeatureView( name="user_stats", entities=[user], ttl=timedelta(days=7), schema=[ Field(name="rolling_7d_revenue", dtype=Float32), Field(name="purchase_count_30d", dtype=Int64), Field(name="last_active_days_ago", dtype=Int64), ], source=user_stats_source, ) # --- تطبيق على السجل وتجسيد في المخزن المتصل --- # feast apply # تسجيل المخطط # feast materialize-incremental $(date -u +%Y-%m-%dT%H:%M:%S) # --- التدريب: استرجاع صحيح بحسب الوقت --- # store.get_historical_features( # entity_df=training_labels, # يجب أن يتضمن event_timestamp # features=["user_stats:rolling_7d_revenue"], # ).to_df() # --- الخدمة: بحث متصل منخفض الكمون --- # store.get_online_features( # features=["user_stats:rolling_7d_revenue"], # entity_rows=[{"user_id": "u_12345"}], # ).to_dict()
الانحراف بين التدريب والخدمة يقتل بصمت. بدون مخزن ميزات، نمط الفشل الشائع هو: يُعيد المهندس كتابة حساب الإيرادات لإصلاح خطأ في الخدمة، تنخفض الدقة 4%، ولا ينطلق أي تنبيه لأن النموذج ما زال يُعيد أعداداً صحيحة. عرِّف الميزات دائماً مرة واحدة، في المخزن، وليكن كل من مهمة التدريب ومسار الخدمة يستدعيان التعريف ذاته.

تنسيق القنوات

قناة التعلم الآلي ليست DAG واحداً — إنها تركيب من ثلاثة DAGs مترابطة: استيعاب البيانات (دفعي أو بث)، هندسة الميزات (تحويلات، تجميعات، وصلات)، والتدريب (يُطلَق ربما بانجراف البيانات لا بجدول زمني). يجب أن يتعامل المنسق مع التبعيات بين الثلاثة، وإعادة المحاولات، والملء الخلفي، والجدولة الواعية بالموارد (عقد GPU مكلفة — المهام التي يمكنها التشغيل على CPU لا ينبغي أن تستحوذ على فترات GPU).

خيارات التنسيق مرتبة حسب العبء التشغيلي:

  • Airflow / Astronomer — المعيار الصناعي لهندسة البيانات. DAGs بلغة Python؛ نظام المشغِّلات واسع. الضعف الرئيسي: عملية الجدولة نقطة فشل واحدة في التثبيتات مفتوحة المصدر؛ Celery أو Kubernetes executor مطلوب على نطاق واسع.
  • Prefect 2 / Prefect Cloud — صُمِّم لإصلاح نقاط ألم Airflow. المهام Python عادي مزيَّن بـ@task؛ التدفقات تنشئ DAGs تلقائياً. مستوى التحكم SaaS؛ العوامل تعمل داخل بنيتك التحتية. مناسب للفرق التي تريد التحرك بسرعة بدون رعاية مجدول.
  • Kubeflow Pipelines (KFP) — Kubernetes أصلي. كل خطوة حاوية؛ YAML القناة مُجمَّع من Python SDK. أنسب عندما يجب أن تكون كل خطوة قابلة للاستنساخ ومحاطة بحاوية — خاصة خطوات التدريب التي تحتاج طلبات موارد Kubernetes.
  • Metaflow — مبني من Netflix، أصلي لـAWS. التفريع واستئناف التشغيلات الفاشلة والإصدار أمور من الدرجة الأولى. مفضل عندما تكون الفرق غنية بعلماء البيانات والخدمة الذاتية أهم من التحكم التشغيلي.
# Prefect 2: تعريف قناة تعلم آلي بسيطة from prefect import flow, task from prefect.task_runners import ConcurrentTaskRunner import pandas as pd @task(retries=3, retry_delay_seconds=60, log_prints=True) def ingest_raw_data(date: str) -> pd.DataFrame: return pd.read_parquet(f"s3://ml-lake/raw/clicks/{date}.parquet") @task def compute_features(df: pd.DataFrame) -> pd.DataFrame: df["rolling_7d_revenue"] = ( df.groupby("user_id")["revenue"] .transform(lambda x: x.rolling(7, min_periods=1).sum()) ) return df[["user_id", "event_timestamp", "rolling_7d_revenue"]] @task def materialize_to_feature_store(features: pd.DataFrame) -> None: # الكتابة إلى المخزن غير المتصل؛ مهمة التجسيد اللاحقة تُزامن مع Redis features.to_parquet( "s3://ml-lake/features/user_stats/", partition_cols=["event_timestamp"], ) @flow(task_runner=ConcurrentTaskRunner(), name="daily-feature-pipeline") def feature_pipeline(date: str = "2025-06-11") -> None: raw = ingest_raw_data(date) features = compute_features(raw) materialize_to_feature_store(features) if __name__ == "__main__": feature_pipeline()
صمِّم للملء الخلفي منذ اليوم الأول. القناة التي لا تعمل إلا على "اليوم" ستكلفك كثيراً أول مرة يُكتشف فيها خطأ في ميزات عمرها أسبوع. وسِّع كل قناة بـexecution_date، عامل الحالة كإضافة فقط في المخزن غير المتصل، واختبر مسار الملء الخلفي في CI. Netflix وAirbnb يجريان اختبارات ملء خلفي آلية على كل مساهمة في كود قناة الميزات.

أنماط الفشل في الإنتاج

يستوعب المهندسون المتقدمون هذه الأعطال الشائعة:

  • البيانات المتأخرة الوصول — مصادر البث تضمن تسليماً مرة على الأقل لكن ليس الترتيب. يجب تهيئة العلامات المائية (Flink، Spark Structured Streaming) بميزانيات الكمون الواقعية، لا النظرية. علامة مائية 10 دقائق على قناة 5% من أحداثها تصل بعد 12 دقيقة تُسقط تلك الأحداث بصمت من نوافذ الميزات.
  • انجراف المخطط — فريق مصدر يضيف عمود، أو يعيد تسمية حقل، أو يغير نوعاً. يجب تشغيل مكتبة Great Expectations أو Soda كأول مهمة في كل قناة استيعاب وتفشل سريعاً عند انتهاكات المخطط قبل أن تلوث البيانات مخزن الميزات.
  • انتشار القيم الفارغة — معرف مستخدم مفقود عبر 10 وصلات يصبح مجموعة بيانات تدريبية بـ3% قيم فارغة في ميزة لم يرَ النموذج فيها قيمة فارغة قط، مما يتحول إلى NaN وقت الخدمة يتعامل معه بعض النماذج بصمت وبعضها يسجله كصفر.
  • انحراف الساعة بين المتصل وغير المتصل — إذا كانت مهمة التجسيد تعمل كل ساعة وتطلب النموذج ميزات أحدث من 60 دقيقة، فلديك مشكلة ميزات قديمة صامتة في نافذة الذروة قُبيل تشغيل التجسيد.
المقياسان اللذان تتتبعهما كل فرق منصة التعلم الآلي على مستوى الميزات: حداثة الميزة (عمر P99 لقيمة المخزن المتصل وقت الاستعلام) وتغطية الميزة (نسبة طلبات الخدمة التي جميع ميزاتها المطلوبة غير فارغة وغير قديمة). انخفاض التغطية تحت 95% على ميزة عالية القيمة يُعامَل كحادثة P1، لا كتذكرة جودة بيانات.