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运算带来的影响。