莫烦老师,Policy Gradient代码学习笔记

详情请见莫烦老师主页: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,进行一次反向传播更新参数



 

你可能感兴趣的:(强化学习)