《深度强化学习实践》Actor-Critic
算法
一、baseline
原理
策略梯度算法
目标:通过训练,增加好动作的概率,减小不好动作被采集到的概率。
实现: ▽ J ≈ E [ Q ( s , a ) ▽ log π ( a ∣ s ) ] {\triangledown }J\approx E[Q(s,a){\triangledown }\log \pi (a|s)] ▽J≈E[Q(s,a)▽logπ(a∣s)]
缺点:不稳定,收敛速度慢的缺点(因此actor-critic算法致力于解决这两个问题。)
对于稳定这个问题,从数学上,可以通过减小梯度的方差来实现。
在Reinforce方法中,使用贴现的总奖励作为策略梯度中关于net梯度前的系数。
下面主要指更新曲折。
对于一个有3种可以选择的动作的状态而言。
在情况一中,3个动作的Q值有两个正一个负,则一定会在前两个正的动作中进行选择;
在情况二中,3个动作的Q值三个都是正的,但是第三个动作的Q值特别小,但是在这种情况下,这个动作不可避免会被选到。
为了尽可能降低这种状况的影响,我们可以采取采样多个样本来应对这种情况;同时我们可以采用一种Q-Learning中提到的baseline的方法。
通过采用baseline的方法
1)、既可以产生有区分的动作,
2)、同时也可以减小训练过程中多次实验带来的方差
从而达到稳定的目的。
但是要注意,这里所说的baseline,是与状态无关的。
代码
代码如下
1、获取参数
GAMMA = 0.99
LEARNING_RATE = 0.001
ENTROPY_BETA = 0.01
BATCH_SIZE = 8
REWARD_STEPS = 10
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--baseline", default=False, action='store_true', help="Enable mean baseline")
args = parser.parse_args()
2、创建环境
env = gym.make("CartPole-v0")
3、记录器
writer = SummaryWriter(comment="-cartpole-pg" + "-baseline=%s" % args.baseline)
4、定义网络
net = PGN(env.observation_space.shape[0], env.action_space.n)
print(net)
其中net为
class PGN(nn.Module):
def init(self, input_size, n_actions):
super(PGN, self).init()
self.net = nn.Sequential(
nn.Linear(input_size, 128),
nn.ReLU(),
nn.Linear(128, n_actions)
)
def forward(self, x):
return self.net(x)
5、定义行动者
agent = ptan.agent.PolicyAgent(net, preprocessor=ptan.agent.float32_preprocessor,
apply_softmax=True)
这里采用ptan中的打包好的策略智能体。
ptan.agent.float32_preprocessor表示数据格式时float32的tensor,使用softmax,默认根据概率分布选择动作。
关于softmax中维度的选择,参见链接,默认axis=1,就是得到行中最大值的索引。
6、定义“经验收集装置”
exp_source = ptan.experience.ExperienceSourceFirstLast(env, agent, gamma=GAMMA, steps_count=REWARD_STEPS)
定义好之后,可以通过之后的for循环不断获取一个episode的值,其中每个episode中步数为REWARD_STEPS。
经验收集有两个函数可以实现:
ExperienceSource、ExperienceSourceFirstLast
第一个函数返回每一步的s,a,r;
第二个函数返回头状态,头动作,尾状态以及总奖励(贴现)。这里我们选择的就是第二种。
还需要注意的是:使用ExperienceSource的每个episode中我们设置了一个固定的步数。所以在一个episode中,可能并没有跑完整个任务,因此当执行下一个episode时,起始状态就是上一个episode的最后状态???
7、定义优化器
optimizer = optim.Adam(net.parameters(), lr=LEARNING_RATE)
8、定义各种变量
total_rewards = [] #记录一幕的总奖励,这里指的是一幕中共进行的episode(这里指定10步为一个episode)数.
step_idx = 0 #记录当前是第几个episode
done_episodes = 0 #记录完整的一幕的个数
reward_sum = 0.0 #以每次的episode为一个单位,累加
batch_states, batch_actions, batch_scales = [], [], []
9、数据收集
##############每走10步收集一次数据#####################
for step_idx, exp in enumerate(exp_source):
##一直累加总奖励
reward_sum += exp.reward
##这里的baseline是平均奖励,与状态无关。
baseline = reward_sum / (step_idx + 1)
writer.add_scalar("baseline", baseline, step_idx)
##将这10步中的状态、动作作为batch中的一条append进去
batch_states.append(exp.state)
batch_actions.append(int(exp.action))
##!!!!!当baseline不是0的时候,就将此10步中每一步都减去baseline的差值作为系数
if args.baseline:
batch_scales.append(exp.reward - baseline)
else:
batch_scales.append(exp.reward)
##############当一幕结束时,会返回一幕中总共运行的episode数,以及最后100个episode数
# handle new rewards
new_rewards = exp_source.pop_total_rewards()
if new_rewards:
done_episodes += 1
reward = new_rewards[0]
total_rewards.append(reward)
mean_rewards = float(np.mean(total_rewards[-100:]))
print("%d: reward: %6.2f, mean_100: %6.2f, episodes: %d" % (
step_idx, reward, mean_rewards, done_episodes))
writer.add_scalar("reward", reward, step_idx)
writer.add_scalar("reward_100", mean_rewards, step_idx)
writer.add_scalar("episodes", done_episodes, step_idx)
if mean_rewards > 195:
print("Solved in %d steps and %d episodes!" % (step_idx, done_episodes))
break
###############当记录的数据不满BATCH_SIZE也就是8的时候,先不计算损失,等前几个状态过去以后,之后每加一个数据都会计算损失以及更新参数。
if len(batch_states) < BATCH_SIZE:
continue
states_v = torch.FloatTensor(batch_states)
batch_actions_t = torch.LongTensor(batch_actions)
batch_scale_v = torch.FloatTensor(batch_scales)
这里有几件事我们需要清楚:
1、CartPole-v0游戏奖励设定:当杆的角度大于15度的时候游戏就会终止,为了获得更多的奖励,需要动态调节小车的前进方向,以使agent可以较长时间获得奖励;
2、ExperienceSourceFirstLast虽然得到的只有初始和结束时的状态以及初始动作值,看源码实现以及print可以发现,其每一条记录(即10步)的初始状态,都是上一条记录中的第二条记录。也就是说,在电脑中游戏是一步一步运行,但是记录的确实隔一步记录此后的10步。
3、 由实验以及,以上分析可以得到exp_source.pop_total_rewards()得到的,其实是总共运行的步数。
可以通过在experiencesourcefirstlast的__iter__中print(exp)得到。
4、而exp.reward()是每一次获得的带贴现10步的奖励,这个reward才是真正训练用得到的奖励,total_rewards仅仅是为了展示总奖励以及方差等特征。
5、由于batch_value/batch_action中每次append的都是experiencesourcefirstlast中得到的初始状态以及初始动作,也就是说都是一个;又因为在后面if len(batch_states) < BATCH_SIZE: continue以及batch_states.clear() batch_actions.clear() batch_scales.clear()使得每次数据到达batch以后就会执行训练以及清空。
10、计算损失、优化
states_v = torch.FloatTensor(batch_states)
batch_actions_t = torch.LongTensor(batch_actions)
batch_scale_v = torch.FloatTensor(batch_scales)
optimizer.zero_grad()
logits_v = net(states_v)
log_prob_v = F.log_softmax(logits_v, dim=1)
log_prob_actions_v = batch_scale_v * log_prob_v[range(BATCH_SIZE), batch_actions_t]
loss_policy_v = -log_prob_actions_v.mean()
loss_policy_v.backward(retain_graph=True)
grads = np.concatenate([p.grad.data.numpy().flatten()
for p in net.parameters()
if p.grad is not None])
prob_v = F.softmax(logits_v, dim=1)
entropy_v = -(prob_v * log_prob_v).sum(dim=1).mean()
entropy_loss_v = -ENTROPY_BETA * entropy_v
entropy_loss_v.backward()
optimizer.step()
loss_v = loss_policy_v + entropy_loss_v
首先是转换为tensor,优化器梯度清空。
然后是计算两个损失的梯度,分别是策略梯度损失和熵损失。
(1)、策略梯度损失:
首先通过网络得到二维数据,分别是样本数*动作数,即8*2;
然后对于每一个样本,也就是对于动作取logsoftmax,得到对于每个动作的log概率分布;
其次将(每次的10步贴现奖励-baseline)作为系数,乘以对应动作的log概率得到8条数据;
最后做了期望处理,以及最大化奖励变最小化损失后,加负号。
注意!这里其实求的是损失,但是由于我们计算的梯度确实与前面的R、b无关,所以可以将公式写作:
▽ J 1 ≈ − E [ ( R − b ) ▽ log π ( a ∣ s ) ] {\triangledown }J1\approx -E[(R-b){\triangledown }\log \pi (a|s)] ▽J1≈−E[(R−b)▽logπ(a∣s)]
这里的关于python的技巧有:
1、梯度回传以后,防止变量的图结构丢失,通俗来讲就是先把梯度保存,等下次回传完熵损失以后一起进行优化
2、flatten()可以使numpy对象返回一维数组;np.concatenate实现对一维数组的拼接。
(2)、熵损失
其最终实现的公式为
▽ J 2 ≈ E [ β ▽ ( π ( s ) ∗ l o g π ( s ) ) ] {\triangledown }J2\approx E[{
{\beta }\triangledown }(\pi (s)*log \pi (s))] ▽J

本文详细解析了Actor-Critic算法,包括Baseline原理及其代码实现,策略梯度和稳定性改进,以及连续动作环境下的Actor-Critic网络结构、参数调整和优化过程。涉及数据收集、经验回放缓冲区、损失计算、KL散度评估等内容。
最低0.47元/天 解锁文章
1185

被折叠的 条评论
为什么被折叠?



