莫烦老师,DQN代码学习笔记(图片版)

详情请见莫烦老师DQN主页:DQN 算法更新 (Tensorflow) - 强化学习 Reinforcement Learning | 莫烦Python

莫烦老师代码(没有我繁琐注释代码直通车):MorvanZhou/Reinforcement-learning-with-tensorflow

上一篇博客有代码版,可粘贴

参考文献:Playing Atari with Deep Reinforcement Learning

https://www.cs.toronto.edu/~vmnih/docs/dqn.pdf

 

第一次在网上写文章,不知怎么注明参考出处,如涉及侵权问题请评论告诉我。

本人初入强化学习,看莫烦老师的课受益匪浅,再次由衷的感谢老师的无私奉献,笔芯❤~

由于本人不懂的地方太多,所以注释的比较多,当然也加入了老师的注释。供以后学习参考,和其他小白一起进步。

建议参考DQN算法的英文流程,我也不知道这种中文注释能在哪里更方便的上传,就在这里发好啦,这里排版没准乱如果真要参考,就拷到编辑器上吧

由于知乎好像关键字搜不到,我又搞了个博客


run_this.py代码

#更新的步骤
from maze_env import Maze
from RL_brain import DeepQNetwork#引入了自己写的maze_env,RL_brain模块中class maze,class DeepQNetwork
def run_maze():
    step = 0#为了记录当前走的第几步,因为先要存储一些记忆,当记忆库中有一些东西的时候才去学习
    for episode in range(300):
        # initial observation
        observation = env.reset()#环境给出初始坐标

        while True:
            # fresh env更新环境
            env.render()

            # RL choose action based on observation根据观测值选择一个动作,根据RL.choose_action,但这个函数只调用了q_eval,即只进行正向传播得到q值进行选择
            action = RL.choose_action(observation)

            # RL take action and get next observation and reward选择动作后得到观测值,奖励,是否终结done的信息
            observation_, reward, done = env.step(action)

            RL.store_transition(observation, action, reward, observation_)##重要:存储记忆:现在这步观测值,采取的动作。会得到的奖励,采取行动后下一步观测值

			# 控制学习起始时间和频率 (先累积一些记忆再开始学习)
            if (step > 200) and (step % 5 == 0):#当步数大于两百的时候才开始学习,每五步学习一次,200步之前跳过learn,并且选择
                RL.learn()#其中会有反向训练第一个q_predict网络

            # swap observation
            observation = observation_#更新观测值

            # break while loop when end of this episode
            if done:
                break
            step += 1

    # end of game
    print('game over')
    env.destroy()


if __name__ == "__main__":
    # maze game
    env = Maze()#引入环境
    RL = DeepQNetwork(env.n_actions, env.n_features,
                      learning_rate=0.01,
                      reward_decay=0.9,
                      e_greedy=0.9,
                      replace_target_iter=200, # 每 200 步替换一次 target_net 的参数
                      memory_size=2000,# 记忆上限
                      output_graph=False # 是否输出 tensorboard 文件
                      )
    env.after(100, run_maze)
    env.mainloop()
    RL.plot_cost() # 观看神经网络的误差曲线

RL_brain.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,#输出多少个action的值
            n_features,#接受多少个观测值的相关特征
            learning_rate=0.01,#NN中learning_rate学习速率
            reward_decay=0.9,#Q-learning中reward衰减因子
            e_greedy=0.9,
            replace_target_iter=300,#更新Q现实网络参数的步骤数
            memory_size=500,#存储记忆的数量
            batch_size=32,#每次从记忆库中取的样本数量
            e_greedy_increment=None,
            output_graph=False,
    ):
        self.n_actions = n_actions#由maze得4
        self.n_features = n_features#由maze得2
        self.lr = learning_rate
        self.gamma = reward_decay
        self.epsilon_max = e_greedy#
        self.replace_target_iter = replace_target_iter#隔多少步后将target net 的参数更新为最新的参数
        self.memory_size = memory_size#整个记忆库的容量,即RL.store_transition(observation, action, reward, observation_)有多少条
        self.batch_size = batch_size#随机梯度下降SGD会用到
        self.epsilon_increment = e_greedy_increment#表示不断扩大epsilon,以便有更大的概率拿到好的值
        self.epsilon = 0 if e_greedy_increment is not None else self.epsilon_max#如果e_greedy_increment没有值,则self.epsilon设置为self.epsilon_max=0.9

        # total learning step
        self.learn_step_counter = 0#用这个记录学习了多少步,可以让self.epsilon根据这个步数来不断提高

        # initialize zero memory [s, a, r, s_]
        self.memory = np.zeros((self.memory_size, n_features * 2 + 2))  #self.memory存储记忆的表
                                                                        #行(高度)为存储记忆的数量
                                                                        #列为(observation, action, reward, observation_)的长度
                                                                        #对于一条记忆信息来说observation和observation_都有n_features的长度
                                                                        #而action,reward都各自有一个单值信息
                                                                        #则总列数为n_features+2+n_features
        #创建 [target_net, evaluate_net]神经网络
        self._build_net()
        
        # 替换 target net 的参数
        t_params = tf.get_collection('target_net_params')#tf.get_collection(key,scope=None)返回具有给定名称的集合中的值列表
                                                         #如果未将值添加到该集合,则为空列表。该列表按照收集顺序包含这些值。
        e_params = tf.get_collection('eval_net_params')
        self.replace_target_op = [tf.assign(t, e) for t, e in zip(t_params, e_params)]#tf.assign(ref,value,validate_shape=None,use_locking=None,name=None)
                                                     #该操作在赋值后输出一个张量,该张量保存'ref'的新值。函数完成了将value赋值给ref的作用
                                                     #zip()函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表。

        self.sess = tf.Session()

        if output_graph:
            # $ tensorboard --logdir=logs
            tf.summary.FileWriter("logs/", self.sess.graph)

        self.sess.run(tf.global_variables_initializer())
        self.cost_his = []#为记录每一步的误差的一个cost表

    def _build_net(self):#搭建网络,q_next, q_eval 包含所有 action 的值
        # ------------------ build evaluate_net ------------------预测值网络具备最新参数,最后输出q_eval
        self.s = tf.placeholder(tf.float32, [None, self.n_features], name='s')  # input输入当前状态,作为NN的输入
        self.q_target = tf.placeholder(tf.float32, [None, self.n_actions], name='Q_target')  # for calculating loss输入Q现实为了后面误差计算反向传递
                                                                                            #NN输出q_predict
        with tf.variable_scope('eval_net'):
            #首先对图层进行配置,w,b初始化,第一层网络的神经元数n_l1      #\表示没有[],()的换行
            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) 
                                                        #c_name作为一个存储变量的集合,其名称为eval_net_params[],将q估计的参数都放入这个集合中
                                                        #Variable()构造函数或get_variable()自动将新变量添加到图形集合:GraphKeys.GLOBAL_VARIABLES(默认)。
                                                        #这个方便函数返回该集合的内容。
                                                        #用于调用参数,将q估计的参数都放在collection这个集合当中
                                                          #tf.random_normal_initializer(mean=0.0, stddev=1.0, seed=None, dtype=tf.float32)
                                                          #返回一个生成具有正态分布的张量的初始化器
                                                            #tf.constant_initializer可以简写为tf.Constant()初始化为常数
                                                            #tf.zeros_initializer()也可以简写为tf.Zeros()
                                                            #tf.ones_initializer(), 也可以简写为tf.Ones()
            # 第一层. 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)
                                                            #创建一个变量对于get_variable(),来说,如果已经创建的变量对象,就把那个对象返回,
                                                            #如果没有创建变量对象的话,就创建一个新的。
                                                            #tf.get_variable(name, shape=None, dtype=None, 
                                                            # initializer=None, regularizer=None,
                                                            # trainable=True, collections=None, 这个collection表示The new variable is added to these collections
                                                            # caching_device=None, partitioner=None, 
                                                            # validate_shape=True, custom_getter=None)
                l1 = tf.nn.relu(tf.matmul(self.s, w1) + b1)#python有广播功能,l1输出维度[None,n_l1]

            # 第二层. 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#L2输出q估计维度[None,self_action]

        with tf.variable_scope('loss'):
            self.loss = tf.reduce_mean(tf.squared_difference(self.q_target, self.q_eval))#基于Q估计与Q现实,构造loss-function
        with tf.variable_scope('train'):
            self._train_op = tf.train.RMSPropOptimizer(self.lr).minimize(self.loss)#进行训练

        # ------------------ build target_net ------------------目标值网络预测Qtarget参数是以前的,最后输出q_next
        self.s_ = tf.placeholder(tf.float32, [None, self.n_features], name='s_')    # input s_表示下一状态,用下一状态计算q_target
        with tf.variable_scope('target_net'):
            # c_names(collections_names) are the collections to store variables   将q现实的参数都放入这个集合中
            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'):#这里与前面的网络结构要相同,只不过存储的参数不同,因为后面不会训练这个target网络,只是单独更新估计网络
                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)

            # 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_):#存储记忆,类里定义的函数语法规定第一个要为self
        if not hasattr(self, 'memory_counter'):#hasattr(object, name)判断一个对象里面是否有name属性或者name方法,返回BOOL值,初始不存在这个索引项,创建
                                               #判断self对象有name特性返回True, 否则返回False。即没有这个索引值memory_counter,则令self.memory_counter=0
            self.memory_counter = 0

        transition = np.hstack((s, [a, r], s_))#numpy.hstack(tup)参数tup可以是元组,列表,或者numpy数组,返回结果为按顺序堆叠numpy的数组(按列堆叠一个)。
        # replace the old memory with new memory
        index = self.memory_counter % self.memory_size# 总 memory 大小是固定的, 如果超出总大小, 取index为余数,旧 memory 就被新 memory 替换
        self.memory[index, :] = transition#将memory中的index这一行用transition这个一行的数组代替

        self.memory_counter += 1#

    def choose_action(self, observation):#选择动作
        # to have batch dimension when feed into tf placeholder
        observation = observation[np.newaxis, :]#因为observation加入时是一维的数值
                                                #np.newaxis 为 numpy.ndarray(多维数组)增加一个轴,多加入了一个行轴

        if np.random.uniform() < self.epsilon:#np.random.uniform生成均匀分布的随机数,默认0-1,大概率选择actions_value最大下的动作
            # forward feed the observation and get q value for every actions
            actions_value = self.sess.run(self.q_eval, feed_dict={self.s: observation})
            action = np.argmax(actions_value)
        else:
            action = np.random.randint(0, self.n_actions)#小概率随机选择actions_value下的一个动作,np.random.randint用于生成一个指定范围内的整数
        return action

    def learn(self):#如何学习, 更新参数的. 这里涉及了 target_net 和 eval_net 的交互使用.
        # check to replace target parameters
        if self.learn_step_counter % self.replace_target_iter == 0:#提前检测是否替换 target_net 参数,self.learn_step_counter记录了步数
                                                                   #隔self.replace_target_iter后将target net 的参数更新为最新的参数
            self.sess.run(self.replace_target_op)
            print('\ntarget_params_replaced\n')

        # 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)#从给定的一维阵列self.memory_size生成一个随机样本,size为Output shape.
        else:
            sample_index = np.random.choice(self.memory_counter, size=self.batch_size)#步数未超过记忆总容量,则最多在self.memory_counter个记忆值中选择32个索引值
        batch_memory = self.memory[sample_index, :]#上面都是从多少个(一维)数中选出多少个数(一维),这里,抽取记忆表self.memory中前sample_index行

        q_next, q_eval = self.sess.run(            #运行这两个神经网络,正向传播
            [self.q_next, self.q_eval],
            feed_dict={#前面store_transition(observation, action, reward, observation_)在run_this里都是参数
                self.s_: batch_memory[:, -self.n_features:],  # fixed params,q_next由目标值网络用记忆库中倒数n_features个列(observation_)的值做输入
                                                              
                self.s: batch_memory[:, :self.n_features],  # newest params,q_eval由预测值网络用记忆库中正数n_features个列(observation)的值做输入
            })

        # change q_target w.r.t q_eval's action
        q_target = q_eval.copy()#浅拷贝,父目录复制,子目录会随原始改变而变化,

        #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 就可以变成我们所需的样子.
        # 具体在下面还有一个举例说明.

        
        
        batch_index = np.arange(self.batch_size, dtype=np.int32)#返回一个长度为self.batch_size的索引值列表aray([0,1,2,...,31])
        eval_act_index = batch_memory[:, self.n_features].astype(int)#返回一个长度为32的动作列表,从记忆库batch_memory中的标记的第2列,self.n_features=2
                                                                    #即RL.store_transition(observation, action, reward, observation_)中的action
                                                                    #注意从0开始记,所以eval_act_index得到的是action那一列
        reward = batch_memory[:, self.n_features + 1]#返回一个长度为32奖励的列表,提取出记忆库中的reward

        q_target[batch_index, eval_act_index] = reward + self.gamma * np.max(q_next, axis=1)
                                                    #返回一个32*4的ndarray数组形式,q_next由target网络输出(样本数*4),前面从记忆库中取了32个输入到网络中
                                                    #np.max(q_next, axis=1)取出q_next这个32*4中每行中取出最大的一个,返回1*32
                                                    #这个相当于将q_target按[batch_index, eval_act_index]索引计算出相应位置的q—_target值
                                                    #这里q_target复制了q_eval可以实现下面相减之后得0
        """
        假如在这个 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 上的值:
                            q_target[batch_index, eval_act_index] = reward + self.gamma * np.max(q_next, axis=1)
        比如在:
            记忆 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
        _, 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)#反向训练

        # increasing epsilon提高选择正确的概率,直到self.epsilon_max
        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)#arange函数用于创建等差数组,arange返回的是一个array类型的数据
                                                            #在for循环中,用到range,而range返回的是list。
        plt.ylabel('Cost')
        plt.xlabel('training steps')
        plt.show()

 


make_env.py代码(没注释 )

 

import numpy as np
import time
import sys
if sys.version_info.major == 2:
    import Tkinter as tk
else:
    import tkinter as tk

UNIT = 40   # pixels
MAZE_H = 4  # grid height
MAZE_W = 4  # grid width


class Maze(tk.Tk, object):
    def __init__(self):
        super(Maze, self).__init__()
        self.action_space = ['u', 'd', 'l', 'r']
        self.n_actions = len(self.action_space)
        self.n_features = 2
        self.title('maze')
        self.geometry('{0}x{1}'.format(MAZE_H * UNIT, MAZE_H * UNIT))
        self._build_maze()

    def _build_maze(self):
        self.canvas = tk.Canvas(self, bg='white',
                           height=MAZE_H * UNIT,
                           width=MAZE_W * UNIT)

        # create grids
        for c in range(0, MAZE_W * UNIT, UNIT):
            x0, y0, x1, y1 = c, 0, c, MAZE_H * UNIT
            self.canvas.create_line(x0, y0, x1, y1)
        for r in range(0, MAZE_H * UNIT, UNIT):
            x0, y0, x1, y1 = 0, r, MAZE_H * UNIT, r
            self.canvas.create_line(x0, y0, x1, y1)

        # create origin
        origin = np.array([20, 20])

        # hell
        hell1_center = origin + np.array([UNIT * 2, UNIT])
        self.hell1 = self.canvas.create_rectangle(
            hell1_center[0] - 15, hell1_center[1] - 15,
            hell1_center[0] + 15, hell1_center[1] + 15,
            fill='black')
        # hell
        # hell2_center = origin + np.array([UNIT, UNIT * 2])
        # self.hell2 = self.canvas.create_rectangle(
        #     hell2_center[0] - 15, hell2_center[1] - 15,
        #     hell2_center[0] + 15, hell2_center[1] + 15,
        #     fill='black')

        # create oval
        oval_center = origin + UNIT * 2
        self.oval = self.canvas.create_oval(
            oval_center[0] - 15, oval_center[1] - 15,
            oval_center[0] + 15, oval_center[1] + 15,
            fill='yellow')

        # create red rect
        self.rect = self.canvas.create_rectangle(
            origin[0] - 15, origin[1] - 15,
            origin[0] + 15, origin[1] + 15,
            fill='red')

        # pack all
        self.canvas.pack()

    def reset(self):
        self.update()
        time.sleep(0.1)
        self.canvas.delete(self.rect)
        origin = np.array([20, 20])
        self.rect = self.canvas.create_rectangle(
            origin[0] - 15, origin[1] - 15,
            origin[0] + 15, origin[1] + 15,
            fill='red')
        # return observation
        return (np.array(self.canvas.coords(self.rect)[:2]) - np.array(self.canvas.coords(self.oval)[:2]))/(MAZE_H*UNIT)

    def step(self, action):
        s = self.canvas.coords(self.rect)
        base_action = np.array([0, 0])
        if action == 0:   # up
            if s[1] > UNIT:
                base_action[1] -= UNIT
        elif action == 1:   # down
            if s[1] < (MAZE_H - 1) * UNIT:
                base_action[1] += UNIT
        elif action == 2:   # right
            if s[0] < (MAZE_W - 1) * UNIT:
                base_action[0] += UNIT
        elif action == 3:   # left
            if s[0] > UNIT:
                base_action[0] -= UNIT

        self.canvas.move(self.rect, base_action[0], base_action[1])  # move agent

        next_coords = self.canvas.coords(self.rect)  # next state

        # reward function
        if next_coords == self.canvas.coords(self.oval):
            reward = 1
            done = True
        elif next_coords in [self.canvas.coords(self.hell1)]:
            reward = -1
            done = True
        else:
            reward = 0
            done = False
        s_ = (np.array(next_coords[:2]) - np.array(self.canvas.coords(self.oval)[:2]))/(MAZE_H*UNIT)
        return s_, reward, done

    def render(self):
        # time.sleep(0.01)
        self.update()

 

  • 10
    点赞
  • 67
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
MATLAB强化学习DQN(深度Q网络)代码是一种实现深度强化学习的方法,通常用于解决控制问题和决策问题。DQN通过结合深度神经网络和Q学习算法来实现对环境的学习和决策。以下是MATLAB中实现DQN算法的简要代码示例: 首先,需要定义一个深度神经网络模型来拟合Q值函数。可以使用MATLAB的Neural Network Toolbox来构建一个适合于解决强化学习问题的神经网络模型,例如多层感知器(MLP)或卷积神经网络(CNN)。 其次,需要定义Q学习算法的参数,包括学习率、贪心策略的选择、回放缓冲区的大小等。 然后,需要定义DQN算法的训练过程。在每一步中,Agent(智能体)根据当前的状态选择动作,并观察环境返回的奖励和下一个状态。Agent将这些信息存储到回放缓冲区中,并周期性地从中随机抽样一批数据用于训练神经网络。 最后,可以使用训练好的DQN模型来进行决策。Agent在每个时间步根据当前状态使用训练好的神经网络模型来选择动作,并与环境交互。 需要注意的是,以上只是一个简要的示例,实际的DQN代码可能还涉及到一些具体问题的处理,比如环境的建模、奖励函数的设计、超参数的调优等。另外,为了更好地理解DQN算法的原理和代码实现,建议阅读相关的文献和资料,例如DeepMind团队的原始论文《Playing Atari with Deep Reinforcement Learning》以及MATLAB官方提供的强化学习工具箱的文档和示例代码
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值