一、理论思想
在此之前,我们讨论了值函数的方法,把优化的重点放在了值函数上,得到了最优值函数,即可得到最优策略。事实上,策略梯度方法的思想则更加简单和直接,即将值函数表示为策略参数的某个函数,便可以求出值函数关于策略参数的梯度,并使参数沿着梯度上升的方向更新。其数学实现和推导过程如下:
强化学习的目标是找到最大化长期回报期望的策略:
其中表示轨迹的回报。
用表示前面提到的目标函数,将轨迹的期望回报展开,可以得到
对公式求导,可以得到
又通过变换,有
故有
又
则
故
因此
以上就是梯度的求解,剩下的即是要进行参数更新,这两步可归结如下:
(1)计算
(2)
基本原理如以上所示,但仍存在一些问题。首先,需要策略梯度乘以所有时刻的回报值总和,而这是与马尔可夫性相违背的,即当前时刻之前的回报值不应该计算在梯度中,则公式改写为:
其次,策略梯度法更像是加权版的最大似然优化法。“权重”将直接影响梯度的更新量,这样就会带来以下两个问题:1)如果计算得出的序列回报数值较大,那么对应的参数更新量就会比较大,这样优化就可能出现一定波动,这些波动很可能影响优化效果;2)在一些问题中,环境给予的回报始终为正, 那么不论决策如何, 最终累积的长期回报值都是一个正数。换句话说,我们会增强所有的策略,只是对于实际效果并不好的策略,我们为其提升的幅度会有所降低。而初衷是提高能最大化长期回报策略的概率,降低无法最大化长期回报策略的概率。
为了实现这个目标,可以调整权重的数值和范围,一个简单的方法就是给所有时刻的长期累积回报减去一个偏移量, 这个偏移量也被称为Baseline ,用变量b表示,于是公式就变为:
其中为同一起始点的不同序列在同一时刻的长期回报均值,这样,所有时刻的权重均值变为0 ,就会存在权重为正或为负的行动。
事实上,引入偏移量并不会使原来的计算有偏,即:
二、AC、A2C、A3C 算法
2.1、Actor-Critic 算法
偏差与方差的平衡
回看一下梯度公式:
用轨迹的回报表示整个序列的价值,这个表示是无偏的,但是在真实的训练过程中,由于交互次数的有限,方差相对较大,为了模型的稳定,可以牺牲一定的偏差来使方差变小,Actor-Critic 算法即是这样的一种方法。
AC 算法的主要特点就是用一个独立的模型估计轨迹的长期回报,而不再直接使用轨迹的真实回报,在估计时使用模型估计轨迹价值,在更新时利用轨迹的回报得到目标价值,然后将模型的估计值和目标值进行比较,从而改进模型。使用TD-Error 估计轨迹的回报,此时梯度公式变为:
由于引入了状态价值模型, 算法整体包含了两个模型,一个是策略模型,一个是价值模型,所以这个算法被称为Actor-Critic , 其中Actor 表示策略模型, Critic 表示价值模型。
2.2、Advantage Actor-Critic(A2C) 算法
A2C算法直接使用优势函数估计轨迹的回报,由此梯度公式变为:
这样的话就需要有两个网络分别计算状态-动作价值Q和状态价值V,此时的Critic变为估计状态价值V的网络。因此Critic网络的损失变为实际的状态价值和估计的状态价值的平方损失。
2.3、Asynchronous Advantage Actor-Critic (A3C)算法
A3C 算法使用了多步回报估计法,即,这个方法可以在训练早期更快地提升价值模型,为了增加模型的探索性,模型的目标函数中加入了策略的熵。由于熵可以衡量概率分布的不确定性,所以我们希望模型的情尽可能大一些,这样模型就可以拥有更好的多样性。这样,完整的策略梯度计算公式就变为
其中为策略的熵在目标中的权重。
A3C 和A2C 更新流程的对比如上图所示。可以看出,在的A3C中,每个线程各自完成参数更新值的计算,并异步完成参数的同步操作;而A2C 将决策和训练的任务集中到一处,其他进程只负责环境模拟的工作,A2C 在并发性和系统简洁性上都优于A3C。
三、案例实践
CartPole 车杆游戏:游戏里面有一个小车,上有竖着一根杆子。小车需要左右移动来保持杆子竖直。如果杆子倾斜的角度大于15°,那么游戏结束。小车也不能移动出一个范围(中间到两边各2.4个单位长度)。动作:左移,右移。状态:车的位置,杆子的角度,车速,角速度。奖励:左移或者右移小车的action之后,env都会返回一个+1的reward。到达200个reward之后,游戏也会结束。
以下使用A2C方法实现CartPole游戏过程。
- 包的调用及参数设置,采用pytorch框架。
import numpy as np
import gym
import os
import math
import random
import pandas as pd
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.autograd import Variable
import torch.optim as optim
MAX_EPISODE = 500
REWARD_THRESHOLD = 200
MAX_EP_STEPS = 5000
SAMPLE_NUMS = 30
RENDER = False
GAMMA = 0.9 # reward discount
LR_A = 0.001 # learning rate for actor
LR_C = 0.001 # learning rate for critic
env = gym.make("CartPole-v0")
init_state = env.reset()
N_F = env.observation_space.shape[0]
N_A = env.action_space.n
- Actor与Critic网络的搭建,均采用两层全连接神经网络
class Actor_net(nn.Module):
def __init__(self, n_features,n_actions):
super(Actor_net,self).__init__()
self.l1 = nn.Linear(n_features,40)
self.l2 = nn.Linear(40,n_actions)
def forward(self,x):
out1 = F.relu(self.l1(x))
out2 = F.log_softmax(self.l2(out1),dim=1)
return out2
class Critic_net(nn.Module):
def __init__(self, n_input,n_output):
super(Critic_net,self).__init__()
self.l1 = nn.Linear(n_input,40)
self.l2 = nn.Linear(40,n_output)
def forward(self,x):
out1 = F.relu(self.l1(x))
out2 = F.relu(self.l2(out1))
return out2
- 辅助函数。包括网络的搭建,观测值的采样,环境的重置等,另外是损失因子的设定,比较简明,不予详述。
def init_env(env,actor_net,critic_net,sample_nums,init_state):
states = []
actions = []
rewards = []
is_done = False
final_r = 0
state = init_state
for i in range(sample_nums):
states.append(state)
log_softmax_action = actor_net(Variable(torch.Tensor([state])))
softmax_action = torch.exp(log_softmax_action)
action = np.random.choice(N_A,p=softmax_action.cpu().data.numpy()[0])
one_hot_action = [int(k==action) for k in range(N_A)]
next_state,reward,done,_ = env.step(action)
actions.append(one_hot_action)
rewards.append(reward)
final_state = next_state
state = next_state
if done:
is_done = True
state = env.reset()
break
if not is_done:
final_r = critic_net(Variable(torch.Tensor([final_state]))).cpu().data.numpy()
return states,actions,rewards,final_r,state
def discount_reward(r,gamma,final_r):
discount_r = np.zeros_like(r)
running_add = final_r
for t in reversed(range(0,len(r))):
running_add = running_add*gamma +r[t]
discount_r[t] = running_add
return discount_r
-
主函数。其各部分功能与流程如下图所示。
actor = Actor_net(n_features=N_F,n_actions=N_A)
actor_optim = optim.Adam(actor.parameters(),lr=LR_A)
critic = Critic_net(n_input=N_F,n_output=1)
critic_optim = optim.Adam(critic.parameters(),lr=LR_C)
steps = []
test_results = []
for step in range(MAX_EP_STEPS):
states,actions,rewards,final_r,current_state = init_env(env,actor,critic,SAMPLE_NUMS,init_state)
init_state = current_state
actions_var = Variable(torch.Tensor(actions).view(-1,N_A))
states_var = Variable(torch.Tensor(states).view(-1,N_F))
#训练策略网络
actor_optim.zero_grad()
log_softmax_actions = actor(states_var)
vs = critic(states_var).detach()
qs = Variable(torch.Tensor(discount_reward(rewards,GAMMA,final_r)))
advantages = qs - vs
actor_loss = -torch.mean(torch.sum(log_softmax_actions*actions_var,1)*advantages)
actor_loss.backward()
nn.utils.clip_grad_norm_(actor.parameters(),0.5)
actor_optim.step()
#训练值网络
critic_optim.zero_grad()
target_values = qs
values = critic(states_var)
critic_loss = nn.MSELoss()(values,target_values)
critic_loss.backward()
nn.utils.clip_grad_norm_(actor.parameters(),0.5)
critic_optim.step()
if (step+1) % 10 == 0:
result = 0
state = env.reset()
for test_epi in range(10):
state=env.reset()
for test_step in range(200):
softmax_action = torch.exp(actor(Variable(torch.Tensor([state]))))
action = np.argmax(softmax_action.data.numpy()[0])
next_state,reward,done,_ = env.step(action)
result += reward
state = next_state
if done:
break
print("step:",step+1,"test result:",result/10.0)
steps.append(step+1)
test_results.append(result/10)
plt.figure(1)
plt.plot(steps,test_results)
plt.grid(True)
plt.xlabel('episodes')
plt.ylabel('reward')
plt.show()
下图为实验中的奖励值变化情况,可见在经过1000余次的迭代之后即达到一个较稳定的收敛值。
[1] Maxim Lapan. Deep Reinforcement Learning Hands-on. Packt Publishing 2018.
[2] 冯超著 强化学习精要:核心算法与TensorFlow实现. ----北京:电子工业出版社 2018.
[3] https://www.jianshu.com/p/428b640046aa
一点浩然气,千里快哉风。 ----苏轼《水调歌头·黄州快哉亭赠张偓佺》