Kodax

Recurrent Neural Networks — التعلم العميق

الشبكات العصبية المتكررة (RNN)

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

الفكرة: ذاكرة متجددة

RNN تقرأ التسلسل خطوة خطوة، وتحمل حالة مخفية h تلخص ما رأته حتى الآن:

hₜ = tanh(Wₓ·xₜ + Wₕ·hₜ₋₁ + b)

نفس الأوزان تُستخدم في كل خطوة (مثل مشاركة الأوزان في CNN لكن عبر الزمن).

import numpy as np

rng = np.random.default_rng(0)
Wx, Wh, b = rng.normal(0, 0.5, (1, 3)), rng.normal(0, 0.5, (3, 3)), np.zeros(3)
sequence = [0.5, -0.1, 0.8, 0.3]

h = np.zeros(3)
for t, x in enumerate(sequence):
    h = np.tanh(np.array([x]) @ Wx + h @ Wh + b)
    print(f"خطوة {t}: h = {h.round(3)}")
# h الأخيرة تلخص التسلسل كله ← تمر لطبقة Dense للتنبؤ

مشكلة الذاكرة القصيرة

عند Backpropagation عبر الزمن، يُضرب التدرج بـ Wₕ ومشتقة tanh عند كل خطوة، فيتلاشى (أو ينفجر) مع التسلسلات الطويلة. النتيجة: SimpleRNN تنسى بداية الجمل الطويلة.

LSTM و GRU

LSTM تضيف "خلية ذاكرة" مع بوابات تتعلم ماذا تنسى وماذا تضيف وماذا تُخرج — فتحافظ على المعلومات لمسافات أطول. GRU نسخة مبسطة ببوابتين، أسرع وغالباً بنفس الجودة.

| الطبقة | متى |

|---|---|

| SimpleRNN | للفهم فقط تقريباً |

| LSTM | الخيار الكلاسيكي للتسلسلات الطويلة |

| GRU | بديل أخف لـ LSTM |

| Bidirectional(LSTM(...)) | عندما يكون التسلسل كاملاً متاحاً (تصنيف النص): تقرأ في الاتجاهين |

الأشكال في Keras

مدخلات RNN ثلاثية الأبعاد: (عينات، خطوات زمنية، ميزات لكل خطوة). سلسلة سعر يومي بنافذة 30 يوماً: (n, 30, 1).

- return_sequences=True: أعد مخرجاً لكل خطوة — لازم عند تكديس طبقة RNN فوق أخرى.

تجهيز سلسلة زمنية

import numpy as np

series = np.sin(np.linspace(0, 20, 200))
window = 10
X = np.array([series[i:i + window] for i in range(len(series) - window)])
y = series[window:]
X = X[..., np.newaxis]           # (عينات، 10، 1)
print(X.shape, y.shape)

⚠️ في السلاسل الزمنية لا تخلط البيانات عشوائياً عند التقسيم: درّب على الماضي واختبر على المستقبل، وإلا تسرب المستقبل إلى التدريب.

الواقع اليوم

لمعالجة اللغة حلّت Transformers محل RNN تقريباً بالكامل (درس قادم): تعالج التسلسل بالتوازي وتربط الكلمات البعيدة مباشرة. ما زالت LSTM/GRU مفيدة للسلاسل الزمنية الصغيرة والأجهزة محدودة الموارد.

تمرين

في المثال الكامل قارن SimpleRNN و LSTM و GRU على نافذة 50 بدل 10. أيها تحسّن أكثر مع النافذة الأطول؟

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
import numpy as np

# ===== بيانات تجريبية: تنبؤ سلسلة =====
def create_sequences(data, seq_length):
    X, y = [], []
    for i in range(len(data) - seq_length):
        X.append(data[i:i+seq_length])
        y.append(data[i+seq_length])
    return np.array(X), np.array(y)

# سلسلة sin
data = np.sin(np.linspace(0, 100, 1000))
seq_length = 20

X, y = create_sequences(data, seq_length)
X = X.reshape(-1, seq_length, 1)  # (samples, timesteps, features)

split = int(len(X) * 0.8)
X_train, X_test = X[:split], X[split:]
y_train, y_test = y[:split], y[split:]

# ===== Simple RNN =====
rnn_model = keras.Sequential([
    layers.SimpleRNN(50, input_shape=(seq_length, 1)),
    layers.Dense(1)
])

# ===== LSTM =====
lstm_model = keras.Sequential([
    layers.LSTM(50, input_shape=(seq_length, 1)),
    layers.Dense(1)
])

# ===== GRU =====
gru_model = keras.Sequential([
    layers.GRU(50, input_shape=(seq_length, 1)),
    layers.Dense(1)
])

# ===== Stacked LSTM =====
stacked_model = keras.Sequential([
    layers.LSTM(50, return_sequences=True, input_shape=(seq_length, 1)),
    layers.LSTM(50),
    layers.Dense(1)
])

# التدريب
lstm_model.compile(optimizer='adam', loss='mse')
history = lstm_model.fit(X_train, y_train, epochs=20, validation_split=0.1, verbose=0)

# التقييم
loss = lstm_model.evaluate(X_test, y_test)
print(f"Test Loss: {loss:.4f}")

# التنبؤ
predictions = lstm_model.predict(X_test[:5])
print(f"Predictions: {predictions.flatten()}")
print(f"Actual: {y_test[:5]}")
اختبر فهمك