Chapter 10 of 18
Cleaning, normalizing, and windowing sequences for real training
একটি LSTM-এর সমীকরণগুলো যত চমৎকারই হোক না কেন, চূড়ান্ত পর্যায়ে সেগুলো কেবল আপনার ইনপুট করা ভেক্টরের সংখ্যাগুলোকেই চেনে। কিন্তু বাস্তব জীবনের ডেটাসেটগুলো অত্যন্ত এলোমেলো ও নোংরা থাকে — কোথাও রিডিং গায়েব থাকে, কোথাও সংখ্যার স্কেল থাকে আকাশ-পাতাল তফাত, আবার টেক্সট ডেটার দৈর্ঘ্যও হয় একেক রকম। এই চ্যাপ্টারে আমরা শিখবো কীভাবে সেই raw নোংরা ডেটাকে প্রসেস করে পরিষ্কার ও সুনির্দিষ্ট শেপের টেনসরে রূপান্তর করা যায়, যা মূলত একটি LSTM ট্রেনিংয়ের জন্য অত্যন্ত জরুরি।
সেন্সর নষ্ট হয়ে রিডিং গায়েব হতে পারে; জরিপে ফাঁক থাকতে পারে; ছুটির দিনে শেয়ার বাজার বন্ধ থাকতে পারে। এই অনুপস্থিত বা missing মানগুলো পূরণ করার তিনটি জনপ্রিয় স্ট্র্যাটেজি নিচে দেওয়া হলো (সহজ থেকে জটিল ক্রমানুসারে):
ফরওয়ার্ড ফিল (Forward fill)
সবশেষ জানা মানটিকে সামনে টেনে নিয়ে খালি জায়গায় বসানো। যখন মান খুব ধীরে ধীরে পরিবর্তিত হয় (যেমন: তাপমাত্রা), তখন এটি ব্যবহার করা সহজ ও যৌক্তিক। তবে বড় ধরনের গ্যাপ থাকলে এটি ব্যবহার করা ঠিক নয়।
লিনিয়ার ইন্টারপোলেশন (Linear interpolation)
অনুপস্থিত মানের আগের ও পরের জানা মান দুটির মধ্যে একটি সরলরেখা টেনে মাঝখানের খালি জায়গা পূরণ করা। যখন মূল সিগন্যালটি মসৃণ হয় এবং গ্যাপগুলো ছোট থাকে, তখন এটি বেশ ভালো কাজ করে।
মডেল-ভিত্তিক ইম্পিউটেশন (Model-based imputation)
অন্যান্য সম্পর্কিত ফিচারগুলোর ওপর ভিত্তি করে নিখোঁজ মানটি প্রেডিক্ট করা। এটি বেশ নিখুঁত হলেও মডেলে বাড়তি জটিলতা যোগ করে এবং অসাবধান হলে ডেটা লিক হওয়ার ঝুঁকি থাকে।
ডেটাকে ট্রেন/ভ্যালিডেশন/টেস্ট সেটে ভাগ করার পরেই কেবল অনুপস্থিত মান পূরণের (imputation) কাজ করবেন, এবং প্রতি পয়েন্টে কেবল অতীতের তথ্যের ওপর ভিত্তি করে এটি করা উচিত। ভ্যালিডেশন সেটের তথ্যের ওপর ভিত্তি করে ট্রেনিং সেটের খালি জায়গা পূরণ করা এক ধরণের সূক্ষ্ম ডেটা লিকেজ (data leakage) — যার ফলে মূল্যায়নের সময় মডেলের অ্যাকুরেসি ভালো দেখাবে কিন্তু বাস্তবে তা অত্যন্ত বাজে পারফর্ম করবে।
LSTM গেটগুলো সিগময়েড এবং ব্যবহার করে, যার দুটিই অতিরিক্ত বড় ইনপুটের জন্য স্যাচুরেটেড হয়ে যায় (অর্থাৎ তাদের গ্রেডিয়েন্ট শূন্যের কাছাকাছি চলে যায়)। স্কেল না করা raw ডেটা — যেমন হাজার হাজার মেগাওয়াট বিদ্যুতের চাহিদা কিংবা শত শত টাকার শেয়ার দর — ট্রেনিংয়ের একদম প্রথম ধাপেই গেটের প্রি-অ্যাক্টিভেশনগুলোকে স্যাচুরেটেড অঞ্চলে ঠেলে দিতে পারে, যা ট্রেনিং প্রক্রিয়াকে পুরোপুরি স্থবির করে দেয়।
| পদ্ধতি | সূত্র (Formula) | আউটপুট রেঞ্জ | কখন ব্যবহার করবেন |
|---|---|---|---|
| মিন-ম্যাক্স (Min-max) | x' = (x − x_min) / (x_max − x_min) | [0, 1] | যখন ইনপুটের একটি নির্দিষ্ট সীমা প্রয়োজন; তবে আউটলায়ার (outliers)-এর জন্য এটি অত্যন্ত সংবেদনশীল |
| স্ট্যান্ডার্ডাইজেশন (Standardization) | x' = (x − μ) / σ | ~গড় শূন্য এবং ভ্যারিয়েন্স ১ | আউটলায়ারের বিরুদ্ধে বেশ শক্তিশালী; অধিকাংশ LSTM কাজের জন্য এটি বেশি পছন্দনীয় |
import numpy as np
from sklearn.preprocessing import MinMaxScaler
series = np.array([10, 12, 13, 15, 14, 16, 18, 17]).reshape(-1, 1)
# প্রথমে ডেটা স্প্লিট করুন, এরপর কেবল ট্রেনিং ডেটার ওপর স্কেলার ফিট করুন
train = series[:6]
test = series[6:]
scaler = MinMaxScaler()
train_scaled = scaler.fit_transform(train) # ট্রেনিং ডেটার ওপর fit এবং transform একসাথে
test_scaled = scaler.transform(test) # কেবল transform — কোনো fit বা রি-ফিট করা যাবে না
# স্কেলার অবজেক্টটি সবসময় সংরক্ষণ করুন: প্রেডিকশনকে আসল ইউনিটে রূপান্তর করতে inverse_transform লাগবে
preds_in_original_units = scaler.inverse_transform(test_scaled)পুরো ডেটাসেটের ওপর ভিত্তি করে x_min এবং x_max (অথবা μ এবং σ) হিসাব করলে টেস্ট সেটের পরিসংখ্যান ট্রেনিংয়ের সময়ই মডেলে লিক হয়ে যায়। তাই সবসময় কেবল ট্রেনিং স্প্লিটের ওপর ফিট করুন, এবং ভ্যালিডেশন ও টেস্ট সেটের ক্ষেত্রে কেবল transform (কখনোই fit_transform নয়) ব্যবহার করুন।
def make_windows(series, window_size, horizon=1):
"""
series : মানের ১-মাত্রিক অ্যারে
window_size : ইনপুট হিসেবে ব্যবহৃত অতীতের ধাপের সংখ্যা
horizon : ভবিষ্যতে কয়টি ধাপ প্রেডিক্ট করবো (ডিফল্ট ১)
রিটার্ন করে (n_windows, window_size) শেপের X,
এবং (n_windows, horizon) শেপের y
"""
X, y = [], []
for i in range(len(series) - window_size - horizon + 1):
X.append(series[i : i + window_size])
y.append(series[i + window_size : i + window_size + horizon])
return np.array(X), np.array(y)
series = np.array([10, 12, 13, 15, 14, 16, 18, 17])
X, y = make_windows(series, window_size=3, horizon=1)
print(X)
# আউটপুট: [[10 12 13], [12 13 15], [13 15 14], [15 14 16], [14 16 18]]
print(y)
# আউটপুট: [[15], [14], [16], [18], [17]]এই ডেটা Keras-এ পাঠাতে হলে X-এর শেপকে (n_windows, window_size) থেকে রি-শেপ করে (n_windows, window_size, 1) করতে হবে — কারণ LSTM ইনপুট হিসেবে (batch, time_steps, features) শেপ প্রত্যাশা করে।
কেবল raw মানের বাইরেও, টাইম সিরিজ ডেটাতে কিছু সহজ ও অর্থবহ ফিচার ইঞ্জিনিয়ারিং করলে মডেলের পারফরম্যান্স অনেক বৃদ্ধি পায়:
ল্যাগ ফিচার (Lag features)
t-1, t-7, t-30 ইত্যাদি সময়ের মানের সরাসরি কপি ইনপুটের সাথে যোগ করা, যা মডেলকে অতীতের গুরুত্বপূর্ণ টাইম স্টেপের তথ্যে সরাসরি প্রবেশাধিকার দেয়।
রানিং পরিসংখ্যান (Rolling statistics)
একটি নির্দিষ্ট উইন্ডোর ওপর রানিং গড় (rolling mean), রানিং স্ট্যান্ডার্ড ডেভিয়েশন কিংবা রানিং সর্বোচ্চ মান হিসাব করা — যা ট্রেন্ড ও পরিবর্তনশীলতা প্রকাশ করে।
ক্যালেন্ডার ফিচার (Calendar features)
দিনের কোন ঘণ্টা, সপ্তাহের কোন দিন, বছরের কোন মাস কিংবা সরকারি ছুটি কি না তা চিহ্নিত করা — যা raw সংখ্যার বাইরেও ঋতুভিত্তিক প্যাটার্ন বুঝতে সাহায্য করে।
ডিফারেন্স ফিচার (Difference features)
পার্থক্য হিসাব করা (x_t − x_{t-1}) যা একটি ট্রেন্ডযুক্ত ডেটাকে স্থিতিশীল (stationary) করে তোলে এবং মডেলকে স্তর বা লেভেলের বদলে পরিবর্তন শিখতে সাহায্য করে।
from tensorflow.keras.preprocessing.text import Tokenizer
from tensorflow.keras.preprocessing.sequence import pad_sequences
texts = ["the bank raised rates", "rates fell sharply", "the bank is stable"]
labels = [0, 1, 0]
# ধাপ ১: টোকেনাইজ (শব্দকে ইন্টিজার আইডিতে রূপান্তর করা)
tokenizer = Tokenizer(num_words=500, oov_token="<OOV>")
tokenizer.fit_on_texts(texts) # কেবল ট্রেনিং টেক্সট থেকেই ভোকাবুলারি শেখে
sequences = tokenizer.texts_to_sequences(texts)
# ধাপ ২: ফিক্সড বা নির্দিষ্ট দৈর্ঘ্য অনুযায়ী প্যাডিং/ট্রাঙ্কেট করা
padded = pad_sequences(sequences, maxlen=8, padding="post", truncating="post")
print(padded)
# প্রতিটি সিকোয়েন্সের শেপ এখন (3, 8) — ছোট সিকোয়েন্সগুলোকে ০ দিয়ে ৮ দৈর্ঘ্য পর্যন্ত প্যাড করা হয়েছেস্কেলারের মতোই, tokenizer.fit_on_texts() কেবল ট্রেনিং স্প্লিটের ওপর কল করা উচিত। পুরো ডেটাসেটের ওপর ফিট করলে তা টেস্ট সেটের ভোকাবুলারি ট্রেনিংয়ের সময়ই মডেলে প্রকাশ করে দেয়।
oov_token="<OOV>" প্যারামিটারটি এমন শব্দের জন্য একটি বিশেষ আইডি রিজার্ভ করে যা ট্রেনিং ভোকাবুলারিতে ছিল না কিন্তু প্রেডিকশন বা ইনফ্যারেন্সের সময় ইনপুটে এসেছে। এটি না থাকলে অজানা শব্দগুলো নীরবে মুছে যায় — যা বাক্যের অর্থ সম্পূর্ণ বদলে দিতে পারে। আর এটি থাকলে অন্তত মডেল বুঝতে পারে যে এখানে একটি অজানা শব্দ ছিল।
যেকোনো বাস্তব জীবনের LSTM প্রজেক্টে — পূর্বাভাস, সেন্টিমেন্ট অ্যানালাইসিস কিংবা অসঙ্গতি শনাক্তকরণ — মডেল আর্কিটেকচারের চেয়ে ডেটা প্রস্তুত করার পাইপলাইন তৈরিতেই বেশি সময় ব্যয় হয়। এখানকার বাগগুলো (যেমন: লিকেজ, ভুল স্কেলিং, ভুল সিকোয়েন্স দৈর্ঘ্য) ধরা সবচেয়ে কঠিন, কারণ মডেল কোনো ত্রুটি ছাড়াই সুন্দরভাবে ট্রেন হয়ে যায়; কিন্তু গোপনে ভুল জিনিস শিখে বসে থাকে।