Split View: 강화학습 완전 정복: DQN, PPO부터 RLHF, DPO까지 LLM 정렬까지
강화학습 완전 정복: DQN, PPO부터 RLHF, DPO까지 LLM 정렬까지
- 들어가며
- 1. 강화학습의 기초: MDP
- 2. 모델 프리 강화학습
- 3. 정책 경사법 (Policy Gradient)
- 4. 고급 기법
- 5. RLHF: 강화학습으로 LLM 정렬하기
- 6. 멀티에이전트 강화학습
- 7. 실전 환경
- 퀴즈: 강화학습 이해도 점검
- 알고리즘 비교 요약
- 마치며
들어가며
강화학습(Reinforcement Learning, RL)은 에이전트가 환경과 상호작용하며 보상을 최대화하는 정책을 스스로 학습하는 머신러닝 패러다임입니다. AlphaGo의 바둑 세계 정복부터 ChatGPT의 RLHF 정렬까지, 현대 AI의 핵심 기술로 자리잡았습니다.
이 포스트는 MDP 수학 기초부터 최신 DPO까지 한 번에 정리합니다.
1. 강화학습의 기초: MDP
마르코프 결정 과정 (Markov Decision Process)
강화학습은 MDP로 공식화됩니다. MDP는 5-튜플 로 정의합니다:
- : 상태 공간 (State space)
- : 행동 공간 (Action space)
- : 전이 확률 (Transition probability)
- : 보상 함수 (Reward function)
- : 할인율 (Discount factor)
마르코프 성질: 미래 상태는 현재 상태만으로 결정됩니다.
정책 (Policy)
정책 는 상태에서 행동으로의 매핑입니다:
- 결정론적 정책:
- 확률론적 정책:
가치 함수 (Value Function)
상태 가치 함수: 정책 를 따를 때 상태 의 기대 누적 보상
행동 가치 함수 (Q-함수): 상태 에서 행동 를 취한 후 정책 를 따를 때의 기대 보상
벨만 방정식 (Bellman Equation)
가치 함수의 재귀적 분해:
벨만 최적 방정식:
2. 모델 프리 강화학습
Q-Learning (오프-폴리시)
Q-Learning은 오프-폴리시(off-policy) 방법입니다. TD 업데이트:
타깃: (그리디 정책 사용)
SARSA (온-폴리시)
SARSA는 온-폴리시(on-policy) 방법입니다:
타깃: (실제 다음 행동 사용)
| 구분 | Q-Learning | SARSA |
|---|---|---|
| 정책 유형 | 오프-폴리시 | 온-폴리시 |
| 업데이트 기준 | greedy 행동 | 실제 행동 |
| 절벽 탐색 | 최적 경로 | 안전 경로 |
DQN (Deep Q-Network)
DQN은 신경망으로 Q-함수를 근사합니다. 두 가지 핵심 안정화 기법:
- 경험 재플레이 (Experience Replay): 버퍼에서 미니배치 샘플링
- 타깃 네트워크 (Target Network): 주기적으로 복사된 별도 네트워크
import torch
import torch.nn as nn
import numpy as np
from collections import deque
import random
class DQN(nn.Module):
def __init__(self, state_dim, action_dim):
super().__init__()
self.net = nn.Sequential(
nn.Linear(state_dim, 128),
nn.ReLU(),
nn.Linear(128, 128),
nn.ReLU(),
nn.Linear(128, action_dim)
)
def forward(self, x):
return self.net(x)
class ReplayBuffer:
def __init__(self, capacity=10000):
self.buffer = deque(maxlen=capacity)
def push(self, state, action, reward, next_state, done):
self.buffer.append((state, action, reward, next_state, done))
def sample(self, batch_size):
batch = random.sample(self.buffer, batch_size)
states, actions, rewards, next_states, dones = zip(*batch)
return (
torch.FloatTensor(np.array(states)),
torch.LongTensor(actions),
torch.FloatTensor(rewards),
torch.FloatTensor(np.array(next_states)),
torch.FloatTensor(dones)
)
def __len__(self):
return len(self.buffer)
def train_dqn_step(online_net, target_net, optimizer, buffer, batch_size=64, gamma=0.99):
if len(buffer) < batch_size:
return
states, actions, rewards, next_states, dones = buffer.sample(batch_size)
# 현재 Q값
current_q = online_net(states).gather(1, actions.unsqueeze(1)).squeeze(1)
# 타깃 Q값 (타깃 네트워크 사용)
with torch.no_grad():
max_next_q = target_net(next_states).max(1)[0]
target_q = rewards + gamma * max_next_q * (1 - dones)
loss = nn.MSELoss()(current_q, target_q)
optimizer.zero_grad()
loss.backward()
optimizer.step()
return loss.item()
Double DQN
DQN의 과대추정(overestimation) 문제 해결. 행동 선택과 가치 평가 분리:
Dueling DQN
Q-값을 가치 함수 와 어드밴티지 함수 로 분해:
class DuelingDQN(nn.Module):
def __init__(self, state_dim, action_dim):
super().__init__()
self.feature = nn.Sequential(
nn.Linear(state_dim, 128), nn.ReLU()
)
# 가치 스트림
self.value_stream = nn.Sequential(
nn.Linear(128, 64), nn.ReLU(), nn.Linear(64, 1)
)
# 어드밴티지 스트림
self.advantage_stream = nn.Sequential(
nn.Linear(128, 64), nn.ReLU(), nn.Linear(64, action_dim)
)
def forward(self, x):
feat = self.feature(x)
value = self.value_stream(feat)
advantage = self.advantage_stream(feat)
# 결합: Q = V + (A - mean(A))
return value + advantage - advantage.mean(dim=1, keepdim=True)
3. 정책 경사법 (Policy Gradient)
REINFORCE
정책을 직접 파라미터화하고 경사를 추정합니다:
여기서 는 시간 에서의 누적 보상입니다.
Actor-Critic
REINFORCE의 높은 분산 문제를 해결하기 위해 베이스라인으로 가치 함수를 사용:
어드밴티지 함수:
TD 오차로 근사:
A3C / A2C
A3C (Asynchronous Advantage Actor-Critic): 여러 워커가 비동기 병렬 학습
A2C (Advantage Actor-Critic): 동기식으로 모든 워커 경사를 평균내어 업데이트
PPO (Proximal Policy Optimization)
현재 가장 널리 쓰이는 정책 경사 알고리즘. 핵심 아이디어: 정책 업데이트 폭 제한으로 안정적 학습.
클리핑된 목적 함수:
여기서 는 확률비입니다.
PPO의 완전한 목적 함수:
- : 가치 함수 손실 (MSE)
- : 엔트로피 보너스 (탐색 장려)
from stable_baselines3 import PPO
import gymnasium as gym
env = gym.make("CartPole-v1")
model = PPO(
"MlpPolicy",
env,
learning_rate=3e-4,
n_steps=2048,
batch_size=64,
n_epochs=10,
gamma=0.99,
gae_lambda=0.95,
clip_range=0.2, # epsilon: 클립 비율
ent_coef=0.01, # 엔트로피 보너스 계수
verbose=1
)
model.learn(total_timesteps=100_000)
obs, _ = env.reset()
for _ in range(1000):
action, _ = model.predict(obs, deterministic=True)
obs, reward, terminated, truncated, info = env.step(action)
if terminated or truncated:
obs, _ = env.reset()
4. 고급 기법
SAC (Soft Actor-Critic)
SAC는 최대 엔트로피 강화학습 프레임워크입니다. 보상 최대화와 엔트로피 최대화를 동시에 추구:
는 온도 파라미터로 탐색의 정도를 조절합니다.
소프트 Q-함수 벨만 방정식:
SAC는 연속 행동 공간 문제에서 탁월하며, 자동 온도 조절로 하이퍼파라미터 민감도가 낮습니다.
from stable_baselines3 import SAC
env = gym.make("HalfCheetah-v4")
model = SAC(
"MlpPolicy", env,
learning_rate=3e-4,
buffer_size=1_000_000,
learning_starts=10_000,
batch_size=256,
tau=0.005,
gamma=0.99,
train_freq=1,
gradient_steps=1,
verbose=1
)
model.learn(total_timesteps=1_000_000)
TD3 (Twin Delayed Deep Deterministic)
DDPG의 과대추정 문제를 해결하는 3가지 기법:
- Twin Critics: 두 Q-네트워크의 최솟값 선택
- Delayed Policy Updates: 액터를 크리틱보다 낮은 빈도로 업데이트
- Target Policy Smoothing: 타깃 행동에 노이즈 추가
HER (Hindsight Experience Replay)
희소 보상(sparse reward) 환경에서 실패한 경험도 재활용합니다. 목표를 실제 도달한 상태로 사후에 교체하여 학습:
- 원래 경험: — 목표 에 실패
- HER 변환: — (실제 최종 상태)
목표 교체 전략:
- future: 같은 에피소드의 미래 상태 중 랜덤 선택 (기본값)
- episode: 같은 에피소드의 임의 상태
- final: 에피소드 마지막 상태
from stable_baselines3 import HerReplayBuffer, SAC
# 로봇 암 목표 도달 환경
env = gym.make("FetchReach-v2")
model = SAC(
"MultiInputPolicy",
env,
replay_buffer_class=HerReplayBuffer,
replay_buffer_kwargs=dict(
n_sampled_goal=4,
goal_selection_strategy="future",
),
verbose=1,
)
model.learn(total_timesteps=100_000)
5. RLHF: 강화학습으로 LLM 정렬하기
InstructGPT 파이프라인
ChatGPT의 핵심인 RLHF(Reinforcement Learning from Human Feedback)는 3단계로 구성됩니다:
1단계: SFT (Supervised Fine-Tuning)
- 고품질 데모 데이터로 기본 모델 파인튜닝
- 인간 전문가가 작성한 이상적인 응답으로 학습
2단계: 보상 모델 훈련 (Reward Model Training)
- 인간이 두 응답 중 더 나은 것을 선택 (선호도 데이터 수집)
- 선호도 데이터로 보상 모델 학습
는 선호된 응답, 은 선호되지 않은 응답입니다.
3단계: PPO로 LLM 파인튜닝
- 보상 모델을 환경으로, LLM을 에이전트로 PPO 적용
- KL 페널티로 원래 모델에서 너무 멀어지지 않도록 제어
import torch
import torch.nn as nn
class RewardModel(nn.Module):
def __init__(self, base_model_name="gpt2"):
super().__init__()
from transformers import AutoModel
self.backbone = AutoModel.from_pretrained(base_model_name)
hidden_size = self.backbone.config.hidden_size
self.reward_head = nn.Linear(hidden_size, 1)
def forward(self, input_ids, attention_mask):
outputs = self.backbone(input_ids=input_ids, attention_mask=attention_mask)
last_hidden = outputs.last_hidden_state[:, -1, :]
return self.reward_head(last_hidden).squeeze(-1)
def compute_reward_model_loss(reward_model, chosen_ids, chosen_mask,
rejected_ids, rejected_mask):
"""Bradley-Terry 모델 기반 선호도 손실"""
chosen_reward = reward_model(chosen_ids, chosen_mask)
rejected_reward = reward_model(rejected_ids, rejected_mask)
loss = -torch.log(torch.sigmoid(chosen_reward - rejected_reward)).mean()
return loss
PPO로 LLM 파인튜닝 (TRL 라이브러리):
from trl import PPOTrainer, PPOConfig, AutoModelForCausalLMWithValueHead
from transformers import AutoTokenizer
ppo_config = PPOConfig(
model_name="gpt2",
learning_rate=1.41e-5,
batch_size=128,
mini_batch_size=16,
gradient_accumulation_steps=4,
target_kl=0.1,
kl_penalty="kl",
)
model = AutoModelForCausalLMWithValueHead.from_pretrained(ppo_config.model_name)
tokenizer = AutoTokenizer.from_pretrained(ppo_config.model_name)
ppo_trainer = PPOTrainer(ppo_config, model, ref_model=None, tokenizer=tokenizer)
for batch in dataloader:
query_tensors = batch["input_ids"]
response_tensors = ppo_trainer.generate(query_tensors, max_new_tokens=200)
rewards = [reward_model(q, r) for q, r in zip(query_tensors, response_tensors)]
stats = ppo_trainer.step(query_tensors, response_tensors, rewards)
DPO (Direct Preference Optimization)
DPO는 보상 모델 없이 직접 선호도 데이터로 LLM을 정렬합니다. PPO의 복잡한 강화학습 루프를 단순한 분류 손실로 대체:
import torch.nn.functional as F
def dpo_loss(pi_logps_chosen, pi_logps_rejected,
ref_logps_chosen, ref_logps_rejected, beta=0.1):
"""
DPO 손실 함수
Args:
pi_logps_chosen: 학습 모델의 선호 응답 log-prob
pi_logps_rejected: 학습 모델의 비선호 응답 log-prob
ref_logps_chosen: 참조 모델의 선호 응답 log-prob
ref_logps_rejected: 참조 모델의 비선호 응답 log-prob
beta: KL 페널티 강도
"""
pi_log_ratio = pi_logps_chosen - pi_logps_rejected
ref_log_ratio = ref_logps_chosen - ref_logps_rejected
# 암묵적 보상 차이
logits = beta * (pi_log_ratio - ref_log_ratio)
loss = -F.logsigmoid(logits).mean()
return loss
DPO vs RLHF 비교:
| 항목 | RLHF + PPO | DPO |
|---|---|---|
| 보상 모델 | 별도 훈련 필요 | 불필요 |
| 필요 모델 수 | 액터 + 크리틱 + RM + 참조 | 학습 모델 + 참조 모델 |
| 학습 안정성 | RL 불안정성 존재 | 지도학습 수준 안정적 |
| 구현 복잡도 | 높음 | 낮음 |
6. 멀티에이전트 강화학습
환경 유형
| 환경 유형 | 설명 | 예시 |
|---|---|---|
| 완전 협력 | 공유 보상, 팀 목표 | 로봇 팀 작업 |
| 완전 경쟁 | 제로섬 게임 | 체스, 바둑 |
| 혼합 | 협력+경쟁 | 축구, MOBA |
MADDPG (Multi-Agent DDPG)
분산 실행 + 중앙집중 학습(CTDE) 패러다임:
- 실행 시: 각 에이전트는 자신의 관찰만으로 행동 결정
- 학습 시: 크리틱은 모든 에이전트의 관찰과 행동을 활용
는 전체 관찰, 는 에이전트 의 행동입니다.
class MADDPGCritic(nn.Module):
"""모든 에이전트의 정보를 활용하는 중앙화 크리틱"""
def __init__(self, n_agents, obs_dim, action_dim):
super().__init__()
# 전체 관찰 + 전체 행동을 입력
input_dim = n_agents * (obs_dim + action_dim)
self.net = nn.Sequential(
nn.Linear(input_dim, 256), nn.ReLU(),
nn.Linear(256, 256), nn.ReLU(),
nn.Linear(256, 1)
)
def forward(self, all_obs, all_actions):
# all_obs: [batch, n_agents * obs_dim]
# all_actions: [batch, n_agents * action_dim]
x = torch.cat([all_obs, all_actions], dim=-1)
return self.net(x)
OpenSpiel
Google DeepMind의 멀티에이전트 RL 프레임워크:
import pyspiel
game = pyspiel.load_game("tic_tac_toe")
state = game.new_initial_state()
while not state.is_terminal():
legal_actions = state.legal_actions()
action = legal_actions[0] # 실제로는 정책으로 선택
state.apply_action(action)
returns = state.returns()
print(f"플레이어 0 보상: {returns[0]}")
print(f"플레이어 1 보상: {returns[1]}")
7. 실전 환경
Gymnasium (OpenAI Gym 후속)
import gymnasium as gym
import numpy as np
class NormalizedObsWrapper(gym.ObservationWrapper):
"""관찰값을 [-1, 1]로 정규화하는 래퍼"""
def __init__(self, env):
super().__init__(env)
self.obs_low = env.observation_space.low
self.obs_high = env.observation_space.high
def observation(self, obs):
normalized = (
2.0 * (obs - self.obs_low)
/ (self.obs_high - self.obs_low + 1e-8)
- 1.0
)
return normalized.astype(np.float32)
class RewardShapingWrapper(gym.RewardWrapper):
"""보상 스케일링 래퍼"""
def __init__(self, env, scale=0.01):
super().__init__(env)
self.scale = scale
def reward(self, reward):
return reward * self.scale
base_env = gym.make("LunarLander-v2")
env = RewardShapingWrapper(NormalizedObsWrapper(base_env))
obs, info = env.reset(seed=42)
MuJoCo
물리 기반 연속 제어 환경으로 로보틱스 연구에 필수:
- HalfCheetah-v4: 치타 로봇 달리기 (17차원 상태, 6차원 행동)
- Humanoid-v4: 인간형 로봇 보행 (376차원 상태, 17차원 행동)
- Ant-v4: 4족 보행 로봇 (111차원 상태, 8차원 행동)
벤치마크 성능 (1M 스텝):
| 환경 | SAC | TD3 | PPO |
|---|---|---|---|
| HalfCheetah | ~12000 | ~9000 | ~3000 |
| Ant | ~5500 | ~4000 | ~1500 |
| Humanoid | ~5000 | ~4500 | ~600 |
Isaac Gym / Isaac Lab
NVIDIA의 GPU 가속 물리 시뮬레이터로 수천 개의 환경을 병렬 실행합니다:
- CPU 시뮬레이션 대비 2000배 빠른 훈련
- 도메인 랜덤화(Domain Randomization)로 실세계 전이(sim-to-real) 향상
- 4096개 환경 동시 실행으로 수시간 내 로봇 학습 완료
# Isaac Lab 예시 (간략화)
from isaaclab.envs import DirectRLEnv, DirectRLEnvCfg
class CartpoleEnvCfg(DirectRLEnvCfg):
num_envs = 4096 # 4096개 병렬 환경
episode_length_s = 5.0
decimation = 2
action_scale = 100.0
# GPU에서 4096개 CartPole 병렬 실행
env = CartpoleEnv(cfg=CartpoleEnvCfg())
obs, _ = env.reset()
# obs.shape: [4096, obs_dim]
실세계 RL 배포 고려사항
- 안전 제약 (Safe RL): 학습 중 위험한 행동 방지. 제약 기반 최적화(CPO, TRPO-Lagrangian)
- 샘플 효율성: 실세계 데이터는 비싸고 느림 — 오프라인 RL, 모델 기반 RL 활용
- Sim-to-Real 전이: 도메인 랜덤화, 어댑테이션 레이어(RMA)로 Reality Gap 줄이기
- 오프라인 RL: Conservative Q-Learning(CQL), IQL로 사전 수집 데이터만으로 학습
퀴즈: 강화학습 이해도 점검
Q1. Q-Learning과 SARSA의 온-폴리시 vs 오프-폴리시 차이를 설명하세요.
정답: Q-Learning은 오프-폴리시, SARSA는 온-폴리시
설명: Q-Learning의 업데이트 타깃은 로, 실제 행동과 무관하게 그리디 최댓값을 사용합니다. 반면 SARSA는 에서 이 실제로 취한 행동입니다. CliffWalking 문제에서 Q-Learning은 절벽 가장자리 최적 경로를 학습하지만 탐색 중 자주 떨어지고, SARSA는 더 안전한 우회 경로를 학습합니다.
Q2. PPO에서 clip ratio 하이퍼파라미터 epsilon이 학습 안정성에 미치는 역할은?
정답: epsilon은 정책 업데이트의 보수성을 제어
설명: 가 범위를 벗어나면 기울기가 차단됩니다. 이 너무 작으면 학습이 느려지고 지역 최솟값에 갇히기 쉽습니다. 반대로 너무 크면 정책이 급격히 변해 불안정해집니다. 일반적으로 가 좋은 기본값이며, 학습 후반부에 점차 줄이는 스케줄링도 효과적입니다.
Q3. RLHF에서 보상 모델 훈련 데이터를 수집하는 방법은?
정답: 인간 평가자가 두 응답 쌍을 비교하여 선호도 표시
설명: 같은 프롬프트에 대한 두 응답 를 인간 평가자에게 보여주고 더 나은 응답을 선택하게 합니다. 절대 평가(1-5점)보다 쌍별 비교(pairwise comparison)가 더 일관성 있고 신뢰할 수 있습니다. 수집된 선호도 데이터 로 Bradley-Terry 모델을 학습합니다. Anthropic의 Constitutional AI에서는 AI가 직접 평가자 역할을 대신하는 RLAIF도 사용합니다.
Q4. DPO가 PPO 기반 RLHF보다 구현이 단순한 이유는?
정답: 보상 모델과 강화학습 루프가 불필요
설명: RLHF+PPO는 세 단계(SFT, 보상 모델 학습, PPO 파인튜닝)가 필요하고, 동시에 여러 모델(액터, 크리틱, 보상 모델, 참조 모델)을 GPU 메모리에 올려야 합니다. DPO는 선호도 데이터로 암묵적 보상을 수식에 직접 통합하여, 단순한 교차 엔트로피 스타일 손실 하나로 파인튜닝합니다. 참조 모델과 학습 모델 두 개만 필요하며 강화학습 특유의 불안정성이 없습니다.
Q5. Soft Actor-Critic에서 엔트로피 정규화의 역할은?
정답: 탐색 장려 + 다중 최적 정책 학습
설명: SAC의 목적 함수에 엔트로피 항 를 추가하면, 에이전트는 보상을 최대화하면서 동시에 행동의 무작위성을 유지하려 합니다. 이는 자동적인 탐색 메커니즘을 제공하여 지역 최솟값 탈출에 도움을 줍니다. 또한 동등하게 좋은 여러 행동이 있을 때 균등하게 선택하는 로버스트한 정책을 학습합니다. 온도 파라미터 는 자동 튜닝(automatic entropy tuning)으로 목표 엔트로피에 맞게 조절됩니다.
알고리즘 비교 요약
| 알고리즘 | 정책 유형 | 행동 공간 | 특징 |
|---|---|---|---|
| Q-Learning | 오프-폴리시 | 이산 | 단순, 표 기반 |
| DQN | 오프-폴리시 | 이산 | 딥러닝 + ER + TN |
| Double DQN | 오프-폴리시 | 이산 | 과대추정 완화 |
| Dueling DQN | 오프-폴리시 | 이산 | V + A 분리 |
| REINFORCE | 온-폴리시 | 이산/연속 | 높은 분산 |
| A2C | 온-폴리시 | 이산/연속 | Actor-Critic |
| PPO | 온-폴리시 | 이산/연속 | 안정적, 범용 |
| SAC | 오프-폴리시 | 연속 | 최대 엔트로피 |
| TD3 | 오프-폴리시 | 연속 | SAC 결정론적 버전 |
| HER | 오프-폴리시 | 목표 기반 | 희소 보상 |
마치며
강화학습은 단순한 게임 AI를 넘어 로보틱스, LLM 정렬, 자율주행, 신약 개발까지 적용 범위가 폭발적으로 확장되고 있습니다. 특히 RLHF와 DPO는 ChatGPT, Claude, Gemini 같은 LLM의 핵심 정렬 기술로, RL을 이해하는 것이 현대 AI 연구자에게 필수 역량이 되었습니다.
추천 학습 경로:
- Gymnasium + Q-Learning/DQN 직접 구현
- Stable-Baselines3로 PPO/SAC 실험
- TRL 라이브러리로 RLHF/DPO 실습
- Isaac Lab으로 로보틱스 RL 탐구
참고 자료:
- Sutton & Barto, "Reinforcement Learning: An Introduction" (2nd ed.)
- Spinning Up in Deep RL (OpenAI)
- Stable-Baselines3 공식 문서
- TRL (Transformer Reinforcement Learning) by Hugging Face
- Isaac Lab 공식 문서
Reinforcement Learning Complete Guide: From DQN, PPO to RLHF and DPO for LLM Alignment
- Introduction
- 1. RL Fundamentals: MDP
- 2. Model-Free RL
- 3. Policy Gradient Methods
- 4. Advanced Methods
- 5. RLHF: Aligning LLMs with RL
- 6. Multi-Agent Reinforcement Learning
- 7. Training Environments
- Quiz: Test Your RL Knowledge
- Algorithm Comparison Summary
- Conclusion
Introduction
Reinforcement Learning (RL) is a machine learning paradigm where an agent learns to maximize reward by interacting with an environment. From AlphaGo conquering the world of Go to ChatGPT's RLHF alignment, it has become a core technology in modern AI.
This post covers everything from MDP mathematical foundations to the latest DPO in one place.
1. RL Fundamentals: MDP
Markov Decision Process
RL is formalized as an MDP — a 5-tuple :
- : State space
- : Action space
- : Transition probability
- : Reward function
- : Discount factor
Markov Property: The future state depends only on the current state.
Policy
A policy maps states to actions:
- Deterministic policy:
- Stochastic policy:
Value Functions
State value function: Expected cumulative reward from state following policy
Action value function (Q-function): Expected reward after taking action in state then following
Bellman Equations
Recursive decomposition of value functions:
Bellman Optimality Equations:
2. Model-Free RL
Q-Learning (Off-Policy)
Q-Learning is an off-policy method. TD update rule:
Target: (greedy policy)
SARSA (On-Policy)
SARSA is an on-policy method:
Target: (actual next action )
| Property | Q-Learning | SARSA |
|---|---|---|
| Policy type | Off-policy | On-policy |
| Update target | Greedy action | Actual action |
| Cliff exploration | Optimal path | Safe path |
DQN (Deep Q-Network)
DQN approximates the Q-function with a neural network. Two key stabilization techniques:
- Experience Replay: Sample mini-batches from a replay buffer
- Target Network: Separate network copied periodically for stable targets
import torch
import torch.nn as nn
import numpy as np
from collections import deque
import random
class DQN(nn.Module):
def __init__(self, state_dim, action_dim):
super().__init__()
self.net = nn.Sequential(
nn.Linear(state_dim, 128),
nn.ReLU(),
nn.Linear(128, 128),
nn.ReLU(),
nn.Linear(128, action_dim)
)
def forward(self, x):
return self.net(x)
class ReplayBuffer:
def __init__(self, capacity=10000):
self.buffer = deque(maxlen=capacity)
def push(self, state, action, reward, next_state, done):
self.buffer.append((state, action, reward, next_state, done))
def sample(self, batch_size):
batch = random.sample(self.buffer, batch_size)
states, actions, rewards, next_states, dones = zip(*batch)
return (
torch.FloatTensor(np.array(states)),
torch.LongTensor(actions),
torch.FloatTensor(rewards),
torch.FloatTensor(np.array(next_states)),
torch.FloatTensor(dones)
)
def __len__(self):
return len(self.buffer)
def train_dqn_step(online_net, target_net, optimizer, buffer,
batch_size=64, gamma=0.99):
if len(buffer) < batch_size:
return
states, actions, rewards, next_states, dones = buffer.sample(batch_size)
# Current Q-values
current_q = online_net(states).gather(1, actions.unsqueeze(1)).squeeze(1)
# Target Q-values (using target network)
with torch.no_grad():
max_next_q = target_net(next_states).max(1)[0]
target_q = rewards + gamma * max_next_q * (1 - dones)
loss = nn.MSELoss()(current_q, target_q)
optimizer.zero_grad()
loss.backward()
optimizer.step()
return loss.item()
Double DQN
Fixes the overestimation problem in DQN by decoupling action selection and value evaluation:
Dueling DQN
Decomposes Q-values into value function and advantage function :
class DuelingDQN(nn.Module):
def __init__(self, state_dim, action_dim):
super().__init__()
self.feature = nn.Sequential(
nn.Linear(state_dim, 128), nn.ReLU()
)
self.value_stream = nn.Sequential(
nn.Linear(128, 64), nn.ReLU(), nn.Linear(64, 1)
)
self.advantage_stream = nn.Sequential(
nn.Linear(128, 64), nn.ReLU(), nn.Linear(64, action_dim)
)
def forward(self, x):
feat = self.feature(x)
value = self.value_stream(feat)
advantage = self.advantage_stream(feat)
return value + advantage - advantage.mean(dim=1, keepdim=True)
3. Policy Gradient Methods
REINFORCE
Directly parameterize the policy and estimate gradients:
where is the return from time .
Actor-Critic
Uses a value function as a baseline to reduce the high variance of REINFORCE:
Advantage function:
Approximated by TD error:
A3C / A2C
A3C (Asynchronous Advantage Actor-Critic): Multiple workers learn asynchronously in parallel
A2C (Advantage Actor-Critic): Synchronously averages gradients from all workers before updating
PPO (Proximal Policy Optimization)
Currently the most widely used policy gradient algorithm. Core idea: constrain the magnitude of policy updates for stable learning.
Clipped objective:
where is the probability ratio.
Full PPO objective:
- : Value function loss (MSE)
- : Entropy bonus (encourages exploration)
from stable_baselines3 import PPO
import gymnasium as gym
env = gym.make("CartPole-v1")
model = PPO(
"MlpPolicy",
env,
learning_rate=3e-4,
n_steps=2048,
batch_size=64,
n_epochs=10,
gamma=0.99,
gae_lambda=0.95,
clip_range=0.2, # epsilon: clip ratio
ent_coef=0.01, # entropy bonus coefficient
verbose=1
)
model.learn(total_timesteps=100_000)
obs, _ = env.reset()
for _ in range(1000):
action, _ = model.predict(obs, deterministic=True)
obs, reward, terminated, truncated, info = env.step(action)
if terminated or truncated:
obs, _ = env.reset()
4. Advanced Methods
SAC (Soft Actor-Critic)
SAC is a maximum entropy RL framework that simultaneously maximizes reward and entropy:
is the temperature parameter controlling the degree of exploration.
Soft Bellman equation:
SAC excels at continuous action space problems and is less sensitive to hyperparameters via automatic temperature tuning.
from stable_baselines3 import SAC
import gymnasium as gym
env = gym.make("HalfCheetah-v4")
model = SAC(
"MlpPolicy", env,
learning_rate=3e-4,
buffer_size=1_000_000,
learning_starts=10_000,
batch_size=256,
tau=0.005,
gamma=0.99,
train_freq=1,
gradient_steps=1,
verbose=1
)
model.learn(total_timesteps=1_000_000)
TD3 (Twin Delayed Deep Deterministic)
Three techniques to fix DDPG's overestimation:
- Twin Critics: Take the minimum of two Q-networks
- Delayed Policy Updates: Update actor less frequently than critic
- Target Policy Smoothing: Add noise to target actions
HER (Hindsight Experience Replay)
Reuses failed experiences in sparse reward environments by retroactively replacing the goal with the state actually reached:
- Original: — failed to reach goal
- HER: — (final state reached)
Goal replacement strategies:
- future: Random future state from same episode (default)
- episode: Any random state from same episode
- final: Last state of the episode
from stable_baselines3 import HerReplayBuffer, SAC
env = gym.make("FetchReach-v2")
model = SAC(
"MultiInputPolicy",
env,
replay_buffer_class=HerReplayBuffer,
replay_buffer_kwargs=dict(
n_sampled_goal=4,
goal_selection_strategy="future",
),
verbose=1,
)
model.learn(total_timesteps=100_000)
5. RLHF: Aligning LLMs with RL
InstructGPT Pipeline
RLHF (Reinforcement Learning from Human Feedback), the core of ChatGPT, consists of 3 stages:
Stage 1: SFT (Supervised Fine-Tuning)
- Fine-tune the base model on high-quality demonstration data
- Human experts write ideal responses
Stage 2: Reward Model Training
- Humans choose the better of two responses (collecting preference data)
- Train reward model on preference data
is the preferred response, is the rejected response.
Stage 3: PPO Fine-Tuning
- Apply PPO with reward model as environment and LLM as agent
- KL penalty keeps the model from drifting too far from the reference
import torch
import torch.nn as nn
class RewardModel(nn.Module):
def __init__(self, base_model_name="gpt2"):
super().__init__()
from transformers import AutoModel
self.backbone = AutoModel.from_pretrained(base_model_name)
hidden_size = self.backbone.config.hidden_size
self.reward_head = nn.Linear(hidden_size, 1)
def forward(self, input_ids, attention_mask):
outputs = self.backbone(input_ids=input_ids, attention_mask=attention_mask)
last_hidden = outputs.last_hidden_state[:, -1, :]
return self.reward_head(last_hidden).squeeze(-1)
def compute_reward_model_loss(reward_model, chosen_ids, chosen_mask,
rejected_ids, rejected_mask):
"""Bradley-Terry model preference loss"""
chosen_reward = reward_model(chosen_ids, chosen_mask)
rejected_reward = reward_model(rejected_ids, rejected_mask)
loss = -torch.log(torch.sigmoid(chosen_reward - rejected_reward)).mean()
return loss
PPO fine-tuning with TRL library:
from trl import PPOTrainer, PPOConfig, AutoModelForCausalLMWithValueHead
from transformers import AutoTokenizer
ppo_config = PPOConfig(
model_name="gpt2",
learning_rate=1.41e-5,
batch_size=128,
mini_batch_size=16,
gradient_accumulation_steps=4,
target_kl=0.1,
kl_penalty="kl",
)
model = AutoModelForCausalLMWithValueHead.from_pretrained(ppo_config.model_name)
tokenizer = AutoTokenizer.from_pretrained(ppo_config.model_name)
ppo_trainer = PPOTrainer(ppo_config, model, ref_model=None, tokenizer=tokenizer)
for batch in dataloader:
query_tensors = batch["input_ids"]
response_tensors = ppo_trainer.generate(query_tensors, max_new_tokens=200)
rewards = [reward_model(q, r) for q, r in zip(query_tensors, response_tensors)]
stats = ppo_trainer.step(query_tensors, response_tensors, rewards)
DPO (Direct Preference Optimization)
DPO aligns LLMs directly from preference data without a separate reward model. It replaces the complex RL loop with a simple classification-style loss:
import torch.nn.functional as F
def dpo_loss(pi_logps_chosen, pi_logps_rejected,
ref_logps_chosen, ref_logps_rejected, beta=0.1):
"""
DPO loss function
Args:
pi_logps_chosen: Policy log-prob for preferred response
pi_logps_rejected: Policy log-prob for rejected response
ref_logps_chosen: Reference model log-prob for preferred response
ref_logps_rejected: Reference model log-prob for rejected response
beta: KL penalty strength
"""
pi_log_ratio = pi_logps_chosen - pi_logps_rejected
ref_log_ratio = ref_logps_chosen - ref_logps_rejected
# Implicit reward difference
logits = beta * (pi_log_ratio - ref_log_ratio)
loss = -F.logsigmoid(logits).mean()
return loss
DPO vs RLHF comparison:
| Aspect | RLHF + PPO | DPO |
|---|---|---|
| Reward model | Separate training required | Not needed |
| Models in memory | Actor + Critic + RM + Reference | Policy + Reference |
| Training stability | RL instability present | Supervised-learning level |
| Implementation complexity | High | Low |
6. Multi-Agent Reinforcement Learning
Environment Types
| Type | Description | Examples |
|---|---|---|
| Fully cooperative | Shared reward, team goal | Robot team tasks |
| Fully competitive | Zero-sum game | Chess, Go |
| Mixed | Cooperation + competition | Soccer, MOBA |
MADDPG (Multi-Agent DDPG)
Decentralized Execution + Centralized Training (CTDE) paradigm:
- At execution: Each agent acts using only its own observation
- At training: Critics leverage all agents' observations and actions
where is the global state, is agent 's action.
class MADDPGCritic(nn.Module):
"""Centralized critic using all agents' information"""
def __init__(self, n_agents, obs_dim, action_dim):
super().__init__()
input_dim = n_agents * (obs_dim + action_dim)
self.net = nn.Sequential(
nn.Linear(input_dim, 256), nn.ReLU(),
nn.Linear(256, 256), nn.ReLU(),
nn.Linear(256, 1)
)
def forward(self, all_obs, all_actions):
x = torch.cat([all_obs, all_actions], dim=-1)
return self.net(x)
OpenSpiel
Google DeepMind's multi-agent RL framework supporting chess, Go, poker, and more:
import pyspiel
game = pyspiel.load_game("tic_tac_toe")
state = game.new_initial_state()
while not state.is_terminal():
legal_actions = state.legal_actions()
action = legal_actions[0] # In practice, choose via policy
state.apply_action(action)
returns = state.returns()
print(f"Player 0 reward: {returns[0]}")
print(f"Player 1 reward: {returns[1]}")
7. Training Environments
Gymnasium (OpenAI Gym successor)
import gymnasium as gym
import numpy as np
class NormalizedObsWrapper(gym.ObservationWrapper):
"""Normalizes observations to [-1, 1]"""
def __init__(self, env):
super().__init__(env)
self.obs_low = env.observation_space.low
self.obs_high = env.observation_space.high
def observation(self, obs):
normalized = (
2.0 * (obs - self.obs_low)
/ (self.obs_high - self.obs_low + 1e-8)
- 1.0
)
return normalized.astype(np.float32)
class RewardShapingWrapper(gym.RewardWrapper):
"""Scales rewards"""
def __init__(self, env, scale=0.01):
super().__init__(env)
self.scale = scale
def reward(self, reward):
return reward * self.scale
base_env = gym.make("LunarLander-v2")
env = RewardShapingWrapper(NormalizedObsWrapper(base_env))
obs, info = env.reset(seed=42)
MuJoCo
Physics-based continuous control environments essential for robotics research:
- HalfCheetah-v4: Cheetah robot running (17-dim state, 6-dim action)
- Humanoid-v4: Humanoid robot walking (376-dim state, 17-dim action)
- Ant-v4: Quadruped robot (111-dim state, 8-dim action)
Benchmark performance at 1M steps:
| Environment | SAC | TD3 | PPO |
|---|---|---|---|
| HalfCheetah | ~12000 | ~9000 | ~3000 |
| Ant | ~5500 | ~4000 | ~1500 |
| Humanoid | ~5000 | ~4500 | ~600 |
Isaac Gym / Isaac Lab
NVIDIA's GPU-accelerated physics simulator running thousands of environments in parallel:
- 2000x faster training than CPU simulation
- Domain Randomization for better sim-to-real transfer
- Train robots in hours rather than weeks
# Isaac Lab example (simplified)
from isaaclab.envs import DirectRLEnvCfg
class CartpoleEnvCfg(DirectRLEnvCfg):
num_envs = 4096 # 4096 parallel environments
episode_length_s = 5.0
decimation = 2
action_scale = 100.0
# 4096 CartPole environments running in parallel on GPU
# obs.shape: [4096, obs_dim]
Real-World RL Deployment Considerations
- Safe RL: Prevent dangerous actions during training — Constrained Policy Optimization (CPO)
- Sample efficiency: Real-world data is expensive and slow — use offline RL or model-based RL
- Sim-to-Real transfer: Domain Randomization and adaptation layers (RMA) to close the Reality Gap
- Offline RL: Learn from pre-collected datasets only — Conservative Q-Learning (CQL), IQL
Quiz: Test Your RL Knowledge
Q1. Explain the on-policy vs off-policy difference between Q-Learning and SARSA.
Answer: Q-Learning is off-policy; SARSA is on-policy.
Explanation: Q-Learning's update target is , using the greedy maximum regardless of the actual action taken. SARSA's target uses , the action actually taken. In the CliffWalking problem, Q-Learning learns the optimal edge path but falls often during exploration; SARSA learns a safer, longer detour.
Q2. What role does the epsilon clip ratio hyperparameter play in PPO's training stability?
Answer: Epsilon controls the conservatism of policy updates.
Explanation: When falls outside , the gradient is blocked. If is too small, learning slows and gets stuck in local minima. If too large, the policy changes dramatically and becomes unstable. A value of is a reliable default; annealing it toward zero during training is also effective.
Q3. How is training data for the reward model collected in RLHF?
Answer: Human annotators compare pairs of responses and indicate which is preferred.
Explanation: Annotators are shown two responses to the same prompt and select the better one. Pairwise comparison is more consistent and reliable than absolute ratings (e.g., 1–5 stars). The collected preference data is used to train a Bradley-Terry model. Anthropic's Constitutional AI uses RLAIF, where an AI itself plays the role of the preference annotator.
Q4. Why is DPO simpler to implement than PPO-based RLHF?
Answer: DPO requires neither a separate reward model nor an RL training loop.
Explanation: RLHF+PPO requires three stages (SFT, reward model training, PPO fine-tuning) and simultaneously loads multiple models (actor, critic, reward model, reference model) into GPU memory. DPO integrates the implicit reward directly into the loss formula from preference data, enabling fine-tuning with a single cross-entropy-style loss. Only two models are needed (policy + reference), and there is no RL-specific instability.
Q5. What role does entropy regularization play in Soft Actor-Critic?
Answer: Encourages exploration and learns robust policies across multiple optima.
Explanation: Adding the entropy term to SAC's objective makes the agent maximize reward while maintaining action randomness. This provides an automatic exploration mechanism, helping escape local minima. When multiple actions are equally good, the policy learns to choose among them uniformly, resulting in more robust behavior. The temperature is automatically tuned to match a target entropy level.
Algorithm Comparison Summary
| Algorithm | Policy Type | Action Space | Key Feature |
|---|---|---|---|
| Q-Learning | Off-policy | Discrete | Simple, table-based |
| DQN | Off-policy | Discrete | Neural net + ER + TN |
| Double DQN | Off-policy | Discrete | Reduces overestimation |
| Dueling DQN | Off-policy | Discrete | V + A decomposition |
| REINFORCE | On-policy | Discrete/Cont. | High variance |
| A2C | On-policy | Discrete/Cont. | Actor-Critic |
| PPO | On-policy | Discrete/Cont. | Stable, general-purpose |
| SAC | Off-policy | Continuous | Max entropy |
| TD3 | Off-policy | Continuous | Deterministic SAC variant |
| HER | Off-policy | Goal-based | Sparse reward |
Conclusion
Reinforcement learning is expanding explosively beyond game-playing AI into robotics, LLM alignment, autonomous driving, and drug discovery. RLHF and DPO in particular are core alignment technologies for LLMs like ChatGPT, Claude, and Gemini, making RL literacy an essential skill for modern AI practitioners.
Recommended learning path:
- Implement Q-Learning and DQN from scratch with Gymnasium
- Experiment with PPO and SAC via Stable-Baselines3
- Practice RLHF/DPO with the TRL library
- Explore robotics RL with Isaac Lab
References:
- Sutton & Barto, "Reinforcement Learning: An Introduction" (2nd ed.)
- Spinning Up in Deep RL (OpenAI)
- Stable-Baselines3 documentation
- TRL (Transformer Reinforcement Learning) by Hugging Face
- Isaac Lab documentation