التسلسلات: RNN و LSTM
مبيعات، أسعار، نصوص: الترتيب هنا جزء من المعنى.
ليه الترتيب مهم؟
«الأكل مش حلو» عكس «مش الأكل حلو»: نفس الكلمات بترتيب مختلف. ومبيعات بكرة مرتبطة بمبيعات النهارده وامبارح والأسبوع اللي فات. الشبكة العادية بتاخد كل المدخلات مرة واحدة ومش فاهمة الترتيب.
الـ RNN: شبكة ليها ذاكرة
الـ RNN بتقرا التسلسل خطوة خطوة، وعند كل خطوة بتاخد المدخل الجديد ومعاه ملخص للي فات (اسمه hidden state). زي ما انت بتقرا الجملة دي: كل كلمة بتفهمها في ضوء اللي قبلها.
مشكلة الـ RNN البسيطة إنها بتنسى بسرعة في التسلسلات الطويلة. الـ LSTM حلّت ده بـ «بوابات» بتقرر إيه اللي يتحفظ وإيه اللي يتنسي وإيه اللي يطلع، زي نوتة بتكتب فيها الحاجات المهمة بس.
توقع مبيعات بكرة
محل مبيعاته بتزيد مع الوقت، وبتعلى في آخر الأسبوع. هنعلّم LSTM يبص على آخر ١٤ يوم ويتوقع اليوم الجاي:
import numpy as np import torch from torch import nn import matplotlib.pyplot as plt torch.manual_seed(0) rng = np.random.default_rng(0) # ---- 1 year of daily sales: trend + weekly pattern + noise ---- days = np.arange(365) sales = 200 + 0.3 * days + 40 * np.sin(2 * np.pi * days / 7) + rng.normal(0, 8, 365) mean, std = sales[:300].mean(), sales[:300].std() # scale using train part only s = (sales - mean) / std WINDOW = 14 X = np.array([s[i:i + WINDOW] for i in range(len(s) - WINDOW)]) y = s[WINDOW:] X = torch.tensor(X, dtype=torch.float32).unsqueeze(-1) # (samples, 14 steps, 1 feature) y = torch.tensor(y, dtype=torch.float32).unsqueeze(-1) split = 300 - WINDOW X_tr, y_tr, X_te, y_te = X[:split], y[:split], X[split:], y[split:] class SalesLSTM(nn.Module): def __init__(self): super().__init__() self.lstm = nn.LSTM(input_size=1, hidden_size=32, batch_first=True) self.head = nn.Linear(32, 1) def forward(self, x): out, _ = self.lstm(x) # out: output at every step return self.head(out[:, -1]) # use the last step to predict tomorrow model = SalesLSTM() opt = torch.optim.Adam(model.parameters(), lr=0.01) for epoch in range(150): opt.zero_grad() loss = nn.functional.mse_loss(model(X_tr), y_tr) loss.backward() opt.step() model.eval() with torch.no_grad(): pred = model(X_te).squeeze().numpy() * std + mean # back to real units actual = y_te.squeeze().numpy() * std + mean print(f"average error on unseen days: {np.abs(pred - actual).mean():.1f} units") plt.figure(figsize=(7, 3.2)) plt.plot(actual, label="actual", color="#14213D") plt.plot(pred, label="LSTM forecast", color="#E0A800", linewidth=2) plt.title("Next-day sales forecast (65 unseen days)") plt.legend(); plt.grid(alpha=0.3) plt.show()
الموديل لقط لوحده النمط الأسبوعي كويس جداً. بس بص تاني: توقعاته بتطلع أقل شوية من الحقيقي، والفرق بيكبر مع الوقت. ليه؟ لأن المبيعات بتزيد، ووصلت لأرقام الموديل عمره ما شافها في التدريب. ده عيب معروف في الموديلات دي مع الـ trend، والحل الشائع إنك تعلّمه يتوقع التغيير عن امبارح بدل الرقم نفسه. والدرس الأهم: دايماً ارسم التوقعات، الرقم لوحده ماكانش هيوريك المشكلة دي.
(batch, sequence_length, features). هنا (286, 14, 1): ٢٨٦ مثال، كل واحد ١٤ يوم، وكل يوم فيه رقم واحد. لو عندك كمان درجة الحرارة وهل اليوم إجازة، الـ features تبقى ٣.