Advertisement

【强化学习】Actor-Critic详解

阅读量:

强化学习

强化学习

  1. 算法的核心思想
    将Actor-Critic算法划分为两个主要组成部分:Actor和Critic。其中Actor的历史借鉴于Policy Gradient方法,在连续动作空间中能够灵活选择合适的动作;而基于价值的方法(Q-learning)在此时可能会导致发散(divergence),但由于Actor采用的是回合更新的方法(episodic training),其学习效率相对较低。这时我们意识到引入一个基于价值的价值网络(Critic)能够实现单步更新。通过这种相互补充的方式就形成了完整的Actor-Critic框架。

Actor 通过概率策略选择动作;Critic 根据 Actor 的动作评估动作的价值;Actor 根据 Critic 给出的评分调整其选择动作的概率。
从某种程度上说,在一定程度上受到Critic估计值的影响。
然而由于Critic本身难以收敛,并且与Actor协同更新的问题更为突出。
从某种程度上说,在一定程度上受到Critic估计值的影响。
然而由于Critic本身难以收敛,并且与Actor协同更新的问题更为突出。
为了解决这一收敛问题,
DeepMind开发了一种改进型的Deep Deterministic Policy Gradient(DDPG),将DQN的优势融入其中,
从而有效地解决了这一难题,
之后我将进一步详细阐述这种算法的工作原理和实现细节。

Actor(玩家):为了在游戏里取得更高的奖励分值, 因此必须设计一个策略: 输入当前的状态信息, 输出相应的动作选择, 如前所述. (可以用人工或机器学习算法来近似表示这一映射关系. 剩下的关键任务是如何有效地训练这一神经网络模型以提高奖励值. 这个经过优化的神经网络系统就被称作actor系统.)

Critic(评委):由于Actor基于策略Policy运作, 因此必须采用先前定义的Q值作为评估依据, 并将其返回给Actor以提供性能反馈. 然而, 在现有的框架下, 我们可以利用神经网络来近似这一概念, 并将该网络命名为Critic.

再提一下之前用过的符号

策略\pi(s)代表了agent的行为,在此过程中其生成的是单一的动作而非概率分布。

\pi(a|s) 代表策略 \pi在状态s下采取动作a的概率。从下文可知,
Critic网络的具体工作原理如下:
其目标是最化算子对状态值函数 V_\pi(s)进行估计,
而这一过程的具体实现细节可参考Q学习算法的相关推导内容。

V π ( s ) = E π [ r + γ V π ( s ′ ) ] V_\pi(s)=E_\pi[r + \gamma V_\pi(s')]V
π

(s)=E
π

[r+γV
π

(s

)]

策略的动作值函数如下

Q π ( s , a ) = R s a + γ V π ( s ′ ) Q_π(s,a)=R^a_s+γV_π(s′)Q
π

(s,a)=R
s
a

+γV
π

(s′)

我们定义了优势函数A(Advantage Function),该函数用于衡量在状态s下采取动作a的效果如何。当采取的动作a比平均表现更好时,在策略评估中我们会发现此时它的advantage值为正值;反之则为负值。

A π ( s , a ) = Q π ( s , a ) − V π ( s ) = r + γ V π ( s ′ ) − V π ( s ) A_\pi(s,a)=Q_π(s,a)-V_\pi(s)= r + γV_π(s′) -V_\pi(s)A
π

(s,a)=Q
π

(s,a)−V
π

(s)=r+γV
π

(s′)−V
π

(s)

在前提条件下,在于这两个公式实际上是等价的。由于对似然比方法的应用存在不同的解释和应用方式,在不同文献中可能会出现差异性表述。提醒各位读者,在面对不同的表述时要保持警惕。

∇ θ π θ ( s , a ) = π θ ( s , a ) ∇ θ π θ ( s , a ) π θ ( s , a ) = π θ ( s , a ) ∇ θ l o g π θ ( s , a ) ∇_θπ_θ(s,a)=π_θ(s,a)\frac{∇_θπ_θ(s,a)}{π_θ(s,a)}=π_θ(s,a)∇_θlogπ_θ(s,a)∇
θ

π
θ

(s,a)=π
θ

(s,a)
π
θ

(s,a)

θ

π
θ

(s,a)


θ

(s,a)∇
θ

logπ
θ

(s,a)

我们接下来聚焦于Actor部分,并基于Policy Gradient的方法从而采用策略梯度定理进行处理

The partial derivative of J(\theta) with respect to \theta at \theta is given by:

\frac{\partial}{\partial \theta}J(\theta) = \sum_{s \in S} d(s) \sum_{a \in A} \pi_\theta(s, a) \cdot \nabla_\theta \log\pi(a|s;\theta) \cdot Q_\pi(s, a)

Furthermore, this can be expressed as:

\nabla_\theta J(\theta) = {\mathbb{E}}_{\pi_\theta}\left[ \nabla_\theta \log\pi_\theta(s,a) \cdot Q_{\pi_\theta}(s,a) \right]


[∇
θ

logπ
θ

(s,a)Q
π
θ


(s,a)]

此处将Q_{\pi}(s, a)替换为上文所述的A_{\pi}(s, a)。基于策略梯度方法的基本原理可知:

\nabla_{\theta}J(\theta) = \sum_{s \in \mathcal{S}} d(s)\sum_{a \in \mathcal{A}} \pi_{\theta}(a|s)\nabla_{\theta}\log\pi(a|s;\theta) A_{\pi}(a|c)

其中J(\theta)代表目标函数\mathbb{E}_{\pi}[G_t]对参数\theta的导数表达式。


[∇
θ

logπ
θ

(s,a)A
π
θ


(s,a)]
更新公式如下
下面几种形式都是一样的所以千万不要蒙
θ t + 1 ← θ t + α A π θ ( s , a ) ∇ θ l o g π θ ( s , a ) \theta_{t+1}←\theta_t+\alpha A_{\pi_θ}(s,a) ∇_θlogπ_θ(s,a)θ
t+1

←θ
t

+αA
π
θ

基于状态-动作对的变化方向, 考虑到了策略函数关于参数的对数导数. 对于每一个时间步, 状态空间中的元素通过相应的概率分布进行映射. 在当前状态下, 我们将下一时刻的参数值设定为当前值加上学习率乘以与策略相关的状态-动作作用量, 其中这一作用量由梯度向量所定义. 这一过程确保了在每一次迭代中, 参数都在朝着优化目标的方向逐步逼近.


(s,a)
π(A
t

∣S
t

,θ)

θ

π(A
t

∣S
t

,θ)

\theta_{t+1} 被赋值为 \theta_{t} 加上 学习率 α 乘以 (奖励 r 加上 折扣因子 γ 乘以 策略 π 在下一状态 s_{t+1} 的价值 减去 策略 π 在当前状态 s_{t} 的价值) 再乘以 策略梯度(即 ∇θ π(A{t}|S_{t},\theta))除以 策略 π 在动作 A_{t} 给定状态 S_{t} 和参数 θ 下的概率。

损失函数如下
A可看做是常数所以可以求和平均打开期望,又因为损失函数要求最小所以加个"-"变换为解最小的问题
Actor:L π = − 1 n ∑ i = 1 n A π ( s , a ) l o g π ( s , a ) L_\pi =-\frac{1}{n}\sum_{i=1}^nA_{\pi}(s,a) logπ(s,a )L
π

=−
n
1


i=1
n

A
π

(s,a)logπ(s,a)
值迭代可以直接使用均方误差MSE作为损失函数
Critic:L v = 1 n ∑ i = 1 n e i 2 L_v = \frac{1}{n}\sum_{i=1}ne_i2L
v

n
1


i=1
n

e
i
2

n-step的伪代码如下

3、代码实现
import numpy as np
import tensorflow as tf
import gym

np.random.seed(2)
tf.set_random_seed(2) # reproducible

超参数

OUTPUT_GRAPH = False
MAX_EPISODE = 3000
DISPLAY_REWARD_THRESHOLD = 200 # 刷新阈值
MAX_EP_STEPS = 1000 #最大迭代次数
RENDER = False # 渲染开关
GAMMA = 0.9 # 衰变值
LR_A = 0.001 # Actor学习率
LR_C = 0.01 # Critic学习率

env = gym.make('CartPole-v0')
env.seed(1)
env = env.unwrapped

N_F = env.observation_space.shape[0] # 状态空间
N_A = env.action_space.n # 动作空间

actor = Actor()
@tf.func
def init(self, sess: tf.Session, num_features: int, num_actions: int,
learning_rate: float = 1e-3):
self(sess)
return actor

self.state = tf.placeholder(tf.float32, [1,n_features], "状态") # 状态占位符
self.action_index = tf.placeholder(tf.int32, None,"动作索引") # 动作索引
self.target_delta_error = tf.placeholder(tf.float32,None,"目标Δ误差") # 目标Δ误差

通过tf.variable_scope函数设置为'Actor'作用域

将作用概率分配给自变量self.acts_prob,并基于输入层l1进行计算。具体来说:
输入层l1将数据传递至全连接层,
其中输出单位数设为n_actions的数量,
激活函数设置为Softmax以获得动作的概率分布,
权重矩阵采用均值为0、标准差为0.1的正态分布初始化,
偏置项使用常数值0.1进行初始化,
网络命名为acts_prob以标识作用概率层。

with tf.variable_scope('experience_value'):
logarithm_of_action_probability = tf.log(tf.squeeze(self.acts_prob[0, self.a])))
experience_value = tf.reduce_mean(logarithm_of_action_probability * self.td_error) # Advantage (TD error) guided loss

tf.variable_scope('train') as self.train_scope
self.train_op = tf.train.AdamOptimizer(lr).assign(-self.exp_v, 'minimize')

minimize(-exp_v) 等价于 maximize(exp_v)

def learn(self, s, a, td):
通过np.newaxis增加维度的操作将一维数组s转换为二维数组
构建feed_dict字典用于传递训练数据至tensorflow会话
首先启动sess以运行train_op和exp_v节点
将exp_v节点的结果赋值给变量exp_v
最后返回exp_v值

此函数用于选择动作。
此输入被转换为二维张量。
通过计算概率分布来获取所有动作的可能性。
根据概率分布选择一个动作并返回其索引。

class Critic(object):
def init(self, sess, n_features, lr=0.01):
self.sess = sess

自变量s被定义为一个形状为[1, n_features]、类型为float32的张量,并命名为’state’。
自变量v_被定义为一个形状为[1, 1]、类型为float32的张量,并命名为’v_next’。
自变量r被定义为一个形状为None、类型为float32的张量,并命名为'r'。

with tf.variable_scope('Critic'): 使用tf.variable_scope函数来定义'Critic'命名空间
l1 = tf.layers.dense 通过tf.layers.dense函数构建l1层
inputs=self.s 输入参数设为self.s
units=20 单位数设为20
activation=tf.nn.relu 激活函数设置为ReLU
为了确保actor的收敛性 必须保证线性逼近
然而线性近似器似乎难以学习正确的Q值
kernelInitializer设置权重初始化器 选择均值为零 标准差为零点一的正态分布作为权重初始化方法
biasInitializer设定偏置初始化器 使用常数值零点一进行偏置项初始化
name='l1' 给该层命名为'l1'

self.v = tf.layers.dense(
inputs=l1,
units=1, # 输出维度设为1个
activation=None, # 激活函数设为无
kernel_initializer=tf.random_normal_initializer(
mean=0.,
stddev=0.1 # 权重矩阵初始化采用均值为0、标准差为0.1的正态分布随机数生成
),
bias_initializer=tf.constant_initializer(0.1), # 偏置项初始化采用常数值0.1生成
name='V'
)

within tf.variable_scope named 'squared_TD_error', we define:
assign self.td_error the value of r plus gamma multiplied by v next minus current v
set self.loss to be the square of self.td_error, which represents the squared difference between target and predicted values
within tf.variable_scope named 'train', we define the training operation as minimizing the loss using AdamOptimizer with learning rate lr

def learn(self, s, r, s_):
s, s_ = s[np.newaxis, :], s_[np.newaxis, :]

通过\texttt{session}计算当前状态值v
通过\texttt{session}计算\texttt{td\_error}\texttt{train\_op}
返回\texttt{td\_error}

创建tensorflow会话对象sess。
初始化actor网络参数。
构建并初始化critic网络。
通过调用tf.global_variables_initializer().run()来启动会话。

if OUTPUT_GRAPH:
tf.summary.FileWriter("logs/", sess.graph) # 输出日志

开始迭代过程 对应伪代码部分

for i_episode in range(MAX_EPISODE):
s = env.reset() # 环境初始化
t = 0
track_r = [] # 每回合的所有奖励
while True:
if RENDER: env.render()
a = actor.choose_action(s) # Actor选取动作
s_, r, done, info = env.step(a) # 环境反馈
if done: r = -20 # 回合结束的惩罚
track_r.append(r) # 记录回报值r
td_error = critic.learn(s, r, s_) # Critic 学习
actor.learn(s, a, td_error) # Actor 学习
s = s_
t += 1

if done or t >= MAX_EP_STEPS:
# 表示回合已结束并完成累积奖励计算
current_episode_reward = sum(track_r)
# 检查是否初始化累积奖励变量
if 'running_reward' not in globals():
running_reward = current_episode_reward
else:
# 对新累积奖励进行加权平均更新
running_reward = running_reward * 0.95 + current_episode_reward * 0.05
# 判断是否满足渲染条件
if running_reward > DISPLAY_REWARD_THRESHOLD:
set rendering状态以触发渲染
print("episode:", i_episode, " reward:", int(running_reward))
break

版权声明部分指出本文由博主独立创作完成,并遵从CC 4.0 BY-SA Creative Commons授权协议。如需转载,请确保在文中注明来源并附上原始文章链接及本版权声明。

版权声明部分指出本文由博主独立创作完成,并遵从CC 4.0 BY-SA Creative Commons授权协议。如需转载,请确保在文中注明来源并附上原始文章链接及本版权声明。

原文链接:

全部评论 (0)

还没有任何评论哟~