Machine Learning: هندسة الميزات

آخر تحديث: 2026-08-26

البيانات تحدد السقف؛ النموذج يقترب منه فقط — هندسة الميزات هي ما يرفع هذا السقف.

1. ما ستتعلمه


2. قصة حقيقية من مهندس ML

(1) نقطة الألم: 3 أيام من الضبط لـ 1% مكسب مقابل ميزة واحدة لـ 10%

قضى Bob 3 أيام في ضبط معاملات XGBoost، دافعًا بـ R² من 0.78 إلى 0.79 — مجرد تحسن 1%. ولكن عندما أضاف ميزة واحدة جديدة، "تكرار الشراء في آخر 7 أيام"، قفز R² مباشرة إلى 0.86 — مكسب 8%. هندسة الميزات تقدم عوائد أكبر بكثير من ضبط المعاملات، ومع ذلك يفتقر معظم المهندسين إلى منهجية منهجية لها.

(2) نهج منهجي لهندسة الميزات

هندسة الميزات ليست تجربة عشوائية — إنها تتبع سير عمل منهجي: فهم العمل → بناء الميزات → اختيار الميزات → التحقق من الفعالية.

PYTHON
from sklearn.preprocessing import StandardScaler, OneHotEncoder
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline

# خط أنابيب هندسة الميزات المنهجي
num_features = ["ad_spend", "traffic", "log_monetary", "purchase_freq_7d"]
cat_features = ["category", "tier"]

preprocessor = ColumnTransformer([
    ("num", StandardScaler(), num_features),
    ("cat", OneHotEncoder(drop="first", handle_unknown="ignore"), cat_features),
])

pipe = Pipeline([("prep", preprocessor), ("model", XGBRegressor())])

(3) النتيجة: هندسة الميزات المنهجية ترفع R² من 0.78 إلى 0.88

بعد أن بنى Bob مصنع ميزاته، كانت كل إضافة ميزة جديدة مدعومة بالأدلة. صعد R² بشكل منهجي من 0.78 إلى 0.88 — متجاوزًا بكثير ما يمكن أن يحققه الضبط وحده.


3. تحويلات الميزات الرقمية

(1) التوحيد مقابل التطبيع

▶ مثال: مقارنة StandardScaler مع MinMaxScaler

PYTHON
from sklearn.preprocessing import StandardScaler, MinMaxScaler, RobustScaler
import numpy as np

data = np.array([[1], [5], [10], [50], [100], [500], [1000]])

scalers = {
    "Original": data,
    "StandardScaler": StandardScaler().fit_transform(data),
    "MinMaxScaler": MinMaxScaler().fit_transform(data),
    "RobustScaler": RobustScaler().fit_transform(data),
}

for name, scaled in scalers.items():
    print(f"{name:16s}: min={scaled.min():8.3f}, max={scaled.max():8.3f}, "
          f"mean={scaled.mean():8.3f}, std={scaled.std():8.3f}")

Output:

TEXT 📖 للعرض فقط
# Executed successfully
الطريقة الصيغة نطاق المخرجات قوة القيم المتطرفة حالة الاستخدام
StandardScaler (x-μ)/σ غير محدود ضعيفة SVM/الانحدار اللوجستي/الشبكات العصبية
MinMaxScaler (x-min)/(max-min) [0,1] ضعيفة مدخلات الشبكة العصبية
RobustScaler (x-median)/IQR غير محدود قوية بيانات ذات قيم متطرفة
التحويل اللوغاريتمي log(1+x) غير محدود قوي توزيعات منحرفة لليمين

(2) التحويل اللوغاريتمي والتقسيم

▶ مثال: التعامل مع التوزيعات المنحرفة لليمين بالتقسيم

PYTHON
import numpy as np
from sklearn.preprocessing import KBinsDiscretizer
import matplotlib.pyplot as plt

rng = np.random.default_rng(42)
income = rng.exponential(50000, 1000)  # بيانات دخل منحرفة لليمين

fig, axes = plt.subplots(1, 3, figsize=(15, 4))

# (1) التوزيع الأصلي
axes[0].hist(income, bins=30, color="#2196F3", edgecolor="white")
axes[0].set_title("Original (Right-Skewed)")

# (2) محول لوغاريتميًا
log_income = np.log1p(income)
axes[1].hist(log_income, bins=30, color="#4CAF50", edgecolor="white")
axes[1].set_title("Log-Transformed (More Symmetric)")

# (3) مقسم إلى صناديق
binner = KBinsDiscretizer(n_bins=5, encode="ordinal", strategy="quantile")
binned = binner.fit_transform(income.reshape(-1, 1))
axes[2].hist(binned, bins=5, color="#FF9800", edgecolor="white")
axes[2].set_title("Quantile Binned (5 Bins)")

plt.tight_layout()
plt.savefig("feature_transform.png", dpi=150)

Output:

TEXT 📖 للعرض فقط
# Executed successfully

(3) ميزات متعددة الحدود

▶ مثال: التقاط العلاقات غير الخطية

PYTHON
from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import Pipeline
from sklearn.model_selection import cross_val_score
import numpy as np

rng = np.random.default_rng(42)
X = rng.uniform(-3, 3, (200, 1))
y = 2 * X.squeeze() ** 2 - 3 * X.squeeze() + 1 + rng.normal(0, 1, 200)

for degree in [1, 2, 3, 5]:
    pipe = Pipeline([
        ("poly", PolynomialFeatures(degree=degree, include_bias=False)),
        ("model", LinearRegression()),
    ])
    scores = cross_val_score(pipe, X, y, cv=5, scoring="r2")
    print(f"Degree={degree}: R²={scores.mean():.3f} +/- {scores.std():.3f}")

Output:

TEXT 📖 للعرض فقط
# Executed successfully

4. ترميز الميزات الفئوية

(1) مقارنة طرق الترميز

▶ مثال: استراتيجيات ترميز مختلفة

PYTHON
import pandas as pd
from sklearn.preprocessing import OneHotEncoder, OrdinalEncoder
import numpy as np

df = pd.DataFrame({
    "category": ["Electronics", "Clothing", "Food", "Electronics", "Food"],
    "tier": ["Low", "Medium", "High", "Medium", "Low"],
    "city": ["New York", "London", "Paris", "Tokyo", "Berlin"],
})

# ترميز One-Hot (لا افتراض ترتيبي)
ohe = OneHotEncoder(drop="first", sparse_output=False)
ohe_result = ohe.fit_transform(df[["category"]])
print(f"One-Hot category:\n{ohe_result}")
print(f"Feature names: {ohe.get_feature_names_out()}")

# الترميز الترتيبي (له ترتيب)
ord_enc = OrdinalEncoder(categories=[["Low", "Medium", "High"]])
ord_result = ord_enc.fit_transform(df[["tier"]])
print(f"\nOrdinal tier: {ord_result.flatten()}")

Output:

TEXT 📖 للعرض فقط
# Executed successfully
الترميز حالة الاستخدام زيادة الأبعاد الترتيب مخاطرة الكاردينالية العالية
One-Hot فئات اسمية (<10 فئات) K-1 لا انفجار الأبعاد
ترتيبي فئات ترتيبية (رتب) 0 محفوظ لا
الهدف كاردينالية عالية (مدن/منتجات) 0 لا فرط التجهيز (يتطلب CV)
تكرار كاردينالية عالية 0 لا فقدان المعلومات
تضمين كاردينالية عالية جدًا (تعلم عميق) قابلة للتحكم متعلم يتطلب تدريبًا

(2) التعامل مع الكاردينالية العالية

▶ مثال: ترميز الهدف لميزات المدينة

PYTHON
from sklearn.model_selection import cross_val_score
from sklearn.linear_model import LogisticRegression
from sklearn.preprocessing import OneHotEncoder, StandardScaler
from sklearn.compose import ColumnTransformer
from sklearn.pipeline import Pipeline
import pandas as pd
import numpy as np

rng = np.random.default_rng(42)
n = 1000
cities = rng.choice([f"City_{i}" for i in range(50)], n)
df = pd.DataFrame({
    "city": cities,
    "income_k": rng.exponential(50, n),
    "age": rng.integers(18, 70, n),
    "purchased": rng.choice([0, 1], n, p=[0.7, 0.3]),
})

# ترميز الهدف مع CV لتجنب التسرب
from category_encoders import TargetEncoder

pipe = Pipeline([
    ("target_enc", TargetEncoder(cols=["city"])),
    ("scaler", StandardScaler()),
    ("model", LogisticRegression(max_iter=500)),
])

X = df[["city", "income_k", "age"]]
y = df["purchased"]
scores = cross_val_score(pipe, X, y, cv=5, scoring="accuracy")
print(f"Target Encoding CV Accuracy: {scores.mean():.3f}")

Output:

TEXT 📖 للعرض فقط
# Executed successfully

5. استخراج ميزات الوقت

▶ مثال: هندسة ميزات الوقت للتجارة الإلكترونية

PYTHON
import pandas as pd
import numpy as np

rng = np.random.default_rng(42)
n = 1000
df = pd.DataFrame({
    "order_date": pd.date_range("2023-01-01", periods=n, freq="6H"),
    "user_register_date": pd.to_datetime(rng.choice(pd.date_range("2020-01-01", "2023-01-01"), n)),
})

# استخراج الميزات المعتمدة على الوقت
df["order_year"] = df["order_date"].dt.year
df["order_month"] = df["order_date"].dt.month
df["order_day"] = df["order_date"].dt.day
df["order_hour"] = df["order_date"].dt.hour
df["day_of_week"] = df["order_date"].dt.dayofweek  # 0=الإثنين، 6=الأحد
df["is_weekend"] = (df["day_of_week"] >= 5).astype(int)
df["is_night"] = ((df["order_hour"] >= 22) | (df["order_hour"] <= 6)).astype(int)

# الربع والفصل
df["quarter"] = df["order_date"].dt.quarter

# الوقت منذ التسجيل
df["days_since_register"] = (df["order_date"] - df["user_register_date"]).dt.days

# هل هو عطلة (مبسط - حدد 20-31 ديسمبر كموسم عطلات)
df["is_holiday_season"] = ((df["order_month"] == 12) & (df["order_day"] >= 20)).astype(int)

# الأيام حتى نهاية الشهر (ميزة الإلحاح)
df["days_to_month_end"] = (df["order_date"] + pd.offsets.MonthEnd(0) - df["order_date"]).dt.days

print(f"Time features: {[c for c in df.columns if c not in ['order_date', 'user_register_date']]}")
print(df.head(3))

Output:

TEXT 📖 للعرض فقط
# Executed successfully
نوع الميزة الأمثلة المعنى التجاري
دورية hour, day_of_week, month فترات ذروة الشراء
مؤشر is_weekend, is_holiday أنماط الإنفاق خلال الفترات الخاصة
الوقت المنقضي days_since_register مرحلة دورة حياة المستخدم
إلحاح days_to_month_end إشارات الشراء الاندفاعي

6. اختيار الميزات

(1) المناهج الكبرى الثلاثة

100%
mindmap
  root((اختيار الميزات))
    Filter
      معامل الارتباط
      المعلومات المتبادلة
      عتبة التباين
      اختبار كاي تربيع
    Wrapper
      القضاء المتكرر على الميزات RFE
      اختيار الميزات التسلسلي
      قائم على التحقق المتقاطع
    Embedded
      تنظيم L1 Lasso
      أهمية ميزات الشجرة
      أهمية XGBoost

▶ مثال: مقارنة ثلاث طرق لاختيار الميزات

PYTHON
from sklearn.feature_selection import SelectKBest, f_regression, mutual_info_regression, RFE
from sklearn.linear_model import Lasso, LinearRegression
from sklearn.ensemble import RandomForestRegressor
from sklearn.preprocessing import StandardScaler
from sklearn.pipeline import Pipeline
import numpy as np

rng = np.random.default_rng(42)
n, p = 500, 20
X = rng.standard_normal((n, p))
true_coefs = np.zeros(p)
true_coefs[:5] = [3, -2, 1.5, 0.8, -0.5]
y = X @ true_coefs + rng.normal(0, 1, n)

feature_names = [f"feat_{i}" for i in range(p)]

# الطريقة 1: Filter (F-regression)
selector_filter = SelectKBest(f_regression, k=5)
selector_filter.fit(X, y)
filter_selected = np.array(feature_names)[selector_filter.get_support()]
print(f"Filter top 5: {filter_selected}")

# الطريقة 2: Wrapper (RFE)
rfe = RFE(LinearRegression(), n_features_to_select=5)
rfe.fit(X, y)
rfe_selected = np.array(feature_names)[rfe.get_support()]
print(f"RFE top 5:    {rfe_selected}")

# الطريقة 3: Embedded (Lasso)
lasso = Lasso(alpha=0.1)
lasso.fit(StandardScaler().fit_transform(X), y)
lasso_selected = np.array(feature_names)[np.abs(lasso.coef_) > 0.01]
print(f"Lasso top 5:  {lasso_selected}")
print(f"True features: {feature_names[:5]}")

Output:

TEXT 📖 للعرض فقط
# Executed successfully
الطريقة السرعة تفاعلات الميزات مخاطرة فرط التجهيز حالة الاستخدام
Filter سريعة غير مُراعاة منخفضة الفحص الأولي، البيانات عالية الأبعاد
Wrapper بطيئة مُراعاة عالية الاختيار الدقيق
Embedded متوسطة مُراعاة جزئيًا منخفضة الاختيار العملي الأول

▶ مثال: مصنع ميزات التجارة الإلكترونية لـ Bob

PYTHON
import pandas as pd
import numpy as np

rng = np.random.default_rng(42)
n = 5000

# بيانات المعاملات الخام
transactions = pd.DataFrame({
    "user_id": rng.choice(range(1000), n),
    "order_date": pd.date_range("2023-01-01", periods=n, freq="3H"),
    "amount_usd": rng.exponential(100, n).clip(1, 5000),
    "category": rng.choice(["Elec", "Cloth", "Food", "Book", "Home"], n),
})

# مصنع الميزات: تجميع المعاملات في ميزات المستخدم
user_features = transactions.groupby("user_id").agg(
    total_orders=("amount_usd", "count"),
    total_spending_usd=("amount_usd", "sum"),
    avg_order_value=("amount_usd", "mean"),
    std_order_value=("amount_usd", "std"),
    last_order_date=("order_date", "max"),
    first_order_date=("order_date", "min"),
    favorite_category=("category", lambda x: x.mode().iloc[0] if len(x.mode()) > 0 else "Unknown"),
    category_diversity=("category", "nunique"),
).reset_index()

# ميزات تجارية مشتقة
user_features["recency_days"] = (pd.Timestamp("2024-01-01") - user_features["last_order_date"]).dt.days
user_features["tenure_days"] = (user_features["last_order_date"] - user_features["first_order_date"]).dt.days
user_features["purchase_frequency"] = user_features["total_orders"] / (user_features["tenure_days"] + 1) * 30

# إنتروبيا تفضيل الفئة (مدى تنوع تفضيل المستخدم للفئات)
from scipy.stats import entropy
def calc_entropy(series):
    probs = series.value_counts(normalize=True)
    return entropy(probs)

cat_entropy = transactions.groupby("user_id")["category"].apply(calc_entropy).reset_index()
cat_entropy.columns = ["user_id", "category_entropy"]
user_features = user_features.merge(cat_entropy, on="user_id")

# حساسية السعر (الانحراف المعياري/المتوسط لقيم الطلب)
user_features["price_sensitivity"] = user_features["std_order_value"] / (user_features["avg_order_value"] + 1)

print(f"User features shape: {user_features.shape}")
print(f"Feature columns: {list(user_features.columns)}")
print(user_features.head(3))

Output:

TEXT 📖 للعرض فقط
# Function defined successfully

❓ أسئلة شائعة

س هل يجب إجراء هندسة الميزات قبل أو بعد تقسيم التدريب/الاختبار؟
ج العمليات التي تتضمن معلومات الهدف (مثل ترميز الهدف) يجب أن تتم بعد التقسيم، مع التدريب فقط على مجموعة التدريب. العمليات التي لا تستخدم الهدف (مثل التحويل اللوغاريتمي، استخراج الشهر) يمكن أن تتم قبل التقسيم. يتعامل Pipeline مع هذا الترتيب تلقائيًا.
س كم عدد الميزات الذي يعتبر كثيرًا؟
ج لا يوجد معيار مطلق. قاعدة شائعة: يجب أن يكون عدد العينات على الأقل 10 أضعاف عدد الميزات (n > 10p). عندما تتجاوز الميزات العينات (أبعاد عالية، عينة صغيرة)، يجب تطبيق اختيار الميزات أو التنظيم.
س كيف تفسر معاملات النموذج بعد التحويل اللوغاريتمي؟
ج في نموذج خطي بـ log(y)، يعني المعامل "زيادة وحدة واحدة في x تغير y تقريبًا coef×100%". هذا تفسير مرونة، شائع في الاقتصاد.
س ما هو فخ المتغير الوهمي بعد ترميز One-Hot؟
ج إنشاء K متغيرات وهمية لـ K فئات يسبب ترابطًا خطيًا مثاليًا (جميع الأعمدة مجموعها 1). يجب ضبط drop_first=True لإزالة واحد، والإبقاء على أعمدة K-1.
س أي طريقة لاختيار الميزات يجب أن تعطيها الأولوية؟
ج ابدأ بطرق Filter للفحص السريع (إزالة الميزات غير ذات الصلة بوضوح)، ثم استخدم الطرق المدمجة (Lasso/أهمية الشجرة) للاختيار الدقيق. طرق Wrapper هي الأكثر دقة ولكن الأبطأ — احتفظ بها للتحسين النهائي.
س كيف تحدد ما إذا كانت ميزة جديدة مفيدة؟
ج ثلاث طرق — 1) تحقق من ارتباطها أحادي المتغير مع الهدف؛ 2) أضفها إلى النموذج وتحقق مما إذا كانت مقاييس التحقق تتحسن؛ 3) استخدم أهمية التبديل لقياس مقدار انخفاض الأداء عند خلط تلك الميزة عشوائيًا.

📖 ملخص

📝 تمارين

  1. أساسي (الصعوبة ⭐): خذ مجموعة بيانات ذات توزيعات منحرفة وعالجها بـ StandardScaler وحده مقابل Log + StandardScaler. قارن إحصائيات التوزيع بعد كل معالجة. تلميح: np.log1p() + StandardScaler().
  2. متوسط (الصعوبة ⭐⭐): أنشئ خط أنابيب هندسة ميزات كامل — StandardScaler للأعمدة الرقمية، OneHotEncoder للأعمدة الفئوية، استخرج الشهر/الأسبوع/is_weekend من أعمدة datetime، ثم استخدم RFE لاختيار الميزات الخمس الأكثر أهمية. تلميح: ColumnTransformer + RFE.
  3. تحدي (الصعوبة ⭐⭐⭐): أنشئ مصنع ميزات التجارة الإلكترونية لـ Bob — بدءًا من بيانات المعاملات الخام، أنشئ 10 ميزات تجارية على الأقل (بما في ذلك RFM، وإنتروبيا تفضيل الفئة، وحساسية السعر، وتكرار الشراء، وما إلى ذلك)، واستخدم Lasso لاختيار الميزات، وقارن أداء النموذج قبل وبعد الاختيار. تلميح: راجع مثال مصنع الميزات في القسم 6.

← الدرس السابق: KNN والتجميع | الدرس التالي: التعلم الجماعي — XGBoost و LightGBM →

Web-Tutorial.com

فريق Web-Tutorial التقني

منصة دروس برمجية يديرها عدة مطورين. كل درس يتم كتابته ومراجعته بواسطة مطورين متخصصين في المجال. نعمل على ضمان دقة وموثوقية المحتوى — إذا لاحظت أي مشكلة، فيرجى إخبارنا.

100%