强化学习笔记

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

强化学习:Reinforcement Learning,又称再励学习或者评价学习。也是机器学习的技术之一。所谓强化学习就是智能系统从环境到行为映射的学习,以使奖励信号(强化信号)函数值最大,由于外部给出的信息很少,强化学习系统必须依靠自身的经历进行自我学习。通过这种学习获取知识,改进行动方案以适应环境。强化学习最关键的三个因素是状态,行为和环境奖励。关于强化学习和深度学习的实例,最典型的莫过于谷歌的AlphaGo和AlphaZero两位了,前者通过深度学习中的深度卷积神经网络,在训练了大约三千万组人类的下棋数据,无数度电的情况下才搞出来的模型,而后者使用强化学习的方式,通过自己和自己下棋的方式搞出来的模型。而最终的实验结果也很让人震撼。AlphaGo干败了人类围棋顶尖高手,而AlphaZero干败了AlphaGo.

提示:以下是本篇文章正文内容,下面案例可供参考

一、强化学习

1.1 强化学习方法

1.Model-Free RL(等待反馈后采取行动)
Qleanning
Sarsa
Policy Gradients

2.Model-Based RL (多了个虚拟,能够想象来得到对应的行为并挑选最好的)
QLearning
Sarsa
Policy Gradients

1.基于概率(Policy-Based RL)
根据概率选择行动
Policy Gradients

2.基于价值(Value-Based RL)
根据最高价值选择动作
QLearning
Sarsa

Actor-Critic (以上两种结合)

1.回合更新
基础班Plicy Gradients
Monte-Carlo Learning
2.单步更新
Q learning
Sarsa
Policy Gradients

1.在线学习(On-Policy)
Sarsa
Sarsa(lambda)

2.离线学习 (Off-Policy)
Q learning
Deep Q Network

二、Q Learning

在这里插入图片描述

2.1 例子1 一维寻宝

import numpy as np
import pandas as pd
import time

np.random.seed(2) #产生伪随机数列


N_STATES=6  # 状态
ACTIONS=['LEFT','RIGHT'] #可用的动作
EPSILON=0.9  #选择动作概率  0.9概率选择最优,0.1选择其他
alpha=0.1  #学习效率
LMBDA=0.9  #奖励衰减值
MAX_EPISODES=13  #迭代次数
FRESH_TIME=0.3  #走一步的时间



def build_q_table(n_states,actions):
    ##初始化Q_table
    ##n_state 即s1 s2...
    ##actions 动作
    table = pd.DataFrame(
        np.zeros((n_states, len(actions))),  # q_table initial values
        columns=actions,  # actions's name
    )
    print(table)
    return table

def choseAction(state,q_table):
    #选择动作
    state_actions=q_table.iloc[state,:] ##类似于matlab 将q_table 的state这一行赋值给变量
    if(np.random.uniform()>EPSILON)or (state_actions.all()==0):
        ##np.random.uniform() 产生一个随机数(0-1)
        action_name=np.random.choice(ACTIONS) #随机选择一个
    else:
        action_name=state_actions.argmax()  #选择其中大的一个
    return action_name


##环境的反馈,采取动作后的状态,以及奖励
def get_env_feedback(S,A):
    if A=='right':
        if S==N_STATES-2: ##采取下一个动作更新后状态就为终止,到达最右边
            S_='terminal'
            R=1  ##得到奖励
        else:
            S_=S+1
            R=0
    else:
        R=0
        if S==0:
            S_=S
        else:
            S_=S-1
    return S_,R


def rl():
    q_table=build_q_table(N_STATES,ACTIONS)
    for episode in range(MAX_EPISODES):
        step_counter=0
        S=0
        is_terminated=False
        while not is_terminated:
            A=choseAction(S,q_table)
            S_,R=get_env_feedback(S,A) #获取下一个状态和奖励
            q_predict=q_table[S,A]  #预估得到的奖励
            if S_ !='terminal':
               q_target=R+LMBDA*q_table.iloc[S_,:].max()  ##真实值
            else:
                q_target=R
                is_terminated=True
            q_table.ix[S,A]+=alpha*(q_target-q_predict)
            S=S_
            ##updateEnv
    return q_table

2.2 例子2 迷宫寻宝

import numpy as np
import pandas as pd

class QLearningTable:
    def __init__(self,actions,learning_rate,reward_decay,e_greedy):
        self.action=actions
        self.lr=learning_rate
        self.gamma=reward_decay
        self.epsilon=e_greedy
        self.q_table=pd.DataFrame(columns=self.action)
    def choose_action(self,observation):
        self.check_state_exist(observation)#检验观测值是否在qtable中
        ##随机选择主要是为了以防qtable中的两个值一样
        if(np.random.uniform<self.epsilon):
            state_action=self.q_table.iloc[observation,:]
            action=state_action.argmax()
        else:
            action=np.random.choice(self.action)
        return action

    def learn(self,s,a,r,s_):
        self.check_state_exist(s_)
        q_predict=self.q_table[s,a]
        if s_!='terminal':
            q_target=r+self.gamma*self.q_table[s_,:].max()
        else:
            q_target=r
        self.q_table[s,a]+=self.lr*(q_target-q_predict)


    def check_state_exist(self,state):
        if state not in self.q_table.index:
            #append,初始化为0

三、Sarsa

类似于QLearning,Sarsa也是用qtable来完成。
不同之处在于Sarsa 不再是预估计下一个状态动作的最大值(它实际上不一定采取该动作),而是直接选取最大的动作。

在这里插入图片描述
具体伪代码如下:
在这里插入图片描述

3.1 例子1 迷宫寻宝

import numpy as np
import pandas as pd



class SarsaTable():
    def __init__(self, actions, learning_rate, reward_decay, e_greedy):
        self.action = actions
        self.lr = learning_rate
        self.gamma = reward_decay
        self.epsilon = e_greedy
        self.q_table = pd.DataFrame(columns=self.action)

    def choose_action(self, observation):
        self.check_state_exist(observation)  # 检验观测值是否在qtable中
        ##随机选择主要是为了以防qtable中的两个值一样
        if (np.random.uniform < self.epsilon):
            state_action = self.q_table.iloc[observation, :]
            action = state_action.argmax()
        else:
            action = np.random.choice(self.action)
        return action

    def learn(self, s, a, r, s_,a_):
        self.check_state_exist(s_)
        q_predict = self.q_table.ix[s, a]
        if s_ != 'terminal':
            q_target = r + self.gamma * self.q_table[s_,a_]
        else:
            q_target = r
        self.q_table[s, a] += self.lr * (q_target - q_predict)

    def check_state_exist(self, state):
        if state not in self.q_table.index:
              # append,初始化为0

while True:
    ## 更新环境
    observation_,reward,done=env.step(action) #获取环境的反馈,包括下一个observation和reward以及是否完成

    action_=RL.choose_action(observation_)##选择动作

    RL.learn(observation,action,reward,observation_,action_) ##sarsa 学习方法

    obervation=observation  ##更新observation 和 action
    action=action_

    if done:
        break

3.2 Sarsa(Lambda)

在这里插入图片描述
可知当lambda=0时,为单步更新,只更新距离宝藏最近的一步。
当lambda=1时,为回合更新,更新找到宝藏路径上的所有步骤。
当lambda<1&&lambda>0 时,则介于两者之间

伪代码如下:
在这里插入图片描述

import numpy as np
import pandas as pd
class SarsaTable():
    def __init__(self, actions, learning_rate, reward_decay, e_greedy,trace_decay):
        self.action = actions
        self.lr = learning_rate
        self.gamma = reward_decay
        self.epsilon = e_greedy
        self.lambda_=trace_decay
        self.q_table = pd.DataFrame(columns=self.action)
        self.eligibility_trace=self.q_table.copy()  #记录路径
    def choose_action(self, observation):
        self.check_state_exist(observation)  # 检验观测值是否在qtable中
        ##随机选择主要是为了以防qtable中的两个值一样
        if (np.random.uniform < self.epsilon):
            state_action = self.q_table.iloc[observation, :]
            action = state_action.argmax()
        else:
            action = np.random.choice(self.action)
        return action

    def learn(self, s, a, r, s_,a_):
        self.check_state_exist(s_)
        q_predict = self.q_table.ix[s, a]
        if s_ != 'terminal':
            q_target = r + self.gamma * self.q_table[s_,a_]
        else:
            q_target = r
        error=q_target - q_predict

        #Method1 路径标记值不封顶
        self.eligibility_trace[s,a]+=1

        #Method2 路径标记值封顶为1
        self.eligibility_trace.ix[s,:]=0
        self.eligibility_trace.ix[s,a]=1
        #q_table更新
        self.q_table[s,a]+=self.lr*error*self.eligibility_trace

        self.eligibility_trace*=self.gamma*self.lambda_  #路径标记衰减

    def check_state_exist(self, state):
        if state not in self.q_table.index:
              # append,初始化为0 both q_table and eligibility_trace

四、Deeq Q Network(DQN)

传统的表格形式有着存储容量,查询问题。
由此我们可以使用
1.将状态和动作作为神经网络的输入,并得到动作的q值
2.将状态s输入,输出所有的动作值,并选择最大值的动作来作为下一步的动作。

在这里插入图片描述
在这里插入图片描述在这里插入图片描述
记忆库:DQN能学习当前经历着的, 也能学习过去经历过的, 甚至是学习别人的经历,所以每次 DQN 更新的时候, 我们都可以随机抽取一些之前的经历进行学习. 随机抽取这种做法打乱了经历之间的相关性, 也使得神经网络更新更有效率

Fixed Q-targets :也是一种打乱相关性的机理, 如果使用 fixed Q-targets, 我们就会在 DQN 中使用到两个结构相同但参数不同的神经网络, 预测 Q 估计 的神经网络具备最新的参数, 而预测 Q 现实 的神经网络使用的参数则是很久以前的.

伪代码如下:
在这里插入图片描述

4.1 例子:迷宫

import numpy as np
import pandas as pd
import time
import tensorflow as tf



class DQN:
    def __init__(self,n_actions,n_features,learning_rate,reward_decay,e_graddy,replace_target_iter,memory_size,batch_size,e_greedy_increment,output_graph):
        self.n_actions=n_actions
        self.n_feature=n_features #特征数
        self.lr=learning_rate
        self.gamma=reward_decay
        self.epsilon_max=e_graddy
        self.replace_target_iter=replace_target_iter  #隔了多少步后将target net 参数更新
        self.memory_size=memory_size ##记忆的容量
        self.epsilon_increment=e_greedy_increment


        self.learn_step_counter=0  #学习的步数

        self.memory=pd.DataFrame(np.zeros(self.memory_size,n_features*2+2)) #记忆

        self.createNetwork()
        self.cost_his=[] #记录每一步的误差


    ##记录memory
    def store_transition(self,s,a,r,s_):
        trainsition=np.hstack(s,[a,r],s_)  #记录
        #append to self.memory


    #选取动作
    def choose_action(self,observation):

        actions_value=self.sess.run()  ##获取神经网络得到的动作的值
        #90%选取最大的,其他的随机选取

    ## 创建两个神经网络已完成暂时冻结q_target
    def createNetwork:
        self.s=tf.placeholder(tf.float32,[None,self.n_feature],name='s') #输入
        self.q_target=tf.placeholder(tf.float32,[None,self.n_actions],name='Q_target')  #计算error
        with tf.variable_scope('eval_net'):
            #c_name 所有的参数的集合
            c_name,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)  #layer 配置
            #nl1 为每层的神经元

            #第一层,
            with tf.variable_creator_scope('l1'):
                w1=tf.get_variable('w1',[self.n.features,n_l1],initializer=w_initializer,collections=c_name)
                b1=tf.get_variable('b1',[1,n_l1],b_initializer,c_name)
                l1=tf.nn.relu(tf.matmul(self.s,w1)+b1)

            #第二层:
            with tf.variable_creator_scope('l2'):
                w2 = tf.get_variable('w2', [n_l1 self.n_actions], initializer=w_initializer, collections=c_name)
                b2 = tf.get_variable('b2', [1, sekf.n_actions], b_initializer, c_name)
                self.q_eval=tf.matmul(l1,w2)+b2
            with tf.name_scope('loss'):
                self.loss=tf.reduce_sum(tf.squared_difference(self.q_target,self.q_eval))
            with tf.name_scope('train'):
                self._train_op=tf.train.RMSPropOptimizer(self.lr).minimize(self.loss)


            #target Net
            self.s_=tf.placeholder(tf.float32,[None,self.n_feature],name='s_')  #input
            with tf.variable_scope('target_net'):
                # c_name 所有的参数的集合
                c_name, n_l1, w_initializer, b_initializer = ['target_net_params', tf.GraphKeys.GLOBAL_VARIABLES], 10,
                tf.random_normal_initializer(0, 0.3), tf.constant_initializer(0.1)  # layer 配置
                # nl1 为每层的神经元

                # 第一层,
                with tf.variable_creator_scope('l1'):
                    w1 = tf.get_variable('w1', [self.n.features, n_l1], initializer=w_initializer, collections=c_name)
                    b1 = tf.get_variable('b1', [1, n_l1], b_initializer, c_name)
                    l1 = tf.nn.relu(tf.matmul(self.s, w1) + b1)

                # 第二层:
                with tf.variable_creator_scope('l2'):
                    w2 = tf.get_variable('w2', [n_l1 self.n_actions], initializer=w_initializer, collections=c_name)
                    b2 = tf.get_variable('b2', [1, sekf.n_actions], b_initializer, c_name)
                    self.q_eval = tf.matmul(l1, w2) + b2

    def _replace_target_param(self):
        ## 更新target net的参数

    def learn(self):
        #检查是否需要更新target net参数
        #抽取记忆
        batch_memory=self.memory.sample(self.batch_size)

        #计算q估计和q现实
        q_next,q_eval=self.sess.run([self.q_next,self.q_eval],
                                    fead_dict={self.s_:batch_memory.iloc[:,-self.n_feature],
                                               self.s:batch_memory.ilos[:,self.n_feature]
                                               })

        q_target=q_eval.copy()
        #训练
        _,self.cost=self.sess.run([self._train_op,self.loss],
                              feed_dict={self.s:batch_memory.iloc[:,:self.n_feature],self.q_target:q_target})

        self.cost_his.append(self.cost)
        #更新epsilon,平衡探索和勘测
        self.learn_step_counter+=1


#get environment
while True:
    #refresh environment

    #获取动作
    action=RL.choose_action(observation)

    #获取外界环境反馈
    observation_,reward,done=env.step(action)

    #存储相关信息
    RL.store_transmition(observation,action,reward,observation_)

    #学习
    if(step>200 and step%5==0):
        RL.learn()

    #更新观测点
    observation=observation_
    if done:
        break
    step+=1

补充:gym?

4.2 DDQN(Double DQN)

主要解决over estimating
(估计值过大)

可知DQN的q现实部分如下
在这里插入图片描述可知神经网络获得的q值有误差,同时由于我们选择了最大的值,所以我们引入了最大的误差,由此导致了over estimating
(q现实来选择动作)

Double DQN
主要想法是引入另一个神经网络来减少误差。
它利用Q估计的神经网络来估计现实中的Qmax)(s’,a’)并用这个q估计出来的动作来选择q现实的qs’
(用q估计来选择对应的动作)
在这里插入图片描述

#qeval4next q现实中用q估计来得到的下一个动作
    q_next, q_eval4next = self.sess.run(
        [self.q_next, self.q_eval],
        feed_dict={self.s_: batch_memory[:, -self.n_features:],    # next observation
                   self.s: batch_memory[:, -self.n_features:]})    # next observation
    q_eval = self.sess.run(self.q_eval, {self.s: batch_memory[:, :self.n_features]})
    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)
    reward = batch_memory[:, self.n_features + 1]

    if self.double_q:   # 如果是 Double DQN
        max_act4next = np.argmax(q_eval4next, axis=1)        # q_eval 得出的最高奖励动作
        selected_q_next = q_next[batch_index, max_act4next]  # Double DQN 选择 q_next 依据 q_eval 选出的动作
    else:       # 如果是 Natural DQN
        selected_q_next = np.max(q_next, axis=1)    # natural DQN

    q_target[batch_index, eval_act_index] = reward + self.gamma * selected_q_next

4.3 DQN Prioritized Experience Replay

随机抽样的优化。

这一套算法重点就在我们 batch 抽样的时候并不是随机抽样, 而是按照 Memory 中的样本优先级来抽. 所以这能更有效地找到我们需要学习的样本.

原来我们可以用到 TD-error, 也就是 Q现实 - Q估计 来规定优先学习的程度. 如果 TD-error 越大, 就代表我们的预测精度还有很多上升空间, 那么这个样本就越需要被学习, 也就是优先级 p 越高.

伪代码:
在这里插入图片描述

补充:排序方法:sumTree
在这里插入图片描述

def store_transition(self, s, a, r, s_):
    if self.prioritized:  # prioritized replay
        transition = np.hstack((s, [a, r], s_))
        #计算q_next,q_eval 估计和现实值
        error=r+np.max(q_next.max(),q_eval)
        self.memory.store(abs(error),transition)
    else:  # random replay
        if not hasattr(self, 'memory_counter'):
            self.memory_counter = 0
        transition = np.hstack((s, [a, r], s_))
        index = self.memory_counter % self.memory_size
        self.memory[index, :] = transition
        self.memory_counter += 1

 def learn(self):
        ...
        # 相对于 DQN 代码, 改变的部分
        #抽取样本按照优先级
        if self.prioritized:
            tree_idx, batch_memory, ISWeights = self.memory.sample(self.batch_size)
        else:
            sample_index = np.random.choice(self.memory_size, size=self.batch_size)
            batch_memory = self.memory[sample_index, :]

        ...

        if self.prioritized:
            #abs_errors 新的误差(新的priority) 
            _, abs_errors, self.cost = self.sess.run([self._train_op, self.abs_errors, self.loss],
                                                     feed_dict={self.s: batch_memory[:, :self.n_features],
                                                                self.q_target: q_target,
                                                                self.ISWeights: ISWeights})
            self.memory.batch_update(tree_idx, abs_errors)  # update priority
        else:
            _, self.cost = self.sess.run([self._train_op, self.loss],
                                         feed_dict={self.s: batch_memory[:, :self.n_features],
                                                    self.q_target: q_target})

        ...

4.4 Dueling DQN

在这里插入图片描述在这里插入图片描述
它分成了这个 state 的值, 加上每个动作在这个 state 上的 advantage. 因为有时候在某种 state, 无论做什么动作, 对下一个 state 都没有多大影响

def _build_net(self):
    def build_layers(s, c_names, n_l1, w_initializer, b_initializer):
        with tf.variable_scope('l1'):  # 第一层, 两种 DQN 都一样
            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(s, w1) + b1)

        if self.dueling:
            # Dueling DQN
            with tf.variable_scope('Value'):  # 专门分析 state 的 Value
                w2 = tf.get_variable('w2', [n_l1, 1], initializer=w_initializer, collections=c_names)
                b2 = tf.get_variable('b2', [1, 1], initializer=b_initializer, collections=c_names)
                self.V = tf.matmul(l1, w2) + b2

            with tf.variable_scope('Advantage'):  # 专门分析每种动作的 Advantage
                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.A = tf.matmul(l1, w2) + b2

            with tf.variable_scope('Q'):  # 合并 V 和 A, 为了不让 A 直接学成了 Q, 我们减掉了 A 的均值
                out = self.V + (self.A - tf.reduce_mean(self.A, axis=1, keep_dims=True))  # Q = V(s) + A(s,a)
        else:
            with tf.variable_scope('Q'):  # 普通的 DQN 第二层
                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)
                out = tf.matmul(l1, w2) + b2

        return out

五、Policy Gradients

Policy Gradients 直接输出动作,优势在于能够在一个连续区间挑选动作

更新步骤:通过奖惩信息来降低或者增加动作被选中的增大幅度。

伪代码:
在这里插入图片描述基于回合,在回合结束后更新。

我们利用神经网络输出动作的采取概率:
在这里插入图片描述
选择行为也不再是用 Q value 来选定的, 而是用概率来选定. 即使不用 epsilon-greedy, 也具有一定的随机性.

对于学习:
首先我们要对这回合的所有 reward 动动手脚, 使他变得更适合被学习. 第一就是随着时间推进, 用 gamma 衰减未来的 reward, 然后为了一定程度上减小 policy gradient 回合 variance, 我们标准化回合的 state-action value

(vt = 本reward + 衰减的未来reward) 引导参数的梯度下降,如果vt是负的,就认为该方向是错误的,(可以对vt进行标准化)

六、Actor Critic

结合了 Policy Gradient (Actor) 和 Function Approximation (Critic) 的方法. Actor 基于概率选行为, Critic 基于 Actor 的行为评判行为的得分, Actor 根据 Critic 的评分修改选行为的概率.

它的优势在于能够进行单步更新,提高了学习效率。

Actor 想要最大化期望的 reward, 在 Actor Critic 算法中, 我们用 比平时好多少 (TD error) 来当做 reward, 所以就是:
actor 学习需要当前状态s,施加的行为a,以及对应的tderror(是否加大或者减少该动作的概率)

with tf.variable_scope('exp_v'):
    log_prob = tf.log(self.acts_prob[0, self.a])    # log 动作概率
    self.exp_v = tf.reduce_mean(log_prob * self.td_error)   # log 概率 * TD 方向
with tf.variable_scope('train'):
    # 因为我们想不断增加这个 exp_v (动作带来的额外价值),
    # 所以我们用过 minimize(-exp_v) 的方式达到
    # maximize(exp_v) 的目的
    self.train_op = tf.train.AdamOptimizer(lr).minimize(-self.exp_v)

Critic 的更新很简单, 就是像 Q learning 那样更新现实和估计的误差 (TD error) 就好了.

critic 学习时需要当前状态s和下一个状态s’

with tf.variable_scope('squared_TD_error'):
    self.td_error = self.r + GAMMA * self.v_ - self.v
    self.loss = tf.square(self.td_error)    # TD_error = (r+gamma*V_next) - V_eval
with tf.variable_scope('train'):
    self.train_op = tf.train.AdamOptimizer(lr).minimize(self.loss)

主流程如下:

#选择动作
a=actor.choose_action(s)
#获取环境反馈
s_,r,done,info=env.step(a)
#获取tderror
td_error=critic.learn(s,r,s_)
#actor学习
actor.learn(s,a,td_error)
#状态更新
s=s_

七、DDPG ?

DDPG: Google DeepMind 提出的一种使用 Actor Critic 结构, 但是输出的不是行为的概率, 而是具体的行为, 用于连续动作 (continuous action) 的预测. DDPG 结合了之前获得成功的 DQN 结构, 提高了 Actor Critic 的稳定性和收敛性.

在这里插入图片描述Policy Gradient 这边, 我们有估计网络和现实网络, 估计网络用来输出实时的动作, 供 actor 在现实中实行. 而现实网络则是用来更新价值网络系统的. 所以我们再来看看价值系统这边, 我们也有现实网络和估计网络, 他们都在输出这个状态的价值, 而输入端却有不同, 状态现实网络这边会拿着从动作现实网络来的动作加上状态的观测值加以分析, 而状态估计网络则是拿着当时 Actor 施加的动作当做输入.

在这里插入图片描述
在这里插入图片描述
actor:
在这里插入图片描述需要两个参数: grad q 有critic得到, gradu 有actor得到
这两个参数分别是对应的梯度,即从获取大Q的方向来修改动作参数
actor中也有两个网络,一个实时更新。
critic:
在这里插入图片描述它由两个计算Q的神经网络(借鉴DQN),Q_target依据下一状态用Actor来选取动作

更新的伪代码:
actor:

with tf.variable_scope('policy_grads'):
    # 这是在计算 (dQ/da) * (da/dparams)
    self.policy_grads = tf.gradients(
        ys=self.a, xs=self.e_params, # 计算 ys 对于 xs 的梯度
        grad_ys=a_grads # 这是从 Critic 来的 dQ/da
    )
with tf.variable_scope('A_train'):
    opt = tf.train.AdamOptimizer(-self.lr)  # 负的学习率为了使我们计算的梯度往上升, 和 Policy Gradient 中的方式一个性质
    self.train_op = opt.apply_gradients(zip(self.policy_grads, self.e_params)) # 对 eval_net 的参数更新

对于a_grad 的计算如下:(该值来源于critic,其中a来自Actor根据s计算得到)

with tf.variable_scope('a_grad'):
    self.a_grads = tf.gradients(self.q, self.a)[0]   # dQ/da

critic:
他需要从actor中得到的动作a_

# 计算 target Q
with tf.variable_scope('target_q'):
    self.target_q = R + self.gamma * self.q_    # self.q_ 根据 Actor 的 target_net 来的
# 计算误差并反向传递误差
with tf.variable_scope('TD_error'):
    self.loss = tf.reduce_mean(tf.squared_difference(self.target_q, self.q))  # self.q 又基于 Actor 的 target_net
with tf.variable_scope('C_train'):
    self.train_op = tf.train.AdamOptimizer(self.lr).minimize(self.loss)
 actor = Actor(...)
critic = Critic(..., actor.a, actor.a_)  # 将 actor 同它的 eval_net/target_net 产生的 a/a_ 传给 Critic
actor.add_grad_to_graph(critic.a_grads) # 将 critic 产出的 dQ/da 加入到 Actor 的 Graph 中去

八、A3A

A3C 的算法实际上就是将 Actor-Critic 放在了多个线程中进行同步训练.

我们认为中央大脑拥有 global net 和他的参数, 每位玩家有一个 global net 的副本 local net, 可以定时向 global net 推送更新, 然后定时从 global net 那获取综合版的更新.
同时中央大脑只保存对应的参数,而不进行训练。
对于worker,可以用push和pull来更新获取对应的中央大脑的参数,同时在本地环境中进行学习。

对于actor:
我们使用了 Normal distribution 来选择动作, 所以在搭建神经网络的时候, actor 这边要输出动作的均值和方差. 然后放入 Normal distribution 去选择动作. 计算 actor loss 的时候我们还需要使用到 critic 提供的 TD error 作为 gradient ascent 的导向.(通过均值和方差来采样,得到对应的动作)

对于critic:
计算TD error。

对应的伪代码:

# 这个 class 可以被调用生成一个 global net.
# 也能被调用生成一个 worker 的 net, 因为他们的结构是一样的,
# 所以这个 class 可以被重复利用.
class ACNet(object):
    def __init__(self, globalAC=None):
        # 当创建 worker 网络的时候, 我们传入之前创建的 globalAC 给这个 worker
        if 这是 global:   # 判断当下建立的网络是 local 还是 global
            with tf.variable_scope('Global_Net'):
                self._build_net()
        else:
            with tf.variable_scope('worker'):
                self._build_net()

            # 接着计算 critic loss 和 actor loss
            # 用这两个 loss 计算要推送的 gradients

            with tf.name_scope('sync'):  # 同步
                with tf.name_scope('pull'):
                    # 更新去 global
                with tf.name_scope('push'):
                    # 获取 global 参数

    def _build_net(self):
        # 在这里搭建 Actor 和 Critic 的网络
        return 均值, 方差, state_value

    def update_global(self, feed_dict):
        # 进行 push 操作

    def pull_global(self):
        # 进行 pull 操作

    def choose_action(self, s):
        # 根据 s 选动作

worker:类似于之前的(?!)

        # s, a, r 的缓存, 用于 n_steps 更新
        buffer_s, buffer_a, buffer_r = [], [], []
        while not COORD.should_stop() and GLOBAL_EP < MAX_GLOBAL_EP:
            s = self.env.reset()

            for ep_t in range(MAX_EP_STEP):
                a = self.AC.choose_action(s)
                s_, r, done, info = self.env.step(a)

                buffer_s.append(s)  # 添加各种缓存
                buffer_a.append(a)
                buffer_r.append(r)

                # 每 UPDATE_GLOBAL_ITER 步 或者回合完了, 进行 sync 操作
                if total_step % UPDATE_GLOBAL_ITER == 0 or done:
                    # 获得用于计算 TD error 的 下一 state 的 value
                    if done:
                        v_s_ = 0   # terminal
                    else:
                        v_s_ = SESS.run(self.AC.v, {self.AC.s: s_[np.newaxis, :]})[0, 0]

                    buffer_v_target = []    # 下 state value 的缓存, 用于算 TD
                    for r in buffer_r[::-1]:    # 对于未来的步骤进行r的递减操作
                        v_s_ = r + GAMMA * v_s_
                        buffer_v_target.append(v_s_)
                    buffer_v_target.reverse()

                    buffer_s, buffer_a, buffer_v_target = np.vstack(buffer_s), np.vstack(buffer_a), np.vstack(buffer_v_target)

                    feed_dict = {
                        self.AC.s: buffer_s,
                        self.AC.a_his: buffer_a,
                        self.AC.v_target: buffer_v_target,
                    }

                    self.AC.update_global(feed_dict)    # 推送更新去 globalAC
                    buffer_s, buffer_a, buffer_r = [], [], []   # 清空缓存
                    self.AC.pull_global()   # 获取 globalAC 的最新参数

                s = s_
                if done:
                    GLOBAL_EP += 1  # 加一回合
                    break   # 结束这回合 ```

九、PPO/DPPO

PPO: OpenAI 提出的一种解决 Policy Gradient 不好确定 Learning rate (或者 Step size) 的问题.

因为如果 step size 过大, 学出来的 Policy 会一直乱动, 不会收敛, 但如果 Step Size 太小, 对于完成训练, 我们会等到绝望. PPO 利用 New Policy 和 Old Policy 的比例, 限制了 New Policy 的更新幅度, 让 Policy Gradient 对稍微大点的 Step size 不那么敏感.

更新Actor:
在这里插入图片描述
补充: Actor在旧策略上依据Advantage(TDerror) 来修改新策略。 当对应的Advantage大时,修改的幅度变大。同时附加一个惩罚项来让新策略和旧策略相差不太多。如果差太多,那么就相当于应用了一个非常大的学习率。这样难以收敛。

结构:
在这里插入图片描述pi 就是我们的 Actor 了. 每次要进行 PPO 更新 Actor 和 Critic 的时候, 我们有需要将 pi 的参数复制给 oldpi. 这就是 update_oldpi 这个 operation 在做的事. 这里的 Actor 使用了 normal distribution 正态分布输出动作.

更新(?!):

 def __init__(self):
        self.tfa = tf.placeholder(tf.float32, [None, A_DIM], 'action')
        self.tfadv = tf.placeholder(tf.float32, [None, 1], 'advantage')
        with tf.variable_scope('loss'):
            with tf.variable_scope('surrogate'):
                ratio = pi.prob(self.tfa) / oldpi.prob(self.tfa)
                surr = ratio * self.tfadv   # surrogate objective
            if METHOD['name'] == 'kl_pen':      # 如果用 KL penatily
                self.tflam = tf.placeholder(tf.float32, None, 'lambda')
                kl = kl_divergence(oldpi, pi)
                self.kl_mean = tf.reduce_mean(kl)
                self.aloss = -(tf.reduce_mean(surr - self.tflam * kl))
            else:                               # 如果用 clipping 的方式
                self.aloss = -tf.reduce_mean(tf.minimum(
                    surr,
                    tf.clip_by_value(ratio, 1.-METHOD['epsilon'], 1.+METHOD['epsilon'])*self.tfadv))

        with tf.variable_scope('atrain'):
            self.atrain_op = tf.train.AdamOptimizer(A_LR).minimize(self.aloss)

与环境的交互?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值