RNN — ক্রমিক ডেটা
এই পাঠে যা শিখবেন
- ক্রমিক ডেটা কী — ও কেন MLP/CNN যথেষ্ট না
- RNN-এর hidden state ও parameter sharing
- RNN cell-এর গাণিতিক রূপ — forward computation
- Three architecture pattern — sentiment, generation, tagging
- PyTorch nn.RNN দিয়ে Bangla character-level model
- Vanilla RNN-এর সীমাবদ্ধতা — গভীরে যাওয়ার আগে
১ · ক্রমিক ডেটা — কেন বিশেষ
একটি ছবি — pixel-এর collection, ক্রম গুরুত্বপূর্ণ না (CNN-এ permutation একটু matter কিন্তু global)। কিন্তু "আমি ভাত খাই না" বনাম "না ভাত খাই আমি" — শব্দের ক্রম বদলালে অর্থ বদলায়। Sequential dataSequential Dataএমন ডেটা যেখানে ক্রম গুরুত্বপূর্ণ — text (বাক্যে শব্দ), audio (sample-এর ক্রম), video (frame), stock price (time-series), DNA (base sequence)।-এ প্রতিটি element-এর "অবস্থান" significant।
- Text: "ঢাকার আবহাওয়া আজ ভাল" — শব্দ-এর sequence।
- Audio: ১৬০০০ sample/সেকেন্ড — number sequence।
- Time-series: Bangladesh-এ চালের দাম — দৈনিক/মাসিক sequence।
- DNA: A, T, G, C — base sequence।
MLP fixed-size input নেয়। বাক্য ৫ শব্দ বা ৫০ শব্দ — sometimes ভিন্ন। তাছাড়া MLP-এর কাছে input-এর প্রতিটি position-এর জন্য আলাদা weight — ক্রম শেখা কঠিন। ছোট ডেটায় overfittingOverfittingমডেল training data মুখস্থ করে ফেলে — কিন্তু নতুন data-তে খারাপ পারফর্ম করে। অবধারিত।
২ · RNN — মূল ধারণা
ভাবুন একজন মানুষ একটি বই পড়ছেন। প্রতিটি শব্দ পড়ার সময় তার "মাথায়" আগের শব্দগুলোর একটি সংক্ষিপ্ত ছাপ আছে। সেই ছাপ + নতুন শব্দ = updated মাথা। RNN ঠিক এই কাজটা করে — একটি hidden state $h_t$ যা "এতক্ষণ যা পড়েছি, তার সংক্ষিপ্ত প্রতিনিধিত্ব"।
প্রতিটি timestep $t$-এ:
$$h_t = \tanh(W_h \, h_{t-1} + W_x \, x_t + b_h)$$ $$y_t = W_y \, h_t + b_y$$
লক্ষণীয়: $W_h, W_x, W_y$ — সমস্ত timestep-এ একই weight (parameter sharing)। এই কারণেই RNN যেকোনো দৈর্ঘ্যের sequence handle করতে পারে।
৩ · Unrolling — সময়ের সাথে network
RNN-কে "একই cell বারবার" হিসেবে দেখলে confuse লাগতে পারে। Unrolling করলে সহজ — প্রতিটি timestep-কে আলাদা copy হিসেবে আঁকুন, সব copy-তে একই weight। তখন এটা একটি deep feed-forward network-এর মতো — কিন্তু weight-share করা।
একটি ৪-শব্দের বাক্য "আমি বাংলা ভালোবাসি" দেখুন:
- $t=1$: $x_1$ = "আমি", $h_1 = f(h_0, x_1)$
- $t=2$: $x_2$ = "বাংলা", $h_2 = f(h_1, x_2)$
- $t=3$: $x_3$ = "ভালোবাসি", $h_3 = f(h_2, x_3)$
$h_3$-এ পুরো বাক্যের সংক্ষিপ্ত ছাপ — sentiment classifier-এর জন্য এটা যথেষ্ট।
৪ · তিনটি RNN architecture pattern
- Many-to-one: পুরো input sequence → একটি output। যেমন sentiment ("এই movie ভাল" → positive)।
- One-to-many: একটি input → output sequence। যেমন image captioning (একটি ছবি → পুরো caption)।
- Many-to-many (sync): প্রতিটি input-এ output। যেমন POS tagging (প্রতিটি শব্দে tag)।
- Many-to-many (encoder-decoder): input sequence → output sequence (ভিন্ন দৈর্ঘ্য)। যেমন translation। L29-এ বিস্তারিত।
৫ · PyTorch nn.RNN — first model
Vanilla RNN PyTorch-এ তৈরি — একটি character-level Bangla model হাতে-কলমে।
import torch
import torch.nn as nn
class CharRNN(nn.Module):
def __init__(self, vocab_size, hidden_size=128):
super().__init__()
self.embed = nn.Embedding(vocab_size, hidden_size)
self.rnn = nn.RNN(hidden_size, hidden_size, batch_first=True)
self.fc = nn.Linear(hidden_size, vocab_size)
def forward(self, x, h=None):
# x: (batch, seq_len)
emb = self.embed(x) # (B, T, H)
out, h = self.rnn(emb, h) # (B, T, H), (1, B, H)
logits = self.fc(out) # (B, T, V)
return logits, h
# Bangla vocab — অ, আ, ই, ঈ, ... + space
vocab = list("অআইঈউঊঋএঐওঔকখগঘঙচছজঝঞটঠডঢণতথদধনপফবভমযরলশষসহ় ")
vocab_size = len(vocab)
print(f"Vocab size: {vocab_size}")
model = CharRNN(vocab_size, hidden_size=64)
x = torch.randint(0, vocab_size, (2, 10)) # 2 sequences, 10 chars
logits, h = model(x)
print(f"Logits shape: {logits.shape}") # (2, 10, vocab_size)
print(f"Hidden shape: {h.shape}") # (1, 2, 64)
nn.RNN — built-in vanilla RNN। batch_first=True মানে input shape (batch, seq, feature)। Hidden state default-এ zero, চাইলে আলাদা pass করা যায়।
৬ · Manual RNN cell — internals
nn.RNN-এর ভিতরে কী হয়? নিচের কোডে নিজেই RNN cell লিখলাম — বুঝার জন্য।
import torch
import torch.nn as nn
class ManualRNN(nn.Module):
def __init__(self, input_size, hidden_size):
super().__init__()
self.W_x = nn.Linear(input_size, hidden_size, bias=False)
self.W_h = nn.Linear(hidden_size, hidden_size, bias=True)
self.hidden_size = hidden_size
def forward(self, x):
# x: (batch, seq, input_size)
B, T, _ = x.shape
h = torch.zeros(B, self.hidden_size, device=x.device)
outputs = []
for t in range(T):
h = torch.tanh(self.W_x(x[:, t, :]) + self.W_h(h))
outputs.append(h)
return torch.stack(outputs, dim=1), h
rnn = ManualRNN(input_size=10, hidden_size=20)
x = torch.randn(4, 7, 10) # batch=4, seq=7, feat=10
out, h = rnn(x)
print(out.shape) # (4, 7, 20)
print(h.shape) # (4, 20)
৭ · Vanilla RNN-এর সমস্যা
- Vanishing gradient: gradient backward-এ tanh derivative-এ বারবার গুণ — long sequence-এ প্রায় শূন্য। শুরুর শব্দ থেকে শেষের শব্দে gradient পৌঁছায় না।
- Exploding gradient: বিপরীত — gradient বেড়ে যায়। Gradient clipping দিয়ে control।
- Long-term dependency দুর্বল: "আমি ছোটবেলায় ভারতে গিয়েছিলাম, তাই আমি ভাল __ বলতে পারি" — blank-এ "হিন্দি" — vanilla RNN-এ এই দীর্ঘ সম্পর্ক ধরা কঠিন।
- Sequential computation: parallelize করা যায় না। Modern GPU-এর সুবিধা পুরো নেয়া যায় না।
৮ · কোথায় RNN ব্যবহৃত — সংক্ষেপে
- Bangla NLP: sentiment, NER, POS tagging — early models RNN/LSTM-based।
- Speech recognition: Bangla voice assistant — RNN অংশ। আজকাল Conformer/Transformer।
- Time-series forecast: Bangladesh-এ rice price, weather prediction।
- Music generation: note sequence।
- Handwriting: stroke sequence।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১ RNN-এ "parameter sharing" — কেন এটা এত গুরুত্বপূর্ণ? Position-নির্ভর আলাদা weight ব্যবহার করলে কী হবে?
Parameter sharing — RNN-এর সবচেয়ে গভীর design choice। সাদৃশ্য: CNN-এ একই kernel image-এর সব position-এ ব্যবহার। RNN-এ একই $W_h, W_x$ সব timestep-এ।
কেন sharing — তিনটি মূল কারণ:
- Variable length handle: ৫-শব্দ বা ৫০০-শব্দ বাক্য — একই network। Position-ভিত্তিক weight হলে নতুন length-এর জন্য নতুন weight train করতে হবে।
- Generalization: "আমি ভাত খাই" বনাম "সে ভাত খায়" — "ভাত" শব্দটি position ১ এবং ২-এ আছে। একই weight হলে — একই "ভাত" representation। আলাদা weight হলে — দু'টি আলাদা embedding শিখতে হবে।
- Sample efficiency: $T$ timestep-এর জন্য $T \times$ parameter — exponentially less data দরকার। Small dataset-এ critical।
Position-নির্ভর weight হলে কী হবে:
- Variable length training impossible।
- Each position separately learn — slow ও data-hungry।
- Translation invariance ভেঙে পড়বে।
- Practical-এ এটা শুধু "MLP on flattened input" — কিন্তু MLP variable length handle করে না।
Trade-off:
- Sharing = less expressive। সব timestep-এ identical "rule"।
- সমাধান — hidden state বড় করুন, যাতে context store হয়।
- Modern attention/Transformer — sharing রেখেও position information যোগ করে (positional encoding)।
Inductive bias:
- "একই rule সব timestep-এ" — একটি strong assumption।
- Most language ঠিক এই assumption মেনে চলে — grammar context-dependent কিন্তু rule আগাগোড়া same।
- Stationary process assumption।
Convolution-এর সাথে comparison:
- CNN — spatial weight sharing।
- RNN — temporal weight sharing।
- Transformer — সব position-এ weight share, কিন্তু attention pattern position-aware।
- সব modern architecture — কোনো না কোনো sharing-এর উপর দাঁড়ানো।
মূল উপলব্ধি: Parameter sharing শুধু efficiency না — এটা একটি inductive bias। Sequence-এ একই rule সর্বত্র apply হওয়ার ধারণা — language, music, time-series-এর core nature। RNN-এর শক্তির মূল উৎস। আজকের ML-এ "right inductive bias" ই সব — এই lesson lifelong।
প্র ০২ Vanishing gradient — গাণিতিকভাবে কেন ঘটে? Tanh activation-এর role কী? ReLU দিয়ে সমাধান হবে?
Vanishing gradient — RNN-এর "achilles heel"। Bengio-Simard-Frasconi (১৯৯৪) এই সমস্যা প্রথম formally identify করেন।
গাণিতিক উৎপত্তি:
- $h_t = \tanh(W_h h_{t-1} + W_x x_t)$।
- $\partial h_t / \partial h_{t-1} = \tanh'(\cdot) \cdot W_h$।
- $T$-step backward — chain rule:
$\frac{\partial L}{\partial h_1} = \prod_{t=2}^{T} \tanh'(\cdot) \cdot W_h \cdot \frac{\partial L}{\partial h_T}$। - $\tanh' \in (0, 1]$, $W_h$-এর largest eigenvalue $\lambda$।
- Product $\to \lambda^T \cdot \prod \tanh'(\cdot)$।
দুটি case:
- $\lambda < 1$ — gradient $\to 0$ exponentially। Vanishing।
- $\lambda > 1$ — gradient $\to \infty$ exponentially। Exploding।
- $\lambda = 1$ — fragile boundary। Practical-এ rare।
Tanh-এর role:
- $\tanh$ — saturating activation। Input বড় হলে derivative $\to 0$।
- প্রতিটি timestep-এ derivative ১-এর কম — accumulate-এ vanishing accelerated।
- Sigmoid-এর চেয়ে ভাল (sigmoid' max ০.২৫), কিন্তু সমাধান নয়।
ReLU দিয়ে সমাধান হবে?
- ReLU-এর derivative = ১ (positive), ০ (negative) — saturate করে না positive side-এ।
- সমস্যা: $W_h$-এর spectral radius unbounded → exploding gradient সহজে।
- Hidden state unbounded — instability।
- IRNN (Le et al., ২০১৫): ReLU + identity init $W_h$ — কাজ করে কিন্তু sensitive।
- সাধারণ practice — vanilla RNN-এ tanh, কিন্তু আসল সমাধান architecture পরিবর্তন।
প্রকৃত সমাধান — gating:
- LSTM (১৯৯৭) — cell state additive update। Gradient pathway "highway"।
- $\partial c_t / \partial c_{t-1} = f_t$ (forget gate)। যদি forget gate ≈ ১, gradient bypass।
- GRU — similar mechanism, simpler।
- Residual connection — same idea, gradient highway।
Practical mitigation:
- Gradient clipping: exploding-এ norm cap। Pascanu et al. (২০১৩)।
- Orthogonal initialization: $W_h$ orthogonal — eigenvalues ১।
- Layer norm: hidden state stabilize।
- Truncated BPTT: short window — fewer multiplications।
Theoretical understanding:
- Hochreiter-এর PhD thesis (১৯৯১) — first identification।
- Bengio-Simard-Frasconi (১৯৯৪) — formal analysis।
- Pascanu-Mikolov-Bengio (২০১৩) — clipping ও solution-এর modern overview।
মূল উপলব্ধি: Vanishing/exploding gradient — sequence model-এর core challenge। Tanh-ই কারণ নয় — root cause হলো repeated multiplication। ReLU সাধারণ সমাধান নয় — instability। আসল সমাধান architecture (LSTM/GRU/Attention) — gradient-এর জন্য "highway" তৈরি। এই insight আধুনিক DL-এর foundational — ResNet, Transformer সব এই principle-এ।
প্র ০৩ Bangla sentiment classifier (positive/negative) — RNN ব্যবহার করব। Architecture choice, embedding strategy, training pipeline কী?
Bangla sentiment — Bangladesh-এর জন্য practical NLP project। E-commerce review (Daraz), social media (Facebook), customer feedback — সব জায়গায় ব্যবহার।
Architecture decisions:
- Word-level vs character-level — word-level সহজ, কিন্তু OOV problem।
- Subword (BPE, SentencePiece) — Bangla-এর জন্য আদর্শ। যুক্তাক্ষর handle।
- RNN type — LSTM/GRU practical (vanilla RNN-এ vanishing)।
- Bidirectional — দু'দিকে context, sentiment-এ বেশ ভাল।
Embedding strategy:
- Random init: small data-এ poor।
- Pretrained: FastText Bangla, BanglaBERT embedding — strong start।
- FastText: subword-aware — OOV-এ গ্রহণযোগ্য।
- Fine-tune vs freeze: 10K+ data-এ fine-tune, কম data-তে freeze।
Suggested architecture:
class BanglaSentiment(nn.Module):
def __init__(self, vocab_size, embed_dim=300,
hidden_size=256, num_classes=2):
super().__init__()
self.embed = nn.Embedding(vocab_size, embed_dim,
padding_idx=0)
self.lstm = nn.LSTM(embed_dim, hidden_size,
num_layers=2,
bidirectional=True,
dropout=0.3,
batch_first=True)
self.fc = nn.Linear(hidden_size * 2, num_classes)
self.dropout = nn.Dropout(0.5)
def forward(self, x, lengths):
emb = self.embed(x)
packed = pack_padded_sequence(emb, lengths,
batch_first=True,
enforce_sorted=False)
_, (h, _) = self.lstm(packed)
# Concat last forward + backward hidden
h = torch.cat([h[-2], h[-1]], dim=1)
return self.fc(self.dropout(h))
Data preparation:
- Source — BanglaABSA, BanglaSenti dataset।
- Cleaning — emoji, URL, code-mix English handle।
- Tokenization — bnlp library বা SentencePiece।
- Class balance — sometimes positive >> negative। SMOTE বা class-weighted loss।
Training pipeline:
- Optimizer — Adam, lr=১e-৩।
- Loss — CrossEntropyLoss (class weight সহ)।
- Batch size — ৩২-৬৪।
- Sequence length — pad/truncate at ১২৮।
- Epochs — ১০-২০, early stopping।
- Gradient clipping — max_norm=1.0।
Evaluation:
- Accuracy — overall।
- F1-score — class imbalance থাকলে ভাল metric।
- Confusion matrix — error pattern।
- Held-out test — diverse domain (review, post, comment)।
Bangla-specific challenges:
- Code-mix — "অনেক good বইটা" — English শব্দ বাংলা বাক্যে।
- Sarcasm — "চমৎকার, আবার সেই pothole" — surface-এ positive কিন্তু intent negative।
- Negation — "খারাপ না" actually positive।
- Region variation — চট্টগ্রামের শব্দ, সিলেটের শব্দ।
Production deployment:
- Model size — bidirectional LSTM ~৫MB-১৫MB।
- Inference speed — CPU-তে real-time।
- Edge deployment (mobile) — quantize, distill।
- Serving — FastAPI/Flask + REST API।
আধুনিক বিকল্প:
- BanglaBERT fine-tune — RNN-এর চেয়ে accuracy বেশি।
- Multilingual XLM-R — Bangla support ভাল।
- RNN/LSTM — যেখানে latency-critical।
- Hybrid — ছোট LSTM cache + BERT fallback।
Realistic numbers:
- 10K train data — Bi-LSTM ~৭৮-৮৩% accuracy।
- BanglaBERT — ~৮৭-৯২% accuracy।
- Production threshold — ৮৫%+।
- Domain-specific fine-tune always helps।
মূল উপলব্ধি: Bangla sentiment — practical impact। Bi-LSTM আজও legacy system-এ relevant। Pretrained embedding + LSTM + class-weighted loss + careful evaluation = production-ready। আধুনিক BanglaBERT সুপিরিয়র, কিন্তু LSTM শেখা গুরুত্বপূর্ণ — ভিত্তি বুঝতে। Bangladesh-এ NLP বাণিজ্যিক সুযোগ বিশাল।
প্র ০৪ RNN sequential — parallelize হয় না। তাহলে আজকের GPU যুগে এটা কেন গুরুত্বপূর্ণ ছিল ও এখন কী হাল?
RNN-এর "sequentiality" — এর শক্তি ও দুর্বলতা দু'টোই। Modern GPU-এর জন্য এটা bottleneck — কিন্তু ২০১৭ পর্যন্ত state-of-the-art ছিল।
Sequentiality-এর core reason:
- $h_t$ compute করতে $h_{t-1}$ লাগে।
- $h_{t-1}$ compute করতে $h_{t-2}$।
- Strict ordering — parallelize impossible across timesteps।
- Within batch — parallel, কিন্তু single sequence-এ sequential।
GPU-এর ক্ষেত্রে impact:
- GPU — thousands of cores, throughput-optimized।
- RNN — sequential operations, GPU under-utilized।
- Long sequence training — slow।
- Inference — every token wait।
Workarounds:
- Truncated BPTT: short window — gradient computation manageable।
- cuDNN RNN: NVIDIA-optimized kernel — significant speedup।
- Quasi-RNN (QRNN): partial parallelism — convolution + recurrence।
- SRU (Simple Recurrent Unit): reduce sequential dependency।
২০১৭-এ প্রবল পরিবর্তন:
- "Attention is All You Need" (Vaswani et al.) — Transformer।
- Self-attention — সম্পূর্ণ parallelize across positions।
- RNN-এর dominance শেষ NLP-তে।
Transformer vs RNN — practical:
- Training — Transformer ১০-১০০x faster।
- Memory — Transformer attention $O(n^2)$, RNN $O(n)$।
- Long sequence (>10K) — RNN-এর memory advantage।
- Short-medium — Transformer dominates।
আজকের state:
- NLP — Transformer প্রায় universal (BERT, GPT, T5)।
- Time-series — RNN/LSTM এখনো relevant (financial, IoT)।
- Speech recognition — Transformer/Conformer dominate।
- Edge deployment — small RNN/GRU compact।
- RWKV (২০২৩) — RNN-এর parallel training, retrieval-friendly inference।
- Mamba/S4 (২০২৩-২৪) — state-space model, RNN renaissance।
RNN-এর renaissance — ২০২৪:
- Transformer-এর $O(n^2)$ memory — long sequence-এ bottleneck।
- Mamba — selective state space, Transformer-competitive accuracy।
- RWKV — open-source, RNN inference efficient।
- Hybrid — Transformer + RNN (Jamba)।
- Active research area।
Bangladesh implications:
- Compute-constrained — RNN/LSTM compact, mobile-friendly।
- Edge deployment — RNN advantage।
- Educational — Transformer-এর আগে RNN শেখা mandatory (foundation)।
- State-of-the-art — Transformer/BanglaBERT।
Lesson learned:
- Hardware-software co-design crucial।
- Algorithm-এর architectural property hardware-এ pay হয়।
- "Universal best" নেই — context-dependent।
- Old idea-এর পুনরুজ্জীবন (Mamba) — স্মরণ রাখুন।
মূল উপলব্ধি: RNN-এর sequentiality — মানুষের চিন্তার "natural" representation, কিন্তু GPU-এর জন্য suboptimal। Transformer parallelism দিয়ে dominate করল। আজ Mamba/RWKV সেই RNN intuition-কে modern hardware-এ adapt করছে। DL-এর evolution — algorithm + hardware-এর dance। Foundation শক্ত করুন — fashion বদলায়, principle থাকে।
অনুশীলন
-
Compute by hand: $W_h = \begin{pmatrix} 0.5 & 0 \\ 0 & 0.5 \end{pmatrix}$, $W_x = \begin{pmatrix} 1 & 0 \\ 0 & 1 \end{pmatrix}$, $b = 0$, $h_0 = (0, 0)$, $x_1 = (1, 0)$, $x_2 = (0, 1)$। $h_1, h_2$ কত? (tanh পরে apply, ছোট value-তে $\tanh(z) \approx z$।)
- $h_1 = \tanh(W_h h_0 + W_x x_1) = \tanh((1, 0)) \approx (0.762, 0)$।
- $h_2 = \tanh(W_h h_1 + W_x x_2) = \tanh((0.381, 1)) \approx (0.364, 0.762)$।
- Notice — $h_1$-এর effect $h_2$-এ ০.৫ গুণ scale হলো (vanishing শুরু)।
-
PyTorch RNN: ৪-শব্দের একটি batch তৈরি করে nn.RNN-এ pass করুন। Output shape verify।
import torch import torch.nn as nn rnn = nn.RNN(input_size=10, hidden_size=20, batch_first=True) x = torch.randn(3, 4, 10) # batch=3, seq=4, feat=10 out, h = rnn(x) print(out.shape) # torch.Size([3, 4, 20]) print(h.shape) # torch.Size([1, 3, 20]) -
চিন্তা: Bangladesh-এ আপনার একটি startup — Bangla SMS-এর spam vs ham classify। RNN কী architecture choose করবেন? Embedding কোথা থেকে আনবেন?
Architecture: Bi-LSTM (১-২ layer), hidden ~১২৮, output Linear → ২ class। Subword (SentencePiece, vocab ৮K) — OOV handle।
Embedding: FastText Bangla pretrained (৩০০-D) — fine-tune করতে পারেন। Small data-তে freeze।
Loss: spam:ham সাধারণত ১:১০ — class-weighted CE।
Latency target: SMS-এ ১০০ms — Bi-LSTM-এ comfortably achievable।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পাঠ ২৬ · BPTT — Backprop through time পরবর্তী পাঠ RNN কীভাবে train হয় — gradient সময়ের পেছনে কীভাবে যায়।
- পাঠ ২৪ · Data augmentation আগের পাঠ M3 শেষ হয়েছে — image augmentation।
- পাঠ ২৭ · LSTM — gate-এর সাহায্যে স্মৃতি পরের module Vanilla RNN-এর সমাধান — gate-এর শক্তি।
- সব AI Courses দেখুন ABCL TECH Python, ML, DL, NLP, CV, GenAI, RL, MLOps — সব AI কোর্স একসাথে।