强化学习(四)—— 探索策略与好奇心驱动学习
Chen Kai BOSS

强化学习的核心难题之一是探索-利用困境(Exploration-Exploitation Dilemma)。一个智能体如果只利用已知的好策略,可能永远发现不了更优解;但如果探索过度,又会浪费大量时间在低回报行为上。传统方法如-greedy 和 Boltzmann 探索依赖随机性,在高维状态空间和稀疏奖励任务中效率极低——想象在《蒙特祖玛的复仇》这样的游戏中,智能体需要执行数百步精确操作才能获得第一个奖励,纯随机探索几乎不可能成功。近年来,受认知科学中"内在动机"理论启发,研究者提出了好奇心驱动学习(Curiosity-Driven Learning)——通过内在奖励(Intrinsic Reward)奖励智能体探索新颖状态,即使外部奖励为零也能持续学习。从基于计数的方法到 ICM 的预测误差、从 RND 的随机网络蒸馏到 NGU 的情节记忆,探索策略已经发展出一套完整的理论和工程体系。本章将系统梳理这一演进路径,深入剖析每种方法的设计动机、数学原理和实现细节,最终在 Atari 硬探索游戏上验证其有效性。

探索-利用困境

![](./强化学习(四)—— 探索策略与好奇心驱动学习/gif1_epsilon_greedy.gif)

:问题的本质

为什么探索如此困难

在第二章的 DQN 和第三章的策略梯度方法中,我们都假设智能体能通过探索收集到有效的训练数据。但在实际应用中,探索本身就是一个核心挑战:

问题 1:稀疏奖励与延迟反馈

考虑经典的 Montezuma’s Revenge 游戏:智能体需要先拿钥匙、再开门、再跳过敌人,执行上百步操作后才能获得第一个+100 分奖励。在这之前,所有状态的回报都是 0 。如果用-greedy 随机探索,找到有效轨迹的概率是(假设每步有 10 个动作),实际上不可能发生。

问题 2:高维状态空间的稀疏性

Atari 游戏的状态是 的像素,总共约 种可能状态。即使智能体探索了一百万个状态,覆盖率也只有——绝大多数状态永远不会被访问到。

问题 3:局部最优的吸引盆

在有奖励的环境中,智能体容易陷入局部最优。比如在迷宫中,如果某个死胡同里有一个小奖励,智能体可能会反复收集这个小奖励,而不去探索通往大奖励的路径。

问题 4:确定性环境的欺骗性

在确定性环境中,-greedy 会导致智能体在同一个状态反复尝试同一个随机动作,浪费大量时间。更糟的是,某些"有趣"的状态(如电视噪声)会让智能体产生"伪探索",以为自己在学习新东西,实际上毫无价值。

传统探索策略的局限

-greedy 探索

在训练初期,以概率选择随机动作,概率选择贪心动作:

$$

a =
$$

优点:简单有效,适合低维离散空间(如 FrozenLake)。

缺点:

  • 探索是无目标的——在高维空间中,随机动作几乎不可能碰到有意义的状态
  • 的衰减策略难以调节——太快会过早收敛,太慢会影响性能
  • 在连续动作空间效果更差——给动作加噪声 容易产生不合理的动作

Boltzmann 探索(Softmax)

根据 Q 值的 softmax 分布采样动作:

其中 是温度参数,控制探索程度。 时趋近贪心, 时趋近均匀随机。

优点:根据 Q 值大小自适应调整探索,理论上更优雅。

缺点:

  • 仍然是基于随机性的无目标探索
  • 在 Q 值估计不准时(训练初期)容易产生偏差
  • 计算 softmax 需要遍历所有动作,在大动作空间不可行

Upper Confidence Bound(UCB)

在多臂老虎机问题中,UCB 算法选择:

$$

a^{*} = \arg\max_a \left[ Q(a) + c\sqrt{\frac{\ln N}{N(a)}} \right]
$$

其中 是总采样次数, 是动作 的采样次数, 是探索系数。第二项是"乐观加成"——越少尝试的动作,置信上界越高。

优点:理论上有遗憾界保证(Regret Bound),在老虎机问题中接近最优。

缺点:

  • 需要显式统计每个状态-动作对的访问次数
  • 在大状态空间中,绝大多数 只被访问一次,,置信区间失效
  • 无法泛化到未见过的状态

Thompson 采样

维护 Q 值的后验分布 ,每次采样一个 Q 函数并贪心选择:

优点:贝叶斯框架优雅,探索自然地来源于不确定性。

缺点:

  • 需要维护整个 Q 函数的分布,在深度网络中计算昂贵(虽然有 Bootstrapped DQN 等近似方法)
  • 仍然依赖于 Q 值估计,在稀疏奖励下 Q 值全为 0 时退化为随机探索

探索的本质

![](./强化学习(四)—— 探索策略与好奇心驱动学习/fig1_exploration_comparison.png)

:寻找新颖性

观察人类和动物的探索行为,我们发现一个共同特征:不是随机乱走,而是主动寻找新奇、意外、不确定的事物。婴儿会反复摆弄新玩具(而不是旧玩具),猴子会主动探索迷宫的未知区域,科学家会追求违反常识的实验现象。

这种"好奇心"可以形式化为内在奖励(Intrinsic Reward)——即使外部环境没有给奖励,智能体也会因为状态的新颖性而获得内部奖励:

$$

r_{\text{total}} = r_{\text{ext}} + \beta \cdot r_{\text{int}}
$$

其中:

  • 是环境给的外在奖励(任务目标)
  • 是智能体自己计算的内在奖励(探索动机)
  • 是平衡系数

关键问题:如何定义和计算,使其真正衡量状态的新颖性?

Count-Based 方法:显式统计访问频率

核心思想

最直观的"新颖性"定义是:这个状态我以前见过多少次? 访问越少,越新颖,内在奖励越高。

最简单的实现是用访问计数 的倒数作为内在奖励:

$$

r_{\text{int}} (s) = \frac{1}{\sqrt{N(s)}}
$$

或者更激进的伪计数(Pseudo-Count):

$$

r_{\text{int}} (s) = \frac{\beta}{\sqrt{N(s) + 0.01}}
$$

理论依据:这相当于在最大化状态访问的熵,鼓励均匀覆盖状态空间。

朴素实现:哈希表计数

在小离散状态空间(如 GridWorld),可以直接用字典记录:

1
2
3
4
5
6
7
8
9
10
11
class CountBasedExploration:
def __init__(self, beta=0.1):
self.counts = {} # 状态 -> 访问次数
self.beta = beta

def get_intrinsic_reward(self, state):
# 将状态转为可哈希的 tuple
state_key = tuple(state.flatten())
count = self.counts.get(state_key, 0)
self.counts[state_key] = count + 1
return self.beta / (count + 1)**0.5

问题:在高维连续状态空间(如 Atari 像素),状态几乎从不重复, 恒成立,退化为常数奖励。

改进:密度模型与伪计数

**Bellemare et al.(2016)**在论文Unifying Count-Based Exploration and Intrinsic Motivation中提出用密度模型 估计状态的"出现频率":

$$

r_{\text{int}} (s) = \frac{\beta}{\sqrt{\rho(s)}}
$$

其中 由生成模型(如 PixelCNN)学习。具体来说:

  1. 维护一个生成模型,用似然估计状态的密度
  2. 在线更新:每访问状态,最大化3. 计算伪计数:,其中 归一化到
    数学推导

在理想情况下,如果 是经验分布,那么:

其中 是访问 后的分布。因此:

$$

N(s) = \frac{1}{p’(s)/p(s) - 1}
$$

实际中,用神经网络 近似,计算访问前后的似然比:

代码实现(简化版)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import torch
import torch.nn as nn

class DensityModel(nn.Module):
"""用 PixelCNN 风格的自回归模型估计状态密度"""
def __init__(self, obs_shape):
super().__init__()
# 简化:用 CNN 直接输出 log-likelihood
self.net = nn.Sequential(
nn.Conv2d(obs_shape[0], 32, 3, padding=1),
nn.ReLU(),
nn.Conv2d(32, 32, 3, padding=1),
nn.ReLU(),
nn.Conv2d(32, 256, 1) # 输出每个像素 256 个类别的 logits
)

def forward(self, obs):
"""obs: (B, C, H, W), 返回 log p(obs)"""
logits = self.net(obs) # (B, 256, H, W)
# 计算每个像素的 log-likelihood(假设像素值已归一化到 0-255)
log_probs = -nn.functional.cross_entropy(
logits.view(-1, 256),
obs.flatten(1).long(),
reduction='none'
)
return log_probs.sum()

class CountBasedIntrinsic:
def __init__(self, obs_shape, beta=0.05, lr=1e-4):
self.model = DensityModel(obs_shape)
self.optimizer = torch.optim.Adam(self.model.parameters(), lr=lr)
self.beta = beta

def compute_intrinsic_reward(self, obs):
"""计算内在奖励并更新密度模型"""
obs_tensor = torch.FloatTensor(obs).unsqueeze(0)

# 计算更新前的密度
with torch.no_grad():
log_p_old = self.model(obs_tensor)
p_old = torch.exp(log_p_old)

# 更新模型
log_p_new = self.model(obs_tensor)
loss = -log_p_new
self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()

# 重新计算更新后的密度
with torch.no_grad():
log_p_new = self.model(obs_tensor)
p_new = torch.exp(log_p_new)

# 伪计数
pseudo_count = p_old / (p_new - p_old + 1e-8)
pseudo_count = torch.clamp(pseudo_count, 1, 1e6) # 防止数值问题

intrinsic_reward = self.beta / torch.sqrt(pseudo_count)
return intrinsic_reward.item()

优势与局限

优势:

  • 理论清晰,直接来源于访问频率的统计意义
  • 在小规模问题上效果显著(如 Montezuma’s Revenge 的早期版本)

局限:

  • 密度模型难训练—— PixelCNN 等模型计算昂贵,容易过拟合
  • “电视噪声问题”(Noisy-TV Problem):在随机环境中,每个状态都是"新"的(因为像素噪声),内在奖励失效
  • 无法泛化——两个视觉上相似但像素值不同的状态,会被认为完全不同

ICM:通过预测误差衡量新颖性

![](./强化学习(四)—— 探索策略与好奇心驱动学习/gif2_curiosity_driven.gif)

动机:从像素到特征

Count-Based 方法直接在原始像素上计数,忽略了一个事实:有些像素变化是无关紧要的(如背景树叶晃动),而有些变化是关键的(如敌人移动)。

Curiosity-Driven Exploration by Self-Supervised Prediction(Pathak et al., ICML 2017)提出 ICM(Intrinsic Curiosity Module):不直接比较像素,而是比较智能体能否预测下一个状态的特征

核心洞察:

  • 如果状态转移可预测 → 智能体已经理解了环境动态 → 奖励低
  • 如果状态转移不可预测 → 智能体遇到了新现象 → 奖励高

ICM 架构:正向模型与逆向模型

ICM 包含两个模块:

1. 特征提取器(Feature Encoder)

将原始状态 映射到低维特征:

通常用卷积网络实现,只保留与智能体动作相关的信息,过滤掉背景噪声。

2. 正向动态模型(Forward Model)

预测下一个状态的特征:

其中 是一个小型 MLP 。正向模型的预测误差即为内在奖励:

$$

r_{\text{int}} (s_t, a_t, s_{t+1}) = \eta |\hat{\phi}(s_{t+1}) - \phi(s_{t+1})|^2
$$

3. 逆向动态模型(Inverse Model)

从特征预测动作(自监督辅助任务):

$$\hat{a}t = g(\phi(s_t), \phi(s{t+1}))
$$

逆向模型的作用是强制特征编码器只保留与动作相关的信息。如果 只编码背景,逆向模型无法预测动作;只有编码了智能体和交互物体的信息,才能成功预测。

数学直觉

为什么预测误差能衡量新颖性?考虑两个极端:

情况 1:智能体在空房间随机走

状态转移是确定性的:。正向模型很快学会完美预测,,智能体停止探索这个区域。

情况 2:智能体进入新房间,遇到移动敌人

敌人的行为未知,正向模型预测误差大,高,智能体被激励探索这个区域。随着交互增多,模型逐渐学会敌人的行为模式, 下降。

情况 3:电视噪声(随机像素)

原始像素完全随机,但如果特征编码器正确工作,应该将噪声映射到相同的特征(因为噪声与动作无关)。逆向模型的监督信号强制 忽略噪声,因此,,解决了电视噪声问题!

完整代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import torch
import torch.nn as nn
import torch.nn.functional as F

class ICM(nn.Module):
"""内在好奇心模块 (Intrinsic Curiosity Module)"""
def __init__(self, obs_shape, action_dim, feature_dim=256, eta=0.1):
super().__init__()
self.eta = eta # 内在奖励缩放因子

# 特征编码器:输入 obs,输出 feature_dim 维特征
self.feature_encoder = nn.Sequential(
nn.Conv2d(obs_shape[0], 32, 8, stride=4),
nn.ReLU(),
nn.Conv2d(32, 64, 4, stride=2),
nn.ReLU(),
nn.Conv2d(64, 64, 3, stride=1),
nn.ReLU(),
nn.Flatten(),
nn.Linear(64 * 7 * 7, feature_dim) # 假设输入是 84x84
)

# 正向模型:输入(feature_t, action),输出 feature_{t+1}
self.forward_model = nn.Sequential(
nn.Linear(feature_dim + action_dim, 256),
nn.ReLU(),
nn.Linear(256, feature_dim)
)

# 逆向模型:输入(feature_t, feature_{t+1}),输出 action
self.inverse_model = nn.Sequential(
nn.Linear(feature_dim * 2, 256),
nn.ReLU(),
nn.Linear(256, action_dim)
)

def forward(self, obs, next_obs, action):
"""
计算内在奖励和损失
Args:
obs: (B, C, H, W)
next_obs: (B, C, H, W)
action: (B,) or (B, action_dim)
Returns:
intrinsic_reward: (B,)
forward_loss: scalar
inverse_loss: scalar
"""
# 特征提取
phi = self.feature_encoder(obs)
phi_next = self.feature_encoder(next_obs)

# 正向模型
if action.ndim == 1: # 离散动作,one-hot 编码
action_onehot = F.one_hot(action, num_classes=self.inverse_model[-1].out_features).float()
else:
action_onehot = action

phi_next_pred = self.forward_model(torch.cat([phi, action_onehot], dim=1))
forward_loss = F.mse_loss(phi_next_pred, phi_next.detach(), reduction='none').sum(dim=1)

# 内在奖励 = 正向模型预测误差
intrinsic_reward = self.eta * forward_loss

# 逆向模型
action_pred_logits = self.inverse_model(torch.cat([phi, phi_next], dim=1))
if action.ndim == 1:
inverse_loss = F.cross_entropy(action_pred_logits, action, reduction='none')
else:
inverse_loss = F.mse_loss(action_pred_logits, action, reduction='none').sum(dim=1)

return intrinsic_reward, forward_loss.mean(), inverse_loss.mean()

def compute_intrinsic_reward(self, obs, next_obs, action):
"""仅计算内在奖励(推理时使用)"""
with torch.no_grad():
reward, _, _ = self.forward(obs, next_obs, action)
return reward

class PPO_with_ICM:
"""集成 ICM 的 PPO 算法"""
def __init__(self, env, obs_shape, action_dim, lr=3e-4, beta_int=0.01):
self.env = env
self.beta_int = beta_int # 内在奖励系数

# PPO 策略网络(省略,与第三章相同)
self.policy = PolicyNetwork(obs_shape, action_dim)

# ICM 模块
self.icm = ICM(obs_shape, action_dim)

# 优化器:同时优化策略和 ICM
self.optimizer = torch.optim.Adam(
list(self.policy.parameters()) + list(self.icm.parameters()),
lr=lr
)

def collect_trajectories(self, num_steps):
"""收集轨迹并计算混合奖励"""
trajectories = []
obs = self.env.reset()

for _ in range(num_steps):
# 选择动作
obs_tensor = torch.FloatTensor(obs).unsqueeze(0)
with torch.no_grad():
action_dist = self.policy(obs_tensor)
action = action_dist.sample()

# 环境交互
next_obs, reward_ext, done, _ = self.env.step(action.item())

# 计算内在奖励
next_obs_tensor = torch.FloatTensor(next_obs).unsqueeze(0)
reward_int = self.icm.compute_intrinsic_reward(
obs_tensor, next_obs_tensor, action
).item()

# 混合奖励
reward_total = reward_ext + self.beta_int * reward_int

trajectories.append({
'obs': obs,
'action': action.item(),
'reward': reward_total,
'reward_ext': reward_ext,
'reward_int': reward_int,
'next_obs': next_obs,
'done': done
})

obs = next_obs if not done else self.env.reset()

return trajectories

def update(self, trajectories):
"""PPO 更新 + ICM 更新"""
# 转换为 tensor
obs = torch.FloatTensor([t['obs'] for t in trajectories])
actions = torch.LongTensor([t['action'] for t in trajectories])
next_obs = torch.FloatTensor([t['next_obs'] for t in trajectories])
rewards = torch.FloatTensor([t['reward'] for t in trajectories])

# ICM 损失
_, forward_loss, inverse_loss = self.icm(obs, next_obs, actions)
icm_loss = forward_loss + inverse_loss

# PPO 损失(省略计算,见第三章)
policy_loss = self.compute_ppo_loss(trajectories)

# 总损失
total_loss = policy_loss + icm_loss

self.optimizer.zero_grad()
total_loss.backward()
self.optimizer.step()

return {
'policy_loss': policy_loss.item(),
'forward_loss': forward_loss.item(),
'inverse_loss': inverse_loss.item()
}

实验效果

在 Pathak et al.的论文中,ICM 在 VizDoom 和 Super Mario Bros 等稀疏奖励任务上取得突破:

  • 无外部奖励():智能体仅靠内在奖励就能学会探索地图、避开敌人
  • Montezuma’s Revenge:在 2500 万步内达到平均 6600 分(DQN 只有 0 分)
  • 泛化能力:在 Mario 的新关卡上,ICM 探索的距离比 baseline 多 3 倍

论文链接:arXiv:1705.05363

局限性

  1. 依赖环境的确定性:在随机环境中,不可预测不一定等于新颖(可能只是噪声)
  2. 特征编码器的质量:如果 学习失败,整个 ICM 失效
  3. 计算开销:需要额外训练 3 个网络(encoder 、 forward 、 inverse),比原始 PPO 慢约 2 倍

RND:随机网络蒸馏

![](./强化学习(四)—— 探索策略与好奇心驱动学习/fig2_rnd_architecture.png)

动机:避免训练动态模型

ICM 的一个问题是需要训练正向模型 来预测。在复杂环境中,动态模型本身就很难学,如果学不好,内在奖励就不可靠。

Exploration by Random Network Distillation(Burda et al., ICLR 2019)提出 RND:完全避免动态建模,改为预测随机网络的输出

核心思想:

  • 固定一个随机初始化的目标网络(从不训练)
  • 训练一个预测网络 去拟合目标网络的输出
  • 预测误差 作为内在奖励

为什么这样有效?

  • 对于经常访问的状态,预测网络见过很多次,预测误差小 →
  • 对于新状态,预测网络没见过,预测误差大 →
  • 随机网络充当"哈希函数",将相似状态映射到相似输出,自然实现泛化

更妙的是,这避免了"电视噪声问题":即使像素随机,随机网络的输出对于同类状态(如"有噪声的屏幕")是相似的,预测网络很快学会忽略噪声。

数学形式化

定义:

  • 目标网络:(随机初始化,固定)
  • 预测网络:(训练中)

内在奖励:

$$

r_{\text{int}} (s) = |g_{\text{pred}} (s) - g_{\text{target}} (s)|^2
$$

预测网络的损失:

$$

L_{\text{RND}} = \mathbb{E}{s \sim \text{buffer}} \left[ |g{\text{pred}} (s) - g_{\text{target}} (s)|^2 \right]
$$

关键细节:为了让内在奖励有意义的尺度,通常对 做归一化:

$$\tilde{r}{\text{int}} = \frac{r{\text{int}} - \mu}{\sigma}
$$

其中 是内在奖励的滑动平均和标准差。

完整代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np

class RandomNetwork(nn.Module):
"""随机初始化的目标网络(不训练)"""
def __init__(self, obs_shape, output_dim=512):
super().__init__()
self.net = nn.Sequential(
nn.Conv2d(obs_shape[0], 32, 8, stride=4),
nn.LeakyReLU(),
nn.Conv2d(32, 64, 4, stride=2),
nn.LeakyReLU(),
nn.Conv2d(64, 64, 3, stride=1),
nn.LeakyReLU(),
nn.Flatten(),
nn.Linear(64 * 7 * 7, output_dim)
)
# 固定参数
for param in self.parameters():
param.requires_grad = False

def forward(self, obs):
return self.net(obs)

class PredictorNetwork(nn.Module):
"""训练的预测网络"""
def __init__(self, obs_shape, output_dim=512):
super().__init__()
self.net = nn.Sequential(
nn.Conv2d(obs_shape[0], 32, 8, stride=4),
nn.LeakyReLU(),
nn.Conv2d(32, 64, 4, stride=2),
nn.LeakyReLU(),
nn.Conv2d(64, 64, 3, stride=1),
nn.LeakyReLU(),
nn.Flatten(),
nn.Linear(64 * 7 * 7, 512),
nn.ReLU(),
nn.Linear(512, output_dim)
)

def forward(self, obs):
return self.net(obs)

class RND:
"""随机网络蒸馏模块"""
def __init__(self, obs_shape, output_dim=512, lr=1e-4):
self.target_net = RandomNetwork(obs_shape, output_dim)
self.predictor_net = PredictorNetwork(obs_shape, output_dim)
self.optimizer = torch.optim.Adam(self.predictor_net.parameters(), lr=lr)

# 归一化统计量
self.reward_mean = 0.0
self.reward_std = 1.0
self.reward_history = []

def compute_intrinsic_reward(self, obs):
"""
计算内在奖励
Args:
obs: (B, C, H, W)
Returns:
intrinsic_reward: (B,)
"""
with torch.no_grad():
target_feat = self.target_net(obs)
pred_feat = self.predictor_net(obs)
reward = torch.pow(pred_feat - target_feat, 2).sum(dim=1)

return reward

def update(self, obs_batch):
"""更新预测网络"""
target_feat = self.target_net(obs_batch).detach()
pred_feat = self.predictor_net(obs_batch)
loss = F.mse_loss(pred_feat, target_feat)

self.optimizer.zero_grad()
loss.backward()
self.optimizer.step()

return loss.item()

def normalize_reward(self, reward):
"""归一化内在奖励"""
self.reward_history.append(reward.mean().item())
if len(self.reward_history) > 1000:
self.reward_history.pop(0)

self.reward_mean = np.mean(self.reward_history)
self.reward_std = np.std(self.reward_history) + 1e-8

return (reward - self.reward_mean) / self.reward_std

class PPO_with_RND:
"""集成 RND 的 PPO 算法"""
def __init__(self, env, obs_shape, action_dim, lr=3e-4, beta_int=1.0, gamma_int=0.99):
self.env = env
self.beta_int = beta_int # 内在奖励系数
self.gamma_int = gamma_int # 内在奖励的折扣因子(通常接近 1)

# 策略网络(省略)
self.policy = PolicyNetwork(obs_shape, action_dim)

# RND 模块
self.rnd = RND(obs_shape)

# 两个价值网络:一个预测外在回报,一个预测内在回报
self.value_ext = ValueNetwork(obs_shape)
self.value_int = ValueNetwork(obs_shape)

self.optimizer = torch.optim.Adam(
list(self.policy.parameters()) +
list(self.value_ext.parameters()) +
list(self.value_int.parameters()),
lr=lr
)

def collect_trajectories(self, num_steps):
"""收集轨迹并计算双重回报"""
trajectories = []
obs = self.env.reset()

for _ in range(num_steps):
obs_tensor = torch.FloatTensor(obs).unsqueeze(0)

with torch.no_grad():
# 选择动作
action = self.policy(obs_tensor).sample()

# 预测价值
v_ext = self.value_ext(obs_tensor)
v_int = self.value_int(obs_tensor)

# 环境交互
next_obs, reward_ext, done, _ = self.env.step(action.item())

# 计算内在奖励
next_obs_tensor = torch.FloatTensor(next_obs).unsqueeze(0)
reward_int = self.rnd.compute_intrinsic_reward(next_obs_tensor)
reward_int = self.rnd.normalize_reward(reward_int).item()

trajectories.append({
'obs': obs,
'action': action.item(),
'reward_ext': reward_ext,
'reward_int': reward_int,
'v_ext': v_ext.item(),
'v_int': v_int.item(),
'done': done
})

obs = next_obs if not done else self.env.reset()

return trajectories

def compute_gae(self, trajectories, gamma, lam):
"""计算 GAE(外在和内在分别计算)"""
advantages_ext = []
advantages_int = []

gae_ext = 0
gae_int = 0

for t in reversed(range(len(trajectories))):
delta_ext = trajectories[t]['reward_ext'] + gamma * trajectories[t+1]['v_ext'] * (1 - trajectories[t]['done']) - trajectories[t]['v_ext']
delta_int = trajectories[t]['reward_int'] + self.gamma_int * trajectories[t+1]['v_int'] - trajectories[t]['v_int'] # 内在奖励不考虑 done

gae_ext = delta_ext + gamma * lam * (1 - trajectories[t]['done']) * gae_ext
gae_int = delta_int + self.gamma_int * lam * gae_int

advantages_ext.insert(0, gae_ext)
advantages_int.insert(0, gae_int)

# 合并优势
advantages_total = [a_ext + self.beta_int * a_int for a_ext, a_int in zip(advantages_ext, advantages_int)]
return advantages_total, advantages_ext, advantages_int

def update(self, trajectories):
"""更新策略、价值网络和 RND"""
obs = torch.FloatTensor([t['obs'] for t in trajectories])

# 更新 RND
rnd_loss = self.rnd.update(obs)

# 计算 GAE 和 PPO 更新(省略细节)
# ...

return {'rnd_loss': rnd_loss}

实验效果

Burda et al.在论文中展示了 RND 的惊人效果:

  • Montezuma’s Revenge:平均得分 8152(首次超越人类水平 7385)
  • Pitfall:得分 70.4(之前的 SOTA 是 0)
  • Gravitar:从 DQN 的 0 分提升到 3500+分

关键发现:

  1. RND 在所有 Atari 硬探索游戏上都优于 ICM
  2. 内在奖励不需要折扣(),因为探索是长期目标
  3. 归一化至关重要——不归一化会导致内在奖励尺度爆炸

论文链接:arXiv:1810.12894

NGU:情节记忆与快慢探索

动机:从全局新颖到情节新颖

RND 有一个隐含假设:一个状态访问过一次,就永远不新颖了。但在实际探索中,这过于严格:

场景 1:钥匙-门任务

在 Montezuma’s Revenge 中,智能体需要反复"拿钥匙 → 开门 → 进房间"。如果第一次拿完钥匙后,RND 认为钥匙所在状态不再新颖,智能体就失去了再次拿钥匙的动力。

场景 2:可逆探索

智能体先探索右侧房间,再探索左侧房间。如果回到起点,RND 给的奖励很低(因为起点访问过很多次),但这个"回到起点"是探索左侧的必经之路。

Never Give Up (NGU)(Badia et al., ICLR 2020)提出情节记忆(Episodic Memory):不仅考虑状态的全局新颖性,还考虑在当前 episode 中是否新颖

核心设计

NGU 的内在奖励包含两部分:

$$

r_{\text{int}} (s_t) = r_{\text{episodic}} (s_t) \cdot \min{\max{r_{\text{lifetime}} (s_t), 1}, L}$$

其中:

  • :情节新颖性——在当前 episode 中, 距离最近邻状态有多远
  • :终身新颖性—— 在整个训练历史中被访问的频率(类似 RND)
  • :上界,防止奖励爆炸

1. 情节新颖性(Episodic Novelty)

维护一个情节记忆,存储当前 episode 中所有状态的嵌入(由可训练网络学习)。

计算 到记忆中 k 个最近邻的距离:

$$

d_k(s_t) = \sqrt{\frac{\sum_{i=1}^k d(f(s_t), f(s_i))^2}{k}} + c
$$

其中 是欧氏距离, 是小常数防止除零。

然后用核函数转换为相似度:

$$

K(s_t, s_i) = \frac{\epsilon}{\frac{d(f(s_t), f(s_i))^2}{\mu} + \epsilon}
$$

其中 是滑动平均的距离, 是小常数。

情节新颖性定义为:

$$

r_{\text{episodic}} (s_t) = \frac{1}{\sqrt{\sum_{i=1}^n K(s_t, s_i)} + c}
$$

直觉:如果 与记忆中的状态都很远,分母小, 大;如果 与某个状态很近(访问过),分母大,奖励小。每个 episode 结束时清空记忆,下个 episode 重新计算。

2. 终身新颖性(Lifetime Novelty)

使用 RND 的预测误差:

$$

r_{\text{lifetime}} (s_t) = |g_{\text{pred}} (s_t) - g_{\text{target}} (s_t)|^2
$$

归一化后作为长期探索信号。

3. 自适应探索系数

NGU 训练多个智能体,每个有不同的探索系数:

$$

r_{\text{total}} ^{(i)} = r_{\text{ext}} + \beta_i \cdot r_{\text{int}}
$$

的智能体专注探索,低 的智能体专注利用。所有智能体共享经验池,形成"探索-利用谱系"。

简化实现(伪代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import torch
import numpy as np
from collections import deque

class EpisodicMemory:
"""情节记忆模块"""
def __init__(self, capacity=5000, k=10):
self.memory = deque(maxlen=capacity)
self.k = k # k 近邻
self.running_mean_dist = 1.0
self.epsilon = 0.001
self.c = 0.001

def add(self, embedding):
"""添加状态嵌入到记忆"""
self.memory.append(embedding.cpu().numpy())

def compute_novelty(self, embedding):
"""计算情节新颖性"""
if len(self.memory) == 0:
return 1.0 # 首个状态,最高奖励

# 计算到所有记忆的距离
emb_np = embedding.cpu().numpy()
distances = [np.linalg.norm(emb_np - mem) for mem in self.memory]

# k 近邻距离
k_nearest = sorted(distances)[:self.k]
dk = np.sqrt(np.sum(np.square(k_nearest)) / self.k) + self.c

# 更新滑动平均距离
self.running_mean_dist = 0.99 * self.running_mean_dist + 0.01 * np.mean(distances)

# 核函数相似度
similarities = []
for d in distances:
kernel = self.epsilon / ((d**2 / self.running_mean_dist) + self.epsilon)
similarities.append(kernel)

# 情节新颖性
novelty = 1.0 / (np.sqrt(sum(similarities)) + self.c)
return novelty

def reset(self):
"""Episode 结束,清空记忆"""
self.memory.clear()

class EmbeddingNetwork(nn.Module):
"""可训练的状态嵌入网络"""
def __init__(self, obs_shape, embed_dim=128):
super().__init__()
self.net = nn.Sequential(
nn.Conv2d(obs_shape[0], 32, 8, stride=4),
nn.ReLU(),
nn.Conv2d(32, 64, 4, stride=2),
nn.ReLU(),
nn.Conv2d(64, 64, 3, stride=1),
nn.ReLU(),
nn.Flatten(),
nn.Linear(64 * 7 * 7, embed_dim)
)

def forward(self, obs):
return self.net(obs)

class NGU:
"""Never Give Up 探索模块"""
def __init__(self, obs_shape, beta=0.3, L=5.0):
self.beta = beta
self.L = L

# 情节记忆
self.episodic_memory = EpisodicMemory()

# 可训练嵌入网络
self.embedding_net = EmbeddingNetwork(obs_shape)

# RND 用于终身新颖性
self.rnd = RND(obs_shape)

def compute_intrinsic_reward(self, obs):
"""计算混合内在奖励"""
# 情节新颖性
with torch.no_grad():
embedding = self.embedding_net(obs)
episodic_novelty = self.episodic_memory.compute_novelty(embedding)
self.episodic_memory.add(embedding)

# 终身新颖性
lifetime_novelty = self.rnd.compute_intrinsic_reward(obs).item()
lifetime_novelty = max(min(lifetime_novelty, self.L), 1.0)

# 组合
intrinsic_reward = episodic_novelty * lifetime_novelty
return intrinsic_reward

def reset_episode(self):
"""Episode 结束,重置情节记忆"""
self.episodic_memory.reset()

实验效果

NGU 在 DeepMind Lab 和 Atari 上取得了史无前例的探索性能:

  • Pitfall:从 RND 的 70 分提升到 5000+分
  • Private Eye:首次解决该游戏(69000 分)
  • Montezuma’s Revenge:平均 11000 分,超越人类高手

关键发现:

  1. 情节记忆至关重要——去掉后性能下降 50%+
  2. 多智能体探索谱系加速学习——相当于同时训练探索者和利用者
  3. 嵌入网络需要谨慎训练——论文用对比学习(Contrastive Learning)优化
    论文链接:arXiv:2002.06038

实战案例:在 Montezuma’s Revenge 上实现 ICM+PPO

环境准备

1
2
3
4
5
# 安装依赖
pip install gym[atari] torch torchvision opencv-python

# 安装 Atari ROM
pip install autorom[accept-rom-license]

完整训练代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
import gym
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
from collections import deque
import cv2

# ========== 环境包装器 ==========
class AtariWrapper:
"""Atari 环境预处理"""
def __init__(self, env_name):
self.env = gym.make(env_name)
self.frame_stack = deque(maxlen=4)

def reset(self):
obs = self.env.reset()
obs = self._preprocess(obs)
for _ in range(4):
self.frame_stack.append(obs)
return np.array(self.frame_stack)

def step(self, action):
total_reward = 0
for _ in range(4): # Frame skip
obs, reward, done, info = self.env.step(action)
total_reward += reward
if done:
break

obs = self._preprocess(obs)
self.frame_stack.append(obs)
return np.array(self.frame_stack), total_reward, done, info

def _preprocess(self, obs):
"""转灰度、裁剪、缩放到 84x84"""
gray = cv2.cvtColor(obs, cv2.COLOR_RGB2GRAY)
resized = cv2.resize(gray, (84, 84))
return resized / 255.0

# ========== 神经网络 ==========
class ActorCritic(nn.Module):
"""Actor-Critic 网络"""
def __init__(self, action_dim):
super().__init__()
self.conv = nn.Sequential(
nn.Conv2d(4, 32, 8, stride=4),
nn.ReLU(),
nn.Conv2d(32, 64, 4, stride=2),
nn.ReLU(),
nn.Conv2d(64, 64, 3, stride=1),
nn.ReLU(),
nn.Flatten()
)

self.fc = nn.Linear(64 * 7 * 7, 512)
self.actor = nn.Linear(512, action_dim)
self.critic = nn.Linear(512, 1)

def forward(self, obs):
x = self.conv(obs)
x = F.relu(self.fc(x))
logits = self.actor(x)
value = self.critic(x)
return logits, value

# ========== ICM 模块(前面已定义) ==========
# ...

# ========== 训练循环 ==========
def train_icm_ppo(env_name='MontezumaRevengeNoFrameskip-v4', total_steps=10_000_000):
env = AtariWrapper(env_name)
action_dim = env.env.action_space.n
obs_shape = (4, 84, 84)

# 初始化网络
policy = ActorCritic(action_dim).cuda()
icm = ICM(obs_shape, action_dim).cuda()

optimizer = torch.optim.Adam(
list(policy.parameters()) + list(icm.parameters()),
lr=3e-4
)

# 超参数
gamma = 0.99
lam = 0.95
clip_eps = 0.1
beta_int = 0.01

# 统计
episode_rewards = []
episode_length = 0
obs = env.reset()

for step in range(total_steps):
# ===== 收集轨迹 =====
trajectories = []
for _ in range(128): # 每次收集 128 步
obs_tensor = torch.FloatTensor(obs).unsqueeze(0).cuda()

with torch.no_grad():
logits, value = policy(obs_tensor)
dist = torch.distributions.Categorical(logits=logits)
action = dist.sample()
log_prob = dist.log_prob(action)

next_obs, reward, done, _ = env.step(action.item())

# ICM 内在奖励
next_obs_tensor = torch.FloatTensor(next_obs).unsqueeze(0).cuda()
reward_int, _, _ = icm(obs_tensor, next_obs_tensor, action)
reward_int = reward_int.item()

trajectories.append({
'obs': obs,
'action': action.item(),
'reward_ext': reward,
'reward_int': reward_int,
'value': value.item(),
'log_prob': log_prob.item(),
'done': done
})

obs = next_obs
episode_length += 1

if done:
episode_rewards.append(sum(t['reward_ext'] for t in trajectories[-episode_length:]))
episode_length = 0
obs = env.reset()

# ===== 计算 GAE =====
advantages = []
returns = []
gae = 0
next_value = 0

for t in reversed(range(len(trajectories))):
reward = trajectories[t]['reward_ext'] + beta_int * trajectories[t]['reward_int']
delta = reward + gamma * next_value * (1 - trajectories[t]['done']) - trajectories[t]['value']
gae = delta + gamma * lam * (1 - trajectories[t]['done']) * gae

advantages.insert(0, gae)
returns.insert(0, gae + trajectories[t]['value'])
next_value = trajectories[t]['value']

# ===== PPO 更新 =====
obs_batch = torch.FloatTensor([t['obs'] for t in trajectories]).cuda()
actions_batch = torch.LongTensor([t['action'] for t in trajectories]).cuda()
old_log_probs = torch.FloatTensor([t['log_prob'] for t in trajectories]).cuda()
advantages = torch.FloatTensor(advantages).cuda()
returns = torch.FloatTensor(returns).cuda()

advantages = (advantages - advantages.mean()) / (advantages.std() + 1e-8)

for _ in range(4): # 4 个 epoch
logits, values = policy(obs_batch)
dist = torch.distributions.Categorical(logits=logits)
log_probs = dist.log_prob(actions_batch)
entropy = dist.entropy().mean()

# PPO 损失
ratio = torch.exp(log_probs - old_log_probs)
surr1 = ratio * advantages
surr2 = torch.clamp(ratio, 1 - clip_eps, 1 + clip_eps) * advantages
actor_loss = -torch.min(surr1, surr2).mean()
critic_loss = F.mse_loss(values.squeeze(), returns)

# ICM 损失
next_obs_batch = torch.FloatTensor([trajectories[i]['obs'] if i+1 < len(trajectories) else trajectories[i]['obs'] for i in range(len(trajectories))]).cuda()
_, forward_loss, inverse_loss = icm(obs_batch, next_obs_batch, actions_batch)

# 总损失
loss = actor_loss + 0.5 * critic_loss - 0.01 * entropy + forward_loss + inverse_loss

optimizer.zero_grad()
loss.backward()
nn.utils.clip_grad_norm_(list(policy.parameters()) + list(icm.parameters()), 0.5)
optimizer.step()

# ===== 日志 =====
if step % 100 == 0:
avg_reward = np.mean(episode_rewards[-10:]) if episode_rewards else 0
print(f"Step {step}, Avg Reward: {avg_reward:.2f}, Episodes: {len(episode_rewards)}")

if __name__ == '__main__':
train_icm_ppo()

调参技巧

  1. 内在奖励系数:从 0.01 开始,在稀疏奖励环境可增大到 0.1
  2. 归一化:内在奖励和外在奖励分开归一化,避免尺度不匹配
  3. Frame skip:Atari 中每个动作重复 4 帧,加速训练
  4. 梯度裁剪:ICM 的梯度可能很大,裁剪到 0.5 防止爆炸

预期效果

在单 GPU(RTX 3090)上训练:

  • 1000 万步:智能体学会前往第一个房间(100 分)
  • 3000 万步:智能体学会拿钥匙开门(400 分)
  • 1 亿步:平均得分 6000+,超越 DQN 的 0 分

理论分析与开放问题

Q&A:探索策略常见问题

Q1: 为什么 ICM 的逆向模型(inverse model)如此重要?

A: 逆向模型强制特征编码器 只保留与动作相关的信息。想象一个反例:如果没有逆向模型, 可能只编码背景,这时正向模型会给背景变化高奖励,导致智能体在随机背景前"伪探索"。逆向模型通过预测动作,迫使 必须编码智能体和交互物体的位置。

Q2: RND 为什么不会被电视噪声欺骗?

A: 随机网络 充当"哈希函数"——虽然每帧像素不同,但属于同一类(噪声屏幕)的状态会被映射到相似的输出。预测网络 很快学会将噪声屏幕映射到某个固定向量,预测误差降为 0,内在奖励消失。关键是:随机网络的输出对无关变化(如噪声)不敏感,但对结构性变化(如新物体出现)敏感。

Q3: NGU 的情节记忆为什么能解决"钥匙-门"任务?

A: 在每个新的 episode 开始时,情节记忆被清空,所以"拿钥匙"这个状态又变成新颖的了。即使智能体在历史上访问过这个状态 100 次,在当前 episode 中它仍然给高奖励。这保证了智能体不会因为全局频繁访问而放弃探索某些必经状态。

Q4: 内在奖励会不会导致智能体忽略外部奖励?

A: 这是一个实际问题,称为"探索陷阱"。解决方法:

  1. 逐渐降低(虽然 NGU 论文发现固定 也可以)
  2. 双价值网络:一个预测外在回报,一个预测内在回报,分别优化
  3. 在任务成功后重置内在奖励(避免过度探索已解决的任务)

Q5: 如何在连续控制任务(如机器人)中使用探索策略?

A: 连续控制的探索更复杂,因为动作空间是连续的。常用方法:

  • 在动作上加高斯噪声:
  • 参数空间噪声(Parameter Space Noise):给策略网络参数加噪声
  • ICM/RND 同样适用于连续控制,只需将动作 也作为输入

Q6: 探索策略能否用于模仿学习?

A: 可以!在 Behavioral Cloning 失败的情况下,探索策略能帮助智能体发现演示轨迹中缺失的状态。结合方法:

  • 用 ICM 探索,但偏好专家轨迹附近的状态
  • 用 RND 识别与专家分布不同的状态,主动学习

Q7: 多智能体环境如何探索?

A: 多智能体探索有独特挑战:

  • 其他智能体的策略在变化,环境是非平稳的
  • 需要探索"联合策略空间",组合爆炸
  • 常用方法:每个智能体独立的 ICM/RND + 中心化的情节记忆

Q8: 如何评估探索策略的质量?

A: 除了任务奖励,还可以用:

  • 状态覆盖率:访问的独特状态数
  • 探索效率:达到某个覆盖率所需的步数
  • 新颖性曲线:内在奖励随时间的变化(应逐渐下降)
  • 成功率:在规定步数内完成任务的概率

Q9: 计数方法为什么在高维空间失效?

A: 因为维度诅咒。在 维连续空间,要覆盖 的体积,需要采样 个点。当(Atari 像素),这个数量是天文数字。密度模型和特征嵌入试图通过学习低维表示来缓解这个问题。

Q10: 未来探索策略的研究方向?

A: 几个前沿方向:

  • 主动探索(Active Exploration):不仅探索新状态,还主动设计实验来快速学习环境动态
  • 基于技能的探索(Skill-Based Exploration):先学习可复用的技能(如"跳跃"、“开门”),再组合技能探索
  • 目标生成(Goal Generation):自动生成有挑战性的目标,引导探索
  • 社会学习(Social Learning):从其他智能体的探索中学习,避免重复

论文推荐

  1. Curiosity-Driven Exploration

    • Curiosity-Driven Exploration by Self-Supervised Prediction (Pathak et al., ICML 2017)
    • 链接:arXiv:1705.05363
  2. Random Network Distillation

    • Exploration by Random Network Distillation (Burda et al., ICLR 2019)
    • 链接:arXiv:1810.12894
  3. Never Give Up

    • Never Give Up: Learning Directed Exploration Strategies (Badia et al., ICLR 2020)
    • 链接:arXiv:2002.06038
  4. Count-Based Exploration

    • Unifying Count-Based Exploration and Intrinsic Motivation (Bellemare et al., NeurIPS 2016)
    • 链接:arXiv:1606.01868
  5. Empowerment

    • Empowerment - An Introduction (Klyubin et al., 2005)
    • Variational Intrinsic Control (Gregor et al., ICLR 2017)
    • 链接:arXiv:1611.07507
  6. Go-Explore

    • First Return Then Explore (Ecoffet et al., Nature 2021)
    • 链接:arXiv:2004.12919
  7. Model-Based Exploration

    • Planning to Explore via Self-Supervised World Models (Sekar et al., ICML 2020)
    • 链接:arXiv:2005.05960
  8. Disagreement-Based Exploration

    • Exploration by Disagreement (Pathak et al., ICML 2019)
    • 链接:arXiv:1906.04161
  9. Information Gain Exploration

    • VIME: Variational Information Maximizing Exploration (Houthooft et al., NeurIPS 2016)
    • 链接:arXiv:1605.09674
  10. Successor Features for Exploration

    • Deep Successor Reinforcement Learning (Kulkarni et al., 2016)
    • 链接:arXiv:1606.02396

核心公式总结

Count-Based 探索

$$

r_{\text{int}} (s) = \frac{\beta}{\sqrt{N(s)}} \quad \text{或} \quad r_{\text{int}} (s) = \frac{\beta}{\sqrt{\rho(s)}}
$$

ICM 内在奖励

$$

r_{\text{int}} (s_t, a_t, s_{t+1}) = \eta |\hat{\phi}(s_{t+1}) - \phi(s_{t+1})|^2
$$

其中
RND 内在奖励

$$

r_{\text{int}} (s) = |g_{\text{pred}} (s) - g_{\text{target}} (s)|^2
$$

NGU 混合奖励

$$

r_{\text{int}} (s) = r_{\text{episodic}} (s) \cdot \min{\max{r_{\text{lifetime}} (s), 1}, L}$$

情节新颖性

$$

r_{\text{episodic}} (s) = \frac{1}{\sqrt{\sum_{s_i \in M} K(s, s_i)} + c}
$$

其中

总结与展望

探索策略是强化学习从玩具问题走向现实应用的关键。传统的-greedy 和 Boltzmann 探索在高维、稀疏奖励环境中效率极低,而基于内在动机的好奇心驱动方法通过奖励状态的新颖性,实现了真正的自主探索。从 Count-Based 的统计方法到 ICM 的预测误差、从 RND 的随机网络蒸馏到 NGU 的情节记忆,每一步改进都源于对探索本质的深刻理解——不是随机乱走,而是主动寻找意外、不确定和未知的事物。

然而,探索问题远未解决。当前方法仍依赖于大量试错(NGU 需要数十亿步训练),在安全关键系统(如自动驾驶)中难以应用。未来的研究可能融合主动学习、因果推理和人类先验知识,实现更高效、可解释、可迁移的探索策略。正如婴儿不需要尝试百万次就能学会走路,真正智能的探索应该是样本高效、目标导向和持续学习的——这是下一个十年强化学习研究的核心挑战之一。

从探索策略的视角出发,下一章我们将进入 Model-Based 强化学习——通过学习环境模型进行想象和规划,从被动探索到主动规划,从反应式控制到前瞻性决策,揭示强化学习的另一条核心路径。

  • 本文标题:强化学习(四)—— 探索策略与好奇心驱动学习
  • 本文作者:Chen Kai
  • 创建时间:2024-08-23 15:30:00
  • 本文链接:https://www.chenk.top/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0%EF%BC%88%E5%9B%9B%EF%BC%89%E2%80%94%E2%80%94-%E6%8E%A2%E7%B4%A2%E7%AD%96%E7%95%A5%E4%B8%8E%E5%A5%BD%E5%A5%87%E5%BF%83%E9%A9%B1%E5%8A%A8%E5%AD%A6%E4%B9%A0/
  • 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
 评论