强化学习之DQN算法——以车杆环境(CartPole)为例

0.简介

深度Q学习(Deep Q-Learning,简称DQN)是一种结合了深度学习和Q-Learning的强化学习算法。其主要目标是使用深度神经网络来近似Q函数,从而解决传统Q-Learning在面对大规模或连续状态空间时的计算困难。

DQN的核心思想是使用一个深度神经网络(通常是卷积神经网络或者多层感知机)作为函数逼近器,来估计动作-值函数(action-value function),即Q函数。网络的输入是环境的状态,输出是每个可能动作的预期奖励。

 

 算法流程:

  • 首先初始化Memory D,它的容量为N;
  • 初始化Q网络,随机生成权重ωω;
  • 初始化target Q网络,权重为ω−=ωω−=ω;
  • 循环遍历episode =1, 2, …, M:
  • 初始化initial state S1S1;
  • 循环遍历step =1,2,…, T:
    • 用ϵ−greedyϵ−greedy策略生成action atat:以ϵϵ概率选择一个随机的action,或选择at=maxaQ(St,a;ω)at=maxaQ(St,a;ω);
    • 执行action atat,接收reward rtrt及新的state St+1St+1;
    • 将transition样本 (St,at,rt,St+1)(St,at,rt,St+1)存入D中;
    • 从D中随机抽取一个minibatch的transitions (Sj,aj,rj,Sj+1)(Sj,aj,rj,Sj+1);
    • 令yj=rjyj=rj,如果 j+1j+1步是terminal的话,否则,令 yj=rj+γmaxa′Q(St+1,a′;ω−)yj=rj+γmaxa′Q(St+1,a′;ω−);
    • 对(yj−Q(St,aj;ω))2(yj−Q(St,aj;ω))2关于ωω使用梯度下降法进行更新;
    • 每隔C steps更新target Q网络,ω−=ωω−=ω。
  • End For;
  • End For.

    原文算法流程如下:

本段摘自动手学强化学习第七章(DQN算法)_dqn更新公式-CSDN博客这位博主内容,可以相互借鉴学习。

1.导库

import random
import numpy as np
import gym
import collections
import torch
import torch.nn.functional 
import matplotlib.pyplot as plt
from tqdm import tqdm

2.经验回访池定义

class ReplayBuffer:
    """ 经验回放池 """
    def __init__(self,capacity):
        self.buffer=collections.deque(maxlen=capacity)#双端队列,即两端头部和尾部都能进行插入和删除等操作,先进先出。
    def add(self,state,action,reward,nextstate,done):#将数据加入buffer
        self.buffer.append((state,action,reward,nextstate,done))
    def sample(self,batch_size):#从buffer中采样数据
        transitions=random.sample(self.buffer,batch_size)
        state,action,reward,nextstate,done=zip(*transitions)
        return np.array(state), action, reward, np.array(nextstate), done
    def size(self):
        return len(self.buffer)

3.单隐藏层Q网络定义

class Qnet(torch.nn.Module):
    """ 定义Q网络 ,只有一层隐藏层的Q网络"""
    def __init__(self,state_dim,hidden_dim,action_dim):
        super(Qnet,self).__init__()
        self.cf1=torch.nn.Linear(state_dim,hidden_dim)
        self.cf2=torch.nn.Linear(hidden_dim,action_dim)
    def forward(self,x):
        x=torch.nn.functional.relu(self.cf1(x))#隐藏层使用RELU激活函数
        return self.cf2(x)

4.DQN算法实现

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.gamma=gamma#折扣因子
        self.epsilon=epsilon#epsilon-贪婪策略
        self.device=device
        self.target_update=target_update#目标网络更新频率
        self.qnet=Qnet(state_dim,hidden_dim,self.action_dim).to(self.device)#Q网络
        self.targetqnet=Qnet(state_dim,hidden_dim,self.action_dim).to(self.device)#目标网络
        self.optimizer=torch.optim.Adam(self.qnet.parameters(),lr=learning_rate)#Adam优化器  parameters() 方法返回该模型中的可学习参数(例如权重和偏置)。lr=learning_rate :这是指定 Adam 优化器的学习率(learning_rate )。学习率决定了每次参数更新的步长大小。
        self.count=0#计数器,记录更新次数
    def takeaction(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.qnet(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)
        nextstates=torch.tensor(transition_dict['nextstates'],dtype=torch.float).to(self.device)
        dones=torch.tensor(transition_dict['dones'],dtype=torch.float).view(-1,1).to(self.device)
        qvalues=self.qnet(states).gather(1,actions)#Q值
        maxnextqvalues=self.targetqnet(nextstates).max(1)[0].view(-1,1)#下一个状态的最大Q值
        qtargets=rewards+self.gamma*maxnextqvalues*(1-dones)#TD误差目标
        dqnloss=torch.mean(torch.nn.functional.mse_loss(qvalues,qtargets))#均方误差损失函数
        self.optimizer.zero_grad()
        dqnloss.backward()#反向传播更新参数
        self.optimizer.step()
        if self.count % self.target_update==0:#更新目标网络
            self.targetqnet.load_state_dict(self.qnet.state_dict())#更新目标网络
        self.count+=1

5.滑窗平均回报函数定义

def moving_average(a, window_size):
    cumulative_sum = np.cumsum(np.insert(a, 0, 0)) 
    middle = (cumulative_sum[window_size:] - cumulative_sum[:-window_size]) / window_size
    r = np.arange(1, window_size-1, 2)
    begin = np.cumsum(a[:window_size-1])[::2] / r
    end = (np.cumsum(a[:-window_size:-1])[::2] / r)[::-1]
    return np.concatenate((begin, middle, end))

6.参数设置

lr=2e-3
hidden_dim=128
gamma=0.98
epsilon=0.01
target_update=10
buffer_size=10000
minimal_size=500
batch_size=64#每次从经验回放池中采样数量
num_episodes=500
pbar_num=10#进度条的数量
printreturnnum=10#打印回报的数量

7.在车杆环境中训练并可视化结果

device=torch.device("cuda")if torch.cuda.is_available() else torch.device("cpu")
env=gym.make("CartPole-v1")
env.reset(seed=20)
# env = gym.make("CarRacing-v2", render_mode='human')
# env.reset()
# env.render()
random.seed(20)
np.random.seed(20)
torch.manual_seed(20)
replaybuffer=ReplayBuffer(buffer_size)
statedim=env.observation_space.shape[0]
actiondim=env.action_space.n
agent=DQN(statedim,hidden_dim,actiondim,lr,gamma,epsilon,target_update,device)
returnlist=[]
for i in range(pbar_num):
    with tqdm(total=int(num_episodes/pbar_num),desc=f"Iteration {i}") as pbar:
        for episode in range(int(num_episodes/pbar_num)):
            episodereturn=0
            state=env.reset(seed=20)[0]
            # env.render()
            done=False
            while not done:
                action=agent.takeaction(state)
                nextstate,reward,done,truncated,_=env.step(action)
                done=done or truncated
                replaybuffer.add(state,action,reward,nextstate,done)
                episodereturn+=reward
                state=nextstate
                # env.render()
                if replaybuffer.size()>minimal_size:
                    bs,ba,br,bns,bd=replaybuffer.sample(batch_size)
                    transition_dict={'states':bs,'actions':ba,'rewards':br,'nextstates':bns,'dones':bd}
                    agent.update(transition_dict)
            returnlist.append(episodereturn)
            if (episode+1) % printreturnnum==0:
                pbar.set_postfix({'episode':'%d'% ((num_episodes/pbar_num)*i+episode+1),'return':'%.3f'% np.mean(returnlist[-printreturnnum:])})
            pbar.update(1)
episodelist=list(range(len(returnlist)))
plt.plot(episodelist,returnlist)
plt.xlabel('Episodes')
plt.ylabel('Return')
plt.title('DQN on {}'.format('carpol-v1'))
plt.show()
mv_return=moving_average(returnlist,9)
plt.plot(episodelist,mv_return)
plt.xlabel('Episodes')
plt.ylabel('Return')
plt.title('DQN on {}'.format('carpol-v1'))
plt.show()
env.close()

8.结果与结论

teration 0: 100%|███████████████████████████████████████████████████████████████████████████████████████| 50/50 [00:00<00:00, 102.65it/s, episode=50, return=10.500]
Iteration 1: 100%|██████████████████████████████████████████████████████████████████████████████████████| 50/50 [00:11<00:00,  4.37it/s, episode=100, return=234.000] 
Iteration 2: 100%|██████████████████████████████████████████████████████████████████████████████████████| 50/50 [00:22<00:00,  2.24it/s, episode=150, return=127.200] 
Iteration 3: 100%|██████████████████████████████████████████████████████████████████████████████████████| 50/50 [00:24<00:00,  2.01it/s, episode=200, return=367.600] 
Iteration 4: 100%|██████████████████████████████████████████████████████████████████████████████████████| 50/50 [00:31<00:00,  1.57it/s, episode=250, return=296.800] 
Iteration 5: 100%|██████████████████████████████████████████████████████████████████████████████████████| 50/50 [00:29<00:00,  1.68it/s, episode=300, return=305.900] 
Iteration 6: 100%|██████████████████████████████████████████████████████████████████████████████████████| 50/50 [00:32<00:00,  1.53it/s, episode=350, return=500.000] 
Iteration 7: 100%|██████████████████████████████████████████████████████████████████████████████████████| 50/50 [00:28<00:00,  1.76it/s, episode=400, return=335.700] 
Iteration 8: 100%|██████████████████████████████████████████████████████████████████████████████████████| 50/50 [00:39<00:00,  1.27it/s, episode=450, return=500.000] 
Iteration 9: 100%|██████████████████████████████████████████████████████████████████████████████████████| 50/50 [00:40<00:00,  1.23it/s, episode=500, return=361.700] 

 总结:DQN性能在序列100之后得到快速提升,DQN性能提升后会持续一定程度震荡,这主要是神经网络过拟合到一些局部经验数据后由arg max运算带来的影响。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值