معاينة مختبر آمنة
RNNPy Torch
هذي معاينة منقّحة للقراءة فقط؛ ما فيه أي شيء يشتغل داخل الصفحة.
قراءة فقط
معاينة الدفتر
RNNPy Torch
> **ملاحظة بيئة التشغيل المدمجة:** هالمعاينة تستخدم عيّنة صغيرة وثابتة وآمنة من ناحية الحقوق عشان تكون النتايج قابلة للتكرار. النتايج بالحجم الكامل تحتاج مجموعة البيانات أو النموذج الموثّق بالدرس داخل بيئة خارجية معتمدة.
# الشبكات العصبية المتكررة (RNN)
في الوحدة اللي راحت استخدمنا تمثيلات دلالية غنية للنص، وحطّينا فوق التضمينات مصنّفًا خطيًا بسيطًا. هالبنية تلتقط المعنى المجمّع لكلمات الجملة، لكنها ما تراعي **ترتيب** الكلمات؛ لأن عملية تجميع التضمينات تشيل معلومات الترتيب من النص الأصلي. وبما إن هالنماذج ما تقدر تمثّل ترتيب الكلمات، فهي ما تحل المهام الأكثر تعقيدًا أو غموضًا، مثل توليد النص والإجابة عن الأسئلة.
عشان نلتقط معنى تسلسل نصي، نحتاج بنية ثانية اسمها **الشبكة العصبية المتكررة** (Recurrent Neural Network أو RNN). في RNN نمرر الجملة عبر الشبكة رمزًا رمزًا، وتنتج الشبكة **حالة** نرجع نمررها لها مع الرمز اللي بعده.
> **وصف الشكل:** RNN
إذا كان تسلسل رموز الإدخال هو $X_0,\dots,X_n$، تنشئ RNN تسلسلًا من كتل الشبكة العصبية وتدرّبه كاملًا باستخدام الانتشار العكسي. كل كتلة تستقبل الزوج $(X_i,S_i)$ وتنتج $S_{i+1}$. بعدها تدخل الحالة الأخيرة $S_n$، أو المخرج $X_n$، في مصنّف خطي يعطي النتيجة. كل كتل الشبكة تشترك في الأوزان نفسها، ونتدرّب عليها من طرف إلى طرف بتمرير واحد للانتشار العكسي.
لأن متجهات الحالة $S_0,\dots,S_n$ تنتقل عبر الشبكة، تقدر الشبكة تتعلّم العلاقات المتتابعة بين الكلمات. مثلًا، إذا ظهرت كلمة *not* في أي موضع من التسلسل، تقدر تتعلّم إنها تنفي عناصر معيّنة داخل متجه الحالة، وبكذا تمثّل معنى النفي.
> بما إن أوزان كتل RNN كلها مشتركة في الصورة، نقدر نمثّل الرسم نفسه بكتلة وحدة، مثل اللي على اليمين، ومعها حلقة تغذية راجعة ترجع حالة المخرج إلى مدخل الشبكة.
الحين خلونا نشوف كيف تساعدنا الشبكات العصبية المتكررة في تصنيف مجموعة البيانات الإخبارية.
import collections
import random
import re
import numpy as np
import torch
# course-edition deterministic NLP utilities v2
COURSE_NEWS_CLASSES = ["World", "Sports", "Business", "Sci/Tech"]
# This compact corpus was written for this edition. It keeps the notebooks
# deterministic, network-free, and quick enough to run on a learner's CPU.
COURSE_NEWS_TRAIN = [
(1, "Paris hosted a regional summit where diplomats discussed water security, peaceful trade routes, and a shared emergency plan for neighboring communities."),
(1, "The elected council approved a cross-border health agreement after delegates reviewed hospital capacity, medicine access, and transparent public reporting."),
(1, "A coastal city welcomed observers for a public vote, while local groups published clear guidance about polling places and accessible transportation."),
(1, "The king and queen met a woman who leads the relief agency and a man who coordinates volunteers, then the group announced a neutral humanitarian corridor."),
(1, "Ministers signed a climate adaptation pledge that funds drought monitoring, resilient farms, and open scientific exchange across several countries."),
(1, "Community mediators opened a week of talks focused on civilian safety, reliable food deliveries, and practical steps toward a lasting ceasefire."),
(2, "The basketball team completed a patient comeback in the final quarter, using quick passes, strong defense, and a balanced scoring plan to win the tournament."),
(2, "A young runner broke the course record after months of careful training, recovery sessions, nutrition planning, and support from her local athletics club."),
(2, "The football coach praised disciplined play after the squad protected an early lead and created chances through accurate movement on both wings."),
(2, "Fans filled the arena for a close volleyball final in which both teams served aggressively, defended long rallies, and respected every referee decision."),
(2, "The tennis champion returned after injury and won a demanding match by varying pace, placing serves carefully, and staying calm during two tie breaks."),
(2, "Organizers added inclusive swimming events to the city games so more athletes can compete with safe facilities, trained officials, and fair timing equipment."),
(3, "Microsoft announced a small-business program that combines cloud credits, security workshops, and practical accounting support for new local companies."),
(3, "Global funds moved cautiously after the central bank held interest rates steady and asked lenders to publish clearer information about consumer borrowing costs."),
(3, "A neighborhood market expanded its delivery service, hired twelve workers, and invested revenue in reusable packaging supplied by another local business."),
(3, "The manufacturer reported stable quarterly sales while noting that shipping delays and higher material prices could reduce margins later in the year."),
(3, "Two payment companies agreed to share fraud signals through a privacy-preserving system designed to protect customers without exposing purchase details."),
(3, "A cooperative offered farmers transparent contracts and faster invoices, helping members plan equipment repairs before the next growing season begins."),
(4, "Researchers trained a compact neural network to identify damaged solar panels, then published the evaluation data and documented where the model still fails."),
(4, "A university technology lab released an open sensor design that measures classroom air quality and stores only anonymous readings for public analysis."),
(4, "Engineers improved a battery recycling process by recovering more useful minerals at lower temperatures, reducing energy use during the pilot study."),
(4, "The space telescope captured a detailed spectrum from a distant planet, giving scientists new evidence about clouds and molecules in its atmosphere."),
(4, "A software team tested an accessibility assistant with keyboard users and screen-reader experts before changing the interface and publishing the results."),
(4, "Students built a small robot that maps indoor obstacles with inexpensive sensors, explains each route choice, and says hello when a test run begins."),
]
COURSE_NEWS_TEST = [
(1, "Regional delegates published a joint disaster response schedule after reviewing evacuation routes and communication gaps."),
(1, "Election monitors confirmed the final count and recommended clearer access rules for voters who need assistance."),
(2, "The basketball captain scored late, but credited the victory to defense, passing, and careful preparation across the whole season."),
(2, "A cycling club opened a safe youth race with trained marshals, marked turns, and free equipment checks."),
(3, "Technology shares rose while retail funds remained cautious after companies issued mixed forecasts for the next quarter."),
(3, "The family business secured a modest loan to replace old equipment and expand its apprenticeship program."),
(4, "A research team released a smaller language model with documented energy measurements and a public evaluation set."),
(4, "Scientists used open satellite data to improve flood warnings and shared the software with local emergency teams."),
]
def course_tokenize(text):
return re.findall(r"[a-z0-9]+(?:'[a-z0-9]+)?", str(text).lower())
def course_ngrams_iterator(tokens, ngrams=1):
tokens = list(tokens)
for token in tokens:
yield token
for size in range(2, max(1, ngrams) + 1):
for start in range(0, len(tokens) - size + 1):
yield " ".join(tokens[start:start + size])
class CourseVocabulary:
def __init__(self, counter, min_freq=1, max_tokens=None):
ordered = sorted(
(token for token, count in counter.items() if count >= min_freq),
key=lambda token: (-counter[token], token),
)
if max_tokens is not None:
ordered = ordered[:max(0, max_tokens - 1)]
self.itos = ["<unk>"] + [token for token in ordered if token != "<unk>"]
self.stoi = {token: index for index, token in enumerate(self.itos)}
def __len__(self):
return len(self.itos)
def __getitem__(self, token):
return self.stoi.get(token, 0)
def get_stoi(self):
return dict(self.stoi)
def get_itos(self):
return list(self.itos)
def build_course_vocab(dataset, ngrams=1, min_freq=1, max_tokens=None, tokenizer=course_tokenize):
counter = collections.Counter()
for _label, text in dataset:
counter.update(course_ngrams_iterator(tokenizer(text), ngrams=ngrams))
return CourseVocabulary(counter, min_freq=min_freq, max_tokens=max_tokens)
def load_course_fixture(ngrams=1, min_freq=1, vocab_size=None, lines_cnt=None):
global vocab, tokenizer
tokenizer = course_tokenize
train_dataset = list(COURSE_NEWS_TRAIN)
test_dataset = list(COURSE_NEWS_TEST)
vocabulary_rows = train_dataset if lines_cnt is None else train_dataset[:lines_cnt]
vocab = build_course_vocab(
vocabulary_rows,
ngrams=ngrams,
min_freq=min_freq,
max_tokens=vocab_size,
tokenizer=tokenizer,
)
return train_dataset, test_dataset, list(COURSE_NEWS_CLASSES), vocab
def encode(text, voc=None, unk=0, tokenizer=course_tokenize):
selected_vocab = vocab if voc is None else voc
stoi = selected_vocab.get_stoi()
return [stoi.get(token, unk) for token in tokenizer(text)]
def train_epoch(net, dataloader, lr=0.01, optimizer=None, loss_fn=None, epoch_size=None, report_freq=200):
optimizer = optimizer or torch.optim.Adam(net.parameters(), lr=lr)
loss_fn = (loss_fn or torch.nn.CrossEntropyLoss()).to(device)
net.train()
total_loss, accuracy, count, batch_index = 0.0, 0, 0, 0
for labels, features in dataloader:
optimizer.zero_grad()
features, labels = features.to(device), labels.to(device)
output = net(features)
loss = loss_fn(output, labels)
loss.backward()
optimizer.step()
total_loss += loss.item()
accuracy += (output.argmax(1) == labels).sum().item()
count += len(labels)
batch_index += 1
if batch_index % report_freq == 0:
print(f"{count}: acc={accuracy / count:.4f}")
if epoch_size and count > epoch_size:
break
return total_loss / max(count, 1), accuracy / max(count, 1)
def padify(batch, voc=None, tokenizer=course_tokenize):
vectors = [encode(item[1], voc=voc, tokenizer=tokenizer) for item in batch]
max_length = max(map(len, vectors))
return (
torch.LongTensor([item[0] - 1 for item in batch]),
torch.stack([
torch.nn.functional.pad(torch.tensor(vector), (0, max_length - len(vector)), value=0)
for vector in vectors
]),
)
def offsetify(batch, voc=None):
vectors = [torch.tensor(encode(item[1], voc=voc)) for item in batch]
offsets = torch.tensor(([0] + [len(vector) for vector in vectors])[:-1]).cumsum(dim=0)
return torch.LongTensor([item[0] - 1 for item in batch]), torch.cat(vectors), offsets
def train_epoch_emb(net, dataloader, lr=0.01, optimizer=None, loss_fn=None, epoch_size=None, report_freq=200, use_pack_sequence=False):
optimizer = optimizer or torch.optim.Adam(net.parameters(), lr=lr)
loss_fn = (loss_fn or torch.nn.CrossEntropyLoss()).to(device)
net.train()
total_loss, accuracy, count, batch_index = 0.0, 0, 0, 0
for labels, text, offsets in dataloader:
optimizer.zero_grad()
labels, text = labels.to(device), text.to(device)
offsets = offsets.to("cpu" if use_pack_sequence else device)
output = net(text, offsets)
loss = loss_fn(output, labels)
loss.backward()
optimizer.step()
total_loss += loss.item()
accuracy += (output.argmax(1) == labels).sum().item()
count += len(labels)
batch_index += 1
if batch_index % report_freq == 0:
print(f"{count}: acc={accuracy / count:.4f}")
if epoch_size and count > epoch_size:
break
return total_loss / max(count, 1), accuracy / max(count, 1)
random.seed(42)
np.random.seed(42)
torch.manual_seed(42)
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
tokenizer = course_tokenize
vocab = None
train_dataset, test_dataset, classes, vocab = load_course_fixture()
vocab_size = len(vocab)## مصنّف RNN بسيط
في RNN البسيطة، كل وحدة متكررة عبارة عن شبكة خطية بسيطة تستقبل متجه الإدخال ومتجه الحالة بعد دمجهما، وتنتج متجه حالة جديدًا. يمثّل PyTorch هالوحدة بالفئة `RNNCell`، ويمثّل شبكة من هالخلايا بالطبقة `RNN`.
عشان نعرّف مصنّف RNN، نطبّق أولًا طبقة تضمين تقلّل أبعاد مفردات الإدخال، ثم نحط فوقها طبقة RNN:
class RNNClassifier(torch.nn.Module):
def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):
super().__init__()
self.hidden_dim = hidden_dim
self.embedding = torch.nn.Embedding(vocab_size, embed_dim)
self.rnn = torch.nn.RNN(embed_dim,hidden_dim,batch_first=True)
self.fc = torch.nn.Linear(hidden_dim, num_class)
def forward(self, x):
batch_size = x.size(0)
x = self.embedding(x)
x,h = self.rnn(x)
return self.fc(x.mean(dim=1))> **ملاحظة:** نستخدم هنا طبقة تضمين ما تدرّبت من قبل عشان نبسّط المثال. ولنتائج أفضل، نقدر نستخدم طبقة تضمين مدرّبة مسبقًا بتضمينات Word2Vec أو GloVe، مثل ما شرحنا في الوحدة اللي راحت. وإذا تبغون تفهمون الفكرة أعمق، عدّلوا الكود عشان يشتغل مع تضمينات مدرّبة مسبقًا.
في مثالنا بنستخدم محمّل بيانات يضيف حشوًا، بحيث تحتوي كل دفعة على تسلسلات محشوّة بالطول نفسه. تستقبل طبقة RNN تسلسل موترات التضمين، وتنتج مخرجين:
* $x$ هو تسلسل مخرجات خلايا RNN في كل خطوة.
* $h$ هي الحالة المخفية الأخيرة لآخر عنصر في التسلسل.
بعدها نطبّق مصنّفًا خطيًا كامل الاتصال عشان نحصل على عدد الفئات.
> **ملاحظة:** تدريب RNN صعب نسبيًا؛ لأن فك خلايا RNN على امتداد التسلسل ينتج عددًا كبيرًا من الطبقات المشاركة في الانتشار العكسي. لذلك نحتاج معدل تعلّم صغيرًا وحجمًا أكبر من مجموعة البيانات عشان نطلع بنتائج زينة. التدريب قد يطوّل، فالأفضل تستخدمون GPU.
train_loader = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=padify, shuffle=True)
net = RNNClassifier(vocab_size,64,32,len(classes)).to(device)
train_epoch(net,train_loader, lr=0.001)## الذاكرة طويلة قصيرة المدى (LSTM)
من أبرز مشاكل RNN التقليدية مشكلة **تلاشي التدرجات**. ولأننا ندرّب RNN من طرف إلى طرف بتمرير واحد للانتشار العكسي، يصعب عليها توصيل الخطأ إلى الطبقات الأولى؛ وبهالسبب ما تتعلّم العلاقات بين الرموز المتباعدة زين. من طرق معالجة المشكلة إضافة **إدارة صريحة للحالة** باستخدام **بوابات**. ومن أشهر البنى اللي تستخدم هالفكرة: **الذاكرة طويلة قصيرة المدى** (Long Short-Term Memory أو LSTM) و**وحدة التكرار ذات البوابات** (Gated Recurrent Unit أو GRU).
> **وصف الشكل:** صورة توضح مثالًا على خلية ذاكرة طويلة وقصيرة المدى
تنظيم شبكة LSTM قريب من RNN، لكن فيها حالتان تنتقلان من طبقة للي بعدها: الحالة $c$ والمتجه المخفي $h$. في كل وحدة، ندمج المتجه المخفي $h_i$ مع الإدخال $x_i$، ومن خلال **البوابات** يتحكمان باللي يصير للحالة $c$. كل بوابة شبكة عصبية تستخدم تنشيط sigmoid، ومخرجها يقع ضمن $[0,1]$. وإذا ضربنا هالمخرج في متجه الحالة، نقدر نتعامل معه كأنه قناع يحدد وش يمر ووش يتوقف. البوابات، من اليسار لليمين في الصورة، هي:
* **بوابة النسيان**: تأخذ المتجه المخفي وتحدد وش من مكوّنات المتجه $c$ ننساه، ووش نخليه يمر.
* **بوابة الإدخال**: تأخذ معلومات من متجهي الإدخال والحالة المخفية، وتضيفها إلى الحالة.
* **بوابة الإخراج**: تحوّل الحالة عبر طبقة خطية بتنشيط $\tanh$، ثم تستخدم المتجه المخفي $h_i$ عشان تختار بعض مكوّناتها وتنتج حالة جديدة $c_{i+1}$.
نقدر نتخيّل مكوّنات الحالة $c$ مثل أعلام تنفتح وتنقفل. مثلًا، إذا مر علينا الاسم *Alice* في التسلسل، نفترض إنه يدل على شخصية مؤنثة ونفعّل في الحالة علمًا يقول إن الجملة فيها اسم مؤنث. وإذا مرّت بعده العبارة *and Tom*، نفعّل علمًا يدل على الجمع. بهالتحكم في الحالة، تقدر الشبكة نظريًا تتابع الخصائص النحوية لأجزاء الجملة.
> **ملاحظة:** إذا تبغون تفهمون تفاصيل LSTM من الداخل، فمقال Christopher Olah [Understanding LSTM Networks](https://colah.github.io/posts/2015-08-Understanding-LSTMs/) مورد ممتاز.
مع إن بنية خلية LSTM الداخلية تبدو معقّدة، يخفي PyTorch تفاصيل تنفيذها داخل الفئة `LSTMCell`، ويوفّر الكائن `LSTM` لتمثيل طبقة LSTM كاملة. لذلك بيكون تنفيذ مصنّف LSTM قريبًا جدًا من RNN البسيطة اللي شفناها فوق:
class LSTMClassifier(torch.nn.Module):
def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):
super().__init__()
self.hidden_dim = hidden_dim
self.embedding = torch.nn.Embedding(vocab_size, embed_dim)
self.embedding.weight.data = torch.randn_like(self.embedding.weight.data)-0.5
self.rnn = torch.nn.LSTM(embed_dim,hidden_dim,batch_first=True)
self.fc = torch.nn.Linear(hidden_dim, num_class)
def forward(self, x):
batch_size = x.size(0)
x = self.embedding(x)
x,(h,c) = self.rnn(x)
return self.fc(h[-1])الحين خلونا ندرّب الشبكة. انتبهوا إن تدريب LSTM بطيء بعد، وممكن ما تشوفون ارتفاعًا واضحًا في الدقة ببداية التدريب. وقد تحتاجون تجرّبون أكثر من قيمة لمعامل معدل التعلّم `lr` لين تلقون قيمة تعطي سرعة تدريب معقولة من دون ما تهدر الذاكرة.
net = LSTMClassifier(vocab_size,64,32,len(classes)).to(device)
train_epoch(net,train_loader, lr=0.001)## التسلسلات المحزّمة
في مثالنا اضطرينا نحشو كل التسلسلات في الدفعة المصغّرة بمتجهات أصفار. هالشي يهدر جزءًا من الذاكرة، لكن المشكلة الأكبر مع RNN إن الحشو ينشئ خلايا RNN إضافية تدخل في التدريب مع إنها ما تحمل أي معلومة مفيدة. الأفضل إننا ندرّب RNN على الطول الفعلي لكل تسلسل بس.
عشان نسوي كذا، يوفّر PyTorch صيغة خاصة لتخزين التسلسلات المحزّمة. افترضوا إن عندنا دفعة مصغّرة محشوّة بالشكل هذا:
```
[[1,2,3,4,5],
[6,7,8,0,0],
[9,0,0,0,0]]
```
هنا يمثّل 0 قيم الحشو، ومتجه الأطوال الفعلية لتسلسلات الإدخال هو `[5,3,1]`.
عشان ندرّب RNN بكفاءة مع التسلسلات المحشوّة، نبدأ أول مجموعة من خلايا RNN بدفعة كبيرة (`[1,6,9]`). بعدها نوقف معالجة التسلسل الثالث ونكمل بدفعات أقصر (`[2,7]`, `[3,8]`)، وهكذا. لذلك نمثّل التسلسل المحزّم بمتجه واحد، وهو في مثالنا `[1,6,9,2,7,3,8,4,5]`، ومعه متجه الأطوال (`[5,3,1]`). ومن الاثنين نقدر نعيد بناء الدفعة المصغّرة المحشوّة الأصلية بسهولة.
عشان ننتج تسلسلًا محزّمًا، نستخدم الدالة `torch.nn.utils.rnn.pack_padded_sequence`. كل الطبقات المتكررة، ومنها RNN وLSTM وGRU، تقبل التسلسلات المحزّمة وتنتج مخرجات محزّمة، ونفكها بالدالة `torch.nn.utils.rnn.pad_packed_sequence`.
ولإنشاء التسلسل المحزّم لازم نمرر متجه الأطوال إلى الشبكة؛ لذلك نحتاج دالة ثانية تجهّز الدفعات المصغّرة:
def pad_length(b):
# build vectorized sequence
v = [encode(x[1]) for x in b]
# compute max length of a sequence in this minibatch and length sequence itself
len_seq = list(map(len,v))
l = max(len_seq)
return ( # tuple of three tensors - labels, padded features, length sequence
torch.LongTensor([t[0]-1 for t in b]),
torch.stack([torch.nn.functional.pad(torch.tensor(t),(0,l-len(t)),mode='constant',value=0) for t in v]),
torch.tensor(len_seq)
)
train_loader_len = torch.utils.data.DataLoader(train_dataset, batch_size=16, collate_fn=pad_length, shuffle=True)الشبكة نفسها بتكون قريبة جدًا من `LSTMClassifier` اللي فوق، لكن تمريرة `forward` تستقبل الدفعة المصغّرة المحشوّة ومتجه أطوال التسلسلات مع بعض. وبعد ما نحسب التضمين، نبني التسلسل المحزّم ونمرره إلى طبقة LSTM، ثم نفك النتيجة من جديد.
> **ملاحظة:** فعليًا ما نستخدم النتيجة المفكوكة `x`؛ لأن الحسابات اللي بعدها تعتمد على مخرج الطبقات المخفية. يعني نقدر نحذف فك الحزمة كاملًا من هالكود. حطّيناه هنا عشان يسهل عليكم تعدّلون المثال إذا احتجتوا مخرج الشبكة في حسابات لاحقة.
class LSTMPackClassifier(torch.nn.Module):
def __init__(self, vocab_size, embed_dim, hidden_dim, num_class):
super().__init__()
self.hidden_dim = hidden_dim
self.embedding = torch.nn.Embedding(vocab_size, embed_dim)
self.embedding.weight.data = torch.randn_like(self.embedding.weight.data)-0.5
self.rnn = torch.nn.LSTM(embed_dim,hidden_dim,batch_first=True)
self.fc = torch.nn.Linear(hidden_dim, num_class)
def forward(self, x, lengths):
batch_size = x.size(0)
x = self.embedding(x)
pad_x = torch.nn.utils.rnn.pack_padded_sequence(x,lengths,batch_first=True,enforce_sorted=False)
pad_x,(h,c) = self.rnn(pad_x)
x, _ = torch.nn.utils.rnn.pad_packed_sequence(pad_x,batch_first=True)
return self.fc(h[-1])الحين خلونا نبدأ التدريب:
net = LSTMPackClassifier(vocab_size,64,32,len(classes)).to(device)
train_epoch_emb(net,train_loader_len, lr=0.001,use_pack_sequence=True)> **ملاحظة:** الوسيط `use_pack_sequence` يخلّي متجه أطوال التسلسلات على المعالج، لأن `pack_padded_sequence` يتطلب أطوالًا موجودة على المعالج. دالة `train_epoch_emb` المكتوبة لهالنسخة والموجودة بأول خلية شفرة تطبّق هالسلوك، لذلك الدفتر مكتفٍ ذاتيًا.
## شبكات RNN ثنائية الاتجاه ومتعددة الطبقات
في أمثلتنا كانت الشبكات المتكررة كلها تمشي باتجاه واحد، من بداية التسلسل إلى نهايته. هالشي يبدو طبيعيًا لأنه يشبه طريقتنا في القراءة وسماع الكلام. لكن في تطبيقات كثيرة يكون عندنا وصول عشوائي إلى تسلسل الإدخال، وساعتها قد يفيدنا نشغّل الحساب المتكرر بالاتجاهين. هذي نسميها شبكات RNN **ثنائية الاتجاه**، وننشئها بتمرير المعامل `bidirectional=True` إلى باني RNN/LSTM/GRU.
مع الشبكة ثنائية الاتجاه نحتاج متجهي حالة مخفية، واحدًا لكل اتجاه. يجمع PyTorch المتجهين في متجه واحد حجمه مضاعف، وهالترتيب مريح؛ لأننا عادةً نمرر الحالة المخفية الناتجة إلى طبقة خطية كاملة الاتصال، وكل اللي علينا نحسب حسابه وقت إنشاء الطبقة هو تضاعف الحجم.
الشبكة المتكررة، سواء كانت أحادية الاتجاه أو ثنائية، تلتقط أنماطًا داخل التسلسل وتخزّنها في متجه الحالة أو تمررها للمخرج. ومثل الشبكات العصبية الالتفافية، نقدر نبني طبقة متكررة ثانية فوق الأولى عشان تلتقط أنماطًا أعلى مستوى انطلاقًا من الأنماط البسيطة اللي استخرجتها الطبقة الأولى. من هنا تجينا فكرة **RNN متعددة الطبقات**: شبكتان متكررتان أو أكثر، يكون مخرج كل طبقة منها مدخلًا للطبقة اللي بعدها.
> **وصف الشكل:** صورة توضّح شبكة RNN متعددة الطبقات من نوع LSTM
*الصورة مأخوذة من [هالتدوينة](https://docs.pytorch.org/docs/stable/generated/torch.nn.LSTM.html) للكاتب Fernando López.*
يسهّل PyTorch بناء هالشبكات؛ كل اللي نحتاجه نمرر المعامل `num_layers` إلى باني RNN/LSTM/GRU، وهو يبني طبقات التكرار تلقائيًا. هذا يعني بعد إن حجم متجه الحالة المخفية يكبر بالتناسب مع عدد الطبقات، ولازم نراعي هالزيادة وإحنا نتعامل مع مخرج الطبقات المتكررة.
## استخدام RNN في مهام ثانية
شفنا في هالوحدة إننا نقدر نستخدم RNN في التصنيف على مستوى التسلسلات. لكنها تتعامل بعد مع مهام أكثر، مثل توليد النص والترجمة الآلية وغيرها. بناخذ هالمهام في الوحدة الجاية.
حذفنا المخرجات وعدّادات التشغيل والودجات والمحتوى النشط وقت الاستيراد. شغّل الدفاتر بس في بيئة خارجية تثق فيها.
سجّل تطبيقك
التسجيل اختياري، يفيدك تتذكر وش طبّقت، ولا يمنع إكمال الدورة.