مراجعة تعلّم الآلة ومثال على الانحدار التدريجي (Gradient Descent)
مراجعة تعلم الآلة ومثال على الانحدار التدريجي (Gradient Descent)
في هذا الدفتر (Notebook)، سنحل مسألة انحدار خطي بسيطة باستخدام الانحدار التدريجي (Gradient Descent).
ستتبع هنا المراحل المعتادة لعملية تعلم الآلة:
- تحميل مجموعة البيانات (Dataset).
- بناء نموذج انحدار خطي متعدد (Multiple Linear Regression).
- تقييم أداء النموذج.
الإعداد
استورد 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توليد بيانات من توزيع معروف
سنقوم أدناه بتوليد بيانات من توزيع معروف.
وتحديدًا، النموذج الحقيقي هو:
يتبع كل من و توزيعًا منتظمًا (Uniform Distribution) على الفترة ، بينما يمثل b حد التقاطع (Intercept).
نحدد قيمًا فعلية لـ ،، و
هنا ، ، و
بعد ذلك نولّد متجهًا (Vector) من قيم وفقًا للنموذج، ونجمع المتغيرات التنبؤية معًا في مصفوفة 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()بعد ذلك، قسّم مجموعة البيانات إلى مجموعتي تدريب واختبار. تأكد من خلط (Shuffle) مجموعة البيانات باستخدام 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). معادلة الانحدار الخطي المتعدد هي كما يلي:
حيث إن
- : متجه الهدف (Target Vector)
- : مصفوفة الخصائص (Feature Matrix)
- : متجه الأوزان (Weight Vector)
- : الانحياز (Bias)
باستخدام المُزخرِف (Decorator) @tf.function، يتم تتبّع كود Python المقابل لتوليد رسم بياني (Graph) قابل للاستدعاء في TensorFlow. هذا الأسلوب مفيد لحفظ النموذج وتحميله بعد التدريب، كما يمكن أن يوفر تحسينًا في الأداء للنماذج ذات الطبقات الكثيرة والعمليات المعقدة.
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) لخصائصه مضافًا إليه حد الانحياز. يمكن بعد ذلك إلغاء توحيد (Unstandardize) هذا التنبؤ للحصول على قيمته بالوحدات الأصلية.
lin_reg = LinearRegression()
prediction = lin_reg(x_train[:1])
prediction.numpy()array([10.493174], dtype=float32)تعريف دالة الخسارة (Loss Function)
الآن، عرّف دالة خسارة لتقييم أداء النموذج أثناء عملية التدريب.
بما أن مسائل الانحدار تتعامل مع مخرجات مستمرة، فإن متوسط مربع الخطأ (Mean Squared Error - MSE) يُعد خيارًا مثاليًا لدالة الخسارة. يُعرَّف MSE بالمعادلة التالية:
حيث إن
- : متجه التنبؤات
- : متجه القيم الحقيقية المستهدفة
هدف مسألة الانحدار هذه هو إيجاد متجه الأوزان الأمثل والانحياز الأمثل اللذين يُدنّيان (Minimize) دالة خسارة 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)، وتوازٍ أكبر على وحدات معالجة الرسومات GPU).
✅ استغلال أفضل للعتاد (Hardware) (حساب أكفأ على وحدات GPU ذات الذاكرة الكبيرة).
✅ تحديثات أكثر استقرارًا (يتم حساب متوسط تحديثات التدرج (Gradient) عبر عدد أكبر من العينات). -
العيوب: ❌ استهلاك أكبر للذاكرة (الدفعات الكبيرة تتطلب ذاكرة GPU أكبر).
❌ خطر ضعف التعميم (قد لا تلتقط أحجام الدُفعات الكبيرة أنماط التدريب المتنوعة بشكل جيد).
❌ تقارب أبطأ (تكون التدرجات أصغر، مما يجعل التحديثات أكثر تحفظًا). -
حالة الاستخدام:
🔹 تُعد أحجام الدُفعات الكبيرة مفيدة عند التدريب على مجموعات بيانات ضخمة جدًا باستخدام وحدات GPU قوية.
2️⃣ ماذا يحدث عند تقليل batch_size؟
إذا قمت بتقليل batch_size (مثل 16، 8، 4، 1):
-
المزايا: ✅ تعميم أفضل (الضوضاء الإضافية في تحديثات التدرج تمنع الإفراط في التخصيص Overfitting).
✅ استهلاك أقل للذاكرة (يمكن التدريب على وحدات GPU أصغر).
✅ تحديثات أكثر تكرارًا (فرص أكبر لضبط الأوزان). -
العيوب: ❌ تدريب أبطأ (تحديثات أكثر لكل حقبة ← يستغرق وقتًا أطول).
❌ تدرجات مشوّشة (Noisy) (تحديثات المعاملات تتذبذب أكثر).
❌ كفاءة أقل على وحدات GPU (تعمل وحدات GPU بشكل أفضل مع أحجام دُفعات أكبر). -
حالة الاستخدام:
🔹 تُعد أحجام الدُفعات الصغيرة مفيدة مع وحدات GPU محدودة الذاكرة أو عندما يكون تعميم أفضل مطلوبًا.
3️⃣ ما هو حجم batch_size المثالي؟
يعتمد أفضل حجم دفعة على:
- حجم مجموعة البيانات: مجموعات البيانات الصغيرة ← حجم دفعة أصغر؛ مجموعات البيانات الكبيرة ← حجم دفعة أكبر.
- العتاد (GPU/TPU): وحدات GPU الكبيرة تتعامل مع الدُفعات الأكبر بشكل أفضل.
- استقرار التدريب: الدُفعات الصغيرة تُحسّن التعميم، والدُفعات الكبيرة تُنعّم التدرجات.
- خوارزمية التحسين (Optimizer): بعض المُحسِّنات (مثل Adam) تتعامل مع الدُفعات الصغيرة بشكل أفضل من غيرها.
أحجام الدُفعات المُوصى بها حسب حالة الاستخدام
| حجم الدفعة | متى تُستخدم |
|---|---|
| 1 | حالات متطرفة (مثل التعلم المعزز Reinforcement Learning، والتعلم عبر الإنترنت Online Learning) |
| 8 - 16 | مجموعات بيانات صغيرة، وحدات GPU محدودة الذاكرة |
| 32 (الافتراضي) | توازن جيد بين السرعة والتعميم |
| 64 - 128 | مجموعات بيانات كبيرة، وحدات GPU قوية |
| 256+ | مجموعات بيانات ضخمة جدًا مع عناقيد (Clusters) من TPU/GPU |
قاعدة عامة عملية (Rule of Thumb)
- ابدأ بـ
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 وتدرجاتها بالنسبة للمعاملات المُدخلة.
تُعرف هذه الطريقة التكرارية باسم الانحدار التدريجي (Gradient Descent). في كل تكرار، يتم تحديث معاملات النموذج بأخذ خطوة في الاتجاه المعاكس لتدرجاتها المحسوبة. يُحدَّد حجم هذه الخطوة بواسطة معدل التعلّم (Learning Rate)، وهو معامل فائق (Hyperparameter) قابل للضبط. تذكّر أن تدرج (Gradient) أي دالة يشير إلى اتجاه أشد صعودها؛ لذا فإن أخذ خطوة في الاتجاه المعاكس يشير إلى اتجاه أشد انحدارها، وهو ما يساعد في النهاية على تدنية (Minimize) دالة خسارة MSE.
تأثير تغيير معدل التعلّم (learning_rate = 0.01)
يتحكم معدل التعلّم (lr) في مقدار تحديث أوزان النموذج أثناء التدريب. اختيار معدل التعلّم الصحيح بالغ الأهمية لأداء النموذج.
🔹 1️⃣ ماذا يحدث عند زيادة learning_rate؟
🔼 معدلات التعلّم الأعلى (مثل 0.1، 0.5، 1.0):
✅ تقارب أسرع (تُحدَّث الأوزان بشكل أكثر جرأة).
✅ يقلل وقت التدريب (تكرارات أقل مطلوبة).
❌ خطر تجاوز (Overshooting) الحل الأمثل.
❌ قد تتذبذب الخسارة أو تتباعد (Diverge) (بلا تقارب).
توضيح لتأثير learning_rate الكبير
تخيّل كرة تتدحرج أسفل تلة (الانحدار التدريجي).
- إذا كان
lrمرتفعًا جدًا، فإن الكرة ترتد بشكل عشوائي وجامح وقد لا تستقر أبدًا عند أدنى نقطة.
🔻 2️⃣ ماذا يحدث عند تقليل learning_rate؟
🔽 معدلات التعلّم الأقل (مثل 0.001، 0.0001):
✅ تحديثات أكثر استقرارًا (التدرجات أصغر).
✅ تقارب أفضل نحو الحل الأمثل.
❌ يستغرق وقتًا أطول بكثير للوصول إلى أدنى خسارة.
❌ قد يعلق في نقاط صغرى محلية (Local Minima) (تقدم بطيء).
توضيح لتأثير learning_rate الصغير
- إذا كان
lrمنخفضًا جدًا، فإن الكرة تتدحرج ببطء شديد، وتستغرق وقتًا طويلاً جدًا للوصول إلى القاع.
🔹 3️⃣ ما هو learning_rate المثالي؟
✅ القيم المُوصى بها لنماذج مختلفة
| نوع النموذج | learning_rate المقترح |
|---|---|
| الانحدار الخطي البسيط (Simple Linear Regression) | 0.01 - 0.1 |
| الشبكات العصبية العميقة (Deep Neural Networks - DNN) | 0.001 - 0.01 |
| الشبكات العصبية الالتفافية (Convolutional Neural Networks - 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 البرمجية منخفضة المستوى (Low-level APIs).
لمزيد من الأمثلة على استخدام واجهات TensorFlow Core البرمجية، راجع الأدلة التالية:
- الانحدار اللوجستي (Logistic Regression) للتصنيف الثنائي
- الإدراك الحسي متعدد الطبقات (Multi-layer Perceptrons) للتعرف على الأرقام المكتوبة بخط اليد