Chapter 6 of 18
Gates, cell state, and the architecture that solved long-range memory
RNN চ্যাপ্টারটি শেষ হয়েছিল একটি বড় সমস্যা নিয়ে: সাধারণ RNN-এ গ্রেডিয়েন্ট ভ্যানিশ হয়ে যায় কারণ হিডেন স্টেট -কে প্রতিটি একক স্টেপে tanh অ্যাক্টিভেশন ফাংশনের মধ্য দিয়ে জোরপূর্বক সংকুচিত (squash) হতে হয়। এই বারবার সংকোচনের কারণে দূরবর্তী টাইম স্টেপের সাথে যুক্ত গ্রেডিয়েন্ট জ্যামিতিক হারে শূন্যের দিকে সংকুচিত হয়ে যায়। LSTM এই সমস্যার সমাধান করেছে এর আর্কিটেকচারাল পরিবর্তনের মাধ্যমে: প্রতি পদক্ষেপে সবকিছুকে সংকুচিত করা বন্ধ করো! মেমরির জন্য তার নিজস্ব একটি সুরক্ষিত হাইওয়ে তৈরি করে দাও।
একটি সাধারণ RNN একটিমাত্র ভেক্টর -কে একই সাথে দুটি বেমানান দায়িত্ব পালন করতে বাধ্য করে: শর্ট-টার্ম ওয়ার্কিং মেমরি (short-term working memory) এবং দীর্ঘ সময় ধরে তথ্যকে বাঁচিয়ে রাখার লং-টার্ম মেমরি (long-term memory) — আর এই পুরো প্রক্রিয়ায় প্রতি পদক্ষেপে একে tanh দিয়ে স্কোয়াশ হতে হয়।
LSTM এই দায়িত্ব দুটিকে দুটি আলাদা পথে ভাগ করে দেয়:
সেল স্টেট (Cell State - Cₜ) — লং-টার্ম মেমরি হাইওয়ে
অত্যন্ত স্থিতিশীল ও নিখুঁত। তথ্য এর ওপর দিয়ে প্রবাহিত হয় কোনো বড় ধরণের বিকৃতি ছাড়াই, কেবল সামান্য কিছু হিসাব করা ও শেখা পরিবর্তনের মাধ্যমে। এটিকে কখনোই সরাসরি স্কোয়াশ করা হয় না — কেবল স্কেল এবং কিছু যোগ করা হয়।
হিডেন স্টেট (Hidden State - hₜ) — শর্ট-টার্ম ওয়ার্কিং মেমরি
প্রতিটি স্টেপে নতুন করে তৈরি হয়। এটি মূলত আপডেটেড সেল স্টেট এবং বর্তমান ইনপুটের ওপর ভিত্তি করে হিসাব করা হয়। এটি ওই স্টেপের আউটপুট এবং পরবর্তী স্টেপের কনটেক্সট হিসেবে কাজ করে।
সেল স্টেট হলো একটি কোম্পানির স্থায়ী লিখিত ফাইলের মতো — যেখানে কেবল তখনই পরিবর্তন করা হয় যখন কেউ ইচ্ছাকৃতভাবে কোনো নির্দিষ্ট পৃষ্ঠা যোগ করে বা ফেলে দেয়। আর হিডেন স্টেট হলো টেবিলের ওপর থাকা একটি ছোট চিরকুট বা স্টিকি নোটের মতো, যা "এই মুহূর্তে কী গুরুত্বপূর্ণ" তার একটি সংক্ষিপ্ত সারসংক্ষেপ বহন করে এবং প্রতিদিন সকালে স্থায়ী ফাইলের পাতাগুলো ঘেঁটে এটিকে নতুন করে লেখা হয়।
এই স্থায়ী ফাইলে বেছে বেছে পরিবর্তন করার কাজটি করার জন্য ব্যবহৃত হয় এক সেট গেট (gates)।
একটি গেট হলো মূলত একটি ছোট নেটওয়ার্ক লেয়ার যা সমীকরণ মেনে চলে। এর আউটপুট সিগময়েড অ্যাক্টিভেশনের কারণে -এর মধ্যে থাকে এবং অন্য একটি ভেক্টরের সাথে এলিমেন্ট-ওয়াইজ বা উপাদান-ভিত্তিক গুণ () হয়ে নিয়ন্ত্রণ করে যে ওই তথ্যের ঠিক কতটুকু অংশ পার হতে দেওয়া হবে।
স্বজ্ঞাত ধারণা: আউটপুট ০ হওয়ার অর্থ হলো "পুরোপুরি আটকে দাও।" আউটপুট ১ হওয়ার অর্থ হলো "সবকিছুকে কোনো পরিবর্তন ছাড়াই পার হতে দাও।" এর মাঝের যেকোনো মান তথ্যের একটি আনুপাতিক অংশকে পার হতে দেয়। গেটগুলোর কোনোটিই মানুষ হাত দিয়ে ডিজাইন করে না — ডেটা থেকে ট্রেনিংয়ের মাধ্যমে এদের ওয়েট নিজে নিজেই তৈরি হয়।
LSTM-এ মূলত দুটি অ্যাক্টিভেশন ফাংশন ব্যবহৃত হয় — এবং এর পেছনের কারণটি জানা খুবই জরুরি:
| ফাংশন | আউটপুট রেঞ্জ | যেখানে ব্যবহৃত হয় | কেন এই রেঞ্জ? |
|---|---|---|---|
| সিগময়েড (Sigmoid - σ) | (0, 1) | তিনটি গেটেই (forget, input, output) | গেট মানে হলো কত শতাংশ তথ্য রাখবো তা ঠিক করা — যা ০ থেকে ১-এর মধ্যে হতে হবে |
| ট্যানএইচ (Tanh) | (-1, 1) | ক্যান্ডিডেট সেল ও আউটপুটের জন্য Cₜ-কে স্কোয়াশ করতে | মেমরির ভেতরের কনটেন্ট পজিটিভ বা নেগেটিভ দুই-ই হতে পারে (মেমরির মান বাড়ানো বা কমানোর জন্য) |
আমরা একটি নির্দিষ্ট উদাহরণ ব্যবহার করে খাতা-কলমে LSTM-এর ছয়টি গণনা ধাপে ধাপে দেখবো:
কোনো নতুন তথ্য গ্রহণ করার আগে, LSTM নিজেকে জিজ্ঞেস করে: "আমার যা মনে আছে, তার মধ্যে কোন তথ্যটি এখন আর প্রাসঙ্গিক নয়?"
ভূমিকা: সেল স্টেটের প্রতিটি ডাইমেনশনের জন্য -এর মধ্যে একটি মান আউটপুট দেওয়া — যা ঠিক করে আগের সেল স্টেট -এর কতটুকু অংশ আমরা ধরে রাখবো।
হিসাব: ধরা যাক , , :
অর্থাৎ ফরগেট গেটটি আগের সেল স্টেটের প্রায় ৬৫.৭% তথ্য ধরে রাখবে।
এরপর, LSTM সিদ্ধান্ত নেয় যে নতুন তথ্যের কোন কোন অংশ মনে রাখা প্রয়োজন। এখানে দুটি জিনিস একসাথে কাজ করে:
স্বজ্ঞাত ধারণা: ক্যান্ডিডেট সেল হলো একটি খসড়া বা ড্রাফট (draft) যা আমরা মেমরিতে যোগ করতে পারি। আর ইনপুট গেট হলো একজন সম্পাদকের (editor) মতো যে সিদ্ধান্ত নেয় ওই ড্রাফটের ঠিক কতটুকু অংশ আমরা চূড়ান্ত মেমরিতে গ্রহণ করবো।
হিসাব: ধরা যাক , , ; , , :
এখানে নেটওয়ার্কটি ০.৫৩৭ পরিমাণ নতুন তথ্যের খসড়া তৈরি করেছে এবং তার ৬৬.৮% গ্রহণ করার সিদ্ধান্ত নিয়েছে।
এখানে হলো এলিমেন্ট-ওয়াইজ বা উপাদান-ভিত্তিক গুণ। এর অর্থ: আগের মেমরির -অংশ ধরে রাখো এবং নতুন প্রস্তাবিত তথ্যের -অংশ তার সাথে যোগ করো।
অনেকেই অসাবধানতাবশত লিখে ফেলেন — যা একটি সম্পূর্ণ ভুল গুণফলের শিকল। সঠিক সমীকরণটি হলো দুটি গেটেড টার্মের যোগফল। ভুলে যাওয়া এবং নতুন কিছু যোগ করা সমান্তরালভাবে ঘটে এবং শেষে যোগফলের মাধ্যমে যুক্ত হয়।
হিসাব:
সবশেষে, LSTM সিদ্ধান্ত নেয় যে তার আপডেটেড সেল স্টেট বা লং-টার্ম মেমরির কোন অংশটি এই টাইম স্টেপের আউটপুট হিসেবে প্রকাশ করা হবে।
স্বজ্ঞাত ধারণা: সেল স্টেটকে প্রথমে tanh দিয়ে সীমায় সংকুচিত করা হয় এবং এরপর আউটপুট গেট দ্বারা ফিল্টার করা হয়। এটি লং-টার্ম মেমরির একটি অত্যন্ত নিখুঁত ও নির্বাচিত অংশকে শর্ট-টার্ম কনটেক্সট বা আউটপুট হিসেবে সামনে পাঠাতে সাহায্য করে।
হিসাব: ধরা যাক , , :
এই টাইম স্টেপের শেষে: (যা লং-টার্ম মেমরি হিসেবে সামনে যাবে) এবং (যা শর্ট-টার্ম আউটপুট ও পরবর্তী কনটেক্সট)।
| মান বা রাশি | সমীকরণ (Equation) | ভূমিকা |
|---|---|---|
| ফরগেট গেট (Forget gate) | fₜ = σ(Wf xₜ + Uf h_{t-1} + bf) | C_{t-1} থেকে কোন তথ্যটি বাদ দেওয়া হবে |
| ইনপুট গেট (Input gate) | iₜ = σ(Wi xₜ + Ui h_{t-1} + bi) | কতটুকু নতুন তথ্য গ্রহণ করা হবে |
| ক্যান্ডিডেট সেল (Candidate) | C̃ₜ = tanh(Wc xₜ + Uc h_{t-1} + bc) | প্রস্তাবিত নতুন তথ্য বা ড্রাফট |
| সেল স্টেট (Cell state) | Cₜ = fₜ ⊙ C_{t-1} + iₜ ⊙ C̃ₜ | আপডেটেড লং-টার্ম মেমরি |
| আউটপুট গেট (Output gate) | oₜ = σ(Wo xₜ + Uo h_{t-1} + bo) | Cₜ-এর কতটুকু প্রকাশ করা হবে |
| হিডেন স্টেট (Hidden state) | hₜ = oₜ ⊙ tanh(Cₜ) | এই স্টেপের আউটপুট / শর্ট-টার্ম মেমরি |
আমরা RNN চ্যাপ্টারে দেখেছি যে সাধারণ RNN-এর গ্রেডিয়েন্ট ভ্যানিশ হয়ে যাওয়ার কারণ ছিল: — যেখানে ১-এর চেয়ে ছোট মান বারবার গুণ হতো।
এবার LSTM-এর সেল স্টেটের লোকাল গ্রেডিয়েন্টটি লক্ষ্য করুন:
সেল স্টেটের পথের লোকাল গ্রেডিয়েন্ট হলো সরাসরি — অর্থাৎ ফরগেট গেটের নিজস্ব আউটপুট। এটি এমন একটি মান যা নেটওয়ার্ক নিজেই শেখে। নেটওয়ার্ক যখনই চায় কোনো তথ্যকে অবিকৃতভাবে বাঁচিয়ে রাখতে, সে খুব সহজেই সেট করে নিতে পারে। আর ফরগেট গেটের মান ১-এর কাছাকাছি থাকলে, শত শত টাইম স্টেপ পরেও গ্রেডিয়েন্ট কোনো বাধা ছাড়াই একদম অক্ষত অবস্থায় পেছনের দিকে প্রবাহিত হতে পারে।
মূল ১৯৯৭ সালের পেপারে এই চমৎকার মেকানিজমটিকে বলা হয়েছিল কনস্ট্যান্ট এরর ক্যারোসেল (constant error carousel) — যা সাধারণ RNN-এর ব্যর্থতার বিপরীতে দাঁড়িয়ে LSTM-কে শত শত টাইম স্টেপের দূরবর্তী মেমরির সম্পর্ক শেখার ক্ষমতা দেয়।
যদি নেটওয়ার্কটি কোনো দীর্ঘ সিকোয়েন্সের জন্য ভুলে যাওয়ার গেট -এর মান ১-এর চেয়ে বেশ কম শেখে, তবে গ্রেডিয়েন্ট কিন্তু তবুও হ্রাস পেতে পারে। একারণেই ব্যবহারিক ক্ষেত্রে ট্রেনিংয়ের শুরুতে ফরগেট গেটের বায়াস -কে সাধারণত পজিটিভ মান (যেমন: ১.০) দিয়ে ইনিশিয়ালাইজ করা হয়, যাতে নেটওয়ার্কটি শুরুতেই সবকিছু ভুলে না গিয়ে মনে রাখার দিকে বেশি ঝুঁকে থাকে।
import numpy as np
def sigmoid(z):
return 1.0 / (1.0 + np.exp(-z))
def lstm_cell_forward(x_t, h_prev, C_prev, params):
"""
একটি LSTM সেলের একটি ফরোয়ার্ড ধাপ।
x_t : shape (input_size,)
h_prev : shape (hidden_size,)
C_prev : shape (hidden_size,)
রিটার্ন করে h_t, C_t এবং ব্যাকপ্রোপাগেশনের জন্য প্রয়োজনীয় ক্যাশ (cache)।
"""
concat = np.concatenate([x_t, h_prev]) # ইনপুট এবং আগের হিডেন স্টেটকে একত্রিত করা
f_t = sigmoid(params["W_f"] @ concat + params["b_f"]) # ফরগেট গেট
i_t = sigmoid(params["W_i"] @ concat + params["b_i"]) # ইনপুট গেট
C_tilde = np.tanh(params["W_c"] @ concat + params["b_c"]) # ক্যান্ডিডেট বা খসড়া
C_t = f_t * C_prev + i_t * C_tilde # সেল আপডেট (লক্ষ্য করুন: যোগফল)
o_t = sigmoid(params["W_o"] @ concat + params["b_o"]) # আউটপুট গেট
h_t = o_t * np.tanh(C_t) # হিডেন স্টেট
cache = (x_t, h_prev, C_prev, f_t, i_t, C_tilde, C_t, o_t, h_t)
return h_t, C_t, cache
# চ্যাপ্টারের গাণিতিক উদাহরণটি রান করে দেখা যাক
params = {
"W_f": np.array([[0.5, 0.3]]), "b_f": np.array([0.0]),
"W_i": np.array([[0.6, 0.2]]), "b_i": np.array([0.0]),
"W_c": np.array([[0.4, 0.4]]), "b_c": np.array([0.0]),
"W_o": np.array([[0.5, 0.5]]), "b_o": np.array([0.0]),
}
h_t, C_t, _ = lstm_cell_forward(np.array([1.0]), np.array([0.5]), np.array([0.2]), params)
print(f"C_t = {C_t}, h_t = {h_t}")
# আউটপুট আসবে: C_t ≈ [0.490], h_t ≈ [0.309]এখানে লক্ষ্য করুন C_t = f_t * C_prev + i_t * C_tilde — এটি দুটি গেটেড মান বা রাশির যোগফল, কোনো দীর্ঘ গুণের শিকল নয়। "LSTM from scratch in NumPy" চ্যাপ্টারে আমরা ব্যাকপ্রোপাগেশন সহ একাধিক টাইম স্টেপের পূর্ণাঙ্গ ট্রেনিং ভার্সন তৈরি করবো।
আজকের দিনের প্রতিটি আধুনিক ডিপ লার্নিং ফ্রেমওয়ার্ক — PyTorch, TensorFlow, JAX — তাদের LSTM লেয়ারের ভেতরে ঠিক এই ছয়টি সমীকরণই ব্যবহার করে। আপনি যখন torch.nn.LSTM কিংবা tf.keras.layers.LSTM কল করেন, ব্যাকগ্রাউন্ডে ফরোয়ার্ড পাসের প্রতি পদক্ষেপে এই ছয়টি গণনাই সম্পন্ন হয়। পরবর্তী চ্যাপ্টারে আমরা ব্যাকওয়ার্ড পাসের গাণিতিক রূপ দেখবো, এবং তার পরের চ্যাপ্টারে আমরা NumPy দিয়ে একদম স্ক্র্যাচ থেকে একটি ট্রেইনেবল LSTM কোড করবো।