الوحدة 5 — الحاويات المخصّصة وسكربتات التدريب
XGBoost المدمج ممتاز عندما تناسب الخوارزميّة المهمّة تمامًا. لكنّ الواقع أقلّ لطفًا: الفريق يريد تجربة RandomForest أوّلًا، ثمّ LogisticRegression كنقطة مرجع، ثمّ إضافة معالجة أوّليّة داخل نفس التدريب. الحلّ الرسميّ لهذه الحالات هو مود سكربت (Script Mode).
مود سكربت في ثلاث جمل
- AWS يقدّم صورة Docker مسبقة الإعداد لكلّ إطار عمل معروف (scikit-learn وPyTorch وTensorFlow وHugging Face...).
- أنت تكتب سكربت بايثون واحدًا يعرف كيف يقرأ البيانات ويدرّب وي حفظ النموذج.
- SageMaker يُشغّل هذا السكربت داخل الحاوية دون أن تلمس Dockerfile.
هذا ما يميّز مود سكربت من «حاوية مخصّصة كلّيًّا»: لا داعي لبناء صورة Docker، ولا لدفعها إلى ECR. 90٪ من مشاريع تعلّم الآلة تكتفي بمود سكربت.
نقطة الدخول: سكربت train.py
نُنشئ ملفًّا اسمه train.py بجانب الدفتر. هذا السكربت هو ما ستُشغّله المهمّة:
# train.py
import argparse
import os
import joblib
import pandas as pd
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import f1_score
def main():
parser = argparse.ArgumentParser()
# المعاملات الفائقة
parser.add_argument("--n-estimators", type=int, default=200)
parser.add_argument("--max-depth", type=int, default=8)
# المسارات (يمرّرها SageMaker عبر متغيّرات بيئة)
parser.add_argument("--model-dir", default=os.environ["SM_MODEL_DIR"])
parser.add_argument("--train", default=os.environ["SM_CHANNEL_TRAIN"])
parser.add_argument("--valid", default=os.environ["SM_CHANNEL_VALIDATION"])
args = parser.parse_args()
train_df = pd.read_parquet(f"{args.train}/train.parquet")
valid_df = pd.read_parquet(f"{args.valid}/valid.parquet")
y_train = train_df.pop("churn")
y_valid = valid_df.pop("churn")
model = RandomForestClassifier(
n_estimators=args.n_estimators,
max_depth=args.max_depth,
n_jobs=-1,
random_state=42,
)
model.fit(train_df, y_train)
preds = model.predict(valid_df)
score = f1_score(y_valid, preds)
print(f"validation:f1={score:.4f}")
joblib.dump(model, f"{args.model-dir}/model.joblib")
if __name__ == "__main__":
main()
يُطبع سطر validation:f1=... بصيغة يعرف SageMaker استخراجها كمقياس منظّم يظهر في Studio وفي عمليّة الضبط التلقائيّ (الوحدة 6).
متغيّرات بيئة SageMaker
يمرّر SageMaker لسكربتك جملةً من المتغيّرات البيئيّة التي يجب معرفتها بأسمائها:
| المتغيّر | ما يعنيه |
|---|---|
SM_MODEL_DIR | مسار محلّيّ (/opt/ml/model) يُنسخ محتواه إلى S3 بعد الانتهاء |
SM_CHANNEL_TRAIN | مسار محلّيّ لقناة train من estimator.fit({"train": ...}) |
SM_CHANNEL_VALIDATION | مسار قناة validation |
SM_HP_<NOM> | كلّ معامل فائق تمرّره صراحةً في hyperparameters |
SM_NUM_GPUS | عدد وحدات GPU المتاحة على المثيل |
SM_CURRENT_HOST | اسم مضيف المهمّة (algo-1, algo-2...) في التدريب الموزّع |
SM_HOSTS | قائم ة كلّ المضيفين في مجموعة التدريب |
الفخّ الأشيع: نسيان SM_CHANNEL_... والاعتماد على مسار مطلق مثل /opt/ml/input/data/train/. السكربت سيعمل، لكنّه يصبح هشًّا إذا أُعيدت تسمية القناة. الاعتماد على متغيّرات البيئة يفصل السكربت عن اسم القناة.
تشغيل المهمّة عبر SKLearn
يقدّم SDK فئة مخصّصة لكلّ إطار عمل. لـscikit-learn:
from sagemaker.sklearn.estimator import SKLearn
sk_estimator = SKLearn(
entry_point="train.py",
role=role,
instance_type="ml.m5.xlarge",
instance_count=1,
framework_version="1.2-1",
py_version="py3",
hyperparameters={
"n-estimators": 300,
"max-depth": 10,
},
output_path=f"s3://{bucket}/churn/models/rf/",
metric_definitions=[
{"Name": "validation:f1", "Regex": "validation:f1=([0-9\\.]+)"},
],
)
sk_estimator.fit({
"train": TrainingInput(f"s3://{bucket}/churn/processed/v1/", content_type="application/x-parquet"),
"validation": TrainingInput(f"s3://{bucket}/churn/processed/v1/", content_type="application/x-parquet"),
})
metric_definitions يحوّل السطر المطبوع إلى مقياس منظّم يظهر في Studio مباشرةً. الاعتناء بهذا التعبير المنتظم شرط للضبط التلقائيّ التالي.
الاعتماديّات: source_dir وrequirements.txt
نادرًا ما يكون train.py كافيًا وحده. عادةً تحتاج إلى ملفّات مساعدة (وظائف تنظيف، معالجات)، وإلى مكتبات إضافيّة. الحلّ: مجلّد source_dir مع requirements.txt.
source/
├── train.py
├── preprocessing.py
└── requirements.txt
sk_estimator = SKLearn(
entry_point="train.py",
source_dir="source/",
role=role,
...
)
كلّ ما في source/ يُنسخ إلى الحاوية، وrequirements.txt يُثبَّت قبل تشغيل train.py. لا تُدرج مكتبات مثبَّتة أصلًا في صورة scikit-learn (مثل pandas)، فيُبطئ ذلك بدء المهمّة لا أكثر.
متى نحتاج حاوية مخصّصة كلّيًّا
مود سكربت غير كافٍ في حالات نادرة:
- اعتماديّة نظاميّة (مكتبة C) لا تُثبَّت عبر
pip. - ثنائيّ خاصّ (Rust, Go) يجب أن يكون حاضرًا.
- إطار عمل غير مدعوم رسميًّا.
في هذه الحالات تكتب Dockerfile، تبني الصورة، تدفعها إلى ECR، ثمّ تمرّر رابطها للـEstimator. حاول تجنّب هذا الطريق قدر الإمكان: بناء الصورة يضيف زمنًا لكلّ مطوّر في الفريق، وحاجة صيانتها مستمرّة.
التصحيح المحلّيّ: LocalMode
قبل صرف الوقت والمال على تشغيل مهمّة كاملة، جرّب سكربتك محلّيًّا:
sk_estimator = SKLearn(
entry_point="train.py",
source_dir="source/",
role=role,
instance_type="local", # يُشغّل الحاوية على جهازك
framework_version="1.2-1",
...
)
instance_type="local" يشغّل نفس صورة Docker على جهازك، بنفس المسارات ومتغيّرات البيئة. تلتقط الأخطاء التافهة (خطأ في اسم عم ود، مسار خاطئ) في ثوانٍ بدل انتظار خمس دقائق لبدء آلة EC2. اختبار ناجح محلّيًّا يعني تدريبًا سحابيًّا بلا مفاجآت في 99٪ من الحالات.
كلّ ما يطبعه السكربت على المخرَج القياسيّ يظهر في CloudWatch وفي واجهة Studio. اجعل السجلّات معنويّة: طبع دقيق للمعاملات المستعملة، والعدد الفعليّ للسطور المقروءة، والزمن المنقضي. لأنّ هذه السجلّات هي كلّ ما ستراه لاحقًا عند تصحيح مهمّة أخفقت.
الخلاصة
- مود سكربت يكفي في 90٪ من الحالات: سكربت بايثون واحد داخل صورة إطار عمل مسبقة الإعداد.
- المسارات وقيم المعاملات تصل عبر متغيّرات بيئة (
SM_MODEL_DIR,SM_CHANNEL_TRAIN,SM_HP_...)؛ لا تعتمد على مسارات مطلقة. metric_definitionsمع تعبير منتظم يحوّل سطرًا مطبوعًا إلى مقياس منظّم قابل للاستهداف في الضبط التلقائيّ.- الوضع المحلّيّ (
instance_type="local") يلتقط الأخطاء البسيطة قبل صرف دقائق ودولارات على السحابة.
الوحدة التالية: نُشغّل عشرات المهامّ بالتوازي بحثًا عن أفضل مجموعة معاملات فائقة.