战斗包子
强化学习?DQN?

强化学习?DQN?

OMPL,Movelt,RRT*,Lattice

ADAM是什么

Deep Q-Network (DQN)

什么是 DQN (Deep Q-Network)?一份初学者指南

DQN(深度Q网络)是一种强化学习(Reinforcement Learning)算法,它巧妙地将神经网络的强大“估算”能力与一种经典的决策理论Q-Learning结合了起来。

您可以把 DQN 想象成一个正在学习玩电子游戏的 AI 🤖。


1. 核心思想:从“作弊表”到“估算器”

a. 传统方法:Q-Learning (制作“作弊表”)

在 DQN 出现之前,一种流行的方法叫 Q-Learning。它试图为游戏建立一张巨大的“作弊表”(称为 Q-Table)。

  • 状态 (State, ss): 游戏中的每一种可能情况(如:“玩家在位置X,敌人在位置Y”)。
  • 动作 (Action, aa): 玩家能做的每一种动作(如:“向左”、“向右”、“跳”)。
  • Q值 (Q-Value), Q(s,a)Q(s, a) 这张表里的“分数”。它代表:

    “在状态 ss 下,如果执行动作 aa,那么从这一刻到游戏结束,我**未来能获得的总奖励(总分)**的最佳估计值。”

如何决策: AI 只需要查表,在当前状态 ss 下,选择那个 Q(s,a)Q(s, a) 分数最高的动作 aa

b. Q-Learning 的“次元诅咒”

这个“作弊表”在简单游戏(如“井字棋”)中很有效。但如果用在自动驾驶或《星际争霸》中:

  1. 状态空间爆炸: 摄像头的每一帧图像都是一个独特的状态。状态的数量是无限的!
  2. 内存灾难: 我们根本不可能创建或存储一个“无限行”的表格。

c. DQN 的解决方案:用“大脑”估算

既然我们不能存储一个无限大的表格,DQN 提出:我们可以训练一个神经网络 🧠 来估算(或称“拟合”)这个表格!

这个神经网络就是一个函数逼近器 (Function Approximator)

  • DQN模型 (QθQ_{\theta}): 一个以参数 θ\theta(即权重)为特征的神经网络。
  • 输入: 游戏状态 ss(比如摄像头的图像,或 CartPole 的4个数字)。
  • 输出: 一个列表,包含每一个可能动作的Q值。
    • 例如,在 CartPole (动作:0=左, 1=右) 中:
      • 输入:[0.01, 0.02, -0.03, 0.04] (一个状态 ss)
      • 输出:[15.2, 18.7] (即 Qθ(s,0)=15.2Q_{\theta}(s, 0)=15.2, Qθ(s,1)=18.7Q_{\theta}(s, 1)=18.7)

2. 核心公式:DQN 如何学习?

DQN 的学习目标是让它的“估算” Qθ(s,a)Q_{\theta}(s, a) 尽可能地接近“真实”的Q值 Q(s,a)Q^*(s, a)。这个“真实”的Q值是由贝尔曼最优方程 (Bellman Optimality Equation) 定义的。

a. 贝尔曼最优方程(AI 的“指导思想”)

这个公式定义了一个“完美”的Q值应该是什么样的:

Q(s,a)=EsE[r+γmaxaQ(s,a)s,a] Q^*(s, a) = \mathbb{E}_{s' \sim \mathcal{E}} \left[ r + \gamma \max_{a'} Q^*(s', a') \mid s, a \right]

用大白话解释这个公式:

“在状态 ss 执行动作 aa未来总分 (Q*),等于:

  1. 立刻拿到的奖励 (rr)
  2. 加上
  3. 你到达下一个状态 (ss') 后,从那个状态出发所能拿到的未来总分的最大值 ( maxaQ(s,a)\max_{a'} Q^*(s', a') )。”
  • γ\gamma (gamma) 是**折扣因子** (Discount Factor),一个0到1之间的数字(比如0.99)。它代表了“未来的奖励有多重要”(越接近1越重要)。

b. DQN 的“学习目标”与“损失函数”

DQN 不能直接解这个方程,它使用这个方程来创造自己的“学习目标”。

DQN 的训练依赖两个关键技术:

1. 经验回放 (Experience Replay)
AI 把它的所有“经验” (s, a, r, s') 存储在一个巨大的“记忆库D\mathcal{D} 中。训练时,它从这个库中随机抽取一批(mini-batch)经验来学习,而不是使用刚发生的经验。这打破了经验之间的相关性,使训练更稳定。

2. 目标网络 (Target Network)
DQN 使用两个神经网络:

  • QθQ_{\theta} (策略网络): 我们正在训练的“主网络”,用来做决策。
  • QθQ_{\theta^-} (目标网络): 一个“主网络”的旧版本(它的权重 θ\theta^- 会定期从 θ\theta 复制而来,然后保持“冻结”)。

我们使用这个“冻结”的 QθQ_{\theta^-} 来计算我们的“学习目标”,这能防止目标值在训练中疯狂摆动,让学习更稳定。


c. 核心公式:损失函数 (The Loss Function)

对于从“记忆库” D\mathcal{D} 中抽取的每一个经验 (s,a,r,s)(s, a, r, s'),我们定义:

1. “目标Q值” yiy_i (The Target / “正确答案”)
我们使用“目标网络” QθQ_{\theta^-} 来计算贝尔曼方程的右侧:

yi=r+γmaxaQθ(s,a) y_i = r + \gamma \max_{a'} Q_{\theta^-}(s', a')
  • (如果 ss' 是一个终止状态,则 yi=ry_i = r

2. “预测Q值” (The Prediction)
这是我们“主网络” QθQ_{\theta} 对我们当时所做动作的估算:

Prediction=Qθ(s,a) \text{Prediction} = Q_{\theta}(s, a)

3. 损失函数 L(θ)L(\theta) (The “Error”)
我们使用均方误差 (MSE) 来衡量“预测值”和“目标值”之间的差距。这就是我们要优化的目标:

L(θ)=E(s,a,r,s)D[(yi目标Qθ(s,a)预测)2] L(\theta) = \mathbb{E}_{(s, a, r, s') \sim \mathcal{D}} \left[ \left( \underbrace{y_i}_{\text{目标}} - \underbrace{Q_{\theta}(s, a)}_{\text{预测}} \right)^2 \right]

训练过程就是通过梯度下降 (Gradient Descent) 来调整“主网络”的权重 θ\theta,以最小化这个损失函数 L(θ)L(\theta)


3. 总结:DQN 算法流程

  1. 初始化“主网络” QθQ_{\theta} 和“目标网络” QθQ_{\theta^-}(权重相同)。
  2. 初始化“记忆库” D\mathcal{D}
  3. 循环(玩 MM 局游戏):
    1. 获取游戏初始状态 ss
    2. 循环(直到游戏结束):
      1. QθQ_{\theta} 评估当前状态 ss,然后使用“ϵ\epsilon-greedy”策略选择一个动作 aa
        • (“ϵ\epsilon-greedy”:有 ϵ\epsilon 的概率随机选动作(探索),有 1ϵ1-\epsilon 的概率选 QθQ_{\theta} 估分最高的动作(利用))
      2. 在游戏中执行动作 aa,获得即时奖励 rr 和下一个状态 ss'
      3. 将这个“经验” (s,a,r,s)(s, a, r, s') 存入“记忆库” D\mathcal{D}
      4. 更新 sss \leftarrow s'
      5. 开始学习:
        • D\mathcal{D}随机抽取一批(mini-batch)经验。
        • 对于抽取的每一条经验,使用“目标网络” QθQ_{\theta^-} 计算**“目标Q值” yiy_i**。
        • 使用“主网络” QθQ_{\theta} 计算**“预测Q值” Qθ(s,a)Q_{\theta}(s, a)**。
        • 计算这批数据的均方误差 (MSE) L(θ)L(\theta)
        • 执行一次梯度下降,更新“主网络”的权重 θ\theta
    3. (关键) 每隔 CC 步,将“主网络”的权重复制给“目标网络”:θθ\theta^- \leftarrow \theta

一个简易的dqn_cartpole验证

环境部署

创建虚拟环境

Windows:

1
2

python -m venv my_rl_env

macOS / Linux:

1
python3 -m venv my_rl_env

激活虚拟环境

Windows (cmd):

1
2

.\my_rl_env\Scripts\activate

Windows (PowerShell):

1
2

.\my_rl_env\Scripts\Activate.ps1

macOS / Linux:

1
2

source my_rl_env/bin/activate

安装必须的库

1
pip install gymnasium[classic-control]

退出环境

1
deactivate

python代码

1. Q-Value (Q值) —— AI的“价值判断”

理论:

Q-Learning(Q学习)的核心是学习一个叫做 “Q值” (Q-Value) 的函数。

Q(s, a) 函数代表:在“状态 s”(State)下,执行“动作 a”(Action),未来能获得的总回报(奖励)的期望值是多少

简单说,Q(s, a) 就是一个“评分”。在某个状态下:

  • Q(s, '向左') = 10
  • Q(s, '向右') = 50

AI会选择“向右”,因为它“预见”到这个动作的“价值”(Q值)更高。

由于CartPole的“状态s”(小车位置、杆子角度等)是连续的数字,有无限多种,我们不能用一张“表”(Q-Table)来存储。因此,我们用一个神经网络来**近似(approximate)**这个Q函数。

代码对应:

① 神经网络的“输出”就是Q值:
DQN 类中,网络的forward方法返回的就是Q值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class DQN(nn.Module):
def __init__(self, n_observations, n_actions):
super(DQN, self).__init__()
self.layer1 = nn.Linear(n_observations, 128) # n_observations = 4
self.layer2 = nn.Linear(128, 128)
self.layer3 = nn.Linear(128, n_actions) # n_actions = 2

def forward(self, x):
# x 是输入的 "state" (状态 s)
x = F.relu(self.layer1(x))
x = F.relu(self.layer2(x))

# 网络的输出,就是这个状态下所有动作的Q值
# 例如: [1.5, -0.3]
return self.layer3(x)

当你调用 policy_net(state) 时,它的返回值 [1.5, -0.3] 就分别代表 Q(s, '向左')Q(s, '向右') 的Q值。

② AI如何“使用”Q值做决策:
select_action 函数中,当AI决定“利用”(Exploitation)时:

1
2
3
4
5
6
7
8
def select_action(state):
# ... (省略 "探索" 部分)
if sample > eps_threshold: # "利用" (Exploitation)
with torch.no_grad():
# policy_net(state) 返回 [Q(s, left), Q(s, right)]
# .max(1)[1] 的意思就是:
# "请告诉我 Q值最高 (max) 的那个动作的索引 (index)"
return policy_net(state).max(1)[1].view(1, 1) # <--- 在这里!

policy_net(state).max(1)[1] 这行代码,就是Q-Learning理论的核心执行步骤:“选择那个Q值最高的动作”。


2. Experience Replay (经验回放) —— AI的“记忆宫殿”

理论:

如果我们让AI“学完一步就忘掉一步”,训练会非常不稳定(因为相邻的经历高度相关)。

为了解决这个问题,我们给AI一个“记忆库”(Replay Buffer)。AI把它的所有经历 (state, action, reward, next_state) 都存进去。这个“经历”在我们的代码里被定义为 Transition

当AI要“学习”(训练)时,它不是学习“上一步”的经历,而是从“记忆库”里随机抽取一批(比如128个)旧经历来进行“反思”。

好处: 打破了经历之间的相关性,让训练更稳定、高效。

代码对应:

① 记忆库的“数据结构”:
ReplayMemory 类和 Transition 命名元组。

1
2
3
4
5
6
7
8
9
# 'Transition' 是我们定义的 "一条经历" 的标准格式
Transition = namedtuple('Transition',
('state', 'action', 'next_state', 'reward'))

class ReplayMemory(object):
def __init__(self, capacity):
# deque 是一个双端队列,当它满了 (maxlen)
# 新经历加进来时,最旧的经历会自动被挤出去
self.memory = deque([], maxlen=capacity) # <--- "记忆库" 本库

② 存入记忆 (push):
在主训练循环(Main Training Loop)中,AI的每一步行动都会被存入记忆。

1
2
3
4
# ... AI 执行完一个动作 ...

# 3. 存入记忆
memory.push(state, action, next_state, reward) # <--- 在这里!

③ 随机抽取 (sample):
optimize_model 函数(AI的“学习”函数)的开头:

1
2
3
4
5
6
7
8
9
10
def optimize_model():
if len(memory) < BATCH_SIZE:
return # 记忆库里的"货"还不够,先不学

# 从记忆库中随机抽取 BATCH_SIZE (128) 条经历
transitions = memory.sample(BATCH_SIZE) # <--- 在这里!

# 把128条 "经历" 转换成一个 "批次 (Batch)",方便神经网络处理
batch = Transition(*zip(*transitions))
# ...

这就是“经验回放”的实现。AI不是在学 memory.push() 刚存进去的那个,而是在学 memory.sample() 随机抽出来的128个。


3. Deep Q-Network (DQN) —— AI的“学习过程”

理论:

AI如何“学习”?它需要一个“目标”和“现实”的差距(即 Loss 损失)。
DQN的“学习”就是不断调整神经网络的权重,让这个“差距”变小。

  • “现实” (Reality): Q(s, a)

    这是我们的 policy_net 当前认为的Q值。

  • “目标” (Target): r + γ * max(Q(s', a'))

    这就是著名的贝尔曼方程 (Bellman Equation)
    它的大白话意思是:一个“完美”的Q(s, a)值,应该等于:你马上拿到的奖励r加上 (γ是折扣因子),你在下一步s'能拿到的“未来最大Q值”max(Q(s', a'))

DQN的训练,就是强迫 policy_net 去满足这个等式。
Loss = ( "目标" - "现实" )²
我们希望这个Loss最小。

代码对应:

整个 optimize_model 函数就是DQN的“学习过程”!

① 计算“现实”:Q(s, a)

1
2
3
4
5
6
7
8
# 3. 计算 Q(s_t, a_t)
state_batch = torch.cat(batch.state) # 128个 s
action_batch = torch.cat(batch.action) # 128个 a

# 得到 [Q(s,左), Q(s,右)],然后用 .gather()
# 来只提取出我们当时 "实际执行的那个动作a" 对应的Q值
state_action_values = policy_net(state_batch).gather(1, action_batch)
# <--- 这就是 "现实" Q(s, a)

② 计算“目标”:r + γ * max(Q(s', a'))

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 4. 计算 V(s_{t+1}) -> 即 "下一步的最大Q值"
reward_batch = torch.cat(batch.reward) # 128个 r

next_state_values = torch.zeros(BATCH_SI, device=device)
with torch.no_grad():
# 用 "target_net" (一个Q值的“稳定”版本)
# 来计算 "下一步s'" 的 "最大Q值"
next_state_values[non_final_mask] = target_net(non_final_next_states).max(1)[0]
# <--- 这就是 max(Q(s', a'))

# 5. 计算“期望Q值” (Expected Q Values)
# Bellman 方程: Expected Q = reward + gamma * next_max_Q
expected_state_action_values = (next_state_values * GAMMA) + reward_batch
# <--- 这就是 "目标"

注: 代码里用了一个 target_net 来计算“目标”,这是DQN的一个高级技巧 (Fixed Q-Targets),它能让训练更稳定。

③ 计算“差距”(Loss)并学习:

1
2
3
4
5
6
7
8
9
10
# 6. 计算损失 (Loss)
criterion = nn.SmoothL1Loss()
# 计算 "目标" 和 "现实" 之间的差距
loss = criterion(state_action_values, expected_state_action_values.unsqueeze(1))
# <--- Loss = (目标 - 现实)²

# 7. 优化 (反向传播)
optimizer.zero_grad() # 清空旧梯度
loss.backward() # 计算新梯度
optimizer.step() # 更新 policy_net 的权重!<--- AI 在这里 "学习"

optimizer.step() 这一行,就是AI在“学习”!它在根据loss调整 policy_net 的权重,让它下一次的预测 (Q(s, a)) 能更接近“目标” (r + ...)。


总结:DQN的“飞轮”

你现在可以把整个流程串起来了:

  1. AI行动 (select_action): 用 policy_net 预测Q值,选一个动作 a
  2. AI记忆 (memory.push): 把经历 (s, a, r, s') 存入记忆库。
  3. AI反思 (optimize_model):
    • 从记忆库随机抽取 (memory.sample) 一批旧经历。
    • 计算这批经历的“现实Q值” (来自 policy_net)。
    • 计算这批经历的“目标Q值” (来自 target_netreward)。
    • 计算两者的差距 Loss
    • 更新 policy_net (optimizer.step()),让“现实”更接近“目标”。

这个“行动-记忆-反思”的飞轮不断旋转,AI的 policy_net 对Q值的估算就越来越准,它的决策(select_action)也就越来越好了。

Atari游戏

环境配置

运行以下命令(它会安装gymnasium的Atari依赖,并自动接受ROM许可证,避免版本问题,指定版本):

1
pip install "gymnasium[atari,accept-rom-license]==0.29.1" 

1. 核心挑战:AI的“视觉系统” (环境封装器)

我们面临的主要问题是:CartPole 的状态是 4 个数字,而 Atari 的状态是 (210, 160, 3) 的彩色图像。AI 必须学会“看懂”像素。

解决方案:环境封装器 (Environment Wrappers)

我们没有直接将原始图像喂给 AI,而是通过一系列“封装器”搭建了一个高效的“视觉预处理流水线”。

  • AtariPreprocessing (官方 v0.29.1 封装器): 这是你解决问题的关键。这一个封装器替我们完成了三件大事:

    1. 灰度化 (Grayscale): 抛弃颜色信息,将 (H, W, 3) 降维到 (H, W, 1)
    2. 缩放 (Resize): 将高分辨率图像缩小到 (84, 84),大幅减少计算量。
    3. 跳帧 (Frame Skip): AI 不需要分析每一帧。我们设置 skip=4,让 AI 每 4 帧才决策一次,这极大加快了训练速度。
  • FrameStack (自定义封装器): 这是为了解决“运动感知”问题。

    • 理论: 单张静态图片没有“速度”信息。AI 无法判断球是在移动还是静止。
    • 实现: 我们将 k=4 帧预处理过的 (84, 84) 图像堆叠在一起,形成一个 (4, 84, 84) 的张量(Tensor)。
    • 结果: AI 现在可以通过对比这 4 帧的差异,来“感知”物体(如球和挡板)的运动方向和速度。

代码对应: wrap_atari 函数就是这个流水线的“总装厂”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# --- 你的 wrap_atari 函数 (v0.29.1) ---
def wrap_atari(env_id, stack_frames=4, render_mode='rgb_array'):
env = gym.make(env_id, render_mode=render_mode)

# 步骤1:灰度化、缩放、跳帧 (由 AtariPreprocessing 一步完成)
env = AtariPreprocessing(
env,
frame_skip=4,
screen_size=84,
grayscale_obs=True,
scale_obs=False # 我们在 PyTorch 中手动归一化
)
# 步骤2:帧堆叠 (感知运动)
env = FrameStack(env, stack_frames)
return env

2. 算法升级:从 MLP “大脑” 到 CNN “视觉皮层”

AI 的“大脑”必须升级,才能处理 (4, 84, 84) 这样的图像数据。

  • 为什么?

    • nn.Linear(全连接层/MLP)无法处理空间信息。它会把 84x84 的像素粗暴地拉平,完全丢失所有“形状”和“位置”信息(例如,“球在挡板上方”)。
    • nn.Conv2d(卷积层/CNN)专门用于提取局部特征(如边缘、角落、形状),并通过层次堆叠来理解复杂的空间关系,完美符合视觉任务的需求。
  • 代码对应:CNN_DQN

    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
    class CNN_DQN(nn.Module):
    def __init__(self, h, w, n_actions):
    super(CNN_DQN, self).__init__()

    # 1. 卷积层 (提取图像特征)
    self.conv1 = nn.Conv2d(4, 32, kernel_size=8, stride=4)
    self.bn1 = nn.BatchNorm2d(32)
    self.conv2 = nn.Conv2d(32, 64, kernel_size=4, stride=2)
    self.bn2 = nn.BatchNorm2d(64)
    self.conv3 = nn.Conv2d(64, 64, kernel_size=3, stride=1)
    self.bn3 = nn.BatchNorm2d(64)

    # 2. 辅助函数:计算卷积层的输出尺寸
    self.feature_size = self._feature_size(h, w)

    # 3. 全连接层 (将特征拉平后,计算最终的Q值)
    self.fc = nn.Linear(self.feature_size, n_actions)

    def _feature_size(self, h, w):
    with torch.no_grad():
    x = torch.zeros(1, 4, h, w)
    x = F.relu(self.bn1(self.conv1(x)))
    x = F.relu(self.bn2(self.conv2(x)))
    x = F.relu(self.bn3(self.conv3(x)))
    return x.numel() # 返回特征总数

    def forward(self, x):
    # 1. 卷积与激活
    x = F.relu(self.bn1(self.conv1(x)))
    x = F.relu(self.bn2(self.conv2(x)))
    x = F.relu(self.bn3(self.conv3(x)))

    # 2. 展平 (展平成一维向量)
    x = x.view(x.size(0), -1)

    # 3. 全连接输出 Q值
    return self.fc(x)

2.1 深度解析:CNN 架构的“魔法数字”

你可能会问:kernel_size=8, stride=4, out_channels=32 这些数字是哪来的?
它们不是随机的,而是直接来源于 DeepMind 2015 年的DQN论文,是久经考验的经典架构。

  • in_channels=4 (输入通道):

    • 对应 FrameStack 输出的 4 帧堆叠图像,这是 AI 感知运动的“眼睛”。
  • conv1 (k=8, s=4, out=32):

    • 大步长 (s=4), 大视野 (k=8): 在网络初期进行“积极降维”。快速将 84x84 的图像大幅缩小,提取最粗糙、最全局的特征,并减少计算量。
    • out=32: 尝试从原始图像中找出 32 种不同的基础特征(如边缘、角落)。
  • conv2 (k=4, s=2, out=64):

    • 中步长 (s=2): 降维力度放缓,专注于将 conv1 找到的 32 种基础特征组合成 64 种更复杂的特征(如“挡板”的轮廓)。
  • conv3 (k=3, s=1, out=64):

    • 小步长 (s=1): 不再缩小图像尺寸。它充当一个“精炼厂”,将 64 种特征进行复杂的混合与精加工,输出最纯粹的特征图,准备交给全连接层。
  • nn.BatchNorm2d (BN层):

    • 这是对原论文的改进。BN 层可以标准化每一批数据在网络中的激活值,能有效防止梯度爆炸/消失,让训练更稳定、更快收敛。

2.2 深度解析:_feature_size 的工程技巧

  • 问题: 卷积层 conv3 输出的是一个 3D 特征图([通道, 高, 宽]),而全连接层 fc 的输入必须是一个 1D 向量。我们如何知道 conv3 输出的 C*H*W 到底等于多少?
  • 笨办法: 手动计算。根据 84x84 的输入,用复杂的卷积公式去计算 conv1 -> conv2 -> conv3 后的最终 (C, H, W)。这非常繁琐且容易出错。
  • 聪明办法 (即本函数):
    1. with torch.no_grad(): 关闭梯度,进入“测量模式”。
    2. x = torch.zeros(1, 4, h, w): 创建一个和真实数据一模一样的“虚拟测试品”。
    3. x = F.relu(...): 让“测试品”流过所有的卷积层。
    4. return x.numel(): 测量“测试品”流出时总共有多少个元素(例如 5184 个)。这个数字就是我们需要的feature_size

2.3 深度解析:“拉平”(Flatten) 操作与数学原理

“拉平”是连接 CNN(视觉皮层)和 FC(决策大脑)的桥梁。

  • 目的: 将 CNN 输出的 3D“空间特征地图” (C, H, W) 转换成 FC 层需要的 1D“抽象特征清单” (N)
  • 代码: x = x.view(x.size(0), -1)
  • 数学原理 (索引映射):
    1. x.size(0) 保留了 Batch 维度,我们不对其操作。
    2. -1 告诉 PyTorch 自动计算 N=C×H×WN = C \times H \times W
    3. 在底层,计算机按照“行主序”(Row-Major Order)将 3D 张量展开。
    4. 一个在 T[c, h, w] 的元素,会被映射到 1D 向量 TT' 中的第 kk 个位置:k=c(HW)+hW+wk = c \cdot (H \cdot W) + h \cdot W + w

2.4 深度解析:全连接层 (FC) 的尺寸

  • 问题: self.fc = nn.Linear(self.feature_size, n_actions) 的尺寸是如何决定的?
  • n_actions (输出尺寸):
    • 这是最关键的参数,它必须等于环境的动作总数(例如 Pong 是 6)。
    • 它定义了 nn.Linear输出多少个神经元,每个神经元对应一个动作的 Q 值。
  • self.feature_size (输入尺寸):
    • 必须等于上一步“拉平”操作后的 1D 向量长度(例如 5184)。
  • 澄清误区: 全连接层的输出尺寸n_actions不是输入尺寸(self.feature_size)的总和。
  • 数学原理 (矩阵乘法):
    • 全连接层执行的是一次矩阵乘法,将一个长向量 XX(尺寸 5184)“压缩”成一个小向量 YY(尺寸 6)。
    • Y=XW+BY = XW + B
    • XX (输入) 的形状是 `[Batch, 5184]`。
    • WW (权重) 的形状是 `[5184, 6]`。
    • BB (偏置) 的形状是 `[6]`。
    • YY (输出) 的形状是 `[Batch, 6]`。
本文作者:战斗包子
本文链接:https://paipai121.github.io/2025/10/24/工作/基于学习的规划/
版权声明:本文采用 CC BY-NC-SA 3.0 CN 协议进行许可