ROS开发笔记(10)——ROS 深度强化学习dqn应用之tensorflow版本(double dqn/dueling dqn/prioritized replay dqn)
在ROS开发笔记(9)中keras版本DQN算法基础上,参考莫烦强化学习的视频教程与代码,编写了应用在ROS中的tensorflow版本DQN算法,本部分代码包含了原始dqn、double dqn、dueling dqn、prioritized replay dqn四种版本的实现:
1、原始DQN
DQN利用神经网络计算Q值,拥有 经验回放(Experience replay) 和 周期性冻结 target 网络参数(Fixed Q-targets)的特性,Experience replay可以充分利用历史数据,Fixed Q-targets可以切断相关性,网络结构图如下:
2、double dqn
double dqn 是用来解决过估计的,其与原始dqn的结构基本相同,只是计算q_target的方法不同,具体参见代码。
3、prioritized replay dqn
前面两种dqn在经验回放时是随机采样经验池中的数据,prioritized replay dqn是按照 Memory 中的样本优先级来抽取,以便能更有效地找到需要学习的样本。
这里用( Q现实 - Q估计 )来规定优先学习的程度. 如果 其差 越大, 就代表预测精度还有很多上升空间, 那么这个样本就越需要被学习, 也就是优先级 p 越高。
4、dueling dqn
dueling dqn算法主要改变是将Q值分为两部分,一部分是状态本身带来的Q值,下图中用value表示,一部分是这个状态下不同的动作产生的不同的Q值,下图中用advantage表示,然后将两种相加得到最终的Q值。
dueling dqn与prioritized replay dqn在一些特定的场合会体现出相对优越性,在我们这个应用场景中效果差不多,这里主要是实现了这几种算法结构,具体的代码及注释如下:
#!/usr/bin/env python
#-*- coding:utf-8 -*-
import rospy
import random
import time
import tensorflow as tf
import numpy as np
import json
import os
import sys
sys.path.append(os.path.dirname(os.path.abspath(os.path.dirname(__file__))))
from collections import deque
from std_msgs.msg import Float32MultiArray
# 导入 Env
from src.turtlebot3_dqn.environment_stage_1 import Env
# 采用莫烦强化学习教程中的实现
class SumTree(object):
data_pointer = 0
def __init__(self,capacity):
# capacity 为回放经验的条数
self.capacity=capacity
# 初始化tree
self.tree=np.zeros(2*capacity-1)
# 初始化回放经验数据
self.data=np.zeros(capacity,dtype=object)
def add(self,p,data):
# tree_idx 为所加data在树中的索引号
self.tree_idx=self.data_pointer+self.capacity-1
self.data[self.data_pointer]=data
# 更新树
self.update(self.tree_idx,p)
self.data_pointer+=1
if self.data_pointer>=self.capacity:
self.data_pointer=0
def update(self,tree_idx,p):
change=p-self.tree[tree_idx]
self.tree[tree_idx]=p
# 更新tree的p
while tree_idx!=0:
# 更新父节点的p值
tree_idx = (tree_idx - 1) // 2
self.tree[tree_idx] += change
def get_leaf(self,v):
parent_idx = 0
# 根据V寻找对应的叶子节点
while True:
cl_idx = 2 * parent_idx + 1
cr_idx = cl_idx + 1
# 判断是否达到树的底部
if cl_idx >= len(self.tree):
leaf_idx = parent_idx
break
else:
if v <= self.tree[cl_idx]:
parent_idx = cl_idx
else:
v -= self.tree[cl_idx]
parent_idx = cr_idx
data_idx = leaf_idx - self.capacity + 1
# 输出叶子节点序号,p值,以及对应的数据
return leaf_idx, self.tree[leaf_idx], self.data[data_idx]
@property
def total_p(self):
return self.tree[0]
# 采用莫烦强化学习教程中的实现
class Memory(object):
# 实际保存数据的条数
saved_size=0
epsilon = 0.01 # 避免 0 priority
alpha = 0.6 # [0~1] 将 importance of TD error转化为 priority
beta = 0.4 # importance-sampling 从这个初始值增加到1
beta_increment_per_sampling = 0.001
abs_err_upper = 50.
def __init__(self,capacity):
self.tree=SumTree(capacity)
def store(self,transition):
# 将新加入的transition 优先级p设为最高
max_p=np.max(self.tree.tree[-self.tree.capacity:])
if max_p==0:
max_p=self.abs_err_upper
self.tree.add(max_p,transition)
if self.saved_size<self.tree.capacity:
self.saved_size+=1
def sample(self,n):
# 初始化 b_idx, b_memory, ISWeights
b_idx, ISWeights = np.empty((n,), dtype=np.int32), np.empty((n, 1))
b_memory=[]
self.beta=np.min([1.,self.beta+self.beta_increment_per_sampling])
min_prob = np.min(self.tree.tree[self.tree.capacity-1:self.tree.capacity-1+self.saved_size]) / self.tree.total_p
# print(self.tree.tree[self.tree.capacity-1:self.tree.capacity-1+self.saved_size])
# 将total_p分为n份,每一份为pri_seg
pri_seg = self.tree.total_p / n
for i in range(n):
a,b= pri_seg*i, pri_seg*(i+1)
v=random.uniform(a,b)
idx, p, data=self.tree.get_leaf(v)
prob=p/self.tree.total_p
ISWeights[i, 0]