【论文阅读】Playing Atari with Deep Reinforcement Learning(自用)

# DQN开山论文。首次提出深度 Q 网络(DQN),其核心思想是用深度神经网络来近似 Q 值函数,引入了经验回放和固定目标网络来提升训练的稳定性,解决高维状态空间(如图像)的强化学习问题。

# 无模型(model - free)、 Q-learning。

# 复现代码链接:Deep-Q-Network-AtariBreakoutGame/breakout at master · SinanGncgl/Deep-Q-Network-AtariBreakoutGame

论文链接:dqn.pdf

目录

1. 论文概要

2. 背景知识

2.1 强化学习

2.1.1 马尔可夫决策过程(MDP)

2.1.2 Q-learning

2.2 深度学习

2.2.1 CNN

2.2.2 端到端

3. 论文代码复现

3.1 代码核心框架(dqn.py)

3.2方法对照

3.2.1 预处理与输入

3.2.2 神经网络架构

3.2.3 训练机制

3.2.3.1 经验回放(Experience Replay)

3.2.3.2 目标网络(Target Network)

3.2.4 细节优化

3.2.4.1 优化器

3.2.4.2 探索策略(ε-greedy)

4. 总结

5. 完整代码与项目架构

5.1 dqn.py

5.2 gameTRY.py

5.3 架构


1. 论文概要

目标:提出一种端到端的深度强化学习方法(DQN),直接从原始像素输入学习控制策略,无需人工设计特征。

核心贡献:(1)经验回放:打破数据相关性,提升训练稳定性。
                  (2)目标网络:固定旧参数计算目标Q值,缓解发散问题。
                  (3)通用性:同一网络结构适配多款游戏,无需调参。

2. 背景知识

将深度学习与强化学习结合,解决高维视觉输入下的决策问题。

2.1 强化学习

2.1.1 马尔可夫决策过程(MDP)

2.1.2 Q-learning

(1)核心:通过估计动作价值函数Q(s, a)来指导决策, Q(s, a)表示在状态 s 执行动作 a 后,智能体预期获得的未来折扣奖励总和(详见Spinning Up in Deep RL学习记录(一)(自用)-CSDN博客)。

(2)Q - learning 的目标:找到最优 Q 函数,或者说尽可能地逼近最优 Q 函数。通过不断与环境交互学习,调整自身对 Q 值的估计,最终使得估计的 Q 值接近最优 Q 函数给出的真实动作价值。

(3)贝尔曼方程$ Q^{*}(s, a) = \underset{s'\sim P}{\mathbb{E}}[r(s, a) + \gamma \max_{a'} Q^{*}(s', a')] $。Q - learning 基于贝尔曼方程进行迭代更新,而贝尔曼最优方程是定义最优 Q 函数的基础。Q - learning 利用贝尔曼方程的迭代性质,逐步向最优 Q 函数的方向进行学习和更新。

(4)最优策略:在 Q - learning 过程中,当估计的 Q 值逐渐逼近最优 Q 函数时,就可以根据 Q 值来制定策略,即选择使得 Q 值最大的动作作为当前状态下的最优动作。一旦学习到了最优 Q 函数,也就意味着找到了最优策略$\pi^*(s) = \arg\max_a Q^*(s, a)$,智能体可以依据这个策略在环境中做出最优决策。

2.2 深度学习

2.2.1 CNN

空间特征提取:CNN 通过卷积层(如 $3 \times 3$或 $8 \times 8$卷积核)对图像进行滑动窗口操作,自动提取边缘、纹理、形状等空间特征。例如,在打砖块游戏中,卷积层可识别球拍的位置、球的运动轨迹、砖块的排列等。
层次化特征学习:多层卷积与池化(如最大池化)交替,低层提取简单特征,高层组合特征形成复杂表示。如第一层卷积提取边缘,第二层组合边缘形成物体轮廓,第三层识别具体物体(球、球拍)。
权值共享:同一卷积核在图像不同位置共享权值,大幅减少参数数量,提高训练效率与泛化能力,适合处理图像这种空间平移不变性强的数据。

2.2.2 端到端

定义:直接将原始输入(如游戏画面像素)映射到决策输出(如动作选择),中间无需人工设计特征或干预。以 DQN 为例,输入 4 帧堆叠的 84×84 灰度图,经 CNN 处理后直接输出各动作的 Q 值,避免了手工特征设计的繁琐与误差,充分利用数据驱动的优势自动学习最优表示。
适应性:能根据不同任务(如不同 Atari 游戏)自动调整特征提取方式,无需为每个游戏单独设计特征工程,提高了算法的通用性与效率。

3. 论文代码复现

3.1 代码核心框架(dqn.py)

神经网络类(NeuralNetwork):定义DQN模型结构。
预处理函数(preprocessing):将原始图像转换为网络输入格式。
训练函数(train):实现经验回放、Q值更新和模型优化。
测试函数(test):使用训练好的模型进行游戏测试。
主函数(main):支持训练、测试和继续训练模式。

3.2方法对照

3.2.1 预处理与输入

(1)论文方法:RGB 转灰度 → 降采样至 84×84 → 裁剪有效区域 → 堆叠连续 4 帧(捕捉动态信息),其中:

RGB 转灰度:将彩色的 RGB 图像转换为灰度图像,减少颜色维度信息。在强化学习处理图像输入时,颜色信息并非总是必要的,灰度图像能保留图像的亮度信息,同时降低数据维度,减少计算量。例如,在打砖块游戏中,球、球拍和砖块的相对位置及运动信息比颜色更关键。
降采样至 84×84:通过降采样操作,将原始图像尺寸调整为 84×84 像素。原始游戏图像尺寸较大,降采样可进一步降低数据维度,去除一些细节信息中可能存在的噪声,同时保留对决策有用的关键特征。
裁剪有效区域:针对游戏画面,裁剪出包含主要游戏元素(如球、球拍、砖块)的有效区域,去除无关背景部分。这样可以聚焦于与智能体决策相关的区域,减少不必要的计算和干扰因素。
堆叠连续 4 帧:把连续的 4 帧图像堆叠在一起作为网络输入。这是因为在动态游戏场景中,单帧图像无法提供足够的运动信息,堆叠多帧可以捕捉到物体的运动轨迹和速度等动态信息,帮助智能体更好地理解环境变化,做出更合理的决策。例如,判断球的运动方向和速度,对于预测球的落点及决定球拍的移动方向至关重要。

通过以上步骤,可大幅降低输入到神经网络的数据维度,减轻计算负担,同时有效地保留了游戏中的时空信息,使智能体能够基于更合适的数据进行学习和决策。

(2)代码复现:

def preprocessing(image):
    image = cv2.resize(image, (84, 84))        # 调整大小
    image = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY)
    image[image > 0] = 255                     # 二值化处理
    return torch.from_numpy(image.transpose(2,0,1))  # 转换为张量

3.2.2 神经网络架构

CNN+Q-learning,CNN自动提取空间特征,实现端到端的强化学习策略。

(1)论文方法:

卷积层 1:16 个 8×8 滤波器(步长 4),ReLU 激活。
卷积层 2:32 个 4×4 滤波器(步长 2),ReLU 激活。
全连接层:256 个 ReLU 单元 → 输出层(每个动作对应 Q 值)。

优势:DQN 网络架构能够在单次前向传播过程中,计算出所有可能动作的 Q 值。这使得智能体可以快速比较不同动作的价值,从中选择最优动作,提高了决策效率,尤其适用于需要实时做出决策的强化学习任务,如 Atari 游戏这类动态交互场景。

(2)代码复现:

class NeuralNetwork(nn.Module):
    def __init__(self):
        self.conv1 = nn.Conv2d(4, 32, kernel_size=8, stride=4)
        self.conv2 = nn.Conv2d(32, 64, 4, 2)
        self.conv3 = nn.Conv2d(64, 64, 3, 1)  # 额外卷积层
        self.fc4 = nn.Linear(7*7*64, 512)
        self.fc5 = nn.Linear(512, self.number_of_actions)

注:相比原文多了一个卷积层。

3.2.3 训练机制

使用经验回放目标网络两大稳定机制。

3.2.3.1 经验回放(Experience Replay)

(1)论文方法

存储经验:将智能体与环境交互过程中产生的历史经验,即状态$s_t$、动作$a_t$、奖励 $r_t$、下一状态$s_{t + 1}$组成的四元组$(s_t, a_t, r_t, s_{t + 1})$存储到记忆库中。记忆库可以采用队列等数据结构实现,用于保存一定数量的历史经验。

随机采样训练:在训练时,从记忆库中随机抽取小批量样本进行训练。这样做打破了样本之间的时序相关性,因为连续的交互样本往往具有较强的相关性,如果直接按顺序使用这些样本训练,可能导致模型学习到的模式存在偏差,且容易过拟合。随机采样使得训练数据更加独立同分布,符合传统机器学习算法的假设,从而提升了数据利用率,使训练更加稳定和有效。

(2)代码复现

D = deque(maxlen=replay_memory_size)  # 双端队列存储经验
minibatch = random.sample(D, minibatch_size)  # 随机采样
3.2.3.2 目标网络(Target Network)

(1)论文方法

独立网络计算目标 Q 值:使用一个独立于当前用于决策的网络(即评估网络)的目标网络来计算目标 Q 值。目标网络的结构与评估网络相同,但参数更新方式不同。目标网络的参数不是每一步都更新,而是定期与评估网络的参数进行同步(例如每 1000 步同步一次) 。在计算目标 Q 值时,使用目标网络的参数,这样可以使目标 Q 值在一段时间内保持相对稳定,避免因评估网络参数频繁更新而导致代码目标 Q 值波动过大,进而使训练过程更加稳定,有助于收敛到更好的解。

(2)代码复现

output_1_batch = model(state_1_batch)  # 直接使用当前网络计算下一状态Q值
y_batch = reward + gamma * max(output_1_batch)

此点和论文有差异。

3.2.4 细节优化

3.2.4.1 优化器

(1)论文方法

优化器:RMSProp,学习率固定。
奖励裁剪:±1。

(2)代码复现

optimizer = optim.Adam(lr=0.0002)       # 使用Adam而非RMSProp
reward = torch.clamp(reward, -1, 1)     # 奖励裁剪
3.2.4.2 探索策略(ε-greedy)

(1)论文方法

ε从1.0线性衰减至0.1(前100万帧),固定为0.1。

(2)代码复现

epsilon = initial_epsilon (0.1) → final_epsilon (0.05)
epsilon -= (initial_epsilon - final_epsilon) / explore

初始ε为0.1,衰减终点为0.05。

4. 总结

使用经验回放和目标网络的训练机制,首次实现从像素到动作的端到端强化学习,推动深度RL发展;但也有长时序任务(如Q*bert需长期规划)表现不及人类,奖励裁剪可能影响策略优化,计算成本高等缺点。

5. 完整代码与项目架构

5.1 dqn.py

实现深度 Q 网络(DQN)的训练和测试逻辑。定义了神经网络的结构,使用 gameTRY.py 中提供的游戏环境进行训练和测试,通过经验回放和 epsilon-greedy 策略来学习如何在游戏中做出最优决策。

import cv2
import numpy as np
from collections import deque
import random
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision.transforms as T
import torch.nn.functional as F
from gameTRY import Breakout
import os
import sys
import time

# 定义神经网络类,继承自 PyTorch 的 nn.Module
class NeuralNetwork(nn.Module):
    def __init__(self):
        super(NeuralNetwork, self).__init__()

        # 定义动作数量,这里表示有 2 个动作可供选择
        self.number_of_actions = 2
        # 折扣因子,用于计算未来奖励的现值
        self.gamma = 0.99
        # 最终的探索率,用于 epsilon-greedy 策略
        self.final_epsilon = 0.05
        # 初始的探索率
        self.initial_epsilon = 0.1
        # 训练的总迭代次数
        self.number_of_iterations = 2000000
        # 经验回放缓冲区的大小
        self.replay_memory_size = 750000
        # 每次训练时从经验回放缓冲区中采样的小批量大小
        self.minibatch_size = 32
        # 从初始探索率到最终探索率所需的时间步长
        self.explore = 3000000

        # 定义卷积层
        # 输入通道数为 4,输出通道数为 32,卷积核大小为 8,步长为 4
        self.conv1 = nn.Conv2d(4, 32, kernel_size=8, stride=4)
        # 输入通道数为 32,输出通道数为 64,卷积核大小为 4,步长为 2
        self.conv2 = nn.Conv2d(32, 64, 4, 2)
        # 输入通道数为 64,输出通道数为 64,卷积核大小为 3,步长为 1
        self.conv3 = nn.Conv2d(64, 64, 3, 1)
        # 定义全连接层
        # 输入特征数为 7 * 7 * 64,输出特征数为 512
        self.fc4 = nn.Linear(7 * 7 * 64, 512)
        # 输入特征数为 512,输出特征数为动作数量
        self.fc5 = nn.Linear(512, self.number_of_actions)

    def forward(self, x):
        # 对输入数据依次通过卷积层和激活函数 ReLU
        x = F.relu(self.conv1(x))
        x = F.relu(self.conv2(x))
        x = F.relu(self.conv3(x))
        # 将卷积层的输出展平,以便输入到全连接层
        x = F.relu(self.fc4(x.view(x.size(0), -1)))
        # 输出每个动作的 Q 值
        return self.fc5(x)

# 图像预处理函数,将输入的图像转换为适合神经网络输入的格式
def preprocessing(image):
    # 将图像转换为灰度图,并调整大小为 84x84
    image_data = cv2.cvtColor(cv2.resize(image, (84, 84)), cv2.COLOR_BGR2GRAY)
    # 将灰度图中大于 0 的像素值设置为 255
    image_data[image_data > 0] = 255
    # 调整图像的形状为 (84, 84, 1)
    image_data = np.reshape(image_data, (84, 84, 1))
    # 交换维度,将通道维度放在前面
    image_tensor = image_data.transpose(2, 0, 1)
    # 将数据类型转换为 float32
    image_tensor = image_tensor.astype(np.float32)
    # 将 NumPy 数组转换为 PyTorch 张量
    image_tensor = torch.from_numpy(image_tensor)
    return image_tensor

# 初始化神经网络的权重
def init_weights(m):
    if type(m) == nn.Conv2d or type(m) == nn.Linear:
        # 对卷积层和全连接层的权重进行均匀初始化,范围在 -0.01 到 0.01 之间
        torch.nn.init.uniform(m.weight, -0.01, 0.01)
        # 将偏置项初始化为 0.01
        m.bias.data.fill_(0.01)

# 训练函数,用于训练神经网络
def train(model, start):
    # 定义 Adam 优化器,用于更新神经网络的参数
    optimizer = optim.Adam(model.parameters(), lr=0.0002)
    # 定义均方误差损失函数
    criterion = nn.MSELoss()

    # 创建游戏环境实例
    game_state = Breakout()

    # 初始化经验回放缓冲区
    D = deque()

    # 初始动作设置为不做任何动作
    action = torch.zeros([model.number_of_actions], dtype=torch.float32)
    action[0] = 0
    # 执行初始动作,获取图像数据、奖励和游戏是否结束的标志
    image_data, reward, terminal = game_state.take_action(action)
    # 对图像数据进行预处理
    image_data = preprocessing(image_data)
    # 将四张相同的预处理图像拼接在一起,作为初始状态
    state = torch.cat((image_data, image_data, image_data, image_data)).unsqueeze(0)

    # 初始化探索率
    epsilon = model.initial_epsilon
    # 初始化迭代次数
    iteration = 0

    # 主训练循环,直到达到最大迭代次数
    while iteration < model.number_of_iterations:
        # 通过神经网络获取当前状态下每个动作的 Q 值
        output = model(state)[0]

        # 初始化动作向量
        action = torch.zeros([model.number_of_actions], dtype=torch.float32)

        # epsilon-greedy 探索策略,以一定概率随机选择动作
        random_action = random.random() <= epsilon
        if random_action:
            print("Random action!")

        # 根据 epsilon-greedy 策略选择动作
        action_index = [torch.randint(model.number_of_actions, torch.Size([]), dtype=torch.int)
                        if random_action
                        else torch.argmax(output)][0]

        # 将选择的动作对应的位置置为 1
        action[action_index] = 1

        # 逐渐降低探索率
        if epsilon > model.final_epsilon:
            epsilon -= (model.initial_epsilon - model.final_epsilon) / model.explore

        # 执行选择的动作,获取下一个状态的图像数据、奖励和游戏是否结束的标志
        image_data_1, reward, terminal = game_state.take_action(action)
        # 对下一个状态的图像数据进行预处理
        image_data_1 = preprocessing(image_data_1)

        # 更新状态,将当前状态的后三张图像和新的图像拼接在一起
        state_1 = torch.cat((state.squeeze(0)[1:, :, :], image_data_1)).unsqueeze(0)
        # 调整动作和奖励的形状
        action = action.unsqueeze(0)
        reward = torch.from_numpy(np.array([reward], dtype=np.float32)).unsqueeze(0)

        # 将当前状态、动作、奖励、下一个状态和游戏是否结束的标志保存到经验回放缓冲区
        D.append((state, action, reward, state_1, terminal))

        # 如果经验回放缓冲区已满,移除最早的经验
        if len(D) > model.replay_memory_size:
            D.popleft()

        # 从经验回放缓冲区中随机采样一个小批量的经验
        minibatch = random.sample(D, min(len(D), model.minibatch_size))
        # 解包小批量的经验
        state_batch = torch.cat(tuple(d[0] for d in minibatch))
        action_batch = torch.cat(tuple(d[1] for d in minibatch))
        reward_batch = torch.cat(tuple(d[2] for d in minibatch))
        state_1_batch = torch.cat(tuple(d[3] for d in minibatch))

        # 获取下一个状态的 Q 值
        output_1_batch = model(state_1_batch)

        # 根据 Bellman 方程计算目标 Q 值
        y_batch = torch.cat(tuple(reward_batch[i] if minibatch[i][4]
                                  else reward_batch[i] + model.gamma * torch.max(output_1_batch[i])
                                  for i in range(len(minibatch))))

        # 计算当前状态下选择动作的 Q 值
        q_value = torch.sum(model(state_batch) * action_batch, dim=1)

        # 清空优化器的梯度
        optimizer.zero_grad()

        # 分离目标 Q 值,避免梯度传播
        y_batch = y_batch.detach()

        # 计算损失
        loss = criterion(q_value, y_batch)

        # 反向传播计算梯度
        loss.backward()
        # 更新神经网络的参数
        optimizer.step()

        # 更新当前状态为下一个状态
        state = state_1
        # 迭代次数加 1
        iteration += 1

        # 每迭代 10000 次,保存一次模型
        if iteration % 10000 == 0:
            torch.save(model, "trained_model/current_model_" + str(iteration) + ".pth")

        # 打印训练信息
        print("total iteration: {} Elapsed time: {:.2f} epsilon: {:.5f}"
              " action: {} Reward: {:.1f}".format(iteration, ((time.time() - start) / 60), epsilon,
                                                  action_index.cpu().detach().numpy(), reward.numpy()[0][0]))

# 测试函数,用于测试训练好的模型
def test(model):
    # 创建游戏环境实例
    game_state = Breakout()

    # 初始动作设置为不做任何动作
    action = torch.zeros([model.number_of_actions], dtype=torch.float32)
    action[0] = 1
    # 执行初始动作,获取图像数据、奖励和游戏是否结束的标志
    image_data, reward, terminal = game_state.take_action(action)
    # 对图像数据进行预处理
    image_data = preprocessing(image_data)
    # 将四张相同的预处理图像拼接在一起,作为初始状态
    state = torch.cat((image_data, image_data, image_data, image_data)).unsqueeze(0)

    # 无限循环进行测试
    while True:
        # 通过神经网络获取当前状态下每个动作的 Q 值
        output = model(state)[0]

        # 初始化动作向量
        action = torch.zeros([model.number_of_actions], dtype=torch.float32)

        # 选择 Q 值最大的动作
        action_index = torch.argmax(output)
        # 将选择的动作对应的位置置为 1
        action[action_index] = 1

        # 执行选择的动作,获取下一个状态的图像数据、奖励和游戏是否结束的标志
        image_data_1, reward, terminal = game_state.take_action(action)
        # 对下一个状态的图像数据进行预处理
        image_data_1 = preprocessing(image_data_1)
        # 更新状态,将当前状态的后三张图像和新的图像拼接在一起
        state_1 = torch.cat((state.squeeze(0)[1:, :, :], image_data_1)).unsqueeze(0)

        # 更新当前状态为下一个状态
        state = state_1

# 主函数,根据输入的模式选择训练、测试或继续训练
def main(mode):
    if mode == 'test':
        # 加载训练好的模型,并将其设置为评估模式
        model = torch.load('trained_model/current_model_420000.pth', map_location='cpu', weights_only = False).eval()
        # 调用测试函数进行测试
        test(model)
    elif mode == 'train':
        # 如果保存模型的目录不存在,则创建该目录
        if not os.path.exists('trained_model/'):
            os.mkdir('trained_model/')
        # 创建神经网络模型实例
        model = NeuralNetwork()
        # 初始化模型的权重
        model.apply(init_weights)
        # 记录训练开始时间
        start = time.time()
        # 调用训练函数进行训练
        train(model, start)
    elif mode == 'continue':
        # 加载训练好的模型,并将其设置为评估模式
        model = torch.load('trained_model/current_model_50000.pth', map_location='cpu', weights_only = False).eval()
        # 记录训练开始时间
        start = time.time()
        # 调用训练函数继续训练
        train(model, start)

if __name__ == "__main__":
    # 根据命令行参数调用主函数
    main(sys.argv[1])

5.2 gameTRY.py

负责实现游戏的具体逻辑,包括游戏元素(砖块、球拍、球)的初始化、绘制、移动,以及碰撞检测和游戏状态的更新。提供了一个游戏环境,智能体可以与这个环境进行交互,获取环境的状态和奖励。

import pygame
import math
import random

# 定义屏幕尺寸常量
SIZE_OF_THE_SCREEN = 424, 430
# 砖块的高度和宽度
HEIGHT_OF_BRICK = 13
WIDTH_OF_BRICK = 32
# 球拍的高度、宽度和Y坐标
HEIGH_OF_PADDLE = 8
PADDLE_WIDTH = 50
PADDLE_Y = SIZE_OF_THE_SCREEN[1] - HEIGH_OF_PADDLE - 10
# 球的直径、半径以及X坐标的最大限制
BALL_DIAMETER = 12
BALL_RADIUS = BALL_DIAMETER // 2
MAX_PADDLE_X = SIZE_OF_THE_SCREEN[0] - PADDLE_WIDTH
MAX_BALL_X = SIZE_OF_THE_SCREEN[0] - BALL_DIAMETER
MAX_BALL_Y = SIZE_OF_THE_SCREEN[1] - BALL_DIAMETER

# 颜色常量定义
BLACK = (0, 0, 0)
WHITE = (255, 255, 255)
BLUE = (0, 0, 255)
COLOR_OF_BRICK = (153, 76, 0)
PADDLE_COLOR = (204, 0, 0)

FPS = 60
FPSCLOCK = pygame.time.Clock()

pygame.init()  # 初始化pygame模块
screen = pygame.display.set_mode(SIZE_OF_THE_SCREEN)
pygame.display.set_caption(" BREAKOUT")
clock = pygame.time.Clock()

class Breakout:
    def __init__(self):
        """初始化游戏状态:包括球的速度、球拍和球的矩形对象,调用方法创建砖块"""
        self.capture = 0
        # 球的速度(x方向,y方向),初始设置为[12, -12]用于训练
        self.ball_vel = [12, -12]
        # 定义球拍的矩形(左x坐标,上y坐标,宽度,高度)
        self.paddle = pygame.Rect(215, PADDLE_Y, PADDLE_WIDTH, HEIGH_OF_PADDLE)
        # 定义球的矩形(左x坐标,上y坐标,直径,直径)
        self.ball = pygame.Rect(225, PADDLE_Y - BALL_DIAMETER, BALL_DIAMETER, BALL_DIAMETER)
        self.create_bricks()  # 调用方法创建砖块

    def create_bricks(self):
        """创建砖块布局:通过循环生成多个砖块矩形对象并添加到bricks列表中"""
        y_ofs = 20
        self.bricks = []
        for i in range(11):
            x_ofs = 15
            for j in range(12):
                # 创建砖块矩形(左x坐标,上y坐标,宽度,高度)并添加到列表
                self.bricks.append(pygame.Rect(x_ofs, y_ofs, WIDTH_OF_BRICK, HEIGHT_OF_BRICK))
                x_ofs += WIDTH_OF_BRICK + 1  # 计算下一个砖块的x坐标
            y_ofs += HEIGHT_OF_BRICK + 1  # 计算下一行砖块的y坐标

    def draw_bricks(self):
        """绘制所有砖块:遍历bricks列表,使用pygame绘制矩形"""
        for brick in self.bricks:
            pygame.draw.rect(screen, COLOR_OF_BRICK, brick)

    def draw_paddle(self):
        """绘制球拍:使用pygame绘制球拍矩形"""
        pygame.draw.rect(screen, PADDLE_COLOR, self.paddle)

    def draw_ball(self):
        """绘制球:使用pygame绘制圆形表示球"""
        pygame.draw.circle(screen, WHITE, (self.ball.left + BALL_RADIUS, self.ball.top + BALL_RADIUS), BALL_RADIUS)

    def check_input(self, input_action):
        """根据输入动作移动球拍:0表示左移,1表示右移"""
        # 处理向左移动
        if input_action[0] == 1:
            self.paddle.left -= 12  # 左移12像素(训练时的速度)
            if self.paddle.left < 0:
                self.paddle.left = 0  # 确保不超出左边界
        # 处理向右移动
        if input_action[1] == 1:
            self.paddle.left += 12  # 右移12像素(训练时的速度)
            if self.paddle.left > MAX_PADDLE_X:
                self.paddle.left = MAX_PADDLE_X  # 确保不超出右边界

    def move_ball(self):
        """移动球并处理边界碰撞:更新球的位置,根据边界反弹"""
        self.ball.left += self.ball_vel[0]  # 按x方向速度移动球
        self.ball.top += self.ball_vel[1]  # 按y方向速度移动球
        # 处理左边界碰撞
        if self.ball.left <= 0:
            self.ball.left = 0
            self.ball_vel[0] = -self.ball_vel[0]  # 反转x方向速度
        # 处理右边界碰撞
        elif self.ball.left >= MAX_BALL_X:
            self.ball.left = MAX_BALL_X
            self.ball_vel[0] = -self.ball_vel[0]  # 反转x方向速度
        # 处理上边界碰撞
        if self.ball.top < 0:
            self.ball.top = 0
            self.ball_vel[1] = -self.ball_vel[1]  # 反转y方向速度
        # 处理下边界碰撞(这里实际是球落到底部的情况,后续逻辑会重置)
        elif self.ball.top >= MAX_BALL_Y:
            self.ball.top = MAX_BALL_Y
            self.ball_vel[1] = -self.ball_vel[1]  # 反转y方向速度

    def take_action(self, input_action):
        """执行一帧的游戏逻辑:处理输入、移动、碰撞检测、绘制并返回数据"""
        pygame.event.pump()  # 处理事件泵

        reward = 0.1  # 初始奖励值
        terminal = False  # 是否结束标志
        randNum = random.randint(0, 1)  # 随机数(未明确具体用途,代码中似未充分使用)

        # 处理退出事件
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                quit()

        screen.fill(BLACK)  # 填充屏幕为黑色
        self.check_input(input_action)  # 处理输入移动球拍
        self.move_ball()  # 移动球

        # 处理球与砖块的碰撞
        for brick in self.bricks:
            if self.ball.colliderect(brick):  # 检测球与砖块碰撞
                reward = 2  # 碰撞到砖块的奖励
                self.ball_vel[1] = -self.ball_vel[1]  # 反转球的y方向速度
                self.bricks.remove(brick)  # 从列表中移除被碰撞的砖块
                break
        # 所有砖块被消除时重置游戏(可视为一局结束)
        if len(self.bricks) == 0:
            self.terminal = True
            self.__init__()  # 重新初始化游戏状态(似有问题,__init__会重新创建所有砖块,但代码逻辑如此)
        # 处理球与球拍的碰撞
        if self.ball.colliderect(self.paddle):
            self.ball.top = PADDLE_Y - BALL_DIAMETER  # 重置球的y坐标到球拍上方
            self.ball_vel[1] = -self.ball_vel[1]  # 反转球的y方向速度
        # 球落到底部(未碰到球拍),游戏结束
        elif self.ball.top > self.paddle.top:
            terminal = True
            self.__init__()  # 重新初始化游戏(可能用于新一局开始)
            reward = -2  # 球掉落的惩罚奖励

        self.draw_bricks()  # 绘制砖块
        self.draw_ball()  # 绘制球
        self.draw_paddle()  # 绘制球拍
        # 捕获屏幕图像数据(转换为三维数组)
        image_data = pygame.surfarray.array3d(pygame.display.get_surface())
        pygame.display.update()  # 更新屏幕显示
        FPSCLOCK.tick(FPS)  # 控制帧率
        return image_data, reward, terminal  # 返回屏幕图像数据、奖励值和是否结束标志

5.3 架构

从头训练模型:

python dqn.py train

中断后接着训练模型:

python dqn.py continue

测试模型:

python dqn.py test

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值