PyTorch中的策略梯度强化学习
介绍
CartPole
在任何时候,推车和杆处于状态S,由四个元素的矢量表示:车位置、车速度、极角和在杆尖测量的极点速度。推车可以采取两种动作中的一种:向左移动或向右移动,以尽可能长地平衡杆。
PyTorch
我们将使用编程语言PyTorch来创建我们的模型。我在其他深度学习框架(如Tensorflow)中发现了CartPole问题的几种解决方案,但在PyTorch中并不多。我们的模型将根据官方PyTorch Github上的例子。该代码提供了一个很好的解决方案,但不包含任何解释。我将尝试在这篇文章中解释策略渐变和PyTorch的实现。
强化学习
强化学习将一个称为Agent的程序放置在模拟环境中,其中Agent的目标是采取一些行动来最大化其奖励。在我们的CartPole示例中,Agent在杆子保持平衡的每一步都会收到1的奖励。当杆子翻倒时,结束。
策略梯度
策略梯度尝试训练代理而不显式地映射环境中的每个状态动作对的值,通过采取小步骤和基于与该步骤相关联的奖励来更新策略。代理可以立即收到奖励,或者代理可以在稍后的时间(如情节结束时)收到奖励。
我们将指定我们的代理试图学习的策略函数,用下面的方程π表示,其中θ是参数矢量,s是特定状态,a是动作。
我们将应用一种叫做Monte Carlo策略梯度的技术,这意味着我们将使代理贯穿整个事件,然后基于获得的奖励更新我们的策略
模型建设
创建神经网络模型
我们将使用一个简单的前馈神经网络,其中一个隐藏层为128个神经元,退出为0.6。我们将使用Adam作为我们的优化器,学习率为0.01。使用退出将显着改善我们策略的表现。我鼓励你比较有无丢失的结果并试验其他超参数值。
#Hyperparameters
learning_rate = 0.01
gamma = 0.99
class Policy(nn.Module):
def __init__(self):
super(Policy, self).__init__()
self.state_space = env.observation_space.shape[0]
self.action_space = env.action_space.n
self.l1 = nn.Linear(self.state_space, 128, bias=False)
self.l2 = nn.Linear(128, self.action_space, bias=False)
self.gamma = gamma
# Episode policy and reward history
self.policy_history = Variable(torch.Tensor())
self.reward_episode = []
# Overall reward and loss history
self.reward_history = []
self.loss_history = []
def forward(self, x):
model = torch.nn.Sequential(
self.l1,
nn.Dropout(p=0.6),
nn.ReLU(),
self.l2,
nn.Softmax(dim=-1)
)
return model(x)
policy = Policy()
optimizer = optim.Adam(policy.parameters(), lr=learning_rate)
选择操作
select_action函数使用PyTorch分发包根据我们的策略概率分布选择操作。我们的策略返回了我们行动空间中每个可能行为(向左或向右移动)的概率,如[0.7,0.3]这样的长度为2的数组。然后,我们根据这些概率选择行动,记录我们的历史记录,并返回我们的行动。
def select_action(state):
#Select an action (0 or 1) by running policy model and choosing based on the probabilities in state
state = torch.from_numpy(state).type(torch.FloatTensor)
state = policy(Variable(state))
c = Categorical(state)
action = c.sample()
# Add log probability of our chosen action to our history
if policy.policy_history.dim() != 0:
policy.policy_history = torch.cat([policy.policy_history, c.log_prob(action)])
else:
policy.policy_history = (c.log_prob(action))
return action
Reward
我们通过采取行动价值函数的示例来更新我们的策略
动作价值函数被定义为通过在策略π之后的状态s中采取行动而获得的预期回报。
我们知道,对于每一步的仿真,我们都会得到1的回报。我们可以用它来计算每个时间步的策略梯度,其中r是特定状态 - 行动对的回报。而不是使用瞬时奖励r,而是使用长期奖励v t,其中v t是集合中所有未来奖励的折扣总和。通过这种方式,时间进入未来的时间越长,当前特定状态动作对的回报就越高。
其中γ是贴现因子(0.99)。例如,如果事件持续5个步骤,则每个步骤的奖励将是[4.90,3.94,2.97,1.99,1]。
接下来,我们通过减去每个元素的平均值并通过除以标准偏差来缩放到单位差异来缩放我们的奖励向量。这种做法对于机器学习应用程序以及与Scikit Learn's StandardScaler相同的操作很常见。它也有补偿未来不确定性的作用。
更新策略
在每次之后,我们应用蒙特卡洛政策梯度来改善我们的政策,根据等式:
然后,我们会将我们的策略历史记录乘以我们的奖励,并将其用于我们的优化程序,并更新我们的神经网络的权重使用随机渐变上升。这应该会增加让我们的代理获得更大回报的行动的可能性。
def update_policy():
R = 0
rewards = []
# Discount future rewards back to the present using gamma
for r in policy.reward_episode[::-1]:
R = r + policy.gamma * R
rewards.insert(0,R)
# Scale rewards
rewards = torch.FloatTensor(rewards)
rewards = (rewards - rewards.mean()) / (rewards.std() + np.finfo(np.float32).eps)
# Calculate loss
loss = (torch.sum(torch.mul(policy.policy_history, Variable(rewards)).mul(-1), -1))
# Update network weights
optimizer.zero_grad()
loss.backward()
optimizer.step()
#Save and intialize episode history counters
policy.loss_history.append(loss.data[0])
policy.reward_history.append(np.sum(policy.reward_episode))
policy.policy_history = Variable(torch.Tensor())
policy.reward_episode= []
训练
这是我们的主要策略培训循环。对于训练集中的每一步,我们选择一个动作,在环境中采取一步,并记录产生的新状态和奖励。我们在每集结尾处调用update_policy()将情节历史记录到我们的神经网络并改进我们的策略。
def main(episodes):
running_reward = 10
for episode in range(episodes):
state = env.reset() # Reset environment and record the starting state
done = False
for time in range(1000):
action = select_action(state)
# Step through environment using chosen action
state, reward, done, _ = env.step(action.data[0])
# Save reward
policy.reward_episode.append(reward)
if done:
break
# Used to determine when the environment is solved.
running_reward = (running_reward * 0.99) + (time * 0.01)
update_policy()
if episode % 50 == 0:
print('Episode {} Last length: {:5d} Average length: {:.2f}'.format(episode, time, running_reward))
if running_reward > env.spec.reward_threshold:
print("Solved! Running reward is now {} and the last episode runs to {} time steps!".format(running_reward, time))
break
运行模型
episodes = 1000
main(episodes)
Episode 0Last length: 8Average length: 9.98
Episode 50Last length: 80Average length: 18.82
Episode 100Last length: 215Average length: 47.54
Episode 150Last length: 433Average length: 145.24
Episode 200Last length: 499Average length: 233.92
Episode 250Last length: 499Average length: 332.90
Episode 300Last length: 499Average length: 383.54
Episode 350Last length: 499Average length: 412.94
Episode 400Last length: 499Average length: 446.52
Episode 450Last length: 227Average length: 462.03
Episode 500Last length: 499Average length: 453.68
Episode 550Last length: 499Average length: 468.94
Solved! Running reward is now 475.15748930299014 and the last episode runs to 499 time steps!
我们的Agent在第200轮附近开始达到400步以上的长度,并在第600轮之前解决环境问题!
结果
你可以看到下面的单个片段长度和平滑的移动平均线。尝试更改策略神经网络结构和超参数,以查看是否可以获得更好的结果。