REINFORCE算法是一个基于策略的方法,我们利用一个神经网络来为策略函数建模,输入某个状态,然后输出一个动作的概率分布,目标是寻找一个策略并最大化策略在环境中的期望回报,目标函数可以定义为:
对这个目标函数求梯度,可以得到如下公式:
其中利用蒙特卡洛方法估计,利用这个公式来更新策略
我们采用车杆环境
导入库
import gym
import torch
import torch.nn.functional as F
import numpy as np
import matplotlib.pyplot as plt
from tqdm import tqdm
import rl_utils
定义策略网络PolicyNet,他的输入是某个状态,输出是该状态下的动作概率分布,我们利用softmax()函数来实现一个多项分布,事实上我们是利用价值的归一化来替代了动作的概率分布,简单来说,就是价值越高,那么他的概率就越大
class PolicyNet(torch.nn.Module):
def __init__(self,state_dim,hidden_dim,action_dim):
super(PolicyNet,self).__init__()
self.fc1=torch.nn.Linear(state_dim,hidden_dim)
self.fc2=torch.nn.Linear(hidden_dim,action_dim)
def forward(self,x):
x=F.relu(self.fc1(x))
#由于损失函数是有关价值的函数,所以神经网络仍然是状态-价值的映射,而我们现在是进行策略梯度更新,需要输出动作的概率分布
#所以需要使用softmax,即用价值的归一化来替代动作的概率分布,也就是价值越高的动作其概率也应越大
#softmax函数将向量的元素归一化为概率分布,即计算每个动作的概率,概率之和为1,便于学习
return F.softmax(self.fc2(x),dim=1)
定义REINFORCE算法
class REINFORCE:
def __init__(self,state_dim,hidden_dim,action_dim,learning_rate,gamma,device):
self.policy_net=PolicyNet(state_dim,hidden_dim,action_dim).to(device)
self.optimizer=torch.optim.Adam(self.policy_net.parameters(),lr=learning_rate) #使用Adam优化器
self.gamma=gamma
self.device=device
def take_action(self,state): #根据动作概率分布随机采样
state=torch.tensor([state],dtype=torch.float).to(self.device)
#probs是动作概率分布
probs=self.policy_net(state)
#根据动作概率分布离散化动作空间
action_dist=torch.distributions.Categorical(probs)
#采样
action=action_dist.sample()
return action.item()
def update(self,transition_dict):
reward_list=transition_dict['rewards']
state_list=transition_dict['states']
action_list=transition_dict['actions']
G=0
#显式地将梯度置0
self.optimizer.zero_grad()
for i in reversed(range(len(reward_list))): #从最后一步算起
reward=reward_list[i]
state=torch.tensor([state_list[i]],dtype=torch.float).to(self.device)
action=torch.tensor([action_list[i]]).view(-1,1).to(self.device)
#神经网络输出的是动作的概率分布,这里是对相应动作取对数,也就是log(π(θ))
log_prob=torch.log(self.policy_net(state).gather(1,action))
#利用蒙特卡洛采样法计算每个时刻t往后的回报G,所以前面循环要翻转
G=self.gamma*G+reward
#我们知道一般的梯度下降法是对损失函数求梯度,往最大梯度的负方向进行更新参数的,目的是最小化损失函数
#而我们这里,损失函数是累计奖励的函数,我们希望将其最大化,而不是最小化,所以这里损失函数应该加上负号
loss=-log_prob*G #每一步的损失函数
loss.backward() #反向传播计算梯度
self.optimizer.step() #梯度下降
设置超参数,开始试验
learning_rate=1e-3
num_episodes=1000
hidden_dim=128
gamma=0.98
device=torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
env_name="CartPole-v0"
env=gym.make(env_name)
env.reset(seed=0)
torch.manual_seed(0)
state_dim=env.observation_space.shape[0]
action_dim=env.action_space.n
agent=REINFORCE(state_dim,hidden_dim,action_dim,learning_rate,gamma,device)
return_list = []
for i in range(10):
with tqdm(total=int(num_episodes / 10), desc='Iteration %d' % i) as pbar:
for i_episode in range(int(num_episodes / 10)):
episode_return = 0
transition_dict = {
'states': [],
'actions': [],
'next_states': [],
'rewards': [],
'dones': []
}
state = env.reset()
done = False
while not done:
action = agent.take_action(state)
next_state, reward, done, _ = env.step(action)
transition_dict['states'].append(state)
transition_dict['actions'].append(action)
transition_dict['next_states'].append(next_state)
transition_dict['rewards'].append(reward)
transition_dict['dones'].append(done)
state = next_state
episode_return += reward
return_list.append(episode_return)
agent.update(transition_dict)
if (i_episode + 1) % 10 == 0:
pbar.set_postfix({
'episode':
'%d' % (num_episodes / 10 * i + i_episode + 1),
'return':
'%.3f' % np.mean(return_list[-10:])
})
pbar.update(1)
画图
episodes_list = list(range(len(return_list)))
plt.plot(episodes_list, return_list)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('REINFORCE on {}'.format(env_name))
plt.show()
mv_return = rl_utils.moving_average(return_list, 9)
plt.plot(episodes_list, mv_return)
plt.xlabel('Episodes')
plt.ylabel('Returns')
plt.title('REINFORCE on {}'.format(env_name))
plt.show()
由于采用蒙特卡洛方法进行估计,REINFORCE算法的梯度估计的方差很大,可能会造成一定程度上的不稳定,同时蒙特卡洛采样法需要在序列结束后进行更新,这同时也要求任务具有有限的步数,这是REINFORCE算法的局限性。