【强化学习】DQN

Deep Q Network算法


选择方法前先搞清楚状态和动作空间是连续的还是离散的!!

1.DQN理论:连续状态-离散动作

参考论文:Mnih, V., Kavukcuoglu, K., Silver, D. et al. Human-level control through deep reinforcement learning. Nature 518, 529–533 (2015). https://doi.org/10.1038/nature14236

表格形式Q-table存储的Q(s,a),状态数太复杂太多,使用网络拟合Q-table,此为近似方法(函数拟合)

DQN适用于离散动作空间,是因为函数Q的更新过程涉及 m a x a max_a maxa的操作。

1.1 Q-网络

使用神经网络拟合Q表。DQN即采用离散动作的第二种方式。
Q w ( s , a ) = N N w ( s ) L o s s = 1 2 N ∑ i = 1 N [ Q w ( s i , a i ) − ( r i + γ m a x a ′ ∈ A Q ( s i ′ , a ′ ) ] 2 参数: w Q_w(\bold{s},\bold{a})=NN_w(\bold{s})\\ Loss = \frac{1}{2N}\sum_{i=1}^{N}[Q_w(s_i,a_i)-(r_i+\gamma max_{a' \in A}Q(s_i',a')]^2\\ 参数:w Qw(s,a)=NNw(s)Loss=2N1i=1N[Qw(si,ai)(ri+γmaxaAQ(si,a)]2参数:w

  • 当动作空间是连续时:
    在这里插入图片描述

  • 当动作空间是离散时:

在这里插入图片描述

1.2 经验回放

​ 深度学习假设样本数据是独立同分布的,但是强化学习中采样的数据是强相关的。DQN使用经验回放机制,将每次从环境中采样得到的四元组数据(状态、动作、奖励、下一状态)存储到回放缓存区。再通过随机采样若干数据进行学习,以打破数据之间相关性,并且可提高训练效率。

1.3 训练网络和目标网络

​ 神经网络的训练目标是使得 Q w ( s , a ) Q_w(\bold{s},\bold{a}) Qw(s,a)不断逼近 r + γ m a x a ′ Q w ( s ′ , a ′ ) r+\gamma max_{\bold{a'} }Q_w(\bold{s'},\bold{a'}) r+γmaxaQw(s,a) 。由于神经网络的训练目标包含神经网络的输入,因此在更新参数的同时目标也在变化,这使得神经网络的训练过程不稳定。因此提出两张网络:训练网络和目标网络(两张网络结构完全一致)。

  • 训练网络 Q w ( s , a ) Q_w(\bold{s},\bold{a}) Qw(s,a)

    计算损失函数中的 1 2 [ Q w ( s , a ) − ( r + γ m a x a ′ Q w − ( s ′ , a ′ ) ] 2 \frac{1}{2}[Q_w(\bold{s},\bold{a})-(r+\gamma max_{\bold{a'} }Q_{w^-}(\bold{s'},\bold{a'})]^2 21[Qw(s,a)(r+γmaxaQw(s,a)]2 中的 Q w ( s , a ) Q_w(\bold{s},\bold{a}) Qw(s,a) ,并使用正常的梯度下降法实时更新参数 w w w

  • 目标网络

​ 计算损失函数中的 1 2 [ Q w ( s , a ) − ( r + γ m a x a ′ Q w − ( s ′ , a ′ ) ] 2 \frac{1}{2}[Q_w(\bold{s},\bold{a})-(r+\gamma max_{\bold{a'} }Q_{w^-}(\bold{s'},\bold{a'})]^2 21[Qw(s,a)(r+γmaxaQw(s,a)]2 中的 ( r + γ m a x a ′ Q w − ( s ′ , a ′ ) ) (r+\gamma max_{\bold{a'} }Q_{w^-}(\bold{s'},\bold{a'})) (r+γmaxaQw(s,a)) ,目标网络使用训练网络的旧参数,目标网络的参数 w − w_- w每隔 C C C步才会与训练网络同步一次,即 w − < − w w_-<-w w<w

使得 C C C步内,神经网络的训练目标是不变的。只有训练网络是参与训练的。

1.4 程度框图和伪代码

在这里插入图片描述

在这里插入图片描述

DQN伪代码

在这里插入图片描述
在这里插入图片描述

2.案例-CartPole

官网:https://gymnasium.farama.org/environments/classic_control/cart_pole/

源码:https://github.com/openai/gym/blob/master/gym/envs/classic_control/cartpole.py

2.1 理论

2.1.1 环境

gym:https://gymnasium.farama.org/api/env/

安装:

pip3 install swig
pip3 install box2d
pip3 install gymnasium
pip3 install gymnasium[box2d]

Tensor.detach — PyTorch 2.1 文档

2.1.2 状态

智能体的状态是一个维数为 4 的向量,每一维都是连续的。

在这里插入图片描述

2.1.3 动作

在这里插入图片描述

2.1.4 奖励

在游戏中每坚持一帧,智能体能获得分数为 1 的奖励,坚持时间越长,则最后的分数越高。(对于CartPole-v1,最大奖励为坚持500帧,即500)

2.1.5 终止条件

在这里插入图片描述

  • 对应env.step()的输出:
s_, r, Termination, Truncation, info = env.step(a)

注意:在目前V1版本的源码中,无论episode长度是否大于500,其Truncation输出均为False,没有官网API说的时间截断功能。

在这里插入图片描述

2.1.6 DQN-回放经验池

此案例使用np.array作为数据存储形式,定义二维数组为回访经验池。

  • 一个epoch即为一次试验,在一个epoch中,智能体可行走多步(step),直至达到该试验终止条件。
  • 前期:智能体每走一个step,即向记忆库写入一条数据 (探索)
  • 后期:当记忆库被填满后进入后期,在每个epoch中,每走一个step,重头开始向记忆库中写入一条数据 (探索);同时在记忆库中抽取BATCH_SIZE(N)条数据训练Q函数网络 (利用)

在这里插入图片描述

2.1.7 进度条库Tdqm

Python tqdm 进度条库的基本使用_tqdm库用法-CSDN博客

GitHub - tqdm/tqdm: ⚡️ A Fast, Extensible Progress Bar for Python and CLI

from tqdm import tqdm
#设置一个pbar即只有一个不断更新的进度条
with tqdm(total = int(400)) as pbar:
    for i_episode   
        pbar.desc = "epoch %d " % (i_episode )
        pbar.set_postfix({
            'avg_loss':
            '%0.3f' % np.mean(episode_loss),
            'return':
            '%0.3f' % episode_r
            })
        pbar.update(1) #循环一次更新一次

在这里插入图片描述

2.2 代码

2.2.1 结果

在这里插入图片描述
在这里插入图片描述

(修正后的奖励):

在这里插入图片描述

2.2.2 code

基于pytorch的DQN算法


import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np 
import gymnasium as gym
import matplotlib.pyplot as plt
from tqdm import tqdm
# 超参数
BATCH_SIZE = 32
LR = 0.01                   # learning rate
EPSILON = 0.1               # 贪心策略:最优选择动作百分比
GAMMA = 0.9                 # 奖励递减参数
TARGET_REPLACE_ITER = 100   # Q目标网络的更新频率C
MEMORY_CAPACITY = 200      # 记忆库大小

#输出环境
env = gym.make('CartPole-v1', render_mode="human")
env = env.unwrapped #环境重置

N_ACTIONS = env.action_space.n  # 杆子能做的动作
N_STATES = env.observation_space.shape[0]   # 杆子能获取的环境信息数
N_NEURON = 128 #单层神经元个数

#当有多个相同结构网络时,使用这种方式固定网络结构
class Net(nn.Module):
    def __init__(self,n_feature,n_hidden,n_output):
        super(Net, self).__init__()
        self.fc1 = nn.Linear(n_feature, n_hidden)
        self.fc1.weight.data.normal_(0, 0.1)   # 初始化网络权重
        self.out = nn.Linear(n_hidden, n_output)
        self.out.weight.data.normal_(0, 0.1)   # 初始化网络权重

    def forward(self, x):
        x = self.fc1(x)
        x = F.relu(x)
        actions_value = self.out(x)
        return actions_value

class DQN(object):
    def __init__(self):
        #建立训练网络和目标网络,以及存储区 可在大类中传递
        self.eval_net = Net(n_feature=N_STATES, n_hidden=N_NEURON, n_output=N_ACTIONS)
        self.target_net = Net(n_feature=N_STATES, n_hidden=N_NEURON, n_output=N_ACTIONS)

        self.learn_step_counter = 0     # 用于 target 更新计时
        self.memory_counter = 0         # 记忆库记数
        self.memory = np.zeros((MEMORY_CAPACITY, N_STATES * 2 + 2))     # 初始化记忆库-np数组
        self.optimizer = torch.optim.Adam(self.eval_net.parameters(), lr=LR)    # torch 的优化器 
        self.loss_func = nn.MSELoss()   # 误差公式
        
        
    def choose_action(self, x):
        #x必须为可梯度下降数据
        x = torch.unsqueeze(torch.FloatTensor(x), 0)
        
        #贪心算法 以epsilon的概率选择低价值动作
        if np.random.uniform() > EPSILON:
            actions_value = self.eval_net.forward(x)
            action = torch.max(actions_value, 1)[1].data.numpy()[0]
            
        else: #随机
            action = np.random.randint(0, N_ACTIONS) #np.int
       
        return action
            
    
    def store_transition(self, s, a, r, s_):  #回放缓存区
        transition = np.hstack((s, [a, r], s_)) #以列为单位合并,合并成一行
        #存储数据
        index = self.memory_counter % MEMORY_CAPACITY
        self.memory[index,:] = transition
        self.memory_counter += 1
        
    def learn(self):
        #检测是否更新目标网络
        if self.learn_step_counter % TARGET_REPLACE_ITER == 0:
            self.target_net.load_state_dict(self.eval_net.state_dict())
        self.learn_step_counter += 1

        #更新训练网络
        sample_index = np.random.choice(MEMORY_CAPACITY, BATCH_SIZE)#随机抽取数据
        b_memory = self.memory[sample_index,:]
        b_s = torch.FloatTensor(b_memory[:,:N_STATES])
        b_a = torch.LongTensor(b_memory[:,N_STATES:N_STATES + 1])
        b_r = torch.FloatTensor(b_memory[:,N_STATES+1:N_STATES+2])
        b_s_ = torch.FloatTensor(b_memory[:, -N_STATES:])
        
        Q = self.eval_net(b_s).gather(1,b_a) # shape=[BATCH_SIZE,1]依据当时的动作选取价值
        Q_ = self.target_net(b_s_).detach()      #下一状态的Q_且不更新
        Q_target = b_r + GAMMA*Q_.max(1)[0].view(BATCH_SIZE,1)
        loss = self.loss_func(Q, Q_target)
       
        
        self.optimizer.zero_grad()
        loss.backward()
        self.optimizer.step()
        #print(loss.data)
        return loss.data
        
        
dqn = DQN()
r_list = []
with tqdm(total = int(100)) as pbar:
    for i_episode in range(100):
        episode_r = 0
        episode_loss = []
        s = env.reset()  #环境重置
        s = np.array(s[0])
        while True:
            env.render()    # 显示实验动画
            a = dqn.choose_action(s)

            # 选动作, 得到环境反馈
            s_, r, Termination, _, info = env.step(a)
            
            #修改 reward, 使 DQN 快速学习 杆子越偏,车位置越偏,奖励越小
            x, x_dot, theta, theta_dot = s_
            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   #每一步的奖励不超过0.7,最多500步
            
            episode_r +=r
            
            # 存记忆
            dqn.store_transition(s, a, r, s_)

            if dqn.memory_counter > MEMORY_CAPACITY:
                step_loss = dqn.learn() # 记忆库满了就进行学习
            else:
                step_loss = 10000
            
            episode_loss.append(step_loss)  #记忆智能体每一步后训练网络的Loss
            
                
            if Termination | (len(episode_loss) >= 500):    # 如果回合结束, 进入下回合
                break

            s = s_
        
        r_list.append(episode_r)
       
        pbar.desc = "epoch %d " % (i_episode )
        pbar.set_postfix({
            'avg_loss':
            '%0.3f' % np.mean(episode_loss),
            'return':
            '%0.3f' % episode_r,
            'epoch_step':  #每个epoch的步数step
            '%d' % len(episode_loss)
            })
        pbar.update(1)
    
    
    
episodes_list = list(range(len(r_list)))
plt.plot(episodes_list, r_list)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('DQN on {}'.format('CartPole-v1'))
plt.show()
        
        
  • 19
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值