প্রজেক্ট: Atari Breakout-এ DQN
এই পাঠে যা শিখবেন
- Atari environment preprocessing — grayscale, resize, frame stacking
- CNN-based DQN architecture (Mnih ২০১৫ "Nature DQN")
- Production-grade replay buffer ও training loop
- Hyperparameters ও common pitfalls — Bangladesh-এর কম GPU-তে কীভাবে train
১ · কেন Atari hard?
CartPole 4-D state, ২ actions, ১ minute training. Atari Breakout — একটি 84×84 grayscale frame ৭০৫৬-D, ৪ actions (left/right/fire/no-op), ১০-৫০ million frames train. Difference quantitative + qualitative.
মূল challenges:
- High-D observation: raw pixels — features manually extract করা impossible. CNN দরকার।
- Partial observability: single frame থেকে ball-এর direction বোঝা না — past frames দরকার।
- Sparse reward: brick break-এ +1, এর মাঝে ০। Long horizon credit assignment.
- Long horizon: episode ১০K+ frames possible. DQN target stable রাখা কঠিন।
- Compute: single training run ১-৫ days A100 GPU. Bangladesh-এ rare resource.
Mnih et al. — same DQN code, ৪৯ Atari games-এ apply. ২৯টি গেমে human-level+, কিছুতে superhuman. শিরোনাম: "Human-level control through deep reinforcement learning" — RL-এর modern era-র শুরু।
২ · Preprocessing pipeline
Raw Atari frame: 210×160×3 (RGB), 60 FPS. DQN-এ যা যা করা হয়:
- Grayscale: 3 channels → 1. Color irrelevant Breakout-এ।
- Resize: 210×160 → 84×84. Compute reduce, খেলায় detail enough.
- Frame skip: agent প্রতি 4 frames-এ একবার action. Same action 4 frames repeat. Speed 4× + temporal coherence.
- Max-pooling 2 consecutive frames: Atari sprite flicker — alternate frames-এ object alternate appear. Pixel-wise max ensure visibility.
- Frame stack: last 4 (preprocessed) frames → 4×84×84 tensor. Velocity/direction inferable.
- Reward clip: $r \in \{-1, 0, +1\}$। Different games-এ scale uniform. (অর্থ হারায় কিন্তু stability জেতে।)
- Episode life: Atari-র অনেক game-এ ৩-৫ "lives". Treat each life-loss as terminal training-এ — বেশি reward signal.
৩ · Setup
# Atari ROMs (legal redistribution via ale-py)
pip install gymnasium[atari,accept-rom-license]
pip install torch numpy opencv-python
python -c "
import gymnasium as gym
env = gym.make('ALE/Breakout-v5')
print('actions:', env.action_space.n)
print('obs:', env.observation_space.shape)
"
# actions: 4
# obs: (210, 160, 3)
৪ · Atari wrappers — preprocessing apply
import gymnasium as gym
from gymnasium.wrappers import (
AtariPreprocessing,
FrameStack,
)
def make_atari(env_id="ALE/Breakout-v5", seed=42):
env = gym.make(env_id, frameskip=1, repeat_action_probability=0.0,
full_action_space=False)
# AtariPreprocessing handles grayscale, 84x84 resize,
# frame skip (4), no-op start, max-pool, terminal-on-life-loss.
env = AtariPreprocessing(
env,
noop_max=30,
frame_skip=4,
screen_size=84,
terminal_on_life_loss=True,
grayscale_obs=True,
scale_obs=False,
)
env = FrameStack(env, num_stack=4)
env.action_space.seed(seed)
return env
env = make_atari()
obs, _ = env.reset(seed=42)
print("Stacked obs:", obs.shape) # (4, 84, 84)
print("Action space:", env.action_space) # Discrete(4)
৫ · CNN Q-network
Mnih ২০১৫ Nature architecture:
import torch, torch.nn as nn
import torch.nn.functional as F
class NatureDQN(nn.Module):
"""DeepMind ২০১৫ Nature paper architecture."""
def __init__(self, n_actions=4):
super().__init__()
self.conv = nn.Sequential(
nn.Conv2d(4, 32, kernel_size=8, stride=4), # 4×84×84 → 32×20×20
nn.ReLU(),
nn.Conv2d(32, 64, kernel_size=4, stride=2), # 32×20×20 → 64×9×9
nn.ReLU(),
nn.Conv2d(64, 64, kernel_size=3, stride=1), # 64×9×9 → 64×7×7
nn.ReLU(),
)
self.fc = nn.Sequential(
nn.Linear(64 * 7 * 7, 512),
nn.ReLU(),
nn.Linear(512, n_actions),
)
def forward(self, x):
# x: (B, 4, 84, 84) uint8 or float
if x.dtype == torch.uint8:
x = x.float() / 255.0
h = self.conv(x)
h = h.flatten(1) # (B, 64*7*7)
return self.fc(h)
# Sanity check
net = NatureDQN(n_actions=4)
sample = torch.zeros(1, 4, 84, 84, dtype=torch.uint8)
print("Output Q-values:", net(sample).shape) # (1, 4)
print("Params:", sum(p.numel() for p in net.parameters())) # ~1.7M
৬ · Memory-efficient replay buffer
Atari replay buffer 1M frames. Naive পাইথন list — 1M × 4 × 84 × 84 × 4 bytes = ~১১৩ GB! সমাধান: uint8 storage + frame deduplication (সব frame stack ছাড়া single frames store).
import numpy as np
import torch
class FrameReplay:
"""Stores SINGLE frames (uint8); reconstructs 4-frame stacks on sample."""
def __init__(self, capacity=1_000_000, frame_dim=(84, 84), n_stack=4):
self.capacity = capacity
self.n_stack = n_stack
self.frames = np.zeros((capacity, *frame_dim), dtype=np.uint8)
self.actions = np.zeros(capacity, dtype=np.int64)
self.rewards = np.zeros(capacity, dtype=np.float32)
self.dones = np.zeros(capacity, dtype=np.bool_)
self.idx = 0
self.full = False
def push(self, frame, action, reward, done):
self.frames[self.idx] = frame
self.actions[self.idx] = action
self.rewards[self.idx] = reward
self.dones[self.idx] = done
self.idx = (self.idx + 1) % self.capacity
if self.idx == 0:
self.full = True
def __len__(self):
return self.capacity if self.full else self.idx
def _stack(self, idx):
"""Build 4-frame stack ending at idx."""
frames = []
for k in range(self.n_stack - 1, -1, -1):
j = (idx - k) % self.capacity
frames.append(self.frames[j])
# If episode boundary inside the window, pad with the latest frame
if k > 0 and self.dones[(j - 1) % self.capacity]:
frames = [self.frames[idx]] * (k) + frames
break
return np.stack(frames, axis=0)
def sample(self, batch_size, device):
max_idx = len(self) - 1
idx = np.random.randint(self.n_stack, max_idx, size=batch_size)
# Avoid sampling crossing buffer wrap
idx = idx[idx != self.idx - 1]
s = np.stack([self._stack(i) for i in idx])
s_n = np.stack([self._stack((i + 1) % self.capacity) for i in idx])
a = self.actions[idx]
r = self.rewards[idx]
d = self.dones[idx].astype(np.float32)
return (torch.from_numpy(s).to(device),
torch.from_numpy(a).to(device),
torch.from_numpy(r).to(device),
torch.from_numpy(s_n).to(device),
torch.from_numpy(d).to(device))
৭ · Training loop — Atari DQN
import torch, torch.nn as nn
import torch.nn.functional as F
import numpy as np
import random
from collections import deque
# ── Hyperparameters (DeepMind Nature settings) ─────────────────────
GAMMA = 0.99
LR = 2.5e-4
BATCH = 32
BUFFER_CAP = 1_000_000
MIN_BUFFER = 50_000
TARGET_SYNC = 10_000
EPS_START = 1.0
EPS_END = 0.1
EPS_DECAY = 1_000_000 # frames
TRAIN_FREQ = 4 # learn every 4 environment steps
TOTAL_FRAMES = 10_000_000 # 10M frames typical
SEED = 42
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
torch.manual_seed(SEED); np.random.seed(SEED); random.seed(SEED)
env = make_atari("ALE/Breakout-v5", seed=SEED)
n_actions = env.action_space.n
online = NatureDQN(n_actions).to(device)
target = NatureDQN(n_actions).to(device)
target.load_state_dict(online.state_dict())
opt = torch.optim.Adam(online.parameters(), lr=LR, eps=1.5e-4)
buffer = FrameReplay(BUFFER_CAP)
def epsilon(frame):
f = min(1.0, frame / EPS_DECAY)
return EPS_START + f * (EPS_END - EPS_START)
obs, _ = env.reset(seed=SEED)
obs = np.asarray(obs) # (4, 84, 84) — frame stack already
ep_return, ep_len, returns_log = 0.0, 0, deque(maxlen=100)
for frame in range(1, TOTAL_FRAMES + 1):
eps = epsilon(frame)
# ε-greedy on the current 4-frame stack
if random.random() < eps:
a = env.action_space.sample()
else:
with torch.no_grad():
q = online(torch.from_numpy(np.asarray(obs)).unsqueeze(0).to(device))
a = int(q.argmax(1).item())
next_obs, r, term, trunc, _ = env.step(a)
next_obs = np.asarray(next_obs)
done = term or trunc
# Store ONLY the latest single frame (last channel of stack)
buffer.push(next_obs[-1], a, np.clip(r, -1, 1), term)
obs = next_obs
ep_return += r; ep_len += 1
# Train step
if len(buffer) >= MIN_BUFFER and frame % TRAIN_FREQ == 0:
S, A, R, SN, D = buffer.sample(BATCH, device)
with torch.no_grad():
q_next = target(SN).max(1).values
y = R + GAMMA * (1 - D) * q_next
q_pred = online(S).gather(1, A.unsqueeze(1)).squeeze(1)
loss = F.smooth_l1_loss(q_pred, y)
opt.zero_grad(); loss.backward()
nn.utils.clip_grad_norm_(online.parameters(), 10.0)
opt.step()
if frame % TARGET_SYNC == 0:
target.load_state_dict(online.state_dict())
if done:
returns_log.append(ep_return)
if len(returns_log) % 10 == 0:
avg = np.mean(returns_log)
print(f"frame {frame:9,d} | ep len {ep_len:4d} | ret {ep_return:5.0f} | "
f"avg100 {avg:6.2f} | ε {eps:.3f}")
ep_return, ep_len = 0.0, 0
obs, _ = env.reset()
obs = np.asarray(obs)
env.close()
৮ · Expected training trajectory
৯ · Bangladesh-এ Atari train — practical tips
আপনার GPU access সীমিত? এই tricks:
- Smaller frame budget: Mnih ২০১৫ ২০০M, কিন্তু ১০-২০M-ই পরিচিত performance. Time/cost সাশ্রয়।
- Cloud spot instances: Lambda Labs (A100 ~$1.5/hr), RunPod, Vast.ai. ১০M frames ৪-৬ ঘণ্টা।
- Google Colab Pro+: A100 access. 1-day continuous train possible.
- EnvPool: parallel environment library — same compute-এ ৪-৮× faster.
- Dopamine framework: DeepMind-এর reference implementation. Don't reinvent wheel.
- CleanRL: Single-file implementations. ছাত্রদের জন্য study করতে easy.
- Smaller game: Breakout heavy, Pong/Space Invaders more forgiving.
১০ · Beyond Nature DQN — modern variants
আপনার Atari DQN কাজ করছে — পরের steps:
- Double DQN (van Hasselt ২০১৬): Q-overestimation কমায়। ১ লাইন code change.
- Dueling DQN (Wang ২০১৬): $Q = V + A$ decomposition.
- Prioritized Experience Replay (Schaul ২০১৬): বড় TD error transitions বেশি sample.
- Rainbow DQN (Hessel ২০১৭): ৬টি improvement একসাথে। SOTA ২০১৭।
- R2D2 (২০১৯): recurrent + distributed. Long-horizon.
- Agent57 (২০২০): ৫৭ Atari games-এ human-level পার!
- MuZero (২০২০): model-based + tree search. Much higher performance.
- EfficientZero (২০২১): Atari-100K — ১০০K frames-এ human-level.
ভাবনার প্রশ্ন
প্র ০১ Frame stacking কেন critical? Single frame-এ এমন কী missing?
চমৎকার partial-observability question.
Setup: Atari frame static image. Ball position দেখা যায় — কিন্তু ball-এর velocity, direction? নেই।
Markov property violation: RL theory ধরে নেয় current state $s_t$ থেকে future predictable. Atari frame markov না — past frames-এ velocity হিডেন।
Frame stack solution: last 4 frames concat — ball position 4 timesteps-এর। Difference থেকে velocity, acceleration inferable. Network implicit shape derive.
Why 4 frames specifically:
- 2 frames — position + velocity.
- 3 frames — + acceleration.
- 4 frames — + jerk (rate of change of acceleration).
- Empirically 4 sweet spot. 8 marginal gain, 2× memory.
Alternative — RNN/LSTM:
- Recurrent DQN (DRQN) — frame stack-এর বদলে hidden state propagate.
- Pros: arbitrary history, no fixed window.
- Cons: training slower, BPTT memory, hyperparam sensitive.
- R2D2, Agent57 use. Simple Atari-এ frame stack যথেষ্ট।
Frame skip + frame stack interaction:
- Frame skip 4: agent decides every 4 game frames.
- Frame stack 4: those 4 decisions-এর last frame each — covering 16 game frames.
- Total temporal window: 16 × ~16ms = 256ms. Enough for ball trajectory.
Other partial-obs hacks in Atari:
- Action repeat (= frame skip): consistency in temporal dimension.
- Sticky actions: 25% chance of repeating prev action — prevent overfitting to ALE determinism.
- No-op start: 1-30 random no-ops at episode start — prevent memorization.
Games where frame stack still insufficient:
- Montezuma's Revenge — info hidden behind doors. Long memory required. DQN famously fails.
- Pitfall — same issue.
- These needed Agent57's intrinsic motivation + memory.
মূল উপলব্ধি: Atari "fully observable" বলা সরল — আসলে partial observability everywhere. Frame stacking — pragmatic solution. RL system design-এ partial-obs সবসময় check.
প্র ০২ Reward clipping ([-1, +1]) Breakout-এ score খুব আলাদা ভাবে scale করে। Bug না কি feature?
চমৎকার trade-off question. DeepMind নিজে paper-এ acknowledge করেছে।
Setup: Breakout brick-এ +1 to +7 (depending on row). 49 games, scales vary wildly — Pong $\pm$1, Space Invaders 5-200, Q*bert 25-1000.
Why clip ([-1, +1]):
- Cross-game uniformity: single hyperparameter set works across 49 games.
- Q-value stability: Q ranges similar — gradient scale consistent.
- Optimization: Adam-এর adaptive learning rate scale-sensitive. Wild reward → wild Q → unstable.
- Loss clipping correlation: Huber loss + reward clipping — bounded TD error.
Information loss:
- Breakout-এ top-row brick (+7) = bottom-row (+1) — same +1. Strategy difference erased.
- "Tunnel trick" — top rows worth more — clipped DQN doesn't directly know.
- Despite this, DQN still discovers tunnel — emergent due to sparse-reward dynamics.
Implications:
- Optimal clipped policy ≠ optimal unclipped policy.
- Breakout's "true score" reaches 800+ — clipped DQN achieves ~400 average.
- For some games (Q*bert), clipping limits ceiling significantly.
Modern alternatives:
- Reward normalization: running mean/std normalize. Per-game adaptive.
- Distributional RL (C51, QR-DQN): learn full reward distribution, not expected value. Naturally handles scale.
- Symlog (Dreamer V3): $\text{symlog}(r) = \text{sign}(r) \log(1 + |r|)$ — invertible, scale-invariant.
- PopArt (Hessel ২০১৯): adaptive output normalization.
Game-specific tuning:
- Single-game research — often skip clipping, use raw rewards.
- Multi-game (universal agent) — clipping or normalization essential.
Empirical study (van Hasselt ২০১৬):
- Reward clipping responsible for ~30% of DQN's gap to human.
- PopArt + DQN — Q*bert 5x improvement.
- Distributional RL — even more gain.
মূল কথা: Reward clipping pragmatic engineering choice, not principled. ২০১৩ DQN's "feature" enabling 49-game generality. ২০২৪ — better alternatives exist, used in Rainbow, Dreamer, IMPALA. Atari DQN paper — historical landmark; modern code uses normalization.
প্র ০৩ "Tunnel trick" Breakout-এ — agent ball-কে wall-এর উপর গিয়ে long bounce করে। শেখার পেছনে কী mechanism?
চমৎকার emergent-behavior question.
What's the trick: Breakout-এর ৬ rows of bricks-এর একপাশে repeated hits করে hole ("tunnel") খুলুন। Then ball wall-এর উপরে চলে যায় — top থেকে bricks consecutive break, paddle-এর কাজ নেই for long time. Score-explode.
How DQN discovers it:
- Initial random: agent random play, occasional brick hit, +1 reward.
- Q-network learns: "hit ball" → reward gradient signal.
- Side preference emerges: by chance, agent occasionally hits same column repeatedly. Long reward streak observed.
- Q-table update: states leading to "side cluster" — high Q. Behavior reinforces.
- Tunneling becomes intentional: 50M+ frames — agent actively aims one column.
Why DQN can find this:
- Off-policy + replay — can recall rare lucky long-bounce streaks.
- Bootstrap — Q-values propagate "tunnel state is high-value".
- Discount factor 0.99 — long-term reward credited.
- ε-exploration — eventually stumbles on the strategy.
Why early DQN couldn't (small budget):
- 10M frames — agent reaches "decent paddle play" but not strategy.
- 50M-150M — tunnel emerges across most seeds.
- Variability across seeds — some seeds find it 30M, others 100M+.
Significance:
- Mnih ২০১৫ paper highlighted this — "DQN discovers strategies humans use".
- Counter-evidence to "RL = brute memorization" critique.
- But: "discover" overstates — chance + reward gradient + persistence.
Modern algorithms — faster discovery:
- Rainbow DQN — tunnel by 30M frames.
- MuZero — tunnel by 5-10M frames. Tree search efficient exploration.
- EfficientZero — tunnel by 1-2M frames. SSL augmentation.
Failure modes:
- Some seeds — agent stuck "boring" paddle play, never tunnels.
- If reward not clipped (top bricks worth 7 vs bottom 1) — incentive stronger.
- If frame skip too aggressive — agent miss the precision required.
Implications for general RL:
- Long-horizon credit assignment hard — but possible with patience + correct algorithm.
- Exploration exploitation balance — too greedy, miss tunnel; too random, never converge.
- Architectural inductive bias minimal — pure CNN + Q-learning sufficient.
মূল কথা: Tunnel trick — "DQN actually learns" demonstration. Not human-level intelligence — but emergent strategic depth from simple rules + lots of practice. Magic trick: nothing in code says "find tunnel"; it emerges.
প্র ০৪ Bangladesh startup-এ Atari-DQN-style RL apply করে কী useful product বানানো যায়?
চমৎকার entrepreneurial question.
Reality check first:
- Atari DQN নিজে — academic benchmark, no direct product.
- কিন্তু underlying techniques (CNN + Q-learning + replay) — many applications.
Product ideas:
(১) Educational gaming AI:
- Bangladeshi school-children's chess/checkers/ludo training opponent.
- Adaptive difficulty — student-এর level match.
- RL-trained agents at multiple skill levels.
- Mobile app, low-bandwidth.
(২) Cricket strategy AI (Bangladesh's love):
- State: current match situation (overs, runs, wickets, pitch).
- Action: bowl change, fielding setup, batting order.
- Reward: probability of winning.
- Trained on historical match data + simulator.
- Coaches, fantasy leagues, broadcasters use.
(৩) Garment factory cutting optimization:
- Atari pixel = fabric pattern image.
- Action: cutting position, angle.
- Reward: -fabric_waste − time_cost.
- Computer vision + RL — automated cutting machine.
- RMG industry $40B annual — 1% efficiency = $400M.
(৪) Smart traffic light (small-scale):
- Pixel-based traffic camera observation.
- Action: phase change.
- Reward: throughput - waiting time.
- Single intersection prototype → multi-intersection scale.
(৫) Aquaculture (fish/shrimp farming):
- Camera observation — fish health, behavior, feed distribution.
- Action: feed amount, pump on/off, oxygenation.
- Reward: growth rate − cost − mortality.
- Bangladesh-er chitoler, golda, vetki industry.
(৬) Educational drone/robot:
- Robotics kit + DQN training — STEM education.
- Sell to private schools, BUET-NSU labs.
(৭) Game-based therapy:
- Stroke recovery, cognitive assessment.
- RL-driven adaptive game difficulty.
- Healthcare partnership.
(৮) Sports analytics-as-a-service:
- BPL teams subscribe — opponent analysis, formation suggestion.
- Cricket data + soccer (BPL football).
Practical considerations:
- Compute: AWS/GCP credits — startup-এ apply. Lambda Labs cheaper.
- Data: domain-specific data hard to gather — partnerships with industry essential.
- Productization: RL research demo → robust software 10× engineering.
- Customer education: "AI" sells, but RL-specific value unclear. Marketing key.
Realistic startup path:
- Phase 1: Solve a clear, narrow industry problem (RMG cutting, aquaculture).
- Phase 2: Validate ROI (~10-30% efficiency gain measurable).
- Phase 3: Scale to similar industries.
- Phase 4: Platform play.
Examples nearby:
- India: Niramai (cancer detection RL/CV), Brain.ai.
- Bangladesh: ACI Agro AI initiatives, BJIT, Brain Station 23.
- Atari-style breakthroughs rare; applied RL niches grow.
মূল কথা: Atari DQN — foundational training ground. Real product-গুলো — domain-specific applications-এ DQN/PPO/SAC apply. Bangladesh-এ untapped opportunities অনেক — domain expertise + ML talent-এর intersection.
অনুশীলন
-
Pong with smaller compute: Pong-এ DQN train করুন (Breakout-এর চেয়ে easier). ১M frames-এ converge হয় কি?
Pong DQN ১-৫M frames-এ -21 (random) থেকে +21 (perfect) পৌঁছে যায়। Single GPU-এ ২-৪ ঘণ্টা। কারণ — single ball, simple reward (+1 score, -1 lose), no strategy hidden.
-
Hyperparameter ablation: Frame skip 1 (no skip), 4 (default), 16 try. কোনটা best?
Frame skip 1: 4× compute, no benefit. Frame skip 4: standard, best balance. Frame skip 16: agent miss precise actions (paddle hit), performance drops. ৪ এই sweet spot Atari games-এর জন্য — temporal granularity vs compute trade-off.
-
Modern improvement: Double DQN implement — single line change. কী?
# Original DQN: q_next = target(SN).max(1).values # Double DQN: a_argmax = online(SN).argmax(1) q_next = target(SN).gather(1, a_argmax.unsqueeze(1)).squeeze(1)Action selection online network থেকে, value evaluation target network থেকে — decouple — overestimation bias কমায়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পাঠ ৩২ · কোর্সের চূড়ান্ত পর্যালোচনা পরবর্তী পাঠ পুরো RL কোর্সের timeline, when-to-use guide, next steps.
- পাঠ ৩০ · প্রজেক্ট: CartPole agent আগের পাঠ CartPole DQN/REINFORCE — Atari-র simpler version.
- পাঠ ২৪ · Model-based RL সম্পর্কিত MuZero, EfficientZero — Atari-তে DQN-এর successor.
- সব AI Courses দেখুন ABCL TECH Python, ML, DL, NLP, CV, GenAI, RL — সব AI কোর্স একসাথে।