Logo

مراجعة تعلّم الآلة ومثال على الانحدار التدريجي

11 دقيقة قراءة
شرائح الدرس
1 / 14

مراجعة تعلم الآلة ومثال على الانحدار التدريجي

بناء وتدريب نموذج انحدار خطي باستخدام واجهات TensorFlow الأساسية (Core APIs)

مراجعة تعلّم الآلة ومثال على الانحدار التدريجي (Gradient Descent)

في هذا الدفتر (Notebook)، سنحل مسألة انحدار خطي (Linear Regression) بسيطة باستخدام الانحدار التدريجي (Gradient Descent).

ستتبع المراحل النموذجية لعملية تعلّم الآلة:

  1. تحميل مجموعة البيانات (Dataset).
  2. بناء نموذج انحدار خطي متعدد المتغيرات.
  3. تقييم أداء النموذج.

الإعداد

استورد TensorFlow والمكتبات الأخرى اللازمة للبدء:

import tensorflow as tf
import pandas as pd
import matplotlib
from matplotlib import pyplot as plt
print("TensorFlow version:", tf.__version__)
# Set a random seed for reproducible results
tf.random.set_seed(22)
TensorFlow version: 2.18.0

توليد بيانات من توزيع معروف

سنقوم أدناه بتوليد بيانات من توزيع معروف. وعلى وجه التحديد، النموذج الحقيقي هو:

Y=b+X1w1+X2w2Y = b + X_1w_1 +X_2w_2

المتغيران X1X_1 وX2X_2 لهما توزيع منتظم (Uniform Distribution) على الفترة [0,10][0,10]، بينما تمثّل b حد التقاطع (Intercept).

نضبط قيمًا فعلية لـbb، وw1w_1، وw2w_2

هنا b=1.5b=1.5، وw1=2w_1=2، وw2=5w_2=5

بعد ذلك نولّد متجهًا من قيم yy وفقًا للنموذج، ونجمع المتغيرات التنبؤية (Predictors) معًا في مصفوفة dataset

 
# Set the random seed for reproducibility
tf.random.set_seed(1234)  # Equivalent to np.random.seed(1234)
 
num_obs = 150
 
# Generate random samples using TensorFlow
x1 = tf.random.uniform([num_obs], minval=0, maxval=10, dtype=tf.float32)
x2 = tf.random.uniform([num_obs], minval=0, maxval=10, dtype=tf.float32)
 
 
# Coefficients
b = 1.5
w_1 = 2.0
w_2 = 5.0
 
# Calculate y
y = b  + w_1 * x1 + w_2 * x2
 
# Stack x_mat to form a matrix with columns: [y, x1, x2]
dataset = tf.stack([y, x1, x2], axis=1)
dataset_tf = tf.convert_to_tensor(dataset, dtype=tf.float32)
# Print shape of x_mat
print(dataset_tf.shape)
print(y.shape)
(150, 3)
(150,)
from mpl_toolkits.mplot3d import Axes3D
 
# Convert to NumPy
x1_np = x1.numpy()
x2_np = x2.numpy()
y_np  = y.numpy()
 
fig = plt.figure(figsize=(8,6)) # (Width, height)
ax = fig.add_subplot(111, projection='3d')
# The 111 is (1st) row, (1st) col, and the index of the plot is 1
# Ex: fig.add_subplot(222, projection='3d')
# +-------+-------+
# |  221  | (222) |
# +-------+-------+
# |  223  |  224  |
# +-------+-------+
 
ax.scatter(x1_np, x2_np, y_np, c='red', marker='o')
ax.set_xlabel('x1') # X
ax.set_ylabel('x2') # Y
ax.set_zlabel('y')  # Z
plt.title("3D Scatter: (x1, x2, y)")
plt.show()
x1_np = x1.numpy()
x2_np = x2.numpy()
y_np  = y.numpy()
 
# ---------------------------------------------------------
# 2D plot: x1 vs. y
plt.figure(figsize=(6,5))
plt.scatter(x1_np, y_np, color='blue', label='x1 vs. y')
plt.xlabel('x1')
plt.ylabel('y')
plt.title('Scatter Plot: x1 vs. y')
plt.legend() # Displays labels for plotted elements
plt.show()
# ---------------------------------------------------------
plt.figure(figsize=(6,5))
plt.scatter(x2_np, y_np, color='green', label='x2 vs. y')
plt.xlabel('x2')
plt.ylabel('y')
plt.title('Scatter Plot: x2 vs. y')
plt.legend()
plt.show()

بعد ذلك، قسّم مجموعة البيانات إلى مجموعتَي تدريب واختبار. تأكّد من خلط مجموعة البيانات باستخدام tf.random.shuffle لتجنّب أي انحياز في التقسيم.

# Explain why
dataset_shuffled = tf.random.shuffle(dataset, seed=22)
train_data, test_data = dataset_shuffled[:125], dataset_shuffled[125:] # 20% Test
x_train, y_train = train_data[:, 1:], train_data[:, 0]
x_test, y_test = test_data[:, 1:], test_data[:, 0]
print(x_train.shape)
print(y_train.shape)
(125, 2)
(125,)

بناء نموذج تعلّم آلة

ابنِ نموذج انحدار خطي باستخدام واجهات TensorFlow الأساسية (Core APIs). معادلة الانحدار الخطي المتعدد هي كالتالي:

Y=Xw+b{\mathrm{Y}} = {\mathrm{X}}w + b

حيث

  • Ym×1\underset{m\times 1}{\mathrm{Y}}: متجه الهدف (Target Vector)
  • Xm×n\underset{m\times n}{\mathrm{X}}: مصفوفة الخصائص (Feature Matrix)
  • wn×1\underset{n\times 1}w: متجه الأوزان (Weight Vector)
  • bb: الانحياز (Bias)

باستخدام الديكوريتر (Decorator) @tf.function، يُتتبَّع كود بايثون المقابل لتوليد مخطط TensorFlow (Graph) قابل للاستدعاء. هذا الأسلوب مفيد لحفظ النموذج وتحميله بعد التدريب. كما يمكن أن يحسّن الأداء في النماذج ذات الطبقات الكثيرة والعمليات المعقّدة.

class LinearRegression(tf.Module):
 
  def __init__(self):
    self.built = False
 
  @tf.function
  def __call__(self, x):
    # Initialize the model parameters on the first call
    if not self.built:
      # Randomly generate the weight vector and bias term
      # Shape (features, 1) === (2,1)
      # The 2 is the num of features from (125, 2) as (125, 2).shape[-1] == 2
      rand_w = tf.random.uniform(shape=[x.shape[-1], 1])
      rand_b = tf.random.uniform(shape=[]) # Scalar (Single value) 
      self.w = tf.Variable(rand_w)
      self.b = tf.Variable(rand_b)
      self.built = True
    # Dot product X(125x2).W(2x1) = (125x1) then add b to every value
    y = tf.add(tf.matmul(x, self.w), self.b)
    return tf.squeeze(y, axis=1) # Treat it as array[1,2,3] not matrix[ [1,2,3] ]

بالنسبة لكل مثال، يُعيد النموذج تنبؤًا لبيانات الإدخال عن طريق حساب المجموع الموزون (Weighted Sum) لخصائصها مضافًا إليه حد الانحياز (Bias). يمكن بعد ذلك إلغاء توحيد (Unstandardize) هذا التنبؤ للحصول على قيمته بالوحدات الأصلية.

lin_reg = LinearRegression()
prediction = lin_reg(x_train[:1])
prediction.numpy()
array([10.493174], dtype=float32)

تعريف دالة الخسارة (Loss Function)

الآن، عرّف دالة خسارة لتقييم أداء النموذج أثناء عملية التدريب.

بما أن مسائل الانحدار تتعامل مع مخرجات مستمرة، فإن متوسط مربع الخطأ (Mean Squared Error - MSE) خيار مثالي لدالة الخسارة. يُعرَّف الـMSE بالمعادلة التالية:

MSE=1mi=1m(y^iyi)2MSE = \frac{1}{m}\sum_{i=1}^{m}(\hat{y}_i -y_i)^2

حيث

  • y^\hat{y}: متجه التنبؤات
  • yy: متجه القيم الحقيقية المستهدفة

الهدف من مسألة الانحدار هذه هو إيجاد متجه الأوزان الأمثل ww، والانحياز bb، اللذين يقلّلان دالة خسارة الـMSE إلى أدنى قيمة.

def mse_loss(y_pred, y):
  return tf.reduce_mean(tf.square(y_pred - y))

تدريب النموذج وتقييمه

استخدام الدُّفعات المصغّرة (Mini-Batches) في التدريب يوفّر كفاءة في الذاكرة وتقاربًا (Convergence) أسرع. توفّر واجهة tf.data.Dataset دوالًا مفيدة للتجميع في دُفعات (Batching) والخلط (Shuffling). تتيح لك هذه الواجهة بناء خطوط أنابيب إدخال (Input Pipelines) معقّدة من قطع بسيطة قابلة لإعادة الاستخدام. تعرّف على المزيد حول بناء خطوط أنابيب الإدخال في TensorFlow في هذا الدليل.

تأثير تغيير batch_size أثناء التدريب

يتحكّم حجم الدُّفعة (Batch Size) في تعلّم الآلة والتعلّم العميق في عدد العيّنات التي تُعالَج قبل أن يحدّث النموذج معاملاته. اختيار حجم دُفعة = 32 شائع، لكن تعديله يمكن أن يؤثر على سرعة التدريب، وأداء النموذج، وقدرته على التعميم (Generalization).


1️⃣ ماذا يحدث عند زيادة batch_size؟

إذا زدت batch_size (مثلًا إلى 64، 128، 256، 512):

  • الإيجابيات:تدريب أسرع (تحديثات أقل في كل حقبة (Epoch)، ومزيد من التوازي (Parallelization) على وحدات GPU). ✅ استغلال أفضل للعتاد (حوسبة فعّالة على وحدات GPU ذات الذاكرة الكبيرة). ✅ تحديثات أكثر استقرارًا (تُحسَب تحديثات التدرّج (Gradient) كمتوسط عبر عدد أكبر من العيّنات).

  • السلبيات:استهلاك ذاكرة أعلى (الدُّفعات الكبيرة تتطلب ذاكرة GPU أكبر). ❌ خطر ضعف التعميم (قد لا تلتقط الدُّفعات الكبيرة أنماط التدريب المتنوعة بشكل جيد). ❌ تقارب (Convergence) أبطأ (التدرّجات (Gradients) تكون أصغر، مما يجعل التحديثات أكثر تحفظًا).

  • حالة الاستخدام: 🔹 الدُّفعات الكبيرة مفيدة عند التدريب على مجموعات بيانات ضخمة جدًا باستخدام وحدات GPU قوية.


2️⃣ ماذا يحدث عند تقليل batch_size؟

إذا قلّلت batch_size (مثلًا إلى 16، 8، 4، 1):

  • الإيجابيات:تعميم أفضل (الضوضاء الإضافية في تحديثات التدرّج تمنع الإفراط في التخصيص - Overfitting). ✅ استهلاك أقل للذاكرة (يمكن التدريب على وحدات GPU أصغر). ✅ تحديثات أكثر تكرارًا (فرص أكثر لتعديل الأوزان).

  • السلبيات:تدريب أبطأ (تحديثات أكثر في كل حقبة ← يستغرق وقتًا أطول). ❌ تدرّجات (Gradients) مشوّشة (Noisy) (تحديثات المعاملات تتذبذب أكثر). ❌ أقل كفاءة على وحدات GPU (تؤدي وحدات GPU بشكل أفضل مع الدُّفعات الأكبر).

  • حالة الاستخدام: 🔹 الدُّفعات الصغيرة مفيدة لوحدات GPU محدودة الذاكرة أو عندما يكون تعميم أفضل مطلوبًا.


3️⃣ ما هو batch_size المثالي؟

يعتمد أفضل حجم دُفعة على:

  • حجم مجموعة البيانات: مجموعات بيانات صغيرة ← حجم دُفعة أصغر؛ مجموعات بيانات كبيرة ← حجم دُفعة أكبر.
  • العتاد (GPU/TPU): وحدات GPU الكبيرة تتعامل مع الدُّفعات الأكبر بشكل أفضل.
  • استقرار التدريب: الدُّفعات الصغيرة تحسّن التعميم، والدُّفعات الكبيرة تُنعِّم التدرّجات.
  • خوارزمية التحسين (Optimization): بعض المحسِّنات (Optimizers) (مثل Adam) تتعامل مع الدُّفعات الصغيرة بشكل أفضل من غيرها.

أحجام الدُّفعات الموصى بها حسب حالة الاستخدام

حجم الدُّفعةمتى تُستخدَم
1حالات متطرفة (مثل التعلّم المعزَّز - Reinforcement Learning، أو التعلّم الفوري - Online Learning)
8 - 16مجموعات بيانات صغيرة، وحدات GPU محدودة الذاكرة
32 (الافتراضي)توازن جيد بين السرعة والتعميم
64 - 128مجموعات بيانات كبيرة، وحدات GPU قوية
256+مجموعات بيانات ضخمة جدًا مع مجموعات TPU/GPU

قاعدة عامة

  • ابدأ بـbatch_size=32 (قيمة افتراضية متوازنة).
  • إذا كان التدريب بطيئًا، زِد حجم الدُّفعة (64 أو 128).
  • إذا كان التعميم ضعيفًا، قلّل حجم الدُّفعة (16 أو 8).

هل تودّ تجربة أحجام دُفعات مختلفة ومقارنة النتائج؟ 🚀

batch_size = 32
train_dataset = tf.data.Dataset.from_tensor_slices((x_train, y_train)) # Creates a dataset object from NumPy arrays or TensorFlow tensors.
train_dataset = train_dataset.shuffle(buffer_size=x_train.shape[0]).batch(batch_size) # Split to batches
test_dataset = tf.data.Dataset.from_tensor_slices((x_test, y_test))
test_dataset = test_dataset.shuffle(buffer_size=x_test.shape[0]).batch(batch_size)
d=list(train_dataset.as_numpy_iterator())
c=0
for x_batch, y_batch in train_dataset:
  c=c+1
  print(x_batch.shape)
(32, 2)
(32, 2)
(32, 2)
(29, 2)

بعد ذلك، اكتب حلقة تدريب (Training Loop) لتحديث معاملات نموذجك تكراريًا باستخدام دالة خسارة الـMSE وتدرّجاتها (Gradients) بالنسبة لمعاملات الإدخال.

تُعرَف هذه الطريقة التكرارية باسم الانحدار التدريجي (Gradient Descent). في كل تكرار، تُحدَّث معاملات النموذج بأخذ خطوة في الاتجاه المعاكس لتدرّجاتها المحسوبة. يحدَّد حجم هذه الخطوة بمعدل التعلّم (Learning Rate)، وهو معامل فائق (Hyperparameter) قابل للضبط. تذكّر أن تدرّج الدالة (Gradient) يشير إلى اتجاه أقصى صعود لها؛ لذا فإن أخذ خطوة في الاتجاه المعاكس يشير إلى اتجاه أقصى انحدار، مما يساعد في النهاية على تقليل دالة خسارة الـMSE إلى أدنى قيمة.

تأثير تغيير معدل التعلّم (learning_rate = 0.01)

يتحكّم معدل التعلّم (lr) في مقدار تحديث أوزان النموذج أثناء التدريب. اختيار معدل التعلّم الصحيح أمر حاسم لأداء النموذج.


🔹 1️⃣ ماذا يحدث عند زيادة learning_rate؟

🔼 معدلات التعلّم الأعلى (مثل 0.1، 0.5، 1.0):

تقارب (Convergence) أسرع (تُحدَّث الأوزان بشكل أكثر عدوانية). ✅ يقلّل وقت التدريب (تكرارات (Iterations) أقل مطلوبة). ❌ خطر تجاوز (Overshooting) الحل الأمثل. ❌ قد تتذبذب الخسارة (Loss) أو تتباعد (لا يحدث تقارب).

توضيح لمعدل تعلّم كبير

تخيّل كرة تتدحرج أسفل تل (الانحدار التدريجي - Gradient Descent).

  • إذا كان lr مرتفعًا جدًا، فستقفز الكرة بشكل عشوائي وقد لا تستقر أبدًا عند أدنى نقطة.

🔻 2️⃣ ماذا يحدث عند تقليل learning_rate؟

🔽 معدلات التعلّم الأقل (مثل 0.001، 0.0001):

تحديثات أكثر استقرارًا (التدرّجات (Gradients) أصغر). ✅ تقارب أفضل نحو الحل الأمثل. ❌ يستغرق وقتًا أطول بكثير للوصول إلى أدنى خسارة. ❌ قد يعلق في حدود دنيا محلية (Local Minima) (تقدّم بطيء).

توضيح لمعدل تعلّم صغير

  • إذا كان lr منخفضًا جدًا، فستتدحرج الكرة ببطء شديد، وتستغرق وقتًا طويلًا جدًا للوصول إلى القاع.

🔹 3️⃣ ما هو learning_rate المثالي؟

القيم الموصى بها لنماذج مختلفة

نوع النموذجlearning_rate المقترح
انحدار خطي بسيط (Simple Linear Regression)0.01 - 0.1
الشبكات العصبية العميقة (DNN)0.001 - 0.01
الشبكات العصبية الالتفافية (CNN)0.0001 - 0.01
نماذج المحوّلات (Transformers) (مثل BERT)0.00001 - 0.0001
التعلّم المعزَّز (Reinforcement Learning)0.0001 - 0.1

🔹 كيف تختار learning_rate؟

  • ابدأ بـ0.01 (القيمة الافتراضية للعديد من النماذج).
  • إذا كانت الخسارة غير مستقرة، قلّله (0.001).
  • إذا كان التدريب بطيئًا جدًا، زِده (0.05).
  • استخدم جدولًا زمنيًا (Scheduler) للضبط الديناميكي.
# Set training parameters
epochs = 100
learning_rate = 0.01
train_losses, test_losses = [], []
 
# Format training loop
for epoch in range(epochs):
  batch_losses_train, batch_losses_test = [], []
 
  # Iterate through the training data
  for x_batch, y_batch in train_dataset:
    with tf.GradientTape() as tape:
      y_pred_batch = lin_reg(x_batch)
      batch_loss = mse_loss(y_pred_batch, y_batch)
    # Update parameters with respect to the gradient calculations
    grads = tape.gradient(batch_loss, lin_reg.variables)
    for g,v in zip(grads, lin_reg.variables):
      v.assign_sub(learning_rate * g)  # Why ?!!!!!!!!!!!!!!!!!!!!! - The perfect step doesn't exist
    # Keep track of batch-level training performance
    batch_losses_train.append(batch_loss)
 
  # Iterate through the -----(testing)----- data
  for x_batch, y_batch in test_dataset:
    y_pred_batch = lin_reg(x_batch)
    batch_loss = mse_loss(y_pred_batch, y_batch)
    # Keep track of batch-level testing performance
    batch_losses_test.append(batch_loss)
 
  # Keep track of epoch-level model performance
  train_loss = tf.reduce_mean(batch_losses_train)
  test_loss = tf.reduce_mean(batch_losses_test)
  train_losses.append(train_loss)
  test_losses.append(test_loss)
  if epoch % 10 == 0:
    print(f'Mean squared error for step {epoch}: {train_loss.numpy():0.3f}')
 
# Output final losses
print(f"\nFinal train loss: {train_loss:0.3f}")
print(f"Final test loss: {test_loss:0.3f}")
#Output final values of trainable parameters
print("\nFinal parameter values:")
for i, var in enumerate(lin_reg.variables):
    print(f"  Variable {i}: {var.numpy()}")
Mean squared error for step 0: 299.503
Mean squared error for step 10: 0.078
Mean squared error for step 20: 0.062
Mean squared error for step 30: 0.051
Mean squared error for step 40: 0.041
Mean squared error for step 50: 0.034
Mean squared error for step 60: 0.028
Mean squared error for step 70: 0.024
Mean squared error for step 80: 0.019
Mean squared error for step 90: 0.016
 
Final train loss: 0.014
Final test loss: 0.012
 
Final parameter values:
  Variable 0: 1.1802641153335571
  Variable 1: [[2.0314107]
 [5.0249186]]

ارسم التغيرات في خسارة الـMSE عبر الزمن. حساب مقاييس الأداء على مجموعة تحقّق (Validation Set) أو مجموعة اختبار (Test Set) مخصَّصة يضمن ألا يُفرِط النموذج في التخصيص (Overfit) لمجموعة بيانات التدريب، وأن يعمّم جيدًا على بيانات لم يرها من قبل.

lin_reg.variables
(<tf.Variable 'Variable:0' shape=() dtype=float32, numpy=1.1802641153335571>,
 <tf.Variable 'Variable:0' shape=(2, 1) dtype=float32, numpy=
 array([[2.0314107],
        [5.0249186]], dtype=float32)>)
matplotlib.rcParams['figure.figsize'] = [9, 6]
 
plt.plot(range(epochs), train_losses, label = "Training loss")
plt.plot(range(epochs), test_losses, label = "Testing loss")
plt.xlabel("Epoch")
plt.ylabel("Mean squared error loss")
plt.legend()
plt.title("MSE loss vs training iterations");

يبدو أن النموذج يقوم بعمل جيد في ملاءمة (Fitting) بيانات التدريب، مع تعميم جيد أيضًا على بيانات الاختبار غير المرئية.

الخلاصة

تهانينا! لقد دربت نموذج انحدار باستخدام واجهات TensorFlow Core منخفضة المستوى.

للمزيد من الأمثلة على استخدام واجهات TensorFlow Core، راجع الأدلة التالية: