简介
强化学习是一个通过奖惩来学习正确行为的机制。 家族中有很多种不一样的成员,有学习奖惩值,根据自己认为的高价值选行为, 比如 Q learning, Deep Q Network, 也有不通过分析奖励值,直接输出行为的方法,这就是今天要说的 Policy Gradient 了。甚至我们可以为 Policy Gradients 加上一个神经网络来输出预测的动作。对比起以值为基础的方法,Policy Gradients 直接输出动作的最大好处就是,它能在一个连续区间内挑选动作,而基于值的,比如 Q-learning,它如果在无穷多的动作中计算价值,从而选择行为,这,它可吃不消。
1.什么是Policy
如上图,一个由 Agent(相当于我们的模型)和 Environment(所处状态)组成的结构。Agent 通过观察当前环境的状态
s
t
s_t
st,得出当前应当执行的动作
a
t
a_t
at。Agent 执行完动作之后环境对应发生了改变,并且环境会给予 Agent 一个反馈 reward
r
t
r_t
rt。此时又会是一个新的环境状态
s
′
s'
s′,基于本次的环境状态,Agent 又会执行对应的动作… 以此类推持续进行下去,直到无法继续。如下图所示,Env 表示环境,Actor 即为 Agent:
上面实际上就是对一系列操作进行了抽象描述。以玩游戏为例说明,我们(Agent)通过观察游戏(Environment)
的运行情况(State),选择接下来要执行的操作(Action),游戏往往还会反馈给我们我们的得分(Rewards)。
2、算法思想
Policy Gradient 不通过误差反向传播,它通过观测信息选出一个行为直接进行反向传播,当然出人意料的是他并没有误差,而是利用 reward 奖励直接对选择行为的可能性进行增强和减弱,好的行为会被增加下一次被选中的概率,不好的行为会被减弱下次被选中的概率。
举例如下图所示:输入当前的状态,输出 action 的概率分布,选择概率最大的一个 action 作为要执行的操作。在不同的状态(State)采取的动作 Action 也就是我们所说的策略 Policy 。 常用符号
π
π
π 来表示策略。
而一个完整的策略
τ
τ
τ 代表的是一整个回合中,对于每个状态下所采取的的动作所构成的序列,而每个回合 episode 中每个动作的回报和等于一个回合的回报值
R
=
∑
t
=
1
T
r
t
R = ∑ _{t = 1} ^T r_t
R=∑t=1Trt
Trajectory
τ
τ
τ :行动 action 和状态 state 的序列
p
θ
(
τ
)
p_θ(τ)
pθ(τ) :
π
π
π 在参数为
θ
θ
θ 情况下时
τ
τ
τ 发生的概率得到了概率之后我们就可以根据采样
得到的回报值计算出数学期望,从而得到目标函数,然后用来更新我们的参数 θ
得出目标函数之后,就需要根据目标函数求解目标函数最大值以及最大值对应的 policy 的参数 θ。类比深度学习中的梯度下降求最小值的方法,由于我们这里需要求的是目标函数的最大值,因此需要采取的方法是梯度上升。也就是说,思想起点是一样的,即需要求出目标函数的梯度。
优点:
- 连续的动作空间(或者高维空间)中更加高效;
- 可以实现随机化的策略;
- 某种情况下,价值函数可能比较难以计算,而策略函数较容易。
缺点:
- 通常收敛到局部最优而非全局最优
- 评估一个策略通常低效(这个过程可能慢,但是具有更高的可变性,其中也会出现很多并不有效的尝试,而且方差高
3.策略函数
- [1] 在 Policy Based 强化学习方法下,我们对策略进行近似表示。此时策略 π π π 可以被描述为一个包含参数 θ θ θ 的函数,即:
π θ ( s , a ) = P ( a ∣ s , θ ) ≈ π ( a ∣ s ) π_θ(s,a)=P(a|s,θ)≈π(a|s) πθ(s,a)=P(a∣s,θ)≈π(a∣s)
- [2] 我们现在来看策略函数
π
θ
(
s
,
a
)
π_θ(s,a)
πθ(s,a) 的设计: 最常用的策略函数就是 softmax 策略函数了,它主要应用于离散空间中,softmax 策略使用描述状态和行为的特征
ϕ
(
s
,
a
)
ϕ(s,a)
ϕ(s,a) 与参数
θ
θ
θ
的线性组合来权衡一个行为发生的几率,即:
π θ ( s , a ) = e ϕ ( s , a ) T θ ∑ b e ϕ ( s , b ) T θ π_θ(s,a)=\frac{e^{ϕ(s,a)^Tθ}}{ \sum_{b}e^{ϕ(s,b)^Tθ}} πθ(s,a)=∑beϕ(s,b)Tθeϕ(s,a)Tθ
- [3] 对于 ∇ θ l o g π θ ( s , a ) ∇_θlogπ_θ(s,a) ∇θlogπθ(s,a),我们一般称为分值函数,则通过求导很容易求出分值函数为:
∇ θ l o g π θ ( s , a ) = ϕ ( s , a ) − E π θ [ ϕ ( s , . ) ] ∇_θlogπ_θ(s,a)=ϕ(s,a)−E_{π_θ}[ϕ(s,.)] ∇θlogπθ(s,a)=ϕ(s,a)−Eπθ[ϕ(s,.)]
4.策略梯度的优化目标
通常情况下目标策略有三种方式(V 是价值函数):
- [1] 最简单的优化目标就是初始状态收获的期望,即优化目标为:
J 1 ( θ ) = V π θ ( s 1 ) = E π θ [ v 1 ] = E ( r 1 + γ r 2 + γ 2 r 3 + . . . . . . . ∣ π θ ) J_1(θ)=V_{π_θ}(s_1)=E_{π_θ}[v_1]=E(r_1+γr_2+γ^2r_3+.......∣π_θ) J1(θ)=Vπθ(s1)=Eπθ[v1]=E(r1+γr2+γ2r3+.......∣πθ) - [2] 但是有的问题是没有明确的初始状态的,那么我们的优化目标可以定义平均价值,即:
J a v V ( θ ) = ∑ s d π θ ( s ) V π θ ( s ) J_{avV}(θ)=\sum_{s}d_{π_θ}(s)V_{π_θ}(s) JavV(θ)=∑sdπθ(s)Vπθ(s)
其中, d π θ ( s ) d_{π_θ}(s) dπθ(s) 是基于策略 π θ π_θ πθ 生成的马尔科夫链关于状态的静态分布。 - [3] 还有就是定义为每一时间步的平均奖励,即:
J a v R ( θ ) = = ∑ s d π θ ( s ) ∑ a π θ ( s , a ) R s a J_{avR}(θ)==\sum_sd_{π_θ}(s)\sum_aπ_θ(s,a)R^a_s JavR(θ)==∑sdπθ(s)∑aπθ(s,a)Rsa
无论我们是采用 J 1 , J a v V J_1,J_{avV} J1,JavV 还是 J a v R J_{avR} JavR 来表示优化目标,最终对 θ θ θ 求导的梯度都可以表示为:
∇ θ J ( θ ) = E π θ [ ∇ θ l o g π θ ( s , a ) Q π ( s , a ) ] ∇_θJ(θ)=E_{π_θ}[∇_θlogπ_θ(s,a)Q_π(s,a)] ∇θJ(θ)=Eπθ[∇θlogπθ(s,a)Qπ(s,a)]
详细推导可以参考原论文:https://homes.cs.washington.edu/~todorov/courses/amath579/reading/PolicyGradient.pdf
5.代码实现
这里使用了 OpenAI Gym 中的 CartPole-v0 游戏来作为我们算法应用。CartPole-v0 游戏的介绍参见这里。它比较简单,基本要求就是控制下面的 cart 移动使连接在上面的 pole 保持垂直不倒。这个任务只有两个离散动作,要么向左用力,要么向右用力。而 state 状态就是这个 cart 的位置和速度, pole 的角度和角速度,4 维的特征。坚持到 200 分的奖励则为过关。
import gym
import tensorflow as tf
import numpy as np
import random
from collections import deque
# Hyper Parameters
GAMMA = 0.95 # discount factor
LEARNING_RATE=0.01
class Policy_Gradient():
def __init__(self, env):
# init some parameters
self.time_step = 0
self.state_dim = env.observation_space.shape[0]
self.action_dim = env.action_space.n
self.ep_obs, self.ep_as, self.ep_rs = [], [], []
self.create_softmax_network()
# Init session
self.session = tf.InteractiveSession()
self.session.run(tf.global_variables_initializer())
def create_softmax_network(self):
# network weights
W1 = self.weight_variable([self.state_dim, 20])
b1 = self.bias_variable([20])
W2 = self.weight_variable([20, self.action_dim])
b2 = self.bias_variable([self.action_dim])
# input layer
self.state_input = tf.placeholder("float", [None, self.state_dim])
self.tf_acts = tf.placeholder(tf.int32, [None, ], name="actions_num")
self.tf_vt = tf.placeholder(tf.float32, [None, ], name="actions_value")
# hidden layers
h_layer = tf.nn.relu(tf.matmul(self.state_input, W1) + b1)
# softmax layer
self.softmax_input = tf.matmul(h_layer, W2) + b2
#softmax output
self.all_act_prob = tf.nn.softmax(self.softmax_input, name='act_prob')
self.neg_log_prob = tf.nn.sparse_softmax_cross_entropy_with_logits(logits=self.softmax_input,
labels=self.tf_acts)
self.loss = tf.reduce_mean(self.neg_log_prob * self.tf_vt) # reward guided loss
self.train_op = tf.train.AdamOptimizer(LEARNING_RATE).minimize(self.loss)
def weight_variable(self, shape):
initial = tf.truncated_normal(shape)
return tf.Variable(initial)
def bias_variable(self, shape):
initial = tf.constant(0.01, shape=shape)
return tf.Variable(initial)
def choose_action(self, observation):
prob_weights = self.session.run(self.all_act_prob, feed_dict={self.state_input: observation[np.newaxis, :]})
action = np.random.choice(range(prob_weights.shape[1]), p=prob_weights.ravel()) # select action w.r.t the actions prob
return action
def store_transition(self, s, a, r):
self.ep_obs.append(s)
self.ep_as.append(a)
self.ep_rs.append(r)
def learn(self):
discounted_ep_rs = np.zeros_like(self.ep_rs)
running_add = 0
for t in reversed(range(0, len(self.ep_rs))):
running_add = running_add * GAMMA + self.ep_rs[t]
discounted_ep_rs[t] = running_add
discounted_ep_rs -= np.mean(discounted_ep_rs)
discounted_ep_rs /= np.std(discounted_ep_rs)
# train on episode
self.session.run(self.train_op, feed_dict={
self.state_input: np.vstack(self.ep_obs),
self.tf_acts: np.array(self.ep_as),
self.tf_vt: discounted_ep_rs,
})
self.ep_obs, self.ep_as, self.ep_rs = [], [], [] # empty episode data
# Hyper Parameters
ENV_NAME = 'CartPole-v0'
EPISODE = 3000 # Episode limitation
STEP = 3000 # Step limitation in an episode
TEST = 10 # The number of experiment test every 100 episode
def main():
# initialize OpenAI Gym env and dqn agent
env = gym.make(ENV_NAME)
agent = Policy_Gradient(env)
for episode in range(EPISODE):
# initialize task
state = env.reset()
# Train
for step in range(STEP):
action = agent.choose_action(state) # e-greedy action for train
next_state,reward,done,_ = env.step(action)
agent.store_transition(state, action, reward)
state = next_state
if done:
#print("stick for ",step, " steps")
agent.learn()
break
# Test every 100 episodes
if episode % 100 == 0:
total_reward = 0
for i in range(TEST):
state = env.reset()
for j in range(STEP):
env.render()
action = agent.choose_action(state) # direct action for test
state,reward,done,_ = env.step(action)
total_reward += reward
if done:
break
ave_reward = total_reward/TEST
print ('episode: ',episode,'Evaluation Average Reward:',ave_reward)
if __name__ == '__main__':
main()
运行结果:
6.参考
1.https://blog.csdn.net/qq_30615903/article/details/80747380
2.https://zhuanlan.zhihu.com/p/42055115
3.https://mofanpy.com