强化学习入门笔记5——DQN 算法(基于DQN的gym登山车)

我们一直在做的一件事就是得到一个Q的分布,之前是一个矩阵,方便我们在哪个格子就知道附近(下一个动作)格子的Q是多少,但是对于很大数据或者连续数据来说就很难办。

但是我们的目的仅仅是通过状态S得到附近的Q来做决策,那Q其实就像一个函数,有输入给你输出就好了,但是如果要自己构造一个函数是很难的,所以引入神经网络。

再思考一下,这个Q函数本质就是机器对这个世界的经验,让他知道自己在悬崖时就不能往下走,让他知道哪里有宝藏,依据经验所以产生策略。

所写皆自己个人思考,有错误请指出,不是权威,谢谢!!

新环境!!!

车杆环境

它的状态值是连续的, 动作值是离散的。在车杆环境中, 有一辆小车, 智能体的任务是通过左右移动保持车上的杆竖直, 若杆的倾斜度数过大, 或者车子离初始位置左右的偏离程度过大, 或者坚持时间到达 200 帧, 则游戏结束。在游戏中每坚持一帧, 智能体能获得分数为1 的奖励, 坚持时间越长,则最后的分数越高, 坚持 200 帧即可获得最高的分数

现在我们想在类似车杆的环境中得到动作价值函数Q(s, a)

若动作是连续(无限)的,神经网络的输入是状态s 和动作 a,然后输出一个标量,表示在状态s下采取动作a 能获得的价值

若动作是离散(有限) 的, 除了可以采取动作连续情况下的做法, 我们还可以只将状态s输入到神经网络中, 使其同时输出每一个动作的Q 值。

通常 DQN(以及 Q-learning) 只能处理动作离散的情况, 因为在函数Q 的更新过程中有max_a 这一操作。

假设神经网络用来拟合函数Q 的参数是w , 即每一个状态 s 下所有可能动作 a的 Q值都能表示为 Q_w( s, a) 。 我们将用于拟合函数Q 的神经网络称为Q网络,

那么 Q 网络的损失函数是什么呢?我们先来回顾一下Q-learning的更新规则

这个式子的目的就是改变黑盒子(网络)里的参数让预测接近实际

接下来引入两个工具:

经验回放

在一般的有监督学习中,假设训练数据是独立同分布的,我们每次训练神经网络的时候从训练数据中随机采样一个或若干个数据来进行梯度下降,随着学习的不断进行,每一个训练数据会被使用多次。在原来的 Q-learning 算法中,每一个数据只会用来更新一次

值。为了更好地将 Q-learning 和深度神经网络结合,DQN 算法采用了经验回放(experience replay)方法,具体做法为维护一个回放缓冲区,将每次从环境中采样得到的四元组数据(状态、动作、奖励、下一状态)存储到回放缓冲区中,训练 Q 网络的时候再从回放缓冲区中随机采样若干数据来进行训练。这么做可以起到以下两个作用。

(1)使样本满足独立假设。在 MDP 中交互采样得到的数据本身不满足独立假设,因为这一时刻的状态和上一时刻的状态有关。非独立同分布的数据对训练神经网络有很大的影响,会使神经网络拟合到最近训练的数据上。采用经验回放可以打破样本之间的相关性,让其满足独立假设。

(2)提高样本效率。每一个样本可以被使用多次,十分适合深度神经网络的梯度学习。

目标网络

DQN 算法最终更新的目标是让

逼近

,由于 TD 误差目标本身就包含神经网络的输出,因此在更新网络参数的同时目标也在不断地改变,这非常容易造成神经网络训练的不稳定性。为了解决这一问题,DQN 便使用了目标网络(target network)的思想:既然训练过程中 Q 网络的不断更新会导致目标不断发生改变,不如暂时先将 TD 目标中的 Q 网络固定住。为了实现这一思想,我们需要利用两套 Q 网络。

(1)原来的训练网络

,用于计算原来的损失函数中的

项,并且使用正常梯度下降方法来进行更新。

(2) 目标网络

,用于计算原先损失函数中的项,其中表示目标网络中的参数。如果两套网络的参数随时保持一致,则仍为原先不够稳定的算法。为了让更新目标更稳定,目标网络并不会每一步都更新。具体而言,目标网络使用训练网络的一套较旧的参数,训练网络在训练中的每一步都会更新,而目标网络的参数每隔步才会与训练网络同步一次,即。这样做使得目标网络相对于训练网络更加稳定。

DQN 代码实践

基于DQN的gym登山车

import random
#import gymnasium as gym
import gym
import numpy as np
import collections
from tqdm import tqdm
import torch
import torch.nn.functional as F
import matplotlib.pyplot as plt
import rl_utils
#from CartPoleEnv import seed
class ReplayBuffer:
    ''' 经验回放池 '''
    def __init__(self, capacity):
        self.buffer = collections.deque(maxlen=capacity) # 队列 , 先进先出
    def add (self, state, action, reward, next_state, done): # 将数据加入 buffer
        self.buffer.append((state, action, reward, next_state, done))
    def sample(self, batch_size): # 从 buffer 中采样数据 , 数量为 batch_size
        transitions = random.sample(self.buffer, batch_size)
        state, action, reward, next_state, done = zip(*transitions)
        return np.array(state), action, reward, np.array(next_state), done
    def size(self): # 目前 buffer 中数据的数量
        return len(self.buffer) 
    
   # 然后定义一个只有一层隐藏层的 Q 网络。
class Qnet(torch.nn.Module):
    ''' 只有一层隐藏层的 Q 网络 '''
    def __init__(self, state_dim, hidden_dim, action_dim):
        super(Qnet, self).__init__()
        self.fc1 = torch.nn.Linear(state_dim, hidden_dim)
        self.fc2 = torch.nn.Linear(hidden_dim, action_dim)
    def forward(self, x):
        x = F.relu(self.fc1(x)) # 隐藏层使用 ReLU 激活函数
        return self.fc2(x)
class DQN:
    ''' DQN 算法 '''
    def __init__(self, state_dim, hidden_dim, action_dim, learning_rate, gamma,epsilon, target_update, device):
        self.action_dim = action_dim
        self.q_net = Qnet(state_dim, hidden_dim, self.action_dim).to(device) # Q 网络
        # 目标网络
        self.target_q_net = Qnet(state_dim, hidden_dim, self.action_dim).to(device)
        # 使用 Adam 优化
        self.optimizer= torch.optim.Adam(self.q_net.parameters(), lr=learning_rate)
        self.gamma = gamma # 折扣因子
        self.epsilon = epsilon # epsilon- 贪婪策略
        self.target_update = target_update # 目标网络更新频率
        self.count = 0 # 计数器 , 记录更新次数
        self.device = device
    def take_action(self, state): # epsilon-贪婪策略采取动作
        if np.random.random() < self.epsilon:
            action = np.random.randint(self.action_dim)
        else:
            state = torch.tensor([state], dtype=torch.float).to(self.device)
            action = self.q_net(state).argmax().item()
        return action

    def update(self, transition_dict):
        states = torch.tensor(transition_dict['states'], dtype=torch.float).to(self.device)
        actions = torch.tensor(transition_dict['actions']).view(-1, 1).to(self.device)
        rewards = torch.tensor(transition_dict['rewards'], dtype=torch.float).view(-1, 1).to(self.device)
        next_states = torch.tensor(transition_dict['next_states'], dtype=
        torch.float).to(self.device)
        dones = torch.tensor(transition_dict['dones'], dtype=torch.float).view(-1, 1).to(self.device)
        q_values = self.q_net(states).gather(1, actions) # Q 值
        # 下个状态的最大 Q 值
        max_next_q_values = self.target_q_net(next_states).max(1)[0].view(-1, 1)
        q_targets = rewards + self.gamma * max_next_q_values * (1 - dones) # TD 误差目标
        dqn_loss = torch.mean(F.mse_loss(q_values, q_targets)) # 均方误差损失函数
        self.optimizer.zero_grad() # PyTorch 中默认梯度会累积 , 这里需要显式将梯度置为 0
        dqn_loss.backward() # 反向传播更新参数
        self.optimizer.step()
        if self.count % self.target_update == 0:
            self.target_q_net.load_state_dict(self.q_net.state_dict()) # 更新目标网络
        self.count += 1




lr = 2e-3
num_episodes = 500
hidden_dim = 128
gamma = 0.98
epsilon = 0.01
target_update = 10
buffer_size = 10000
minimal_size = 100
batch_size = 64
device = torch.device( "cuda") if torch.cuda.is_available() else torch.device("cpu")
env_name = 'CartPole-v0'
#env = gym.make("MountainCar-v0", render_mode="human")

episode_max=-200

env = gym.make("MountainCar-v0")
random.seed(0)
np.random.seed(0)
env.seed(0)
torch.manual_seed(0)
replay_buffer = ReplayBuffer(buffer_size)
state_dim = env.observation_space.shape[0]
action_dim = env.action_space.n
agent = DQN(state_dim, hidden_dim, action_dim, lr, gamma, epsilon, target_update, device)
best_net= Qnet(2,128,3)
return_list = []
for i in range(500):
        state = env.reset()
        episode_return=0
        done = False
        while 1:
                action = agent.take_action(state)
                next_state, reward ,terminated, truncated= env.step(action)
                replay_buffer.add(state, action, reward, next_state, done)
                state = next_state
                episode_return += reward
                if replay_buffer.size() > minimal_size:
                    b_s, b_a, b_r, b_ns, b_d = replay_buffer.sample(batch_size)
                    transition_dict = {
                        'states': b_s,
                        'actions': b_a,
                        'next_states': b_ns,
                        'rewards': b_r,
                        'dones': b_d
                    }
                    agent.update(transition_dict)
                    if terminated or truncated:
                        break
        print(episode_return)
        if episode_return>episode_max:
             episode_max=episode_return
             best_net=agent.q_net
torch.save(best_net,"./best.pkl")
env = gym.make("MountainCar-v0", render_mode="human")
for i in range(6):
        state = env.reset()
        episode_return=0
        done = False
        while 1:
            state = torch.tensor([state], dtype=torch.float).to(device)
            action = agent.q_net(state).argmax().item()
            next_state, reward ,terminated, truncated= env.step(action)
            state = next_state
            episode_return += reward
            
            if terminated or truncated:
                break
        print(episode_return)
env.close()

自己写的,有问题评论

  • 21
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值