import gym
from My_DQN import DQN
env = gym.make('CartPole-v0')
#env = env.unwrapped
print(env.action_space)
print(env.observation_space)
print(env.observation_space.high)
print(env.observation_space.low)
RL = DQN("./",n_action=2,n_feature=4,learning_rate=0.01,e_greedy=0,gamma=0.8,replace_target_iter=60,memory_size=500,batch_size=30)
total_steps = 0
for i_episode in range(100):
observation = env.reset()
ep_r = 0
while True:
env.render()
action = RL.choose_action(observation)
observation_, reward, done, info = env.step(action)
# the smaller theta and closer to center the better
x, x_dot, theta, theta_dot = observation_
r1 = (env.x_threshold - abs(x))/env.x_threshold - 0.8
r2 = (env.theta_threshold_radians - abs(theta))/env.theta_threshold_radians - 0.5
reward = r1 + r2
RL.store_memory(observation, action, reward, observation_)
ep_r += reward
if total_steps > 200:
RL.learn()
if done:
print('episode: ', i_episode,
'ep_r: ', round(ep_r, 2),
' epsilon: ', round(RL.e_greedy, 2))
break
observation = observation_
total_steps += 1
RL.plot_result()
observation = env.reset()
初始化执行reset()函数,返回观测值——observation。
observation为一个1*n_features的numpy数组。
action = RL.choose_action(observation)
调研RL的动作选择函数,输入为观测值,输出为动作值。
observation_, reward, done, info = env.step(action)
更新环境输入为action,输出为新状态,奖励,完成标志以及信息(没啥用)。
RL.store_memory(observation, action, reward, observation_)
if total_steps > 200:
RL.learn()
并不是直接开始学习,而是200步之后才开始学习,根据实际情况还可以设置每经过一定步数学习一次,而不是每次都学习。
if done:
print('episode: ', i_episode,
'ep_r: ', round(ep_r, 2),
' epsilon: ', round(RL.e_greedy, 2))
break
满足结束条件之后结束并输出循环次数,奖励,以及e-greedy值。
import numpy as np
import tensorflow as tf
np.random.seed(1)
tf.set_random_seed(1)
# Deep Q Network off-policy
class DeepQNetwork:
def __init__(
self,
n_actions,
n_features,
learning_rate=0.01,
reward_decay=0.9,
e_greedy=0.9,
replace_target_iter=300,#隔多少步更换q-target
memory_size=500,
batch_size=32,
e_greedy_increment=None,
):
self.n_actions = n_actions
self.n_features = n_features
self.lr = learning_rate
self.gamma = reward_decay
self.epsilon_max = e_greedy#epsilon 的最大值
self.replace_target_iter = replace_target_iter#更换 target_net 的步数
self.memory_size = memory_size# 记忆上限
self.batch_size = batch_size# 每次更新时从 memory 里面取多少记忆出来,为什么是32????
self.epsilon_increment = e_greedy_increment # epsilon 的增量
self.epsilon = 0 if e_greedy_increment is not None else self.epsilon_max # 是否开启探索模式, 并逐步减少探索次数
# 记录学习次数 (用于判断是否更换 target_net 参数)
self.learn_step_counter = 0
# 初始化全 0 记忆 [s, a, r, s_]
self.memory = np.zeros((self.memory_size, n_features * 2 + 2))
# 创建 [target_net, evaluate_net]
self._build_net()
# 替换 target net 的参数
t_params = tf.get_collection('target_net_params') # 提取 target_net 的参数
'''
get_collection:
创建一个名为“target_net_params”的集合
'''
e_params = tf.get_collection('eval_net_params') # 提取 eval_net 的参数
self.replace_target_op = [tf.assign(t, e) for t, e in zip(t_params, e_params)]# 更新 target_net 参数
'''
tf.assign:
将t的值赋给e
zip:
将集合中对应的元素打包成一个个元组
'''
'''
小结
这段就是创建了两个collection,然后通过self._build_net()创建两层神经网络,
对应参数分别位于这两个collection中,
然后将两个collection中的变量一一替换(eval替换target)
'''
self.sess = tf.Session()
self.sess.run(tf.global_variables_initializer())#初始化变量
self.cost_his = []
def _build_net(self):
# ------------------ build evaluate_net ------------------
self.s = tf.placeholder(tf.float32, [None, self.n_features], name='s') # input,定义状态
self.q_target = tf.placeholder(tf.float32, [None, self.n_actions], name='Q_target') # for calculating loss
with tf.variable_scope('eval_net'):
'''
tf.variable_scope('eval_net'):
变量封装,将with包含的变量封装在‘eval_net’变量空间中
一般和tf.get_variable()结合使用。
tf.Variable和tf.get_variable的区别:
后者直接创建新变量
后者先搜索已有变量,有的直接用,没有再创建
'''
# c_names(collections_names) are the collections to store variables
c_names, n_l1, w_initializer, b_initializer = \
['eval_net_params', tf.GraphKeys.GLOBAL_VARIABLES], 10, \
tf.random_normal_initializer(0., 0.3), tf.constant_initializer(0.1) # config of layers
'''
c_name:存储变量的集合(collection),疑似对应e_params = tf.get_collection('eval_net_params')
n_l1:第一层神经网络节点数(疑似)
w_initializer:Weight初始值
b_initializer:biases初始值
'''
# first layer. collections is used later when assign to target net
with tf.variable_scope('l1'):#第一层神经网络
w1 = tf.get_variable('w1', [self.n_features, n_l1], initializer=w_initializer, collections=c_names)
b1 = tf.get_variable('b1', [1, n_l1], initializer=b_initializer, collections=c_names)
l1 = tf.nn.relu(tf.matmul(self.s, w1) + b1)
'''
w1 = tf.get_variable('w1', [self.n_features, n_l1], initializer=w_initializer, collections=c_names)
变量名:w1
尺寸:self.n_feature,nl1
初始化:w_initializer
所属集合:eval_net_params
'''
# second layer. collections is used later when assign to target net
with tf.variable_scope('l2'):#第二层神经网络
w2 = tf.get_variable('w2', [n_l1, self.n_actions], initializer=w_initializer, collections=c_names)
b2 = tf.get_variable('b2', [1, self.n_actions], initializer=b_initializer, collections=c_names)
self.q_eval = tf.matmul(l1, w2) + b2
with tf.variable_scope('loss'):
self.loss = tf.reduce_mean(tf.squared_difference(self.q_target, self.q_eval))
'''
tf.squared_difference(self.q_target, self.q_eval)
返回二者的平方差
'''
with tf.variable_scope('train'):
self._train_op = tf.train.RMSPropOptimizer(self.lr).minimize(self.loss)
'''
创建训练函数
'''
# ------------------ build target_net ------------------
self.s_ = tf.placeholder(tf.float32, [None, self.n_features], name='s_') # input
with tf.variable_scope('target_net'):
# c_names(collections_names) are the collections to store variables
c_names = ['target_net_params', tf.GraphKeys.GLOBAL_VARIABLES]
# first layer. collections is used later when assign to target net
with tf.variable_scope('l1'):
w1 = tf.get_variable('w1', [self.n_features, n_l1], initializer=w_initializer, collections=c_names)
'''
变量名:w1
尺寸:self.n_feature×nl1
初始化:w_initializer
所属集合:target_bet_params
'''
b1 = tf.get_variable('b1', [1, n_l1], initializer=b_initializer, collections=c_names)
l1 = tf.nn.relu(tf.matmul(self.s_, w1) + b1)
# second layer. collections is used later when assign to target net
with tf.variable_scope('l2'):
w2 = tf.get_variable('w2', [n_l1, self.n_actions], initializer=w_initializer, collections=c_names)
b2 = tf.get_variable('b2', [1, self.n_actions], initializer=b_initializer, collections=c_names)
self.q_next = tf.matmul(l1, w2) + b2
def store_transition(self, s, a, r, s_):
if not hasattr(self, 'memory_counter'):
self.memory_counter = 0
'''
判断self是否含有memory_counter属性,有则略过,无则创建
'''
# 记录一条 [s, a, r, s_] 记录
transition = np.hstack((s, [a, r], s_))#将参数元组的元素数组按水平方向进行叠加
# 总 memory 大小是固定的, 如果超出总大小, 旧 memory 就被新 memory 替换
index = self.memory_counter % self.memory_size
self.memory[index, :] = transition # 替换过程
#滚动存储self.memory_size个transition
self.memory_counter += 1
def choose_action(self, observation):
print("STATE"+str(observation))
# to have batch dimension when feed into tf placeholder
# 统一 observation 的 shape (1, size_of_observation)
observation = observation[np.newaxis, :]
print("STATE_SSSS" + str(observation))
#no.newaxis:给原数组增加一个维度,但是为什么要加一个新的维度???
if np.random.uniform() < self.epsilon:
# forward feed the observation and get q value for every actions
# 让 eval_net 神经网络生成所有 action 的值, 并选择值最大的 action
actions_value = self.sess.run(self.q_eval, feed_dict={self.s: observation})
action = np.argmax(actions_value)
'''
如果随机数小于e-gereedy
取得最新q_eval_value,在其中找Q值最大的,选择其序号为新的action序号
'''
else:
action = np.random.randint(0, self.n_actions)
return action
def learn(self):
# check to replace target parameters
# 检查是否替换 target_net 参数
if self.learn_step_counter % self.replace_target_iter == 0:
self.sess.run(self.replace_target_op)
print('\ntarget_params_replaced\n')
'''
如果self.learn_step_counter为self.replace_target_iter的整数倍,则将eval参数赋给target
'''
# sample batch memory from all memory
# 从 memory 中随机抽取 batch_size 这么多记忆
if self.memory_counter > self.memory_size:
sample_index = np.random.choice(self.memory_size, size=self.batch_size)
else:
sample_index = np.random.choice(self.memory_counter, size=self.batch_size)
'''
如果记忆总数大于记忆库容量,则从 记忆库 随机选取batch_size这么多的记忆
如果记忆总数小于或等于记忆库容量,则从 已有的记忆 选取batch_size个记忆
'''
batch_memory = self.memory[sample_index, :]
'''
从记忆库取出对应记忆,赋给batch_memory
'''
# 获取 q_next (target_net 产生的 q) 和 q_eval(eval_net 产生的 q)
q_next, q_eval = self.sess.run([self.q_next, self.q_eval],feed_dict={
self.s_: batch_memory[:, -self.n_features:], # 取得是memory的最后n_feature位,即s_——下一步状态
self.s: batch_memory[:, :self.n_features], # 取得是memory的开头n_feature位,即s ——当前步状态
})
'''
运行build_net中的两个神经网络,得到两个Q值
'''
# 下面这几步十分重要. q_next, q_eval 包含所有 action 的值,
# 而我们需要的只是已经选择好的 action 的值, 其他的并不需要.
# 所以我们将其他的 action 值全变成 0, 将用到的 action 误差值 反向传递回去, 作为更新凭据.
# 这是我们最终要达到的样子, 比如 q_target - q_eval = [1, 0, 0] - [-1, 0, 0] = [2, 0, 0]
# q_eval = [-1, 0, 0] 表示这一个记忆中有我选用过 action 0, 而 action 0 带来的 Q(s, a0) = -1, 所以其他的 Q(s, a1) = Q(s, a2) = 0.
# q_target = [1, 0, 0] 表示这个记忆中的 r+gamma*maxQ(s_) = 1, 而且不管在 s_ 上我们取了哪个 action,
# 我们都需要对应上 q_eval 中的 action 位置, 所以就将 1 放在了 action 0 的位置.
# 下面也是为了达到上面说的目的, 不过为了更方面让程序运算, 达到目的的过程有点不同.
# 是将 q_eval 全部赋值给 q_target, 这时 q_target-q_eval 全为 0,
# 不过 我们再根据 batch_memory 当中的 action 这个 column 来给 q_target 中的对应的 memory-action 位置来修改赋值.
# 使新的赋值为 reward + gamma * maxQ(s_), 这样 q_target-q_eval 就可以变成我们所需的样子.
# 具体在下面还有一个举例说明.
# change q_target w.r.t q_eval's action
q_target = q_eval.copy()
batch_index = np.arange(self.batch_size, dtype=np.int32)
eval_act_index = batch_memory[:, self.n_features].astype(int)#指向的是action!!!
reward = batch_memory[:, self.n_features + 1]#指向的是anction对应的reward!!
q_target[batch_index, eval_act_index] = reward + self.gamma * np.max(q_next, axis=1)#axis=1:行方向最值-》每一行的最大Q值
"""
假如在这个 batch 中, 我们有2个提取的记忆, 根据每个记忆可以生产3个 action 的值:
q_eval =
[[1, 2, 3],
[4, 5, 6]]
q_target = q_eval =
[[1, 2, 3],
[4, 5, 6]]
然后根据 memory 当中的具体 action 位置来修改 q_target 对应 action 上的值:
比如在:
记忆 0 的 q_target 计算值是 -1, 而且我用了 action 0;
记忆 1 的 q_target 计算值是 -2, 而且我用了 action 2:
q_target =
[[-1, 2, 3],
[4, 5, -2]]
所以 (q_target - q_eval) 就变成了:
[[(-1)-(1), 0, 0],
[0, 0, (-2)-(6)]]
最后我们将这个 (q_target - q_eval) 当成误差, 反向传递会神经网络.
所有为 0 的 action 值是当时没有选择的 action, 之前有选择的 action 才有不为0的值.
我们只反向传递之前选择的 action 的值,
"""
# train eval network
# 训练 eval_net
_, self.cost = self.sess.run([self._train_op, self.loss],
feed_dict={self.s: batch_memory[:, :self.n_features],
self.q_target: q_target})
self.cost_his.append(self.cost) # 记录 cost 误差
# increasing epsilon
# 逐渐增加 epsilon, 降低行为的随机性
self.epsilon = self.epsilon + self.epsilon_increment if self.epsilon < self.epsilon_max else self.epsilon_max
self.learn_step_counter += 1
def plot_cost(self):
import matplotlib.pyplot as plt
plt.plot(np.arange(len(self.cost_his)), self.cost_his)
plt.ylabel('Cost')
plt.xlabel('training steps')
plt.show()
初始化
DQN的初始化比较繁琐,主要分成五个部分:
def __init__(self,floder,n_action=2,n_feature=4,learning_rate=0.01,e_greedy=0,gamma=0.8,replace_target_iter=60,memory_size=500,batch_size=30):
self.n_action=n_action
self.n_feature=n_feature
self.learning_rate=learning_rate
self.e_greedy=e_greedy
self.replace_target_iter=replace_target_iter
self.gamma = gamma
self.batch_size = batch_size
self.learn_step_counter = 0
self.f=floder
self.cost_his = []
self.memory_size=memory_size
self.memory_counter=0
self.memory=np.zeros((self.memory_size,self.n_feature*2+2))
t_param=tf.get_collection("t_param")
e_param=tf.get_collection("e_param")
self.replace_target_op=[tf.assign(t,e) for t,e in zip(t_param,e_param)]
self._build_net()
self.sess=tf.Session()
self.sess.run(tf.global_variables_initializer())
建立神经网络
建立神经网络函数需要建立两个一模一样的神经网络,以及损失函数和训练函数。
self.s=tf.placeholder(tf.float32,[None,self.n_feature],name='s')
self.q_target=tf.placeholder(tf.float32,[None,self.n_action],name="q_target")
网络1:实时更新网络
实时更新网络为三层神经网络,(self.n_deature*50*70*self.n_action)
with tf.variable_scope("enva_net"):
c_name=["e_param",tf.GraphKeys.GLOBAL_VARIABLES]
w_initializer=tf.random_normal_initializer(0.0,0.3)
b_initializer=tf.constant_initializer(0.1)
with tf.variable_scope("l1"):
w1=tf.get_variable("w1",[self.n_feature,50],initializer=w_initializer,collections=c_name)
b1=tf.get_variable("b1",[1,50],initializer=b_initializer,collections=c_name)
l1=tf.nn.relu(tf.matmul(self.s,w1)+b1)
with tf.variable_scope("l2"):
w2=tf.get_variable("w2",[50,70],initializer=w_initializer,collections=c_name)
b2=tf.get_variable("b2",[1,70],initializer=b_initializer,collections=c_name)
l2=tf.matmul(l1,w2)+b2
with tf.variable_scope("l3"):
w3=tf.get_variable("w3",[70,self.n_action],initializer=w_initializer,collections=c_name)
b3=tf.get_variable("b3",[1,self.n_action],initializer=b_initializer,collections=c_name)
self.q_eval=tf.matmul(l2,w3)+b3
损失函数
with tf.variable_scope("loss"):
self.loss=tf.reduce_mean(tf.squared_difference(self.q_target,self.q_eval))
训练函数
self._train_op=tf.train.RMSPropOptimizer(self.learning_rate).minimize(self.loss)
self.s_=tf.placeholder(tf.float32,[None,self.n_feature],name="s_")
网络2:记忆学习网络
with tf.variable_scope("target_net"):
c_name=["t_param",tf.GraphKeys.GLOBAL_VARIABLES]
with tf.variable_scope("l1"):
w1=tf.get_variable("w1",[self.n_feature,50],initializer=w_initializer,collections=c_name)
b1=tf.get_variable("b1",[1,50],initializer=b_initializer,collections=c_name)
l1=tf.nn.relu(tf.matmul(self.s_,w1)+b1)
with tf.variable_scope("l2"):
w2=tf.get_variable("w2",[50,70],collections=c_name,initializer=w_initializer)
b2=tf.get_variable("b2",[1,70],initializer=b_initializer,collections=c_name)
l2=tf.matmul(l1,w2)+b2
with tf.variable_scope("l3"):
w3=tf.get_variable("w3",[70,self.n_action],collections=c_name,initializer=w_initializer)
b3=tf.get_variable("b3",[1,self.n_action],initializer=b_initializer,collections=c_name)
self.q_next=tf.matmul(l2,w3)+b3
选择动作
动作选择函数输入为状态信息(observation),输出为动作值
def choose_action(self,observation):
observation=observation[np.newaxis,:]
if np.random.uniform()
存储记忆
def store_memory(self,s,a,r,s_):
self.memory[self.memory_counter%self.memory_size,:]=np.hstack((s,[a,r],s_))
self.memory_counter+=1
学习
学习函数是整个强化学习中最重要的一步。
首先判断学习次数是否到达替换步数,到了则替换,否则不替换。
第2步从记忆库取出batch_size个记忆
如果记忆计数器值大于记忆数,则从记忆库随机取出(0-memory_size)batch_size个记忆,否则从(0-memory_counter)去除batch_size个记忆。注意此处取出的只是ID。
下一步才是从记忆库取出记忆。
第3步计算q_next和q_eval
self.s_赋值batch_memory[:, -self.n_feature:],即为各记忆行的最后n_feature列——也就是每个记忆中的下个状态值
self.s 赋值batch_memory[:, :self.n_feature],即为各记忆行的开头n_feature列——也就是每个记忆中的当前状态值
第4步计算q_target值
这一步是我看了很久才看明白的一步,对于我来说有点难懂,毕竟不是专业学强化学习的啊~~
首先将q_eval的值赋给q_target
然后生成batch_size长度的数组备用
接下来取出每一步选择的动作值,eval_act_index = batch_memory[:, self.n_feature].astype(int),这一步实际上是选择了动作值,而不是状态值。
接下来去除每一步的奖励值,reward = batch_memory[:, self.n_feature + 1],理解上一步之后这一步就很好理解了。
然后逐一替换q_target中每个状态被选择动作的q值,这就导致了q_target和q_eval的差异——各状态下没有被选择的action的Q值相同,被选择的action的Q值不同,所以q_target-q_eval才会有意义,所以self.loss=tf.reduce_mean(tf.squared_difference(self.q_target,self.q_eval))作为损失函数才是有意义的。
第5步计算损失和训练神经网路
_, self.cost = self.sess.run([self._train_op, self.loss],
feed_dict={self.s: batch_memory[:, :self.n_feature],
self.q_target: q_target})
第6步也是最后一步:更新e_greedy值
e_greedy小于规定最大值之前每学习一次增加一定量,到达最大值之后保持不变。个人认为这个设定是为了让神经网学习到足够的负面经验。
def learn(self):
# check to replace target parameters
if self.learn_step_counter % self.replace_target_iter == 0:
self.sess.run(self.replace_target_op)
print('\ntarget_params_replaced\n')
# sample batch memory from all memory
if self.memory_counter > self.memory_size:
sample_index = np.random.choice(self.memory_size, size=self.batch_size)
else:
sample_index = np.random.choice(self.memory_counter, size=self.batch_size)
batch_memory = self.memory[sample_index, :]
q_next, q_eval = self.sess.run(
[self.q_next, self.q_eval],
feed_dict={
self.s_: batch_memory[:, -self.n_feature:], # fixed params
self.s: batch_memory[:, :self.n_feature], # newest params
})
# change q_target w.r.t q_eval's action
q_target = q_eval.copy()
batch_index = np.arange(self.batch_size, dtype=np.int32)
eval_act_index = batch_memory[:, self.n_feature].astype(int)
reward = batch_memory[:, self.n_feature + 1]
q_target[batch_index, eval_act_index] = reward + self.gamma * np.max(q_next, axis=1)
_, self.cost = self.sess.run([self._train_op, self.loss],
feed_dict={self.s: batch_memory[:, :self.n_feature],
self.q_target: q_target})
self.cost_his.append(self.cost)
# increasing epsilon
if self.e_greedy < 0.9:
self.e_greedy += 0.001
self.learn_step_counter += 1
打印损失
打印损失指在学习完成之后打印出损失历史,可以直观的看到神经网路的学习经历。
def plot_result(self):
plt.plot(self.cost_his)
f=self.f+"loss.png"
plt.savefig(f)
plt.show()
随着学习倒立杆坚持的时间逐渐增加并保持不变,其损失曲线如下图: