Deep Q Network控制openAI-gym Cartpole学习笔记

学习了莫烦大神的DQN,在原来代码的基础上做了一点点修改,特意记录一下。

学习过程分为两个文件:

  1. run.py————导入强化学习模型和CartPole环境

  2. DQN.py————建立强化学习模型


  • run.py

  • 代码原文


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值。


  • DQN.py

  • 代码原文

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()

学习成果

随着学习倒立杆坚持的时间逐渐增加并保持不变,其损失曲线如下图:

Deep Q Network控制openAI-gym Cartpole学习笔记_第1张图片

 

 

你可能感兴趣的:(Python,强化学习,OpenAI,Gym)