الدرس 1 من 8
من Notebook لمشروع
الـ notebook ممتاز للتجريب، بس مش مكان الكود اللي هيشتغل ٢٤ ساعة.
ليه مش notebook؟
الـ notebook بيتشغل بإيد، والـ cells ممكن تتشغل بترتيب غلط، وصعب تختبره أو تراجعه في Git. الكود اللي هيتنشر لازم يبقى ملفات .py بتتشغل من أولها لآخرها بأمر واحد، وبنفس النتيجة كل مرة.
شكل المشروع
price-predictor/
data/
apartments.csv
models/ # trained models (not in git if large)
src/
train.py # train and save the model
api.py # serve predictions (next lesson)
tests/
test_api.py
requirements.txt
Dockerfile
.env # secrets (in .gitignore!)
.gitignore
README.mdسكريبت التدريب
هنرجع لموديل أسعار الشقق من المرحلة ٥، ونحوله لسكريبت نضيف: بيقرا الداتا، يدرب، يقيّم، ويحفظ الموديل ومعاه ملف فيه النتايج. وبنستخدم logging بدل print عشان كل رسالة يبقى معاها وقتها ومستواها.
import json import logging import sys from datetime import datetime import joblib import pandas as pd from sklearn.compose import ColumnTransformer from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_absolute_error from sklearn.model_selection import train_test_split from sklearn.pipeline import Pipeline from sklearn.preprocessing import OneHotEncoder, StandardScaler logging.basicConfig(level=logging.INFO, format="%(levelname)s | %(message)s", handlers=[logging.StreamHandler(sys.stdout)]) log = logging.getLogger("train") NUMERIC = ["area", "rooms", "floor", "age", "metro_km"] CATEGORICAL = ["city"] MODEL_VERSION = "1.0.0" def main(): df = pd.read_csv("apartments.csv") log.info("loaded %d rows", len(df)) X_train, X_test, y_train, y_test = train_test_split( df[NUMERIC + CATEGORICAL], df["price_k"], test_size=0.2, random_state=42) pipe = Pipeline([ ("prep", ColumnTransformer([ ("num", StandardScaler(), NUMERIC), ("cat", OneHotEncoder(handle_unknown="ignore"), CATEGORICAL), ])), ("model", LinearRegression()), ]) pipe.fit(X_train, y_train) mae = mean_absolute_error(y_test, pipe.predict(X_test)) log.info("test MAE = %.1fK EGP", mae) joblib.dump(pipe, "price_model.joblib") with open("metrics.json", "w") as f: json.dump({"version": MODEL_VERSION, "mae_k": round(mae, 1), "trained_at": datetime.now().date().isoformat()}, f) log.info("saved model v%s", MODEL_VERSION) if __name__ == "__main__": main()
logging زي print بس بمستويات (INFO و WARNING و ERROR)، وتقدر تبعته لملف أو لخدمة مراقبة.model version رقم نسخة الموديل. لما تدرب نسخة جديدة وحاجة تبوظ، تعرف ترجع للقديمة.reproducible إن نفس الكود على نفس الداتا يطلع نفس النتيجة، وعشان كده `random_state`.💡 ثبّت النسخ في requirements.txt
scikit-learn==1.8.0 مش scikit-learn بس. الموديل اللي اتحفظ بنسخة ممكن ميفتحش بنسخة تانية، وده من أشهر أسباب «كان شغال عندي!».