详情请见莫烦老师主页:https://morvanzhou.github.io/tutorials/machine-learning/reinforcement-learning/5-1-A-PG/
论文见:https://papers.nips.cc/paper/1713-policy-gradient-methods-for-reinforcement-learning-with-function-approximation.pdf
源代码见:https://github.com/MorvanZhou/Reinforcement-learning-with-tensorflow/tree/master/contents/7_Policy_gradient_softmax
RL_brain.py
import numpy as np
import tensorflow as tf
# reproducible
np.random.seed(1)
tf.set_random_seed(1)
class PolicyGradient:
def __init__( #初始化
self,
n_actions,
n_features,
learning_rate=0.01,
reward_decay=0.95,
output_graph=False,
):
self.n_actions = n_actions
self.n_features = n_features
self.lr = learning_rate #反向训练用到
self.gamma = reward_decay
self.ep_obs, self.ep_as, self.ep_rs = [], [], []#分别用于存储当前回合的状态,动作,奖励值
self._build_net()
self.sess = tf.Session()
if output_graph:
# $ tensorboard --logdir=logs
# http://0.0.0.0:6006/
# tf.train.SummaryWriter soon be deprecated, use following
tf.summary.FileWriter("logs/", self.sess.graph)
self.sess.run(tf.global_variables_initializer())
def _build_net(self): # 建立 policy gradient 神经网络
with tf.name_scope('inputs'):
self.tf_obs = tf.placeholder(tf.float32, [None, self.n_features], name="observations")
self.tf_acts = tf.placeholder(tf.int32, [None, ], name="actions_num")
self.tf_vt = tf.placeholder(tf.float32, [None, ], name="actions_value")
# fc1
layer = tf.layers.dense( #全连接层,输入为观测值,单元个数10,
inputs=self.tf_obs,
units=10,
activation=tf.nn.tanh, # tanh activation
kernel_initializer=tf.random_normal_initializer(mean=0, stddev=0.3), #返回一个生成具有正态分布的张量的初始化器,权重矩阵的初始化函数
bias_initializer=tf.constant_initializer(0.1),
name='fc1'
)
# fc2
all_act = tf.layers.dense(#输入为上一级输出layer,单元数为动作个数
inputs=layer,
units=self.n_actions,
activation=None,
kernel_initializer=tf.random_normal_initializer(mean=0, stddev=0.3), #返回一个生成具有正态分布的张量的初始化器
bias_initializer=tf.constant_initializer(0.1),
name='fc2'
)
self.all_act_prob = tf.nn.softmax(all_act, name='act_prob') # 加一个softmax回归,将动作值转化为概率值
with tf.name_scope('loss'): #计算损失函数,将下一次想要增加或减少的幅度传回网络进行训练
# 注意to maximize total reward (log_p * R) is to minimize -(log_p * R), and the tf only have minimize(loss)
# neg_log_prob = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=all_act, labels=self.tf_acts) # this is negative log of chosen action
#sparse_softmax_cross_entropy_with_logits(_sentinel=None, labels=None, logits=None, name=None)
#函数内部自动计算softmax,然后再计算交叉熵代价函数,也就是说logits必须是没有经过tf.nn.softmax函数处理的数据,
#否则导致训练结果有问题。所以logits=all_act而不是all_act_prob
#函数含义:计算logits和标签之间的softmax交叉熵。
#labels:为神经网络期望的输出 logits:为神经网络最后一层的输出(内部会进行softmax)
#和tf.nn.softmax_cross_entropy_with_logits函数比较明显的区别在于它的参数labels的不同,
#这里的参数label是非稀疏表示的,比如表示一个3分类的一个样本的标签,稀疏表示的形式为[0,0,1]这个表示这个样本为第3个分类,
#而非稀疏表示就表示为2(因为从0开始算,0,1,2,就能表示三类),同理[0,1,0]就表示样本属于第二个分类,而其非稀疏表示为1。
#tf.nn.sparse_softmax_cross_entropy_with_logits()比tf.nn.softmax_cross_entropy_with_logits多了一步将labels稀疏化的操作。
#因为深度学习中,图片一般是用非稀疏的标签的,所以用tf.nn.sparse_softmax_cross_entropy_with_logits的频率高
#或者下面这种形式:
neg_log_prob = tf.reduce_sum(-tf.log(self.all_act_prob)*tf.one_hot(self.tf_acts, self.n_actions), axis=1)#这里计算logp
#这里注意:输入self.all_act_prob是一个n维向量
#tf.one_hot(self.tf_acts,self.n_actions)中self.tf_acts是n维向量,self.n_actions表示热编码向量的维度,这里会产生self.tf_acts个热编码向量,互相相乘得到一个值,全部求和
#这里one_hot可以实现让选的动作self.tf_acts作为indices变成一个矩阵形式,self.n_actions(维数,depth)与前面相乘,即返回一个one_hot张量,将indices中每一个值都进行热编码
#one_hot(indices,depth,on_value=None,off_value=None,axis=None,dtype=None,name=None)
#tf.one_hot中self.tf_act是一维数据,我要把它进行稀疏化one——hot编码
loss = tf.reduce_mean(neg_log_prob * self.tf_vt) # (vt = 本reward + 衰减的未来reward) 引导参数的梯度下降
#tf.reduce_mean取平均
with tf.name_scope('train'):#进行训练
self.train_op = tf.train.AdamOptimizer(self.lr).minimize(loss)
def choose_action(self, observation): # 选行为
prob_weights = self.sess.run(self.all_act_prob, feed_dict={self.tf_obs: observation[np.newaxis, :]})#obser插入新维度一行作为NN输入,得到softmax值
action = np.random.choice(range(prob_weights.shape[1]), p=prob_weights.ravel()) # select action w.r.t the actions prob
# .ravel() .flatten()本质都是想把多维数据降到一维,而flatten的变化不会影响原来的矩阵,而ravel的变化会改变原来矩阵
#x = np.array([[1,3,4],[2,3,5]])
#x.ravel()
#array([1, 3, 4, 2, 3, 5])
#x.flatten()
#array([1, 3, 4, 2, 3, 5])
#numpy.random.choice(a, size=None, replace=True, p=None)从给定的一维数组a中生成随机数;size为数组维度;p为数组中的数据出现的概率要为一维的(p中数组之和要为1)
#直接用.shape可以快速读取矩阵的形状,使用shape[1]读取矩阵第二维度的长度,表示总共有几个动作可以选择
#这里就是按照概率值去选择
return action
def store_transition(self, s, a, r): # 存储回合 transition
self.ep_obs.append(s)#当前观测值
self.ep_as.append(a)#使用的行动
self.ep_rs.append(r)#获得奖励
#policy gradient是在一个完整的episode结束后才开始训练的,因此,在一个episode结束前,我们要存储这个episode所有的经验,即状态,动作和奖励。
def learn(self): # 学习更新参数 (有改变)
# discount and normalize episode reward
discounted_ep_rs_norm = self._discount_and_norm_rewards()
# train on episode
self.sess.run(self.train_op, feed_dict={
self.tf_obs: np.vstack(self.ep_obs), # shape=[None, n_obs],将self.ep_obs纵向堆叠起来,NONE表示不确定的行数,注意这里与选动作(只输入一个observation)不同
self.tf_acts: np.array(self.ep_as), # shape=[None, ] 将self.ep_as纵向堆叠起来
self.tf_vt: discounted_ep_rs_norm, # shape=[None, ]
})
self.ep_obs, self.ep_as, self.ep_rs = [], [], [] # empty episode data学习完整个回合后,清空
return discounted_ep_rs_norm
def _discount_and_norm_rewards(self): # 衰减回合的 reward (新内容)
# discount episode rewards
discounted_ep_rs = np.zeros_like(self.ep_rs)#得到处理后的reward列表,为输入到网络中的vt值
#numpy.zeros_like(a)返回一个零矩阵与给定的矩阵相同形状
running_add = 0
for t in reversed(range(0, len(self.ep_rs))):
#reversed()函数是返回序列seq的反向访问的迭代子,如果self.ep_rs列表中存储有5个reward值,长度为5,则这里t取值为43210,倒序是为了可以取未来值计算
#我们之前存储的奖励是当前状态s采取动作a获得的即时奖励,而当前状态s采取动作a所获得的真实奖励应该是即时奖励加上未来直到episode结束的奖励贴现和。
running_add = running_add * self.gamma + self.ep_rs[t]#计算矩阵平均值
discounted_ep_rs[t] = running_add#计算矩阵标准差
# normalize episode rewards# normalize episode rewards进行正则化,对discounted_ep_rs列表进行正则化规约reward值
discounted_ep_rs -= np.mean(discounted_ep_rs)
discounted_ep_rs /= np.std(discounted_ep_rs)
return discounted_ep_rs
#疑惑点:反向传播neg_log_prob = tf.reduce_sum(-tf.log(self.all_act_prob)*tf.one_hot(self.tf_acts, self.n_actions), axis=1)
#假若动作个数为3,这里all_act_prob输出3*1的向量值,如(0.2,0.5,0.3),在进行每个元素取log值
#tf.one_hot中传入self.tf_acts,是针对一个回合中的所有动作值,假若进行了4次,4个非稀疏表示的动作经过热编码变成了4个向量[[0,0,1],[0,0,1],[1,0,0],[0,1,0]]
#一个[0,0,1]与tf.log(self.all_act_prob)相乘,最后得到四个值,再进行sum
run_CartPole.py
import gym
from RL_brain import PolicyGradient
import matplotlib.pyplot as plt
DISPLAY_REWARD_THRESHOLD = 400 # 当回合总 reward 大于 400 时显示模拟窗口
RENDER = False # 在屏幕上显示模拟窗口会拖慢运行速度, 我们等计算机学得差不多了再显示模拟
env = gym.make('CartPole-v0') # CartPole 这个模拟
env.seed(1) # 普通的 Policy gradient 方法, 使得回合的 variance 比较大, 所以我们选了一个好点的随机种子
env = env.unwrapped # 取消限制
print(env.action_space)# 显示可用 action-->discrete(2),action取非负整数0或1
print(env.observation_space) # 显示可用 state 的 observation-->Box(4,)Box表示一个n维的盒子
print(env.observation_space.high) # 显示 observation 最高值-->四列一行的值
print(env.observation_space.low) # 显示 observation 最低值-->四列一行的值
RL = PolicyGradient(
n_actions=env.action_space.n,
n_features=env.observation_space.shape[0],
learning_rate=0.02,
reward_decay=0.99,
# output_graph=True, # 输出 tensorboard 文件
)
for i_episode in range(3000):
observation = env.reset()
while True:
if RENDER: env.render()#因为RENTER为true时,才会显示模拟窗口,否则不需要要刷新界面。环境刷新
action = RL.choose_action(observation) #根据观测值选择一个动作
observation_, reward, done, info = env.step(action)
RL.store_transition(observation, action, reward) # 存储这一回合的 transition
if done:
ep_rs_sum = sum(RL.ep_rs) #将reward列表中的内容求和
if 'running_reward' not in globals(): #globals() 函数会以字典类型返回当前位置的全部全局变量
running_reward = ep_rs_sum
else:
running_reward = running_reward * 0.99 + ep_rs_sum * 0.01
if running_reward > DISPLAY_REWARD_THRESHOLD: RENDER = True # rendering判断是否显示模拟
print("episode:", i_episode, " reward:", int(running_reward))
vt = RL.learn() # 学习, 输出 vt,即这里是return discounted_ep_rs_norm
if i_episode == 0:
plt.plot(vt) # plot 这个回合的 vt
plt.xlabel('episode steps')
plt.ylabel('normalized state-action value')
plt.show()
break
observation = observation_
#一个回合更新一次网络参数,一个回合是指在杆子倒下的一系列动作和状态
#所以在done之前将一系列动作、状态、奖赏值都存储在列表中,即done前网络中的输入的观测值,只是我当前一条observation
#done后堆叠起来observation输入给NN,进行一次反向传播更新参数