莫烦老师,DQN代码学习笔记

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

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

参考文献:Playing Atari with Deep Reinforcement Learning

cs.toronto.edu/~vmnih/d

看图片版代码,见下一篇,等我学会贴代码。。。


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

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

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

建议参考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根据观测值选择一个动作

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):#当步数大于两百的时候才开始学习,每五步学习一次

RL.learn()


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




---------为代码完整性,这里是maze_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()

  • 37
    点赞
  • 129
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值