策略梯度笔记
策略梯度
由于笔者对Actor-critic模型掌握的一直不是很好,导致最近一篇论文看的不是很明白,所以我决定好好复习一下强化学习里的Actor-critic算法,而复习Actor-critic算法,需要先从策略梯度开始,首先我们先介绍一下什么是策略梯度算法,之后我们再介绍一些实现技巧。
强化学习的组成
在强化学习的过程中,我们有三个因素,即演员,奖励函数,环境。
演员我们可以想象成一个黑箱策略网络,其由参数θ决定,输入是当前游戏的状态state,输出是一个动作。演员是根据策略来执行动作的,而策略有确定性动作和非确定性策略之分,对于非确定性策略,策略网络的输出层的神经元的个数往往与动作的个数有关,此时网络的输出为一个向量,表示采取各个动作的概率。而确定性策略则直接输出演员的动作(往往适用于连续动作空间,因为连续动作空间下动作的个数是无穷的,无法采用非确定性策略),策略梯度为非确定性策略,即会输出一个概率向量,表示执行各动作的概率。
环境也是一个函数,输入是当前状态St和演员根据当前状态执行的动作at,输出是执行该动作后的下一个状态St+1(不是绝对的函数,对于相同的输入St和at,环境可能输出不同的St+1)
奖励函数则接受当前状态St和演员执行的动作at,给出它对演员此次动作的评分rt(此处也有说法是rt+1,不过无关紧要,表示方式而已)。
强化学习的目标
强化学习的目标就是训练演员actor,使其学习到一个策略能最大化演员在一局游戏里获得的奖励期望。
其中τ为轨迹,表示一个演员从初始状态S0开始与环境进行一系列交互,直到该局游戏结束过程中遇到的状态S和演员执行的动作a的序列。
其中Sn表示一局游戏结束的状态。
而R(τ)表示这条轨迹带来的奖励,即r0+r1+……rn(往往需要带上折扣Γ,此处先省略,后续会补)
我们可以计算某条轨迹出现的概率:
其中P为环境决定的,是我们不可控的,我们能做的就是训练Pθ,最终目的就是最大化J(θ)。将J(θ)变化一下
最大化期望的方法
分析期望的式子,我们可以明显看出,这个式子我们能控制的只有参数θ(环境中的状态转移函数和奖励函数都是我们无法控制的),自然能想到,是否可以通过梯度上升的方法,修改θ来增大E(θ)呢?答案是可以的。
我们先对θ求梯度:
此处只有一个参数θ,所以求梯度其实和求导是一样的,我们省略梯度符号后面的θ,整理如下:
回想一下中学我们的复合函数求导,是不是有
所以有
所以移项,将导数符号换成梯度符号,有
那么自然就有
带入(1)式:
进一步,我们可以把log(pθ(τ))再化简一些
两个log相加,前面的与θ无关,梯度为0,故只保留后面即可
最后可得
由于这个期望很难求得真实值,我们往往是采样n个轨迹,然后去平均来近似这个期望:
至此,我们以及有了大致的提高J(θ)的思路了,那就是通过actor与环境交互产生N条轨迹,然后根据上式求出梯度,然后根据下式更新θ。
实现细节:
我们可以将演员的策略网络看成一个分类网络,即输入当前的状态s,输出动作预测概率向量(可以类比为分类任务中神经网络中输出层softmax后的结果)
在分类里面,我们需要最小化的是损失,也就是
其中K表示类的个数,对于一个样本xi,其标签为yt,输出是一个K维向量p,当yi等于yt的适合,yi为1,否则为0
那么也就是我们需要最大化对数似然:
现在我们要最大化奖励的期望:
我们将其转换一下,在前面乘以个权重(奖励),就可以得到新的损失公式:
我们的目的就是在网络中调整参数θ以最大化该公式,实现来说,我们是每执行完一回合(即一个轨迹)就进行梯度更新,而不是采样N个轨迹,思想有些类似于随机梯度下降。
我的代码实现:
import torch
from torch import nn
from torch.nn import functional as F
import gym
import tqdm
import numpy as np
import rl_utils
import matplotlib.pyplot as plt
class Net(torch.nn.Module):
def __init__(self , input_dim , hidden_dim , output_dim):
super().__init__()
self.fc1 = nn.Linear(input_dim , hidden_dim)
self.fc3 = nn.Linear(hidden_dim , output_dim)
def forward(self , x):
x = F.relu(self.fc1(x))
x = F.softmax(self.fc3(x) , dim = 1)
return x
class GradientPolicy:
def __init__(self , state_dim , hidden_dim , action_dim , lr , device):
self.net = Net(state_dim , hidden_dim , action_dim).to(device)
self.optimizer = torch.optim.Adam(self.net.parameters() , lr = lr)
self.device = device
def take_action(self , state):
state = torch.tensor([state] , dtype = torch.float).to(self.device)
"""
if the shape of probs is (batch_num , classes_num) ,
the output of sample is also (batch_num , classes_num)
"""
probs = self.net(state)
action_dist = torch.distributions.Categorical(probs)
action = action_dist.sample()
return action.item()
def learn(self , state_list , action_list , episodic_reward):
self.optimizer.zero_grad()
states = torch.tensor(state_list).to(self.device)
actions = torch.tensor(action_list).to(self.device)
action_prob = self.net(states)
log_prob = torch.sum( torch.log(action_prob) * F.one_hot(actions , num_classes= action_prob.shape[1]))
cost = -1.0 * log_prob * episodic_reward
cost.backward()
self.optimizer.step()
def runAepisode(env , agent):
obs_list , action_list = [] , []
res = 0
obs , _ = env.reset()
while True:
obs_list.append(obs)
action = agent.take_action(obs)
action_list.append(action)
obs , reward , done,_,_ = env.step(action)
res += reward
if done or res > 200:
break
return obs_list , action_list , res
if __name__ == "__main__":
lr = 1e-3
hidden_dim = 128
num_episodes = 1000
if torch.cuda.is_available():
device = torch.device("cuda")
else:
device = torch.device("cpu")
env = gym.make("CartPole-v0")
state_dim = env.observation_space.shape[0]
action_dim = env.action_space.n
agent = GradientPolicy(state_dim ,hidden_dim , action_dim , lr , device)
return_list = []
for i in range(num_episodes):
states , actions , rewards = runAepisode(env , agent)
print(i , rewards)
return_list.append(rewards)
agent.learn(states , actions , rewards)
torch.save(agent.net.state_dict() , 'model.pth')
episodes_list = list(range(len(return_list)))
plt.plot(episodes_list, return_list)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.show()
实验结果:
可见智能体是学习到了知识的,但是效果不是很理想,所以下面我们有一些优化方法:
实现技巧
增加基线
由于有的游戏里面,奖励可能是都为正的,只不过大小不同,以反映不同动作之间的好坏。
那么会有一个问题,我们难道要提高所有动作的概率吗,这样显然是不合理的
考虑上图,由于abc三个动作的奖励值不同,所以提升的幅度不同,在归一化之后,提升幅度最小的b相当于减少了
那么会引出一个新的问题,如果a动作运气好没被采样到,这样归一化后,它的概率也会减少,这肯定是不合理的,因为没选过a不代表a不好,所以我们引出基线的概念
所谓基线,就是给奖励都减去一个bias,使得它们有正有负,通常b取值是已采样出的轨迹r的奖励的平均值。
分配合适的分数
考虑下图,有这样一个问题,如果整个轨迹的奖励是正的,那么代表这个轨迹上的每个动作状态对都是好的吗,答案显然不是,那么我们怎样避免这个问题呢?
我们可以采用如下方法,对于一条轨迹,我们可以认为,对于一个动作状态对来说,只有这条轨迹的且位于该动作状态对之后的动作状态对才受该动作状态对影响。也就是我们在计算一个动作状态对的价值的时候,我们只考虑该动作状态对及其之后的动作状态对。
由于离该动作状态对越远的动作状态对受该动作状态对的影响越小,所以乘以一个折扣因子γ
笔者将增加基线的方法实现了一下,效果如下(为了尽量避免偶然性,跑了两次):
本文作者:卡西西卡
本文链接:https://www.cnblogs.com/wuhaof12/p/17875480.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步