写在前面:交叉熵属于无模型和基于策略的在线策略方法
所有RL方法的分类方法:
1.无模型或基于模型:无模型表示该方法不构建环境或奖励的模型,直接将观察和动作连接起来(智能体获取当前观察结果并对其进行一些计算,计算结果就是他应该采取的动作)。基于模型的方法试图预测下一个观察或者奖励会是什么,根据预测试图选取最好的动作执行。
优劣:无模型方法更容易训练,确定性环境中通常都会使用基于模型的方法
2.基于价值或基于策略:基于价值的方法智能体将计算每个可能的动作的价值,然后选择价值最大的动作。基于策略的方法中直接计算智能体的策略,策略通常被表示成可用的动作的概率分布。
3.在线策略和离线策略:离线策略是用来学习历史数据的。
交叉熵方法的描述:(神经网络生成策略)
1)使用当前模型和环境产生N次片段(每个片段由一系列的观察o,动作a,和奖励r组成)
2)计算每个片段的总奖励R,并确定奖励边界
3)奖励在边界之下的片段丢掉
4)训练剩余的片段(好的片段)---->o作为输入,智能体产生的r作为目标输出
5)回去重复1)直到满意
代码说明:
如下图我做了输出,由于NN最后没有用sofymax所以可见NN输出的是原始的动作分数 而用了softmax就使两动作形成了0-1间的概率并加和为1
<1> act_probs = act_probs_v.data.numpy()[0]
因为NN和softmax层都是返回了梯度的张量,所以需要访问tensor.data字段将将数据取出来,将张量转换为Numpy数组,这个数组和输入一样,二维,所以我们需要获取第一个元素得到动作概率的一维向量
<2> yield batch#将片段数量返回给调用者处理
我们的函数是一个生成器,所以每次执行yield时,控制权就转移到了迭代函数外面,下次会从yield的下一行继续执行!!
要注意:NN的训练和片段的生成是同时进行的,他们不是完全并行的,因为每次积累了足够的片段(16)后就会yield将控制权转移到调用方,训练NN。所以每次yield返回时,NN都会有点进步。(所以才不需要同步数据)
片段中的R是指智能体真正去交互得到的r
#!/usr/bin/env python3
import gym
from collections import namedtuple
import numpy as np
from tensorboardX import SummaryWriter
import torch
import torch.nn as nn
import torch.optim as optim
HIDDEN_SIZE = 128 #隐藏层中神经元的的数量
BATCH_SIZE = 16 #每次迭代中训练的片段数
PERCENTILE = 70 #过滤精英片段的奖励边界的百分位(这里意味着会留下排序后的前30%)
class Net(nn.Module):
def __init__(self, obs_size, hidden_size, n_actions):
super(Net, self).__init__()
self.net = nn.Sequential(
nn.Linear(obs_size, hidden_size),
nn.ReLU(),
nn.Linear(hidden_size, n_actions)
)
def forward(self, x):
return self.net(x)
Episode = namedtuple('Episode', field_names=['reward', 'steps']) #单个片段,保存了总的R以及EpisodeStep集合
EpisodeStep = namedtuple('EpisodeStep', field_names=['observation', 'action']) #表示只能提在片段中执行的一步,保存来自环境的观察以及智能体采取了什么动作
def iterate_batches(env, net, batch_size):#接收环境,NN,输入的片段数
batch = []#列表,存Episode对象
episode_reward = 0.0
episode_steps = []#步骤列表,存EpisodeStep对象
obs = env.reset()
sm = nn.Softmax(dim=1)#用来将NN的输出转换成动作的概率分布
while True:
obs_v = torch.FloatTensor([obs])#将当前的观察转换成Pytorch张量
act_probs_v = sm(net(obs_v))#传入NN获得动作的概率分布
#print(net(obs_v))
#print("hello")
#print(sm(net(obs_v)))
act_probs = act_probs_v.data.numpy()[0] #讲解<1>
action = np.random.choice(len(act_probs), p=act_probs) #对分布采样获取动作
next_obs, reward, is_done, _ = env.step(action) #采取动作
episode_reward += reward #奖励加入当前片段的总奖励
step = EpisodeStep(observation=obs, action=action)#保存o,a对注意是哪个o哈
episode_steps.append(step)
if is_done:#处理片段结束的情况
e = Episode(reward=episode_reward, steps=episode_steps)
batch.append(e)
episode_reward = 0.0
episode_steps = []
next_obs = env.reset()
if len(batch) == batch_size:
yield batch#将片段数量返回给调用者处理 讲解<2>
batch = []
obs = next_obs
def filter_batch(batch, percentile):
rewards = list(map(lambda s: s.reward, batch))
reward_bound = np.percentile(rewards, percentile)#根据给定的一批片段和百分位值计算奖励边界
reward_mean = float(np.mean(rewards))#计算平均奖励用于监控,只用于放入Tensorboard中
train_obs = []
train_act = []
for reward, steps in batch:
if reward < reward_bound:#筛选精英片段
continue
train_obs.extend(map(lambda step: step.observation, steps))
train_act.extend(map(lambda step: step.action, steps))
train_obs_v = torch.FloatTensor(train_obs)
train_act_v = torch.LongTensor(train_act)
return train_obs_v, train_act_v, reward_bound, reward_mean
if __name__ == "__main__":
env = gym.make("CartPole-v0")
# env = gym.wrappers.Monitor(env, directory="mon", force=True)#将智能体的性能以视频方式展现
obs_size = env.observation_space.shape[0]
n_actions = env.action_space.n
net = Net(obs_size, HIDDEN_SIZE, n_actions)
objective = nn.CrossEntropyLoss()#目标函数
optimizer = optim.Adam(params=net.parameters(), lr=0.01)#优化器
writer = SummaryWriter(comment="-cartpole")#tensorboard用于监控
for iter_no, batch in enumerate(iterate_batches(
env, net, BATCH_SIZE)):
obs_v, acts_v, reward_b, reward_m = \
filter_batch(batch, PERCENTILE)#过滤精英片段
optimizer.zero_grad()#梯度置零
action_scores_v = net(obs_v)#获得该状态下动作的分数!!
loss_v = objective(action_scores_v, acts_v)#计算NN输出和智能体真正执行的动作之间的交叉熵!!!目的是用已经获得比较好的分数的动作来强化NN!!要理解!!
loss_v.backward()
optimizer.step()
print("%d: loss=%.3f, reward_mean=%.1f, rw_bound=%.1f" % (
iter_no, loss_v.item(), reward_m, reward_b))
writer.add_scalar("loss", loss_v.item(), iter_no)#监控
writer.add_scalar("reward_bound", reward_b, iter_no)
writer.add_scalar("reward_mean", reward_m, iter_no)
if reward_m > 199:#这个199的来源是GYM中CartPole游戏当最近100个片段的平均奖励已经>195时可以认为训练的不错了
print("Solved!")
break
writer.close()
要想记录不同训练阶段的视频:
就删除代码中注释那一行代码,
执行即可
持续更新。。。。