সরাসরি পর্বে যান
পর্ব ৩১ · ৯১-এর মধ্যে · মডিউল ৫

Sequence-এর জন্য 1D convolution আর time series-এ deep learning

1D Convolution for Sequences and Deep Learning for Time Series

Taught in Bangla, this lesson applies 1D convolution to sequences: shapes and parameter counts, causal left padding checked with an impulse test, and receptive field growth from dilation. It runs a TCN forward pass in in-page NumPy, shows how padding='same' leaks the future, and compares TCN, LSTM, HistGradientBoosting and seasonal naive in a four-fold MASE backtest from a real desktop PyTorch run, then tests a 1D text CNN on negation against a TF-IDF bigram baseline.

এই পর্বে যা শিখবেন

  • Sequence-এ 1D convolution-এর shape আর parameter, RNN-এর সঙ্গে তার মূল পার্থক্য
  • Causal convolution, left padding-এর হিসাব আর impulse পাঠিয়ে ভবিষ্যৎ ফাঁস ধরা
  • Dilation দিয়ে receptive field-এর হিসাব, আর TCN-এর একটি forward pass, shape-সহ
  • কয়েক দিন সামনের অনুমান, আর আগের কোর্সের backtest ও MASE দিয়ে TCN, LSTM, boosting আর baseline-এর সৎ তুলনা
  • Boosting আর deep model-এর মধ্যে বাছাই, আর টেক্সট শ্রেণিবিভাগে 1D CNN

এই মডিউলের আগের পর্বগুলোতে (পর্ব ২৮ Recurrent neural network, পর্ব ২৯ LSTM আর GRU, পর্ব ৩০ Seq2seq) sequence পড়া হয়েছে এক ধাপ করে, একটি hidden state হাতে নিয়ে। এই পর্ব অন্য পথ দেখায়: পর্ব ২০-এর convolution-কে এক মাত্রায় নামানো। ছবির জানালা এবার সময়ের জানালা। প্রশ্ন দুটি: জানালা ভবিষ্যৎ দেখছে না তো? আর এত কিছুর পরে deep model আসলে সরল পদ্ধতিকে হারায় কি? পাতার চারটি ব্লক ব্রাউজারের Python আর NumPy-তে চলে; প্রথমবার প্যাকেজ নামতে কয়েক সেকেন্ড লাগে। PyTorch-এর সব আউটপুট ডেস্কটপের সত্যিকারের run থেকে, CPU-তে।

১ · Sequence-এ জানালা — 1D convolution

ক্রিকেটের ধারাভাষ্যকার ম্যাচের গতি বোঝেন “শেষ পাঁচ ওভারের রান” দেখে। পুরো ইনিংস মুখস্থ নয়, একটি ছোট জানালা, যা প্রতি ওভারে এক ঘর সরে। প্রথম স্তরের convolution ঠিক এটুকুই করে। পরের স্তর আগের স্তরের সারাংশ দেখে, তাই ধীরে ধীরে লম্বা সময়ের নকশা ধরে।

ছবিতে kernel দুই অক্ষে সরত। Sequence-এ সরে একটিতে: সময়। Input-এর shape (N, C, T): N-টি উদাহরণ, প্রতিটি সময়ে C-টি channel, T-টি সময়-ধাপ। বাজারঘরে C হতে পারে আজকের অর্ডার, সপ্তাহের বার আর ঈদের দূরত্ব; টেক্সটে C হলো embedding-এর মাত্রা। একটি স্তরের weight-এর shape (O, C, k), যেখানে O output channel আর k kernel-এর দৈর্ঘ্য। তাই:

parameter = O · C · k + O  ·  Tout = ⌊(T + 2p − d(k − 1) − 1)/s⌋ + 1

দ্বিতীয়টি পর্ব ২০-এর সূত্রই, এক অক্ষে। nn.Conv1d(10, 32, 3)-এ ৩২ × ১০ × ৩ + ৩২ = ৯৯২টি parameter. হাতে একটি ছোট হিসাব: x = [2, 4, 6, 8, 10] আর kernel [1, 0, −1], padding ছাড়া। প্রথম জানালা [2, 4, 6] দেয় ১·২ + ০·৪ − ১·৬ = −৪; দ্বিতীয় আর তৃতীয়ও −৪. Output-এ ৫ − ৩ + ১ = ৩টি সংখ্যা। ধীরে বাড়তে থাকা অর্ডারের ওপর এই kernel স্থির −৪ দেয়, অর্থাৎ এটি একটি trend-খোঁজা filter. CNN-এ এমন filter কেউ হাতে বসায় না; backpropagation ডেটা থেকে শেখে। একটি সতর্কতা: PyTorch-এর nn.LSTM(batch_first=True) নেয় (N, T, C), আর nn.Conv1d নেয় (N, C, T). দুটি পাশাপাশি লিখলে transpose(1, 2) লাগে; ভুল অক্ষ অনেক সময় ক্র্যাশও করে না। Windowing আর padding-এর ভিত্তি পর্ব ২৭ (Sequence ডেটা) আর পর্ব ১১-এ।

প্রশ্নRNN, LSTM1D CNN, TCN
স্মৃতিHidden state; তত্ত্বে যত দূর খুশি, বাস্তবে সীমিতনির্দিষ্ট receptive field; তার বাইরে কিছুই পৌঁছায় না
গণনাধাপে ধাপে; সময়ের অক্ষে ভাগ করা যায় নাসব জানালা একসঙ্গে; সমান্তরালে চলতে পারে
Gradient-এর পথT ধাপ লম্বা (পর্ব ১৭-এর সমস্যা)গভীরতার সমান লম্বা, T-এর নয়
নতুন একটি মান এলেএকটি ধাপ, state থেকেসাধারণ forward-এ পুরো জানালা আবার; cache করে কমানো যায়

২ · Causal convolution — জানালা যেন ভবিষ্যৎ না দেখে

ছবিতে ডান পাশ দেখায় কোনো দোষ নেই। সময়ে আছে। মঙ্গলবারের অনুমান করতে বুধবারের অর্ডার দেখলে মডেল আসলে উত্তরপত্র দেখছে। যে convolution-এর output y[t] শুধু x[t] আর তার আগের মান দিয়ে তৈরি, তাকে বলে causal convolution. উপায় একটাই: kernel-এর পুরো চওড়া, (k − 1)·d ঘর, শুধু বাঁয়ে শূন্য বসানো।

Left padding for k = 3, dilation 4 padding x[0] … x[9] 0 0 0 0 0 0 0 0 3 1 4 1 5 9 2 6 5 3 ×1×2×3 ×1×2×3 ×1×2×3 ×1×2×3 ×1×2×3 আজ পরে output y
  1. Padding ছাড়া. k = ৩, d = ৪: জানালা নিচের তিনটি ঘর ছোঁয়, চার ঘর পরপর; তার পুরো চওড়া ৪ × ২ + ১ = ৯ দিন। ১০ দিনের series-এ জানালা বসে মাত্র দুই জায়গায়, তাই output মাত্র ২টি (t = ৮ আর ৯)। আগের আট দিনের কোনো অনুমান নেই।
  2. বাঁয়ে ৮ ঘর. Padding = (k − ১)·d = ২ × ৪ = ৮, সবটা বাঁয়ে। মোট দৈর্ঘ্য ১৮, আর output ১৮ − ৮ = ১০: input-এর সমান, প্রতিটি দিনের জন্য একটি। PyTorch-এ F.pad(x, (8, 0)); টাপলের প্রথম সংখ্যা বাঁ পাশ।
  3. প্রথম দিন, t = ০. জানালার তিন ঘর: দুটি padding-এর শূন্য আর x[0] = ৩. y[0] = ১·০ + ২·০ + ৩·৩ = ৯. কোনো ইতিহাস নেই, শুধু আজকের মান।
  4. মাঝের দিন, t = ৫. জানালা দেখে t − ৮ (padding), t − ৪ আর t. y[5] = ১·০ + ২·১ + ৩·৯ = ২৯. kernel-এর ডানের ঘর ঠিক “আজ”-এর ওপর, তার ডানে কিছু নেই।
  5. শেষ দিন, t = ৯. y[9] = ১·১ + ২·৯ + ৩·৩ = ২৮. সব ১০টি output হয়ে গেল, আর একটিও নিজের দিনের পরের কিছু দেখেনি। t = ৮ আর ৯-এর মান প্রথম ধাপের সঙ্গে হুবহু মেলে।
  6. ভুল পথ: দুই পাশে ৪. padding=4 বা padding="same" দিলেও দৈর্ঘ্য ১০, তাই ভুলটি চোখে পড়ে না। কিন্তু t = ৫-এর জানালা এখন x[1], x[5], x[9] ছোঁয়: ৪ দিন পরের x[9] ঢুকে গেছে। y[5] = ১·১ + ২·৯ + ৩·৩ = ২৮, যা causal-এর ২৯ নয়।

চোখে না দেখে পরীক্ষা করবেন কীভাবে? Impulse test: সব শূন্য, শুধু এক দিনে ১. Causal হলে সেই দিনের আগে কোনো output নড়বে না। দ্বিতীয় পরীক্ষা: ভবিষ্যতের মান বদলে দেখা, অতীতের output বদলায় কি না। দুটিই নিচে, হাতে লেখা convolution-এ।

import numpy as np
np.set_printoptions(precision=2, suppress=True, linewidth=72)

def conv1d(x, w, d=1, left=0, right=0):
    """x (T,), w (k,). দুই পাশে শূন্য বসিয়ে, dilation-সহ জানালা চালানো।"""
    xp = np.r_[np.zeros(left), x, np.zeros(right)]
    idx = d * np.arange(len(w))                # kernel-এর ঘরগুলোর দূরত্ব
    T_out = len(xp) - idx[-1]
    return np.array([w @ xp[t + idx] for t in range(T_out)])

w = np.array([0.2, 0.3, 0.5])                  # শেষ ঘর জানালার সবচেয়ে নতুন দিনে
imp = np.zeros(12); imp[5] = 1.0               # t = 5-এ একটি ধাক্কা
causal = conv1d(imp, w, left=2)                # (k-1)d = 2, সবটা বাঁয়ে
centred = conv1d(imp, w, left=1, right=1)      # দুই পাশে 1: আকার একই
print("impulse", imp)
print("causal ", causal)
print("centred", centred)
print("first nonzero t: causal", np.flatnonzero(causal)[0],
      "| centred", np.flatnonzero(centred)[0])

rng = np.random.default_rng(31)                # ভবিষ্যৎ বদলালে অতীতের output বদলায়?
x = rng.poisson(200, 16).astype(float)
x2 = x.copy(); x2[9:] += 50                    # শুধু t >= 9 বদলানো হলো
for name, kw in [("causal", dict(left=2)), ("centred", dict(left=1, right=1))]:
    a, b = conv1d(x, w, **kw), conv1d(x2, w, **kw)
    print(f"{name:8s} first changed output at t =", np.flatnonzero(a != b)[0])

imp2 = np.zeros(21); imp2[10] = 1.0
y = conv1d(imp2, w, d=4, left=8)               # k = 3, d = 4: বাঁয়ে 8
print("d = 4, left pad 8: length", len(y), "| nonzero at", np.flatnonzero(y))

আউটপুট (পাতার Python-এ চালানো):

impulse [0. 0. 0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]
causal  [0.  0.  0.  0.  0.  0.5 0.3 0.2 0.  0.  0.  0. ]
centred [0.  0.  0.  0.  0.5 0.3 0.2 0.  0.  0.  0.  0. ]
first nonzero t: causal 5 | centred 4
causal   first changed output at t = 9
centred  first changed output at t = 8
d = 4, left pad 8: length 21 | nonzero at [10 14 18]

ধাক্কা t = ৫-এ। Causal-এর প্রথম সাড়াও t = ৫-এ; সাড়াটি ০.৫, ০.৩, ০.২, অর্থাৎ kernel-এর উল্টো ক্রম। Centred-এর সাড়া শুরু t = ৪-এ, ধাক্কার এক দিন আগে: এটাই ফাঁস। দ্বিতীয় পরীক্ষায় t = ৯ থেকে মান বদলানো হয়েছে; centred-এর t = ৮-এর output বদলে গেছে। শেষ লাইনে d = ৪: দৈর্ঘ্য অক্ষত, সাড়া t = ১০, ১৪, ১৮-এ, কখনো আগে নয়। দুই পরীক্ষাই ছোট unit test হিসেবে আপনার যেকোনো forecasting model-এ বসানো যায়।

এবার PyTorch. একই NumPy হিসাব nn.Conv1d-এর সঙ্গে মেলানো, তারপর padding-এর চারটি লেখার ধরন, আর শেষে আসল ক্ষতি: তিন স্তরের একই ছোট network, শুধু padding আলাদা, বাজারঘরের ছয় জেলার series-এ এক দিন সামনের অনুমান। সত্যিকারের run (ডেস্কটপ, CPU-তে; লেখার সময়ের সংস্করণ: Python 3.14.4, torch 2.15.0, numpy 2.5.4):

~/bazarghar-dl$ uv run python conv1d_check.py
torch 2.15.0+cpu
weight (4, 2, 3) | bias (4,) | params 28
left pad 8: output (1, 4, 20) | max |torch - numpy| = 1.2e-07
impulse at t=10, k=3, d=4
  F.pad left 8        len 21 nonzero at [10, 14, 18]
  padding=8           len 29 nonzero at [10, 14, 18]
  padding=8 + chomp 8 len 21 nonzero at [10, 14, 18]
  padding='same'      len 21 nonzero at [6, 10, 14]
one-step MASE on the last 112 days, padding=causal: 0.949
one-step MASE on the last 112 days, padding=same  : 0.060
one-step seasonal naive MASE: 1.626

পড়ুন: weight (4, 2, 3): ৪ output, ২ channel, k = ৩; NumPy-র সঙ্গে পার্থক্য শুধু float32-এর গোলের ঘরে। padding=8 দুই পাশেই বসায়, তাই দৈর্ঘ্য ২৯; ডানের ৮ ঘর কেটে দিলে (এই কাটার নাম chomp) তবেই causal, যা F.pad সরাসরি দেয়। padding="same" সাড়া শুরু করে t = ৬-এ, ধাক্কার চার দিন আগে। শেষ তিন লাইন আসল শিক্ষা: leak-যুক্ত model-এর MASE ০.০৬০, যেন প্রায় নির্ভুল। কারণ এক ধাপ সামনের target-টিই তার input-এ আছে। Backtest-এ এই ফাঁস ধরা পড়ে না, কারণ সেখানে “ভবিষ্যৎ” ফাইলেই আছে; production-এ আগামীকালের মান থাকে না, আর model ভেঙে পড়ে। অবিশ্বাস্য ভালো ফল দেখলে আগে leakage খুঁজুন (AI-এর ভিত্তি কোর্সের পর্ব ৫৬)।

৩ · Dilation — অল্প স্তরে লম্বা দৃষ্টি

Dilation ছাড়া k = ৩-এর প্রতিটি স্তর receptive field-এ ২ যোগ করে। ৫৬ দিন দেখতে ২৮টি স্তর লাগত। পর্ব ২০-এর dilation-এ kernel-এর ঘরগুলোর মাঝে ফাঁক থাকে, আর স্তরের পর স্তরে d দ্বিগুণ করলে দৃষ্টিও প্রায় দ্বিগুণ হয়। সেই পর্বের সূত্রে stride ১ হলে jump ১ থাকে, আর ফাঁকটি (k − 1)-এর সঙ্গে গুণ হয়:

r = 1 + (k − 1) · ∑ di   (di = i-তম স্তরের dilation)

Receptive field of four dilated causal layers L4 L3 L2 L1 in আজ →← অতীত
  1. শেষ স্তরের একটি output. ওপরের সারি L4, ডান প্রান্তে “আজ”। প্রশ্ন: নিচে নামতে নামতে কোন কোন input-এ পৌঁছায়? এখন পর্যন্ত একটি বিন্দু। (ডান থেকে বাঁয়ে মানে আজ থেকে অতীতে।)
  2. L4, d = ৮. তিনটি ঘর: ০, ৮ আর ১৬ দিন পেছনে। এই এক স্তরের অবদান (k − ১)·d = ২ × ৮ = ১৬.
  3. L3, d = ৪. তিনটি বিন্দুর প্রত্যেকে আবার তিনটি ঘর ছোঁয়, ৪ দিন পরপর। মিলিয়ে ৭টি বিন্দু; অবদান ২ × ৪ = ৮.
  4. L2, d = ২. এবার ১৫টি বিন্দু, প্রতি ২ দিনে একটি। অবদান ২ × ২ = ৪.
  5. L1, d = ১. Input-এ ৩১টি ঘর, কোনো ফাঁক ছাড়া। সামনের দিক থেকে গুনলে r: L1-এর পরে ৩, L2-এর পরে ৭, L3-এর পরে ১৫, L4-এর পরে ৩১ = ১ + ২(১ + ২ + ৪ + ৮). চার স্তর, অথচ d = ১-এর চার স্তরে r মাত্র ৯.
  6. প্রতি block-এ দুটি স্তর. TCN-এর একটি block-এ সাধারণত একই d-এর দুটি convolution. তখন r = ১ + ২ × ২ × ১৫ = ৬১, যা ৫৬ দিনের জানালাকে ঢাকে। Weight-এর সংখ্যা dilation-এ বদলায়নি।

সূত্র নিজে না মেনে মেপে দেখা যাক। নিচের ব্লক একটি impulse পাঠায় পুরো stack-এর মধ্যে (np.convolve শুধু অতীত থেকে নেয়, তাই causal), আর গোনে কত দিন আগের মান শেষ output-এ পৌঁছায়।

import numpy as np
import matplotlib.pyplot as plt

def offsets(dils, k=3):                 # impulse পাঠিয়ে মাপা: কত দিন আগের মান পৌঁছায়
    x = np.zeros(200); x[0] = 1.0
    for d in dils:
        kern = np.zeros(d * (k - 1) + 1); kern[::d] = 1.0
        x = np.convolve(x, kern)[:len(x)]   # সবটা অতীত থেকে: causal
    return np.flatnonzero(x)

def r_formula(dils, k=3):
    return 1 + sum((k - 1) * d for d in dils)

print("stack               k   r  measured  inputs seen")
cases = [("plain 1,1,1,1", [1] * 4, 3), ("dilated 1,2,4,8", [1, 2, 4, 8], 3),
         ("two conv per block", [1, 1, 2, 2, 4, 4, 8, 8], 3),
         ("same d 2,2,2,2", [2] * 4, 3), ("k=2, 1,2,4,8", [1, 2, 4, 8], 2)]
for name, dils, k in cases:
    o = offsets(dils, k)
    print(f"{name:18s} {k:2d} {r_formula(dils, k):3d} {o.max() + 1:7d} {len(o):9d}")
print("same d sees offsets:", offsets([2] * 4)[:6], "... only even days")

layers = np.arange(1, 11)
plain = 1 + 2 * layers                  # d = 1 সব স্তরে
dilated = 2 ** (layers + 1) - 1         # d = 1, 2, 4, ...
for days in (56, 365):
    print(f"layers to cover {days} days: dilated", layers[dilated >= days][0],
          "| plain", int(np.ceil((days - 1) / 2)))
fig, ax = plt.subplots(figsize=(6, 3))
ax.semilogy(layers, dilated, "o-", label="dilated, d = 1, 2, 4, ...")
ax.semilogy(layers, plain, "s-", label="plain, d = 1")
ax.axhline(56, color="gray", ls="--", label="56-day lookback")
ax.set(xlabel="layers (k = 3)", ylabel="receptive field (days)")
ax.legend(fontsize=8)
plt.show()

আউটপুট (পাতার Python-এ চালানো; সঙ্গে একটি লেখচিত্র):

stack               k   r  measured  inputs seen
plain 1,1,1,1       3   9       9         9
dilated 1,2,4,8     3  31      31        31
two conv per block  3  61      61        61
same d 2,2,2,2      3  17      17         9
k=2, 1,2,4,8        2  16      16        16
same d sees offsets: [ 0  2  4  6  8 10] ... only even days
layers to cover 56 days: dilated 5 | plain 28
layers to cover 365 days: dilated 8 | plain 182

সূত্র আর মাপা মান পাঁচটিতেই মিলেছে। চতুর্থ সারিটি ফাঁদ: d = ২ চার স্তরে দৃষ্টি ১৭ দিন, কিন্তু দেখে মাত্র ৯টি দিন, শুধু জোড় দিনগুলো। বিজোড় দিনের তথ্য কোনো স্তরেই পৌঁছায় না; এই সমস্যার নাম gridding. d = ১ দিয়ে শুরু করলে ফাঁক থাকে না। লেখচিত্রে y-অক্ষ log; dilated রেখা প্রায় সোজা ওপরে, মানে সূচকীয় বৃদ্ধি। ৩৬৫ দিন দেখতে ৮টি dilated স্তর, সাধারণ স্তরে ১৮২টি। তবে নিচে দেখবেন, বাৎসরিক ধরন ধরতে এত বড় জানালার চেয়ে ক্যালেন্ডার feature প্রায়ই সস্তা।

r মানে দৃষ্টির সীমা, গুরুত্ব নয়. ৩১ দিনের ভেতরের সব দিন সমান প্রভাব ফেলে না, আর dilated stack-এ একটি দিন শেষ output-এ পৌঁছানোর পথের সংখ্যাও সমান নয়। পর্ব ২০-এর কার্যকর receptive field-এর সতর্কতা এখানেও খাটে।

৪ · TCN — causal, dilated আর residual একসঙ্গে

“Temporal convolutional network” বা TCN নামটি ছড়ায় Bai, Kolter আর Koltun-এর ২০১৮ সালের একটি প্রবন্ধে। তাঁদের বর্ণনায় এটি কোনো একক আবিষ্কার নয়, পুরোনো উপাদানের একটি সরল জোড়া: causal আর dilated convolution, পর্ব ১৭-এর residual connection, প্রতি স্তরে dropout আর weight normalization (পর্ব ১৪-এর normalization পরিবারের একটি)। আমাদের সংস্করণে weight normalization বাদ, কোড ছোট রাখতে।

Residual কেন? একটি block এমনিতেই y + x শেখে: কিছু শেখার না থাকলে y শূন্যের কাছে থাকলেই চলে, আর x অক্ষত যায়। আট স্তরের convolution-এ gradient-ও এই সোজা পথে সহজে ফেরে। লক্ষ করুন, এখানে “গভীরতা” T-এর ওপর নির্ভর করে না; ৫৬ দিন হোক বা ৫০০, gradient-এর পথের দৈর্ঘ্য স্তরের সংখ্যায় বাঁধা। RNN-এর backpropagation through time-এর সঙ্গে এখানেই বড় তফাত।

বাজারঘরের TCN forecaster-এর shape

Four residual blocks read 56 days; a small head turns the last step into 28 forecasts.

Shapes in the TCN forecaster input, ৫৬ দিন(N, 10, 56) Block d = 1 → 2 → 4 → 8(N, 32, 56), r = 61 শুধু শেষ দিন: h(N, 32) ভবিষ্যৎ ক্যালেন্ডার(N, 28, 10) h, ২৮ বার কপি(N, 28, 32) জোড়া দিয়ে(N, 28, 42) MLP head → অনুমান(N, 28): ২৮ দিন
ছবি ৩১.১ — ৫৬ দিনের ১০ channel যায় চারটি block-এ। সময়ের দৈর্ঘ্য অক্ষত থাকে (causal padding), শেষে শুধু শেষ দিনের ৩২টি সংখ্যা h নেওয়া হয়। তার সঙ্গে প্রতিটি ভবিষ্যৎ দিনের জানা ক্যালেন্ডার জুড়ে একই ছোট MLP ২৮টি অনুমান দেয়।

নিচে এই পুরো forward pass NumPy-তে। শেখানো হচ্ছে না, weight এলোমেলো; লক্ষ্য shape আর causal গুণ যাচাই। einsum-এর অক্ষরগুলো: output channel o, input channel c, batch n, সময় t.

import numpy as np
rng = np.random.default_rng(31)
relu = lambda a: np.maximum(a, 0)
def causal(x, W, b, d):                  # x (N, C, T), W (O, C, k), b (O,)
    k, T = W.shape[2], x.shape[2]
    xp = np.pad(x, ((0, 0), (0, 0), ((k - 1) * d, 0)))
    y = sum(np.einsum("oc,nct->not", W[:, :, i], xp[:, :, i * d:i * d + T]) for i in range(k))
    return y + b[None, :, None]

def layer(c_in, c_out, k=3):             # এলোমেলো weight আর শূন্য bias
    return rng.normal(0, 0.5 / np.sqrt(c_in * k), (c_out, c_in, k)), np.zeros(c_out)

blocks = [(d, layer(10 if d == 1 else 32, 32), layer(32, 32),
           layer(10, 32, k=1) if d == 1 else None) for d in (1, 2, 4, 8)]

def tcn(x, trace=False):
    for d, l1, l2, skip in blocks:
        y = relu(causal(relu(causal(x, *l1, d)), *l2, d))   # দুটি causal conv
        x = relu(y + (causal(x, *skip, 1) if skip else x))  # residual connection
        if trace: print(f"block d={d}: {x.shape}")
    return x

x = rng.normal(size=(2, 10, 56))         # (batch, channels, time): 2টি জানালা, 56 দিন
print("input:", x.shape)
out = tcn(x, trace=True)
h = out[:, :, -1]                        # শুধু শেষ দিনের সারাংশ
fut = rng.normal(size=(2, 28, 10))       # আগামী 28 দিনের জানা ক্যালেন্ডার
hf = np.concatenate([np.repeat(h[:, None], 28, axis=1), fut], axis=2)
A, B = rng.normal(0, 0.1, (42, 64)), rng.normal(0, 0.1, (64, 1))
yhat = (relu(hf @ A) @ B)[:, :, 0]       # প্রতিটি দিনের জন্য একই ছোট MLP
print("h:", h.shape, "| h + future:", hf.shape, "| forecast:", yhat.shape)
x2 = x.copy(); x2[:, :, 40:] += 1.0      # 40 নম্বর দিন থেকে বদলানো
o2 = tcn(x2)
print("t < 40 unchanged:", np.array_equal(out[:, :, :40], o2[:, :, :40]),
      "| t = 55 changed:", not np.allclose(out[:, :, 55], o2[:, :, 55]))

আউটপুট (পাতার Python-এ চালানো):

input: (2, 10, 56)
block d=1: (2, 32, 56)
block d=2: (2, 32, 56)
block d=4: (2, 32, 56)
block d=8: (2, 32, 56)
h: (2, 32) | h + future: (2, 28, 42) | forecast: (2, 28)
t < 40 unchanged: True | t = 55 changed: True

প্রথম block-এ channel ১০ থেকে ৩২, তাই skip পথে একটি 1 × 1 convolution লাগে (পর্ব ২০); পরের block-গুলোতে সরাসরি যোগ। T = ৫৬ সব জায়গায় অক্ষত। ৩২ + ১০ = ৪২-এর মধ্যে ১০ হলো ভবিষ্যৎ দিনের ক্যালেন্ডার। শেষ লাইন causal গুণের প্রমাণ: ৪০ নম্বর দিন থেকে input বদলালে t < ৪০-এর সব hidden মান হুবহু এক (array_equal, কাছাকাছি নয়), আর শেষ দিনের মান বদলায়। Parameter হাতে: প্রথম block ৯৯২ + ৩,১০৪ + ৩৫২ = ৪,৪৪৮, বাকি তিনটি ৬,২০৮ করে, head ২,৮১৭; মোট ২৫,৮৮৯, যা নিচের PyTorch run-এর সংখ্যা।

PyTorch-এ একই জিনিস অল্প কোডে (ডেস্কটপ run-এর কোড, এখানে শুধু নামগুলো একটু পরিষ্কার করে):

class CausalConv(nn.Module):
    def __init__(self, c_in, c_out, k, d):
        super().__init__()
        self.pad = (k - 1) * d
        self.conv = nn.Conv1d(c_in, c_out, k, dilation=d)
    def forward(self, x):
        return self.conv(F.pad(x, (self.pad, 0)))     # শুধু বাঁয়ে

class Block(nn.Module):
    def __init__(self, c_in, c, k, d, p=0.1):
        super().__init__()
        self.c1, self.c2 = CausalConv(c_in, c, k, d), CausalConv(c, c, k, d)
        self.drop = nn.Dropout(p)
        self.skip = nn.Conv1d(c_in, c, 1) if c_in != c else nn.Identity()
    def forward(self, x):
        y = self.drop(F.relu(self.c1(x)))
        y = self.drop(F.relu(self.c2(y)))
        return F.relu(y + self.skip(x))

৫ · কয়েক দিন সামনে — multi-horizon output

গুদামের লোক শুধু আগামীকাল জানতে চান না, আগামী ২৮ দিন চান। মেশিন লার্নিং কোর্সের পর্ব ৫২ দুটি পথ দেখিয়েছে: recursive (আগামীকালের অনুমানকে input বানিয়ে পরের দিন) আর direct (প্রতিটি দিনের জন্য সরাসরি অনুমান)। Neural network-এ তৃতীয় একটি স্বাভাবিক পথ আছে: একসঙ্গে সব দিন।

ধরনOutputসুবিধাঝুঁকি
Recursive(N, 1), বারবার চালানোএকটি model, যেকোনো দূরত্বভুল জমে; দূরের দিনে সমস্যা
একটি Linear → H সংখ্যা(N, H)সরল, দ্রুতভবিষ্যতের জানা তথ্য নিতে পারে না
একই head, প্রতি দিন + ক্যালেন্ডার(N, H)শুক্রবার, ঈদের দূরত্ব জানে; weight ভাগ হয়দিনের বিশেষ আচরণ শেখাতে দিনের নম্বরও দিতে হয়

আমরা শেষেরটি নিই, ছবি ৩১.১-এর মতো। এই ধাঁচ forecasting-এর গবেষণায় পরিচিত; MQ-RNN (Wen ও সহকর্মী, ২০১৭)-এর মতো কাজে এমন “প্রতি দিনের decoder” পাওয়া যায়। আরও তিনটি সিদ্ধান্ত: (১) log1p নিয়ে প্রতিটি জানালা থেকে তার ৫৬ দিনের গড় বিয়োগ, যাতে ঢাকার কয়েকশো আর বরিশালের কয়েক ডজন অর্ডার একই আকারে আসে আর ধীর বৃদ্ধি সমস্যা না করে; পরিসংখ্যান শুধু জানালার ভেতরের, তাই ফাঁস নেই। (২) Loss L1 (পর্ব ১৩), কারণ metric-ও absolute error. (৩) সব জেলার জানালা একসঙ্গে: global model, মেশিন লার্নিং কোর্সের পর্ব ৫২-এর মতো।

৬ · আগে baseline — seasonal naive আর MASE

Backtest আর metric নতুন করে শেখানো হবে না। Rolling-origin backtest-এর জন্য মেশিন লার্নিং কোর্সের পর্ব ৫২, আর MASE ও baseline-এর জন্য পর্ব ৫১ দেখুন। এক লাইনে: origin পর্যন্ত জানা ডেটায় শিখে পরের ২৮ দিন অনুমান, তারপর origin সামনে সরিয়ে আবার। MASE = test-এর MAE ÷ training-এ এক-ধাপ seasonal naive-এর MAE; ধরুন test-এর MAE ১৬ আর scale ২০, তাহলে MASE ০.৮. ডেটাও মেশিন লার্নিং কোর্সের পর্ব ৫২-এর: একই seed, ছয় জেলার ৭৩০ দিনের কাল্পনিক অর্ডার, তাই সেখানকার টেবিল চেনা লাগবে। নিচের ব্লকে baseline দুটি। এদের কোনো শেখা নেই, তাই আপনার ব্রাউজারেই ডেস্কটপের সংখ্যা হুবহু আসবে।

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from bazar_orders import make_orders, DISTRICTS
wide = make_orders().pivot(index="date", columns="district", values="orders")[DISTRICTS]
dates, Y = wide.index, wide.to_numpy(dtype=float)
T, H = Y.shape[0], 28
origins = [T - H * (4 - i) for i in range(4)]        # চারটি origin, প্রতিটি 28 দিনের test

def mase(pred, o):                                   # scale শুধু origin-এর আগের ডেটায়
    scale = np.abs(Y[7:o] - Y[:o - 7]).mean(axis=0)
    return np.abs(pred - Y[o:o + H]).mean(axis=0) / scale

def seasonal_naive(o):
    return Y[o - 7:o][np.arange(H) % 7]              # গত সপ্তাহটি বারবার

def naive(o):
    return np.tile(Y[o - 1], (H, 1))                 # গতকালের মান বারবার

print(wide.shape, "| districts:", len(DISTRICTS))
print("first day    naive  s-naive  s-naive by district")
sn, nv = [], []
for o in origins:
    m = mase(seasonal_naive(o), o); sn.append(m.mean()); nv.append(mase(naive(o), o).mean())
    print(dates[o].date(), f"{nv[-1]:6.3f} {sn[-1]:7.3f} ", m.round(2))
print(f"mean of folds: naive {np.mean(nv):.3f} | seasonal naive {np.mean(sn):.3f}")

o = origins[0]
fig, ax = plt.subplots(figsize=(6.4, 2.8))
ax.plot(dates[o - 21:o + H], Y[o - 21:o + H, 0], label="Dhaka actual")
ax.plot(dates[o:o + H], seasonal_naive(o)[:, 0], "--", label="seasonal naive")
ax.axvline(dates[o], color="gray", lw=1)
ax.set(ylabel="orders per day", title="Fold 1: the forecast repeats a week, Eid does not")
ax.tick_params(axis="x", labelsize=7); ax.legend(fontsize=8)
plt.show()

আউটপুট (পাতার Python-এ চালানো; সঙ্গে একটি লেখচিত্র):

(730, 6) | districts: 6
first day    naive  s-naive  s-naive by district
2026-03-11  1.588   1.604  [1.61 1.69 2.13 1.38 1.45 1.36]
2026-04-08  0.854   0.902  [1.15 0.72 0.92 0.71 0.85 1.06]
2026-05-06  1.807   1.724  [2.32 1.84 1.27 2.2  1.25 1.45]
2026-06-03  1.143   1.797  [2.2  1.5  1.59 2.07 1.43 2.  ]
mean of folds: naive 1.348 | seasonal naive 1.507

দুটি জিনিস লক্ষ করুন। Scale এক-ধাপের, test ২৮ দিনের; তাই ২৮ দিনের অনুমানে MASE ১-এর ওপরে থাকা স্বাভাবিক, মেশিন লার্নিং কোর্সের পর্ব ৫১-এর ৫৬ দিনের holdout-এর ১.০৭-এর মতো। Fold-গুলো সমান নয়: ডেটার জেনারেটরে ঈদ ধরা আছে ২০ মার্চ আর ২৭ মে, ২০২৬; প্রথম আর তৃতীয় fold তাদের ওপর পড়েছে, আর সেখানে baseline ভেঙেছে। চতুর্থ fold (৩ জুন থেকে) একটি মজার ফাঁদ: origin-এর আগের সপ্তাহে ঈদের মন্দা, আর seasonal naive সেই মন্দা সপ্তাহটিই চার বার পুনরাবৃত্তি করেছে, তাই ১.৭৯৭; সরল naive এখানে ভালো। অর্থাৎ একটি fold-এর একটি সংখ্যা দেখে কিছু বলা যায় না। লক্ষ্য রইল: চার fold-এর গড় ১.৫০৭-কে হারানো।

৭ · মুখোমুখি — TCN, LSTM, boosting আর baseline

চারটি পদ্ধতি, একই চার fold, একই MASE. নিয়মগুলো আগে থেকে ঠিক করা:

  • TCN: ৪ block, ৩২ channel, k = ৩, d = 1, 2, 4, 8 (r = ৬১). LSTM: এক স্তর, ৩২ hidden (পর্ব ২৯). দুটিরই input একই ৫৬ দিন × ১০ channel, একই head.
  • HistGradientBoosting: পর্ব ৫২-এর ধাঁচে, একটি direct global model; lag ১, ২, ৩, ৭, ১৪, ২১, ২৮, ৭ আর ২৮ দিনের গড়, জেলা, h, লক্ষ্য দিনের বার আর ঈদের দূরত্ব। একই log-স্কেল, একই absolute-error loss, ৩০০ iteration, কোনো tuning নয়।
  • শেখানো: AdamW, one-cycle schedule (পর্ব ১৫), gradient clipping. প্রতিটি target origin-এর আগে শেষ। Epoch-এর সংখ্যা (৫) বেছে নেওয়া হয়েছে প্রথম fold-এর আগের দুটি origin-এ, test fold ছুঁয়ে নয়। প্রতিটি fold-এ নতুন করে শেখানো, ৩টি seed.

সত্যিকারের run (ডেস্কটপ, CPU-তে ৪টি thread; একটি local run):

~/bazarghar-dl$ uv run python backtest.py
Python 3.14.4 | torch 2.15.0+cpu | numpy 2.5.4 | pandas 3.1.1
scikit-learn 1.9.1 | CPU, 4 threads
series (730, 6) | lookback 56 | horizon 28 | folds 4 | seeds [0, 1, 2]
fold first days: ['2026-03-11', '2026-04-08', '2026-05-06', '2026-06-03']
params TCN 25889 | LSTM 8449
  HistGB + lags seed 0: [1.151 0.854 0.959 0.732] mean 0.924
  HistGB + lags seed 1: [1.195 0.868 0.956 0.723] mean 0.935
  HistGB + lags seed 2: [1.197 0.866 0.943 0.71 ] mean 0.929
  LSTM seed 0: [1.179 0.843 1.242 0.67 ] mean 0.983
  LSTM seed 1: [1.344 0.841 1.45  0.684] mean 1.080
  LSTM seed 2: [1.14  0.843 1.227 0.683] mean 0.973
  TCN seed 0: [1.229 0.854 1.302 0.648] mean 1.008
  TCN seed 1: [1.195 0.852 1.275 0.668] mean 0.997
  TCN seed 2: [1.3   0.849 1.373 0.66 ] mean 1.046

model            fold1  fold2  fold3  fold4   MASE seed-sd  s/fold
seasonal naive   1.604  0.902  1.724  1.797  1.507   0.000    0.00
HistGB + lags    1.181  0.863  0.953  0.721  0.929   0.006    2.35
LSTM             1.221  0.842  1.307  0.679  1.012   0.059    1.82
TCN              1.241  0.852  1.316  0.659  1.017   0.025    6.92
MASE by forecast week (mean of folds and seeds):
  seasonal naive   1.304  1.831  1.630  1.263
  HistGB + lags    0.844  1.198  0.905  0.771
  LSTM             0.890  1.255  1.085  0.818
  TCN              0.865  1.296  1.066  0.840

কে জিতল. এই run-এ HistGradientBoosting: MASE ০.৯২৯, LSTM ১.০১২, TCN ১.০১৭. সবাই seasonal naive-এর ১.৫০৭-কে অনেক ব্যবধানে হারিয়েছে। TCN আর LSTM-এর ০.০০৫ পার্থক্য seed-এর ওঠানামার (০.০২৫, ০.০৫৯) চেয়ে অনেক ছোট, তাই এদের মধ্যে কোনো বিজয়ী ঘোষণা করা যায় না।

Fold ধরে দেখলে ছবিটা অন্য রকম. শান্ত fold ৪-এ TCN (০.৬৫৯) আর LSTM (০.৬৭৯), boosting-এর (০.৭২১) চেয়ে ভালো; fold ২-এ তিনটি প্রায় সমান। পুরো পার্থক্যটি এসেছে ঈদের fold ৩ থেকে: boosting ০.৯৫৩, deep model দুটি প্রায় ১.৩১. এর একটি সম্ভাব্য ব্যাখ্যা: ইতিহাসে ঈদ এসেছে মাত্র তিনবার। Tree এত অল্প উদাহরণেও “ঈদের দুদিনের মধ্যে” একটি তীক্ষ্ণ split বানাতে পারে, আর ৫ epoch-এর একটি মসৃণ network সেই খাড়া পতন কম শেখে। এই ব্যাখ্যা এখানে পরীক্ষা করা হয়নি; ঈদের feature বদলে বা বেশি tuning করে ফল বদলাতে পারে।

সপ্তাহ ধরে পড়ুন. সাধারণত ভাবা হয়, যত দূরের দিন তত বেশি ভুল। শেষের টেবিল তা বলছে না: boosting-এর চার সপ্তাহের MASE ০.৮৪৪, ১.১৯৮, ০.৯০৫, ০.৭৭১. দ্বিতীয় সপ্তাহ সবার জন্য কঠিন, কারণ প্রথম fold-এর ঈদ ঠিক সেখানে পড়ে। তৃতীয় সপ্তাহে deep model দুটি (প্রায় ১.০৭–১.০৯) boosting-এর চেয়ে বেশি পিছিয়ে, কারণ সেখানে তৃতীয় fold-এর ঈদের আগের ভিড় পড়ে। অর্থাৎ এই ডেটায় ক্যালেন্ডারের ঘটনা দূরত্বের চেয়ে বেশি প্রভাব ফেলছে। শুধু গড় MASE দেখলে এই তথ্য হারিয়ে যেত; তাই ভুলকে fold, সপ্তাহ আর জেলা ধরে ভেঙে দেখা অভ্যাস করুন।

সময়. প্রতি fold-এ LSTM ১.৮২ সেকেন্ড, boosting ২.৩৫, TCN ৬.৯২. “Convolution সমান্তরাল, তাই RNN-এর চেয়ে দ্রুত” কথাটি এখানে মেলেনি: এই ছোট আকারে, CPU-তে, TCN সবচেয়ে ধীর, আর তার parameter-ও (২৫,৮৮৯) LSTM-এর (৮,৪৪৯) তিন গুণ। লম্বা sequence আর GPU-তে অনুপাত আলাদা হতে পারে। দাবি নয়, মাপুন।

এই ফল কতদূর খাটে. ডেটা কাল্পনিক, আমরাই নিয়ম বানিয়েছি (গুণফল-ধরনের বার ও ঈদের প্রভাব), series মাত্র ছয়টি, seed তিনটি, tuning প্রায় নেই। এটি “boosting সবসময় জেতে” প্রমাণ করে না, শুধু বলে: এই আকারের সমস্যায় deep model-কে আগে baseline আর boosting হারাতে হবে। M প্রতিযোগিতার ইতিহাস মেশিন লার্নিং কোর্সের পর্ব ৫২-তে আছে, যেখানে M5-এ tree-ভিত্তিক পদ্ধতি এগিয়ে ছিল, আর M4-এ জিতেছিল exponential smoothing আর RNN-এর একটি মিশ্রণ।

৮ · Boosting না deep model — তিনটি ক্ষেত্রে সিদ্ধান্ত

কোনো সূত্র নেই, তবে চারটি প্রশ্ন আছে। নিচের তিনটি ক্ষেত্রই কাল্পনিক; সিদ্ধান্তগুলো শুরুর অনুমান, চূড়ান্ত রায় নয়।

প্রতিটি ক্ষেত্রে একই চারটি প্রশ্ন
ক্ষেত্র
ডেটা কত
Feature কে বানাবে
খরচ ও ঝুঁকি
শুরুর সিদ্ধান্ত
  1. চার প্রশ্ন. ডেটার পরিমাণ, feature-এর উৎস, খরচ আর শুরুর পদক্ষেপ। প্রতিটি ক্ষেত্রে সবার আগে একটি baseline, কারণ baseline ছাড়া “ভালো” কথাটির মানে নেই।
  2. ক্ষেত্র ১: ছয় জেলা. উদাহরণ অল্প, সংকেত মূলত বার, ক্যালেন্ডার আর সাম্প্রতিক স্তর। অংশ ৭-এর run ঠিক এই ক্ষেত্র। Boosting দিয়ে শুরু করাই যুক্তিসঙ্গত; deep model-এর কাছে বাড়তি কিছু প্রমাণের দায় থাকে।
  3. ক্ষেত্র ২: হাজারো পণ্য. ডেটা অনেক, আর টেবিলের feature-ও ভালো কাজ করে। M5-এর অভিজ্ঞতা boosting-এর পক্ষে, কিন্তু অনেক series একসঙ্গে শেখার গবেষণায় global deep model-ও ভালো করেছে। তাই দুটিই একই backtest-এ চালান; সিদ্ধান্ত নিজের ডেটায়।
  4. ক্ষেত্র ৩: কাঁচা সংকেত. এক সেকেন্ডের জানালায় ১,০০০টি মান; lag feature অর্থহীন হয়ে যায়। Convolution তরঙ্গের স্থানীয় নকশা নিজে শিখতে পারে, আর লেবেল প্রচুর। এখানে deep model-ই স্বাভাবিক শুরু, তবু হাতে-গড়া ফ্রিকোয়েন্সি feature আর boosting পাশে baseline হিসেবে রাখুন।
  5. সাধারণ নিয়ম. ডেটা অল্প আর feature সহজ হলে সরল দিকে ঝুঁকুন; ডেটা প্রচুর আর সংকেত কাঁচা হলে deep দিকে। মাঝখানের সব ক্ষেত্রে একই rolling-origin backtest, কয়েকটি seed আর সময়ের হিসাব সিদ্ধান্ত দেবে।

নতুন forecasting model-এর নাম শুনলে. N-BEATS (Oreshkin ও সহকর্মী, ২০১৯–২০) শুধু fully connected স্তরের গভীর residual stack. Temporal Fusion Transformer (Lim ও সহকর্মী, ২০১৯–২১) জানা ভবিষ্যৎ আর স্থির feature আলাদা করে নেয়, আর attention যোগ করে। PatchTST (Nie ও সহকর্মী, ২০২৩) series-কে টুকরো করে Transformer-এ দেয় (Transformer আসছে পর্ব ৩৫-এ)। অন্যদিকে Zeng ও সহকর্মীদের ২০২৩ সালের প্রবন্ধ দেখায়, কিছু benchmark-এ একটি খুব সরল linear model অনেক Transformer-কে হারিয়েছে, আর এই বিতর্ক এখনো চলছে। এদের নিজ নিজ প্রবন্ধের স্কোর নিজের ডেটায় খাটবে এমন ধরে নেবেন না; সবাইকে একই backtest-এ আনুন।

৯ · টেক্সটে 1D CNN — “ভালো না” ধরা

টেক্সটও একটি sequence. শব্দগুলো embedding হয়ে (N, E, T), আর E-টি মাত্রা হয় channel. Kim-এর ২০১৪ সালের প্রবন্ধ এই রূপটি জনপ্রিয় করে: k = ৩, ৪, ৫-এর মতো কয়েক দৈর্ঘ্যের filter, তারপর প্রতিটি filter-এ সময় ধরে max, শেষে linear স্তর। k = ২-এর একটি filter একটি দুই-শব্দের নকশা খোঁজে, আর max বলে “নকশাটি কোথাও আছে কি না”। তাই “ভালো না” বাক্যের যেখানেই থাকুক, ধরা পড়ে। এখানে causal হওয়ার দরকার নেই: পুরো বাক্যটি আগেই হাতে আছে।

ডেটা আবার কাল্পনিক ও সাজানো: বিষয় + বিশেষণ, চার ধরনে (“পণ্য ভালো”, “পণ্য ভালো না”, “পণ্য ভালো একদম না”, “না পণ্য ভালো”), মাঝে এলোমেলো অপ্রাসঙ্গিক শব্দ। পরীক্ষা কঠিন করতে ২০টি বিষয়-বিশেষণ জোড়া training থেকে পুরো বাদ, সেগুলো শুধু test-এ। Baseline: পর্ব ২৭-এর bag of embeddings, আর AI-এর ভিত্তি কোর্সের পর্ব ৬৭-এর ধাঁচে TF-IDF + logistic regression, শব্দ আর bigram নিয়ে।

class TextCNN(nn.Module):
    def __init__(self, V, E=32, F_=32, ks=(2, 3, 4), p=0.3):
        super().__init__()
        self.emb = nn.Embedding(V, E, padding_idx=0)
        self.convs = nn.ModuleList(nn.Conv1d(E, F_, k) for k in ks)
        self.ks, self.drop = ks, nn.Dropout(p)
        self.fc = nn.Linear(F_ * len(ks), 2)
    def forward(self, x):                                # x (N, T)
        e, pooled = self.emb(x).transpose(1, 2), []      # (N, E, T)
        for k, c in zip(self.ks, self.convs):
            a = F.relu(c(F.pad(e, (0, k - 1))))          # ছোট review-ও যেন চলে
            a = a.masked_fill(~(x != 0).unsqueeze(1), 0.0)   # pad-এ শুরু হওয়া জানালা বাদ
            pooled.append(a.max(dim=2).values)           # (N, F): সময় ধরে max
        return self.fc(self.drop(torch.cat(pooled, 1)))

সত্যিকারের run (CPU-তে, তিন seed; TF-IDF-এ একটি fit; “X” মানে যেকোনো বিশেষণ; সময় এক fit-এর):

~/bazarghar-dl$ uv run python text_cnn.py
unique reviews 2408 | train 1936 | test 472 | positive share 0.49
examples: ['না ব্যাটারি মজবুত আগের', 'আগের গতকাল ব্যাগ সুন্দর', 'পণ্য দুর্বল']
x torch.Size([1936, 6]) | vocab 37
Python 3.14.4 | torch 2.15.0+cpu
bag of embeddings    acc 0.471 ±0.012 | plain 0.79 | X না 0.18 | X একদম না 0.23 | না X 0.80 |  0.69 s | params 1250
1D CNN (k=2,3,4)     acc 1.000 ±0.000 | plain 1.00 | X না 1.00 | X একদম না 1.00 | না X 1.00 |  3.89 s | params 10690
TF-IDF 1-2gram + LR  acc 0.998 ±0.000 | plain 1.00 | X না 1.00 | X একদম না 1.00 | না X 0.99 |  0.04 s 
পণ্য ভালো না       P(positive): CNN 0.00 | bag 0.67
না পণ্য ভালো       P(positive): CNN 1.00 | bag 0.67
ব্যাগ খারাপ না     P(positive): CNN 1.00 | bag 0.47
না ব্যাগ খারাপ     P(positive): CNN 0.00 | bag 0.47
জুতা মজবুত একদম না P(positive): CNN 0.00 | bag 0.48

পড়ুন: Bag of embeddings-এর accuracy ০.৪৭১, মুদ্রা টসের চেয়েও কম। সে সব শব্দ গড় করে, তাই “পণ্য ভালো না” আর “না পণ্য ভালো”-র জন্য হুবহু একই সম্ভাবনা দেয় (০.৬৭ আর ০.৬৭)। “X না” ধরনে তার ঠিক মাত্র ১৮%: ভালো শব্দ দেখে ভালো বলে দিচ্ছে। 1D CNN সব ধরনে ১.০০০, না দেখা জোড়াতেও, কারণ “ভালো না”-র নকশা বিষয় থেকে স্বাধীন। কিন্তু উচ্ছ্বসিত হবেন না. TF-IDF bigram-ও ০.৯৯৮ পেয়েছে, ০.০৪ সেকেন্ডে, CNN-এর ৩.৮৯ সেকেন্ডের প্রায় শতভাগের এক ভাগ। ডেটা সাজানো, শব্দভান্ডার মাত্র ৩৭ ঘরের, তাই ১.০০০ দেখে আসল review-এ এমন ফল আশা করা ভুল। আসল বাংলায় বানানের ভিন্নতা, ব্যাকরণের বৈচিত্র্য আর না দেখা শব্দে CNN-এর সুবিধা pretrained embedding (পর্ব ৪১) থেকে আসতে পারে, কিন্তু সেটি নিজের ডেটায় মেপে দেখতে হবে। আধুনিক text classification-এর পুরো পথ পর্ব ৪২-এ।

আরও গভীরে: সময়ের অক্ষে convolution পুরোনো: Waibel ও সহকর্মীদের ১৯৮৯ সালের time-delay neural network ধ্বনি চেনার কাজে এটি ব্যবহার করে। WaveNet (van den Oord ও সহকর্মী, ২০১৬) causal আর dilated convolution-এ সরাসরি শব্দের তরঙ্গ তৈরি করে দেখায়। Bai ও সহকর্মীদের ২০১৮ সালের প্রবন্ধ বেশ কিছু sequence benchmark-এ TCN-কে প্রচলিত RNN-এর সমান বা ভালো পায়, তবে সেগুলো ছিল মূলত synthetic ও ক্লাসিক কাজ। Streaming-এ পুরো জানালা বারবার না চালিয়ে প্রতি স্তরের শেষ কিছু মান cache করা যায়, যাতে নতুন মানে শুধু এক ধাপ হিসাব লাগে।

পরীক্ষা ও ইন্টারভিউতে:

  • k = 3, d = 4-এ causal-এর জন্য বাঁয়ে কত padding? (k − ১)·d = ৮.
  • k = 3, d = 1, 2, 4, 8, প্রতি dilation-এ একটি স্তর: r? ১ + ২ × ১৫ = ৩১; দুটি স্তর হলে ৬১.
  • Conv1d(10, 32, 3, dilation=8)-এর parameter? ৯৯২; dilation-এ weight বাড়ে না।
  • Causal কি না কীভাবে যাচাই করবেন? Impulse পাঠিয়ে বা ভবিষ্যৎ বদলে দেখা, অতীতের output নড়ে কি না।
  • TCN আর LSTM-এর স্মৃতির পার্থক্য? TCN-এর স্মৃতি ঠিক r-এ সীমিত; LSTM-এর state-এ কোনো কঠিন সীমা নেই, তবে শেখা সহজ নয়।

১০ · AI-এর যুগে — sequence model-এর কোড যাচাই

AI assistant TCN বা forecasting কোড মিনিটে লিখে দেয়, আর চলেও। সমস্যা হলো, এই কোডের সবচেয়ে বড় ভুলগুলো ক্র্যাশ করে না, ফলকে আরও সুন্দর দেখায়।

AI প্রায়ই যা করেকেন ভুলকীভাবে ধরবেন
padding="same" বা chomp ছাড়া padding=(k-1)*dভবিষ্যৎ ফাঁস বা দৈর্ঘ্য বদলImpulse test; output-এর shape ছাপা
পুরো series-এ StandardScaler fit করে তারপর ভাগTest-এর গড় training-এ ঢোকেপ্রতিটি origin-এ শুধু অতীতে fit
Bidirectional LSTM দিয়ে forecastingউল্টো দিকের পথ ভবিষ্যৎ পড়েভবিষ্যৎ বদলে অতীতের output দেখা
(N, T, C) সরাসরি Conv1d-এসময়কে channel ভাবে; C = T হলে নীরবে চলেtranspose(1, 2); একটি batch-এ shape
একটি holdout, একটি seed-এ “TCN জিতল”আমাদের run-এ fold ভেদে ফল উল্টে গেছেকয়েক fold, কয়েক seed, baseline পাশে
Bai ২০১৮ দেখিয়ে “TCN সবসময় ভালো ও দ্রুত”নির্দিষ্ট কাজের ফল, সাধারণ সত্য নয়প্রবন্ধটি পড়া; নিজের ডেটায় সময় মাপা

কোড চাওয়ার সময় পরীক্ষাগুলোও চাইতে বলুন:

আমার কাছে কয়েকটি জেলার দৈনিক অর্ডার আছে। PyTorch-এ (version লিখে দিচ্ছি) ২৮ দিন সামনের অনুমানের জন্য একটি TCN লিখে দাও। ১. শুধু বাঁয়ে padding দাও, আর (k-1)·d হিসাবটি প্রতিটি স্তরে দেখাও। ২. প্রতিটি স্তরের পরে tensor-এর shape আর receptive field-এর হিসাব একটি টেবিলে দাও। ৩. একটি impulse test আর একটি "ভবিষ্যৎ বদলে দেখা" test লিখে দাও। ৪. Rolling-origin backtest দাও, scaling প্রতিটি origin-এ শুধু অতীতের ডেটায়। ৫. MASE দাও, seasonal naive আর lag feature-সহ gradient boosting পাশে রেখে, কয়েকটি seed-এ। ৬. যে API বা দাবি নিয়ে নিশ্চিত নও, তা অনুমান বলে চিহ্নিত করো; কোনো সংখ্যা বা প্রবন্ধ বানিয়ে লিখো না।

AI-এর লেখা forecasting কোড হাতে পেলে — যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১সময়ের ওপর convolution ১৯৮০-এর দশকেই ছিল। তাহলে দীর্ঘদিন “sequence মানেই RNN” কেন ধরা হতো, আর TCN সেই ধারণায় কী বদলাল?

১৯৮৯ সালে Alex Waibel ও সহকর্মীরা জাপানি ধ্বনি চেনার কাজে time-delay neural network প্রকাশ করেন, যা মূলত সময়ের অক্ষে weight ভাগ করা একটি convolution. কিন্তু ১৯৯০-এর দশকের পরে RNN, বিশেষ করে ১৯৯৭ সালের LSTM, sequence-এর স্বাভাবিক হাতিয়ার হয়ে ওঠে। এর একটি যুক্তি ছিল ধারণাগত: state-এর স্মৃতি তাত্ত্বিকভাবে যত লম্বা খুশি হতে পারে, আর ভিন্ন দৈর্ঘ্যের input স্বাভাবিকভাবে পড়া যায়। সাধারণ convolution-এর দৃষ্টি তখন খুব ছোট; লম্বা দৃষ্টি মানে বহু স্তর, আর তখনকার দিনে গভীর network শেখানোই কঠিন।

দুটি উপাদান ছবিটি বদলায়। Residual connection (২০১৫) গভীরতা সহজ করে, আর dilation সূচকীয় দৃষ্টি দেয়। WaveNet (২০১৬) দেখায়, causal dilated convolution হাজার হাজার ধাপের অডিও সামলাতে পারে। ২০১৮ সালে Bai, Kolter আর Koltun একই কাঠামোকে সাধারণ রূপ দিয়ে বেশ কিছু sequence benchmark-এ প্রচলিত LSTM আর GRU-র সঙ্গে তুলনা করেন, এবং তাঁদের পরীক্ষায় TCN সাধারণত সমান বা এগিয়ে থাকে। তাঁদের বার্তা ছিল: “sequence মানেই RNN” অভ্যাসটি পুনর্বিবেচনা করা উচিত।

তবে এই পর্বের run দেখায়, একটি নির্দিষ্ট প্রবন্ধের ফল সবখানে খাটে না: আমাদের ছোট কাজে TCN আর LSTM সমান, আর CPU-তে TCN ধীর। আর ইতিহাস এখানে থামেনি: কিছুদিনের মধ্যে attention আর Transformer ভাষার কাজে দুটিকেই পেছনে ফেলে দেয়, যা মডিউল ৬-এ দেখবেন। এখনো TCN পাওয়া যায় কম খরচের অডিও আর সেন্সর model-এ, অন্য সবকিছুর পাশে একটি প্রার্থী হিসেবে।

মূল উপলব্ধি: “কোন model sequence-এর জন্য” কোনো স্থায়ী সত্য নয়; উপাদান, হার্ডওয়্যার আর benchmark বদলালে উত্তর বদলায়। তাই প্রতিবার মাপতে হয়।

প্র ০২ছোট টেবিল-ধরনের forecasting-এ gradient boosting কেন প্রায়ই deep model-এর সমান বা ভালো হয়? Deep model কোথায় এগিয়ে যেতে পারে?

প্রথম কারণ ডেটার পরিমাণ। বাজারঘরে ৭৩০ দিন আর ছয়টি জেলা; পরপর দিনের জানালাগুলো প্রায় একই তথ্য বহন করে, তাই “কয়েক হাজার জানালা” আসলে অনেক কম স্বাধীন উদাহরণ। ঈদ এসেছে তিনবার। দ্বিতীয় কারণ inductive bias: গাছ হঠাৎ বদলের তীক্ষ্ণ সীমা সহজে আঁকে, যেমন “ঈদের ২ দিনের মধ্যে”, আর সেই সীমার বাইরে অন্য কিছুকে প্রভাবিত করে না। Grinsztajn ও সহকর্মীদের ২০২২ সালের একটি তুলনায় মাঝারি আকারের টেবিল ডেটায় tree-ভিত্তিক model-গুলো এগিয়ে ছিল; তাঁদের একটি ব্যাখ্যা, neural network মসৃণ function-এর দিকে ঝোঁকে। তৃতীয় কারণ প্রকৌশল: lag আর rolling feature একজন বিশ্লেষক হাতে যে জ্ঞান ঢোকান, deep model-কে তা ডেটা থেকে নিজে খুঁজতে হয়।

প্রতিযোগিতার ইতিহাসও মিশ্র। মেশিন লার্নিং কোর্সের পর্ব ৫২-এ আছে: M4-এ জয়ী ছিল exponential smoothing আর RNN-এর একটি মিশ্রণ, M5-এ tree-ভিত্তিক পদ্ধতি এগিয়ে ছিল। অর্থাৎ প্রশ্নটি “ML না deep” নয়, “কোন ডেটায় কোন ধারণা”।

Deep model এগিয়ে যেতে পারে যেখানে সংকেত কাঁচা (অডিও, সেন্সর), feature হাতে বানানো কঠিন, series অসংখ্য আর একে অন্যের থেকে শেখার আছে, বা ছবি ও টেক্সটের মতো ভিন্ন ধরনের input মেশাতে হয়। এই পর্বের fold ৪-এ দেখেছেন, শান্ত সময়ে ধারাবাহিক নকশায় TCN সামান্য এগিয়ে। আবার গাছ training-এ দেখা মানের সীমার বাইরে অনুমান দিতে পারে না, তাই target-কে সাম্প্রতিক স্তর থেকে পার্থক্য হিসেবে না লিখলে trend-এ ঠকে; এই পর্বে সেই কারণেই log-স্তর বিয়োগ করা।

মূল উপলব্ধি: Boosting-এর জয় কোনো নিয়ম নয়, একটি পরিচিত প্রবণতা, ছোট ডেটা, হাতে-গড়া feature আর হঠাৎ বদল যার পক্ষে। প্রমাণের দায় দামি model-এর ওপর।

প্র ০৩MASE ০.০৬ পেলে একজন অভিজ্ঞ বিশ্লেষক কী করেন? Causal padding ছাড়া আর কোন পথে ভবিষ্যৎ model-এ ঢোকে?

তিনি উদযাপন করেন না, সন্দেহ করেন। অর্ডারের মতো শব্দ-ভরা সংখ্যায় এক ধাপের অনুমানেরও একটি স্বাভাবিক সীমা আছে; এই পর্বের ডেটায় Poisson শব্দ আর AR(1) ধাক্কা ইচ্ছা করেই আছে। ০.০৬ মানে model-এর ভুল সেই শব্দের চেয়েও ছোট, যা তথ্য চুরি ছাড়া সম্ভব নয়। প্রথম কাজ impulse test আর “ভবিষ্যৎ মুছে দেখা”: test-এর ভবিষ্যৎ মান NaN বা অন্য সংখ্যায় বদলে অনুমান আবার চালান। অনুমান বদলালে ফাঁস নিশ্চিত।

Padding একমাত্র পথ নয়। পুরো series-এ scaling করলে test-এর গড় আর spread training-এ ঢোকে। rolling(7).mean() shift ছাড়া নিলে আজকের মান feature-এ থাকে; মেশিন লার্নিং কোর্সের পর্ব ৫২-এ এটি হাতে-কলমে দেখেছেন। Bidirectional RNN উল্টো দিক থেকে ভবিষ্যৎ পড়ে (পর্ব ২৮)। “মাসের মোট বিক্রি”-র মতো feature মাসের শেষের তথ্য বহন করে। এলোমেলো train-test ভাগে পরপর দিনের প্রায়-একই জানালা দুই দিকে পড়ে, যা সরাসরি প্রায় মুখস্থ করার সুযোগ দেয়। আর সবচেয়ে সূক্ষ্ম: জানালার target আর পরের জানালার input-এর মধ্যে ওভারল্যাপ, যা fold-এর সীমায় একটি ফাঁক না রাখলে অনেক সময় ধরা পড়ে না। এই পর্বের run-এ তাই সব target origin-এর আগেই শেষ।

ঝুঁকিটি প্রযুক্তিগত ছাড়াও সাংগঠনিক। Leak-যুক্ত সুন্দর সংখ্যা উপস্থাপনায় উৎসাহ জাগায়, মজুদের সিদ্ধান্ত তার ওপর দাঁড়ায়, আর সমস্যা ধরা পড়ে production-এ, যখন আসল ভবিষ্যৎ নেই। তাই AI-এর লেখা কোডে এই পরীক্ষাগুলো CI-এর অংশ করে রাখা সস্তা বিমা।

মূল উপলব্ধি: Forecasting-এ অবিশ্বাস্য ভালো ফল একটি bug report. প্রতিটি feature আর স্তরে জিজ্ঞাসা করুন: origin-এ দাঁড়িয়ে এই তথ্য কি সত্যিই আমার হাতে থাকত?

প্র ০৪Receptive field ৩৬৫ দিন করলেই কি model বছরের ধরন শিখবে? জানালা বড় করা আর ক্যালেন্ডার feature দেওয়ার মধ্যে কোনটি কখন?

জানালা ৩৬৫ দিন হলে model গত বছরের ঈদ দেখতে পায়, কিন্তু শেখার মতো উদাহরণ পায় না। দুই বছরের ডেটায় বছরের ধরনের উদাহরণ প্রায় একবার; আর একটি ৩৬৫ দিনের জানালা বানাতে শুরুর এক বছরের কোনো training target থাকে না, অর্থাৎ ডেটার অর্ধেকই হারায়। তাছাড়া ঈদের তারিখ প্রতি বছর প্রায় ১০–১১ দিন এগোয়, তাই “ঠিক ৩৬৫ দিন আগে” সেখানে ঈদ থাকে না। একটি ছোট eid_ahead কলাম এই জ্ঞান এক সংখ্যায় দেয়, আর ভবিষ্যতের দিনের জন্যও জানা থাকে। বাজারঘরে এটিই সবচেয়ে কাজের তথ্যগুলোর একটি।

একই যুক্তি ক্লাসিক forecasting-এ আছে: পর্ব ৫১-এর ধরন ভাগ করা (decomposition) এবং ক্যালেন্ডার regressor. Deep model-এ এই ভাগাভাগি নতুন নয়; N-BEATS-এর একটি ব্যাখ্যাযোগ্য রূপে trend আর seasonality-র জন্য আলাদা অংশ আছে, আর Temporal Fusion Transformer জানা ভবিষ্যৎ input-কে আলাদা করে নেয়।

বড় জানালার সত্যিকারের লাভ অন্য জায়গায়: যেখানে নকশাটি ক্যালেন্ডারে বাঁধা নয়, যেমন সেন্সরের ধীরে বাড়তে থাকা কাঁপুনি, বা অডিওর লম্বা ছন্দ। সেখানে dilation সস্তায় দৃষ্টি দেয়। খরচও ভাবতে হবে: প্রতিটি অনুমানে জানালা দীর্ঘ হলে সময় ও মেমরি বাড়ে, যদিও স্তর-ভিত্তিক cache তা কমায়। আর LSTM-এর সঙ্গে তুলনায় TCN-এর সীমা কঠিন: r-এর এক দিন আগের মান গাণিতিকভাবে শূন্য প্রভাব রাখে। সমস্যাটিতে তা গ্রহণযোগ্য কি না, সেটি একটি নকশার সিদ্ধান্ত।

মূল উপলব্ধি: দৃষ্টি বাড়ানো আর জ্ঞান দেওয়া এক কাজ নয়। ক্যালেন্ডারে বাঁধা নকশায় feature সস্তা ও শক্তিশালী; কাঁচা সংকেতের লম্বা নকশায় dilation.

প্র ০৫Forecasting-এ Transformer নিয়ে বিতর্ক কী? টেক্সটে 1D CNN আর আজকের দিনে কতটা প্রাসঙ্গিক?

২০২০-এর দশকের শুরুতে Informer আর Autoformer-এর মতো Transformer-ভিত্তিক forecasting model অনেক প্রবন্ধে দীর্ঘ-মেয়াদি benchmark-এ এগিয়ে থাকার দাবি করে। ২০২৩ সালে Zeng ও সহকর্মীরা “Are Transformers Effective for Time Series Forecasting?” প্রবন্ধে দেখান, ওই benchmark-গুলোর অনেকগুলোতে একটি একস্তর linear model এদের হারায়; তাঁদের যুক্তি, attention ক্রম-নিরপেক্ষ বলে সময়ের ক্রম সহজে হারায়। একই বছর PatchTST দেখায়, series-কে টুকরো করে প্রতিটি channel আলাদা নিলে Transformer আবার ভালো করতে পারে। বিতর্কটি মীমাংসিত নয়, আর একটি শিক্ষা এতে স্পষ্ট: benchmark-এর সংখ্যা জনপ্রিয় ডেটাসেটের অদ্ভুত গঠনেও নির্ভর করে, আর সরল baseline ছাড়া তুলনা অর্থহীন।

টেক্সটে Kim-এর ২০১৪ সালের প্রবন্ধটি প্রভাবশালী ছিল এই কারণে যে pretrained word vector-এর ওপর একটি মাত্র convolution স্তর তখনকার কয়েকটি benchmark-এ ভালো ফল দেয়। আজ বড় ভাষার কাজে Transformer আর LLM মূল ধারায় (পর্ব ৩৫ আর পর্ব ৪৯-এ দেখবেন)। 1D CNN-এর একটি সীমা স্পষ্ট: নির্দিষ্ট k-এর জানালার বাইরের সম্পর্ক সে ধরতে পারে না, আর max-over-time শব্দের অবস্থান ফেলে দেয়। তবু যেখানে ডেটা অল্প, উত্তর কয়েকটি স্থানীয় নকশায় ধরা, আর ডিভাইস ছোট, সেখানে একটি ছোট CNN বা এমনকি TF-IDF এখনো যুক্তিসঙ্গত প্রার্থী, যা এই পর্বের run-ও দেখিয়েছে।

বাংলা টেক্সটে সিদ্ধান্তটি আরও বাস্তব: কম ডেটা, বানানের বৈচিত্র্য আর সীমিত কম্পিউটিং-এ সব সময় বড় model সঠিক নাও হতে পারে। তাই ছোট থেকে শুরু করে প্রতি ধাপে মেপে এগোনোই নিরাপদ।

মূল উপলব্ধি: নতুন model-এর জনপ্রিয়তা baseline-এর প্রয়োজন কমায় না, বাড়ায়। নিজের ডেটা, নিজের খরচ আর একই backtest শেষ কথা বলে।

অনুশীলন

প্রথমে খাতায় করুন, তারপর উত্তর মেলান।

  1. পূর্ণ numerical: (ক) k = 5, d = 3-এ causal-এর জন্য বাঁয়ে কত padding? Padding ছাড়া T = 100-এ output কত লম্বা? (খ) Conv1d(16, 64, 5, dilation=3)-এর parameter. (গ) k = 5, d = 1, 2, 4, প্রতি dilation-এ দুটি স্তর: receptive field. (ঘ) চার দিনের আসল মান 120, 100, 130, 90; অনুমান 110, 105, 120, 100; training-এ seasonal naive-এর MAE ১২.৫: MASE কত?

    (ক) (৫ − ১) × ৩ = ১২; padding ছাড়া ১০০ − ১২ = ৮৮. (খ) ৬৪ × ১৬ × ৫ + ৬৪ = ৫,১৮৪; dilation নেই সূত্রে। (গ) ১ + ২ × ৪ × (১ + ২ + ৪) = ৫৭. (ঘ) ভুল ১০, ৫, ১০, ১০; MAE = ৩৫/৪ = ৮.৭৫; MASE = ৮.৭৫/১২.৫ = ০.৭০.

  2. নকশা ও সমালোচনা: একজন সহকর্মী লিখেছেন: পুরো series-এ StandardScaler, Conv1d(..., padding="same")-এর TCN, শেষ ২৮ দিনের একটি holdout, একটি seed. ফল: MASE ০.০৮, “seasonal naive-এর চেয়ে ২০ গুণ ভালো, এখনই production-এ দিই।” আপনি কী বলবেন, আর কীভাবে ঠিক করবেন?

    (১) padding="same" ভবিষ্যৎ দেখে; অংশ ২-এর run-এ এতে MASE ০.০৬-এ নেমেছিল। ফলটিই ফাঁসের প্রমাণ। বদল: F.pad(x, ((k-1)*d, 0)) আর impulse test. (২) পুরো series-এ scaling: origin-এর পরের গড় training-এ ঢুকছে; প্রতিটি origin-এ শুধু অতীতে fit, বা জানালাভিত্তিক স্কেল। (৩) একটি holdout: অংশ ৬-এ দেখেছেন, fold ভেদে একই baseline ০.৯ থেকে ১.৮ হয়; কমপক্ষে চারটি rolling-origin fold, ঈদ-সহ। (৪) একটি seed: আমাদের LSTM-এ এক seed-এর গড় ০.৯৭৩, আরেকটির ১.০৮০; কয়েকটি seed. (৫) Boosting পাশে নেই; যোগ করুন। এরপর সংখ্যা যা-ই আসুক, production-এর আগে কিছুদিন shadow mode-এ আসল অনুমান রেখে মিলিয়ে দেখুন।

  3. আউটপুট অনুমান করুন: নিচের কোড কী ছাপবে?
    import torch, torch.nn.functional as F
    x = torch.arange(1., 7.).reshape(1, 1, 6)
    w = torch.tensor([[[1., 0., 1.]]])
    print(F.conv1d(F.pad(x, (2, 0)), w))
    print(F.conv1d(x, w, padding=1))
    print(F.conv1d(x, w, dilation=2).shape)

    সত্যিকারের run-এর আউটপুট:

    tensor([[[ 1.,  2.,  4.,  6.,  8., 10.]]])
    tensor([[[ 2.,  4.,  6.,  8., 10.,  5.]]])
    torch.Size([1, 1, 2])

    Kernel [1, 0, 1] জানালার প্রথম আর শেষ ঘর যোগ করে। প্রথম লাইন causal: [0, 0, 1, 2, …]-এ y[0] = ০ + ১ = ১, y[2] = ১ + ৩ = ৪; প্রতিটি মান শুধু নিজের আর আগের দিন দেখে। দ্বিতীয় লাইনে দুই পাশে ১: y[0] = ০ + ২ = ২, যা t = ১-এর মান, অর্থাৎ ভবিষ্যৎ; শেষে ৫ + ০ = ৫. তৃতীয়: d = ২-এ জানালা ৫ ঘর চওড়া, padding নেই, ৬ − ৪ = ২.

  4. কোড লিখুন: rf(k, dils, per_block=2) ফাংশন লিখুন, আর d = 1, 2, 4, … দ্বিগুণ করে কত block-এ ৩৬৫ দিন ঢাকা যায় বের করুন (k = 3, প্রতি block-এ দুই স্তর)। তারপর Conv1d(32, 32, 3)-এর parameter d = 1 আর d = 8-এ গুনে মিলিয়ে দেখুন।
    import torch.nn as nn
    
    def rf(k, dils, per_block=2):
        return 1 + per_block * (k - 1) * sum(dils)
    
    dils, d = [], 1
    while rf(3, dils) < 365:
        dils.append(d); d *= 2
    print(dils, rf(3, dils))
    for dd in (1, 8):
        print(dd, sum(p.numel() for p in nn.Conv1d(32, 32, 3, dilation=dd).parameters()))

    সত্যিকারের run-এর আউটপুট:

    [1, 2, 4, 8, 16, 32, 64] 509
    1 3104
    8 3104

    ৭টি block-এ r = ৫০৯; ৬টিতে ২৫৩, যা কম। Parameter দুই ক্ষেত্রেই ৩,১০৪: dilation ফাঁক বাড়ায়, weight নয়। সংখ্যাটি ঠিক হলেও ভাবনার প্রশ্ন ৪ মনে রাখুন: ঈদের মতো ধরনে এত বড় জানালা যথেষ্ট নয়।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমএকটি fold-এ deep forecasting model-এর সৎ মূল্যায়নের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. Forecast origin ঠিক করা, যার পরের কিছুই শেখায় ঢুকবে না
  2. Origin-এর আগে শেষ হওয়া target-সহ জানালা বানানো
  3. Causal model শেখানো, একটি নির্দিষ্ট seed-এ
  4. Origin-এর ঠিক আগের জানালা থেকে পরের H দিনের অনুমান
  5. Origin-এর আগের ডেটায় scale বের করে MASE হিসাব
  6. Origin সামনে সরিয়ে আবার, শেষে সব fold আর seed-এর গড় ও ওঠানামা

k = 3 আর dilation 4-এর একটি 1D convolution-কে causal রাখতে আর output-এর দৈর্ঘ্য input-এর সমান রাখতে বাঁ পাশে কত ঘর padding লাগে?

  • ৪ ঘর
  • ৮ ঘর
  • ৯ ঘর
  • ২ ঘর

k = 3, dilation 1, 2, 4, 8-এর চারটি causal স্তর (প্রতি dilation-এ একটি) পরপর থাকলে শেষ output-এর receptive field কত?

  • ৯
  • ১৬
  • ৩১
  • ৬১

nn.Conv1d(10, 32, kernel_size=3, dilation=8) স্তরে (bias-সহ) কতটি parameter?

  • ৯৯২
  • ৯৬০
  • ৭,৯৩৬
  • T না জানলে বলা যায় না

t = 10-এ একটি impulse দিয়ে একটি forecasting convolution স্তর পরীক্ষা করা হলো; output-এ অ-শূন্য মান পাওয়া গেল t = 6, 10, 14-এ। এ থেকে কী বোঝা যায়?

  • স্তরটি causal, কোনো সমস্যা নেই
  • Gradient vanish করছে
  • Dilation ভুল, ৪ হওয়ার কথা নয়
  • ভবিষ্যৎ ফাঁস হচ্ছে, সম্ভবত padding দুই পাশে

একটি model-এর test-এর MAE ১৮, আর training ডেটায় এক-ধাপ seasonal naive-এর MAE ১৫. MASE কত, আর তার অর্থ কী?

  • ০.৮৩; model ভালো
  • ১.২; একই test-এ baseline-এর সঙ্গে মিলিয়ে দেখতে হবে
  • ১২০%; model পুরো ব্যর্থ
  • ৩; ভুল ৩ অর্ডার বেশি

Kim (২০১৪)-ধাঁচের একটি text CNN-এ convolution-এর output (N, F, T)-তে সময়ের অক্ষে max নেওয়ার প্রধান কাজ কী?

  • বাক্যের অপ্রয়োজনীয় শব্দ মুছে দেওয়া
  • পুরো বাক্যের শব্দের ক্রম মনে রাখা
  • যেকোনো দৈর্ঘ্যকে (N, F)-এ এনে “নকশাটি কোথাও আছে কি না” বলা
  • Filter-এর সংখ্যা কমানো

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. 1D convolution: shape (N, C, T), weight (O, C, k), parameter O·C·k + O. nn.LSTM-এর অক্ষের ক্রম আলাদা।
  2. Causal: (k − 1)·d padding শুধু বাঁয়ে। Impulse test দিয়ে প্রতিবার যাচাই; অবিশ্বাস্য ভালো ফল মানে আগে leakage খোঁজা।
  3. Dilation: r = 1 + (k − 1)·∑d; d = 1 থেকে দ্বিগুণ করুন, সব এক রাখলে gridding. Weight বাড়ে না।
  4. সৎ তুলনা: একই backtest, কয়েক seed, baseline পাশে। এই run-এ boosting ০.৯২৯, LSTM ১.০১২, TCN ১.০১৭, seasonal naive ১.৫০৭; ঈদে boosting, শান্ত সময়ে deep সামান্য এগিয়ে।
  5. টেক্সট: 1D CNN স্থানীয় নকশা ধরে, যা bag of embeddings পারে না; কিন্তু TF-IDF bigram-কে আগে হারাতে হবে।

পরের পর্বে: পর্ব ৩২ (Attention — query, key, value আর alignment): পর্ব ৩০-এর এক vector-এর ঘাটতি আর এই পর্বের নির্দিষ্ট জানালার সীমা, দুটিরই একটি উত্তর: কোন অংশ দেখতে হবে, তা model নিজে ঠিক করা।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান