基于DQN算法解决Cart-Pole问题

基于DQN的方法解决Cart-Pole问题

本文主要是对莫烦的DQN程序进行理解及注释,供自己理解以及向大家提供参考

import torch                                    # 导入torch
import torch.nn as nn                           # 导入torch.nn
import torch.nn.functional as F                 # 导入torch.nn.functional
import numpy as np                              # 导入numpy
import gym                                      # 导入gym

# 超参数
BATCH_SIZE = 32                                 # 样本数量
LR = 0.01                                       # 学习率
EPSILON = 0.9                                   # greedy policy
GAMMA = 0.9                                     # reward discount
TARGET_REPLACE_ITER = 100                       # 目标网络更新频率
MEMORY_CAPACITY = 2000                          # 记忆库容量
env = gym.make('CartPole-v0').unwrapped         # 使用gym库中的环境:CartPole,且打开封装 (若想了解该环境,请自行百度)
N_ACTIONS = env.action_space.n                  # 杆子动作个数 (2个):0:车往左移动 1:车往右移动
N_STATES = env.observation_space.shape[0]       # 杆子状态个数 (4个)就是每一个状态用:车的位置、车的速度、杆子倾斜角度、杆子的角速度


"""
torch.nn是专门为神经网络设计的模块化接口。nn构建于Autograd之上,可以用来定义和运行神经网络。
nn.Module是nn中十分重要的类,包含网络各层的定义及forward方法。
定义网络:
    需要继承nn.Module类,并实现forward方法。
    一般把网络中具有可学习参数的层放在构造函数__init__()中。
    不具有可学习参数的层(如ReLU)可放在构造函数中,也可不放在构造函数中(而在forward中使用nn.functional来代替)。
    只要在nn.Module的子类中定义了forward函数,backward函数就会被自动实现(利用Autograd)。
    在forward函数中可以使用任何Variable支持的函数,毕竟在整个Pytorch构建的图中,是Variable在流动。还可以使用if,for,print,log等python语法。
    注:Pytorch基于nn.Module构建的模型中,只支持mini-batch的Variable输入方式。
"""


# 定义Net类 (定义网络)
class Net(nn.Module):
    def __init__(self):                                                         # 定义Net的一系列属性

        # nn.Module的子类函数必须在构造函数中执行父类的构造函数
        super(Net, self).__init__()                                             # 等价与nn.Module.__init__()

        self.fc1 = nn.Linear(N_STATES, 50)                                      # 设置第一个全连接层(输入层到隐藏层): 状态数个神经元到50个神经元
        self.fc1.weight.data.normal_(0, 0.1)                                    # 权重初始化 (均值为0,方差为0.1的正态分布)
        self.out = nn.Linear(50, N_ACTIONS)                                     # 设置第二个全连接层(隐藏层到输出层): 50个神经元到动作数个神经元
        self.out.weight.data.normal_(0, 0.1)                                    # 权重初始化 (均值为0,方差为0.1的正态分布)

    def forward(self, x):                                                       # 定义forward函数 (x为状态)
        x = F.relu(self.fc1(x))  #  这里的x是指隐含层的输出,数据类型是张量形式,[1,50]连接输入层到隐藏层,且使用激励函数ReLU来处理经过隐藏层后的值
        # print(np.shape(x))
        # print(x)
        # print("----------------------------")
        actions_value = self.out(x)  # 连接隐藏层到输出层,获得最终的输出值 (即动作值) [1,2]
        return actions_value                                                    # 返回动作值


# 定义DQN类 (定义两个网络)
class DQN(object):
    def __init__(self):                                                         # 定义DQN的一系列属性
        self.eval_net, self.target_net = Net(), Net()                           # 利用Net创建两个神经网络: 评估网络和目标网络 评估网络用来进行计算价值
        self.learn_step_counter = 0                                             # for target updating
        self.memory_counter = 0                                                 # for storing memory
        self.memory = np.zeros((MEMORY_CAPACITY, N_STATES * 2 + 2))             # 初始化记忆库,一行代表一个transition 就是s(位置、速度、角度、角速度) 动作、奖励、s'(位置、速度、角度、角速度)
        self.optimizer = torch.optim.Adam(self.eval_net.parameters(), lr=LR)    # 使用Adam优化器 (输入为评估网络的参数和学习率)
        self.loss_func = nn.MSELoss()                                           # 使用均方损失函数 (loss(xi, yi)=(xi-yi)^2)

    def choose_action(self, x):   # 定义动作选择函数 (x为状态,由位置、速度、角度、角速度构成当前的一个动作
        x = torch.unsqueeze(torch.FloatTensor(x), 0)                            # 将x转换成32-bit floating point形式,并在dim=0增加维数为1的维度
        if np.random.uniform() < EPSILON:                                       # 生成一个在[0, 1)内的随机数,如果小于EPSILON,选择最优动作
            actions_value = self.eval_net.forward(x)                            # 通过对评估网络输入状态x,前向传播获得动作值
            print(actions_value)
            print("----------end-----------")
            action = torch.max(actions_value, 1)[1].data.numpy()                # 输出每一行最大值的索引,并转化为numpy ndarray形式
            #print(action)
            #print("----------end-----------")
            action = action[0]                                                  # 输出action的第一个数
        else:                                                                   # 随机选择动作
            action = np.random.randint(0, N_ACTIONS)                            # 这里action随机等于0或1 (N_ACTIONS = 2)
        return action                                                           # 返回选择的动作 (0或1)

    def store_transition(self, s, a, r, s_):                                    # 定义记忆存储函数 (这里输入为一个transition)
        transition = np.hstack((s, [a, r], s_))                                 # 在水平方向上拼接数组
        # 如果记忆库满了,便覆盖旧的数据
        index = self.memory_counter % MEMORY_CAPACITY                           # 获取transition要置入的行数
        self.memory[index, :] = transition                                      # 置入transition
        self.memory_counter += 1                                                # memory_counter自加1

    def learn(self):                                                            # 定义学习函数(记忆库已满后便开始学习)
        # 目标网络参数更新
        if self.learn_step_counter % TARGET_REPLACE_ITER == 0:                  # 一开始触发,然后每100步触发
            self.target_net.load_state_dict(self.eval_net.state_dict())         # 将评估网络的参数赋给目标网络
        self.learn_step_counter += 1                                            # 学习步数自加1

        # 抽取记忆库中的批数据
        sample_index = np.random.choice(MEMORY_CAPACITY, BATCH_SIZE)            # 在[0, 2000)内随机抽取32个数,可能会重复
        b_memory = self.memory[sample_index, :]                                 # 抽取32个索引对应的32个transition,存入b_memory
        b_s = torch.FloatTensor(b_memory[:, :N_STATES])
        # 将32个s抽出,转为32-bit floating point形式,并存储到b_s中,b_s为32行4列
        b_a = torch.LongTensor(b_memory[:, N_STATES:N_STATES+1].astype(int))
        # 将32个a抽出,转为64-bit integer (signed)形式,并存储到b_a中 (之所以为LongTensor类型,是为了方便后面torch.gather的使用),b_a为32行1列
        b_r = torch.FloatTensor(b_memory[:, N_STATES+1:N_STATES+2])
        # 将32个r抽出,转为32-bit floating point形式,并存储到b_s中,b_r为32行1列
        b_s_ = torch.FloatTensor(b_memory[:, -N_STATES:])
        # 将32个s_抽出,转为32-bit floating point形式,并存储到b_s中,b_s_为32行4列

        # 获取32个transition的评估值和目标值,并利用损失函数和优化器进行评估网络参数更新
        q_eval = self.eval_net(b_s).gather(1, b_a)  # 得出的在状态s下的(当然是32个episode)的最佳动作值q(s,a)
        # eval_net(b_s)通过评估网络输出32行每个b_s对应的一系列动作值,然后.gather(1, b_a)代表对每行对应索引b_a的Q值提取进行聚合
        q_next = self.target_net(b_s_).detach()
        # q_next不进行反向传递误差,所以detach;q_next表示通过目标网络输出32行每个b_s_对应的一系列动作值
        q_target = b_r + GAMMA * q_next.max(1)[0].view(BATCH_SIZE, 1)
        # q_next.max(1)[0]表示只返回每一行的最大值,不返回索引(长度为32的一维张量);.view()表示把前面所得到的一维张量变成(BATCH_SIZE, 1)的形状;最终通过公式得到目标值
        loss = self.loss_func(q_eval, q_target)
        # 输入32个评估值和32个目标值,使用均方损失函数
        self.optimizer.zero_grad()                                              # 清空上一步的残余更新参数值
        loss.backward()                                                         # 误差反向传播, 计算参数更新值
        self.optimizer.step()                                                   # 更新评估网络的所有参数


dqn = DQN()                                                                     # 令dqn=DQN类
print('\nCollecting experience...')                                             # 打印“Collecting experience...”

for i_episode in range(400):                                                    # 400个episode循环
    s = env.reset() # 输出的是一小车当前的状态,也就是一个状态:位置、速度、角度、角速度     # 重置环境
    print(s)
    print("-----------one time--------- ")
    # print(s)
    # print("---------time-----------")
    ep_r = 0                                                                    # 初始化该循环对应的episode的奖励
    while True:                                                                 # 开始一个episode (每一个循环代表一步)
        env.render()  # 开启图像引擎,显示实验动画
        a = dqn.choose_action(s)  # 输入该步对应的状态s,根据epsilon-greedy 选择动作a
        s_, r, done, info = env.step(a)    # 执行动作,获得反馈,下一个动作s、当前动作的奖励r、完成标志、

        # 修改奖励 (不修改也可以,修改奖励只是为了更快地得到训练好的摆杆)
        x, x_dot, theta, theta_dot = s_  # (这里的s_是下一个动作,这个动作包含4个特征:位置、速度、角度、角速度)
        #  ------??????怎么修改的奖励--------#
        r1 = (env.x_threshold - abs(x)) / env.x_threshold - 0.8
        r2 = (env.theta_threshold_radians - abs(theta)) / env.theta_threshold_radians - 0.5
        r = r1 + r2

        dqn.store_transition(s, a, r, s_)   # 4+1+1+4=4*2+2存储样本
        ep_r += r  # 该episode对应的奖励自加本步执行动作获得的奖励r

        if dqn.memory_counter > MEMORY_CAPACITY:                                # 如果累计的transition数量超过了记忆库的固定容量2000
            dqn.learn()
            # 开始学习 (抽取记忆,即32个transition,并对评估网络参数进行更新,并在开始学习后每隔100次将评估网络的参数赋给目标网络)
            if done:
                # 如果该episode对应的done为True,则输出以下内容 (注意并不是第一个episode就开始,而是记忆库已满后才开始)
                # 因此每次开始进行输出的对应的episode不一样
                print('Ep: ', i_episode,                                        # 输出该episode数
                      '| Ep_r: ', round(ep_r, 2))                               # round()方法返回ep_r的小数点四舍五入到2个数字

        if done:                                                                # 如果满足终止条件
            break                                                               # 该episode结束
        s = s_                                                                  # 更新状态

参考:https://blog.csdn.net/weixin_43559819/article/details/106012662?utm_medium=distribute.pc_aggpage_search_result.none-task-blog-2allfirst_rank_v2~rank_v25-3-106012662.nonecase

  • 4
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: DQN (Deep Q-Network) 是一种基于深度神经网络的强化学习算法,它可用于解决强化学习环境中的问题。PyTorch 是一个开源的深度学习框架,提供了一种简单而强大的方式来构建和训练神经网络模型。CartPole-v0 是 OpenAI Gym 提供的一个强化学习环境,目标是控制一个摆杆平衡在垂直位置上。 在使用 PyTorch 实现 DQN 解决 CartPole-v0 问题时,需要首先定义一个深度神经网络模型作为 Q 函数的近似。这个模型通常包含若干隐藏层和一个输出层,用于预测在给定状态下采取各个动作的 Q 值。 然后,需要定义一个经验回放(Experience Replay)的缓冲区,用于存储智能体在环境中的经验,包括当前状态、动作、奖励和下一个状态。 接下来,使用 epsilon-greedy 策略选择动作,epsilon 表示随机探索的概率,即以一定概率选择随机动作,以一定概率选择当前 Q 值最大的动作。 将选择的动作应用于环境中,观察下一状态和奖励,并将这些经验存储到经验回放缓冲区中。 每隔一定步数,从经验回放缓冲区中采样一批数据,然后利用这些样本数据来更新神经网络的参数。DQN 使用经验回放的方式进行训练,这样可以减少样本间的相关性,提高样本的利用效率。 通过反向传播算法计算损失函数,并利用优化器更新神经网络的参数,使得神经网络的输出 Q 值逼近真实的 Q 值。 重复进行上述步骤,直到智能体能够有效地平衡摆杆,或者达到预定的训练次数。 在实际实现 DQN 算法过程中,还需要注意学习速率、discount factor 等超参数的选择,以及选择合适的损失函数和优化器来训练神经网络模型。 总结来说,使用 PyTorch 实现 DQN解决 CartPole-v0 问题,需要先定义一个深度神经网络模型作为 Q 函数的近似,然后利用经验回放的方式进行训练,通过反向传播算法来更新神经网络参数,使模型能够逼近真实的 Q 值,最终达到使摆杆平衡的目标。 ### 回答2: DQN(深度Q网络)是一种强化学习算法,用于解决各种控制问题,包括CartPole-v0这个经典的强化学习环境。PyTorch是一种深度学习框架,可以方便地构建神经网络模型。 在使用PyTorch实现DQN解决CartPole-v0问题时,我们首先需要定义网络模型。可以使用PyTorch提供的nn模块创建一个多层感知机网络,包含输入层、若干隐藏层和输出层。这个网络的输入是CartPole-v0环境的状态,输出是动作的Q值。使用ReLU作为激活函数可以增加网络的非线性表示能力。 定义好网络模型后,我们需要定义DQN的训练过程。首先,根据当前环境状态输入网络获取各个动作的Q值,然后选择Q值最大的动作作为当前的行动。执行动作后,环境将返回下一个状态、奖励和是否结束的信息。将这些信息存储在经验回放缓冲区中。 接下来,我们从经验回放缓冲区中随机采样一批数据,包括之前的状态、行动、奖励和下一个状态。然后,使用目标网络(Target Network)计算下一个状态的Q值,并根据贝尔曼方程计算当前状态的目标Q值。通过最小化当前状态的动作Q值和目标Q值的差距,更新网络的参数。 在DQN的训练过程中,还需要设置超参数,包括学习率、批大小、epsilon-greedy策略的参数等。为了提高收敛速度和稳定性,可以使用经验回放和目标网络两个技术。 最后,通过多次迭代训练,不断优化网络参数,直到DQN模型在CartPole-v0环境上能够稳定地获得较高的得分。 总之,使用PyTorch实现DQN算法解决CartPole-v0问题需要定义网络模型、训练过程和超参数,并使用经验回放和目标网络等技术进行优化,以提高性能和稳定性。 ### 回答3: DQN是一种使用深度神经网络进行强化学习算法,它使用PyTorch框架实现,在CartPole-v0环境中非常有用。 CartPole-v0是一个经典的强化学习问题,任务是控制一个平衡杆,使其在变化的条件下保持平衡。这个环境具有四个状态变量:杆的角度、杆的速度、小车的位置和小车的速度。在每个时间步骤,智能体可以向左或向右施加力来控制小车的动作。目标是使杆保持在竖直位置,并且尽可能长时间地保持平衡。 DQN算法使用了深度神经网络来估计每种动作的Q值函数。在PyTorch中,我们可以使用nn.Module类创建深度神经网络模型,可以包含一些全连接层和非线性激活函数。DQN算法还使用了经验回放机制和目标网络来提高训练效果。 在CartPole-v0中,我们可以使用PyTorch中的torchvision.transforms对环境状态进行处理。然后,我们可以使用DQN模型以一定的epsilon-greedy策略来选择动作,并与环境进行交互。每个时间步之后,我们从经验回放缓冲区中随机样本一批数据,然后计算损失并更新网络参数。我们还会定期更新目标网络的权重,以确保稳定的学习过程。 通过使用DQN算法和PyTorch框架,我们可以在CartPole-v0环境中实现高效的强化学习训练。我们可以通过调整网络结构、超参数和训练步骤来提高性能,并使智能体在该环境中获得长时间平衡杆的能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值