تخطَّ إلى المحتوى

الانحدار: توقع الأرقام وتقييمها

الدرس 7 من 8 · تعلّم الآلة

كل النماذج التي قيّمتها وحسّنتها حتى الآن كانت تصنيفاً: ورم حميد أم خبيث، ونوع الزهرة. لكن أسئلة كثيرة جوابها رقم: كم سعر هذه الشقة؟ كم سنبيع الشهر القادم؟ هذا هو الانحدار (Regression) الذي لمحته في درس أنواع التعلّم. في هذا الدرس ستفهم ما يتعلمه الانحدار الخطي فعلاً، وكيف تقيس جودة توقع رقمي، فالدقة ومصفوفة الالتباس لا تصلحان هنا.

الخط المستقيم: ميل ومقطع

Section titled “الخط المستقيم: ميل ومقطع”

نعود إلى الشقق، ببيانات أكثر قليلاً:

from sklearn.linear_model import LinearRegression
# المساحة بالمتر، والسعر بالألف
X = [[50], [65], [80], [95], [100], [120], [140], [160]]
y = [520, 640, 790, 930, 1010, 1180, 1370, 1590]
model = LinearRegression()
model.fit(X, y)
print(round(model.coef_[0], 2))
print(round(model.intercept_, 2))
الناتج
9.75
16.85

هذان الرقمان هما كل ما تعلّمه النموذج:

  • coef_ هو الميل (Slope): كل متر إضافي يرفع السعر 9.75 ألفاً تقريباً.
  • intercept_ هو المقطع (Intercept): قيمة البداية التي يُضاف إليها.

أي أن النموذج معادلة بسيطة: السعر = 9.75 × المساحة + 16.85. مثل عدّاد سيارة الأجرة: مبلغ ثابت عند الركوب، ثم مبلغ لكل كيلومتر.

print(round(model.predict([[110]])[0]))
print(round(9.75 * 110 + 16.85))
الناتج
1089
1089

التوقع بـ predict والحساب اليدوي بالمعادلة يعطيان الرقم نفسه. لا سحر في الأمر.

بيانات حقيقية بعشر ميزات

Section titled “بيانات حقيقية بعشر ميزات”

بيانات load_diabetes الجاهزة في scikit-learn فيها 442 مريضاً بالسكري. لكل مريض عشر ميزات (العمر، ومؤشر كتلة الجسم bmi، وضغط الدم bp، وستة تحاليل دم)، والمطلوب توقعه رقم يقيس تطور المرض بعد سنة:

from sklearn.datasets import load_diabetes
from sklearn.model_selection import train_test_split
data = load_diabetes()
X, y = data.data, data.target
print(X.shape)
print(y.min(), y.max(), round(y.mean(), 1))
الناتج
(442, 10)
25.0 346.0 152.1

الرقم المطلوب يتراوح بين 25 و 346، ومتوسطه نحو 152. احفظ هذا المجال، فسنحتاجه للحكم على حجم الخطأ.

مع عشر ميزات يتعلم النموذج ميلاً لكل ميزة ومقطعاً واحداً. والتدريب بالخطوات التي تعرفها:

X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42
)
reg = LinearRegression()
reg.fit(X_train, y_train)
pred = reg.predict(X_test)
print(pred[:5].round())
print(y_test[:5])
الناتج
[138. 183. 130. 293. 125.]
[219. 70. 202. 230. 111.]

السطر الأول توقعات النموذج لخمسة مرضى، والثاني القيم الحقيقية. لا يوجد هنا «صح» و«خطأ» كما في التصنيف: كل توقع قريب أو بعيد. المريض الخامس توقعه قريب (125 مقابل 111)، والثاني بعيد جداً (183 مقابل 70).

from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score
mae = mean_absolute_error(y_test, pred)
rmse = mean_squared_error(y_test, pred) ** 0.5
r2 = r2_score(y_test, pred)
print(f'MAE: {mae:.1f}')
print(f'RMSE: {rmse:.1f}')
print(f'R2: {r2:.3f}')
الناتج
MAE: 41.5
RMSE: 53.4
R2: 0.485
المقياس معناه كيف تقرؤه
MAE متوسط الخطأ المطلق النموذج يخطئ في المتوسط بـ 41.5 درجة، زيادة أو نقصاً
RMSE جذر متوسط مربعات الأخطاء مثل MAE لكنه يعاقب الأخطاء الكبيرة أكثر، لذلك هو أكبر
R2 معامل التحديد نسبة ما يفسره النموذج من اختلاف القيم: 1 مثالي، و 0 بلا فائدة

MAE و RMSE بوحدة الرقم نفسه، وكلما صغرا كان أفضل. استخدم MAE حين تريد رقماً سهل الشرح، و RMSE حين يكون الخطأ الكبير أخطر بكثير من عدة أخطاء صغيرة.

رقم الخطأ وحده لا يقول شيئاً. تحتاج نموذج أساس (Baseline) للمقارنة: أبسط توقع ممكن، وهو أن تتوقع المتوسط لكل المرضى مهما كانت ميزاتهم:

from sklearn.dummy import DummyRegressor
dummy = DummyRegressor(strategy='mean')
dummy.fit(X_train, y_train)
dummy_pred = dummy.predict(X_test)
print(f'MAE: {mean_absolute_error(y_test, dummy_pred):.1f}')
print(f'R2: {r2_score(y_test, dummy_pred):.3f}')
الناتج
MAE: 65.5
R2: -0.014

التخمين بالمتوسط يخطئ بـ 65.5، ونموذجنا بـ 41.5. إذن النموذج تعلّم شيئاً حقيقياً من الميزات، لكنه بعيد عن الكمال: R2 يساوي 0.485، أي أنه يفسر نحو نصف اختلاف المرضى فقط. وهذا طبيعي، فتطور المرض يتأثر بأشياء كثيرة غير موجودة في البيانات.

قراءة ما تعلّمه النموذج

Section titled “قراءة ما تعلّمه النموذج”
import pandas as pd
coefs = pd.Series(reg.coef_, index=data.feature_names)
print(coefs[['bmi', 'bp', 'age']].round(1))
الناتج
bmi 532.0
bp 381.6
age 47.7
dtype: float64

الإشارات الموجبة تعني: كلما ارتفع مؤشر كتلة الجسم أو ضغط الدم، ارتفع التطور المتوقع للمرض. وتأثير bmi أكبر بكثير من تأثير العمر. هذه ميزة الانحدار الخطي: نموذج تستطيع شرحه لطبيب في جملة.

شجرة القرار والغابة العشوائية لهما نسختان للانحدار. نقارن الثلاثة بالتحقق المتقاطع على بيانات التدريب، ومقياسنا MAE:

from sklearn.model_selection import cross_val_score
from sklearn.tree import DecisionTreeRegressor
from sklearn.ensemble import RandomForestRegressor
models = {
'linear': LinearRegression(),
'tree': DecisionTreeRegressor(random_state=42),
'forest': RandomForestRegressor(n_estimators=100, random_state=42),
}
for name, m in models.items():
scores = cross_val_score(m, X_train, y_train, cv=5,
scoring='neg_mean_absolute_error')
print(name, f'{-scores.mean():.1f}')
الناتج
linear 45.6
tree 64.9
forest 47.8

scikit-learn يعتبر الرقم الأكبر أفضل دائماً، لذلك يعطيك الخطأ بإشارة سالبة (neg_mean_absolute_error)، ونحن نعكس الإشارة عند الطباعة.

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

شققنا كانت بين 50 و 160 متراً. ماذا لو سألنا عن قصر مساحته 300 متر؟

flats = [[50], [65], [80], [95], [100], [120], [140], [160]]
prices = [520, 640, 790, 930, 1010, 1180, 1370, 1590]
line = LinearRegression().fit(flats, prices)
tree = DecisionTreeRegressor(random_state=0).fit(flats, prices)
print(round(line.predict([[300]])[0]), round(tree.predict([[300]])[0]))
الناتج
2941 1590

الخط المستقيم يمدّ نفسه إلى ما لا نهاية ويقول 2941 ألفاً، والشجرة تكرر أكبر سعر رأته: 1590. لا أحد منهما يعرف الحقيقة، فلم ير أي منهما شقة بهذا الحجم. هذا اسمه الاستقراء خارج المجال (Extrapolation): النموذج موثوق داخل مجال البيانات التي تدرّب عليها فقط.

  1. أضف إلى بيانات الشقق شقة مساحتها 100 متر وسعرها 3000 ألف (قيمة شاذة)، وأعد التدريب، ولاحظ كم تغيّر الميل والمقطع.
  2. احسب MAE يدوياً لأول خمسة مرضى من السطرين المطبوعين في الدرس، وتأكد بـ mean_absolute_error(y_test[:5], pred[:5]).
  3. جرّب DecisionTreeRegressor(max_depth=3, random_state=42) في المقارنة، وانظر هل يقترب من النموذج الخطي.
  4. تحدٍّ: درّب النموذج الخطي على ميزة bmi وحدها (X_train[:, [2]])، وقارن MAE مع النموذج الذي يستخدم الميزات العشر.
  • استخدام الدقة في الانحدار: accuracy_score تسأل «هل التوقع مطابق تماماً؟»، والأرقام المتصلة لا تتطابق تقريباً. استخدم MAE أو RMSE أو R2.
  • الحكم على الخطأ بلا مقارنة: خطأ 41.5 ممتاز إذا كان المجال بالآلاف، وسيئ إذا كان بالعشرات. قارن دائماً بنموذج أساس وبمجال القيم.
  • الثقة في التوقع خارج المجال: لا تسأل النموذج عن قيم أبعد بكثير مما رآه في التدريب.
  • نسيان الإشارة السالبة: cross_val_score مع neg_mean_absolute_error يعطي أرقاماً سالبة، فاعكسها قبل قراءتها.

سؤال

نموذج انحدار خطي لأسعار الشقق ميله 9.75 ومقطعه 16.85. ماذا يعني الميل؟

سؤال

نموذجك حقق R2 يساوي 0.02 على بيانات الاختبار. ما معنى ذلك؟

  • الانحدار الخطي يتعلم ميلاً لكل ميزة ومقطعاً واحداً، وتوقعه معادلة يمكنك حسابها يدوياً.
  • MAE متوسط الخطأ بوحدة الرقم نفسه، و RMSE يعاقب الأخطاء الكبيرة، و R2 يقارن النموذج بتوقع المتوسط.
  • قارن نموذجك دائماً بنموذج أساس مثل DummyRegressor قبل أن تحكم عليه.
  • النموذج الأبسط قد يفوز، فلا تختر الأعقد إلا إذا أثبتت الأرقام ذلك.
  • لا تثق في توقع خارج مجال بيانات التدريب.