在上文中我们介绍了Acort-Critic的一种实现方式,本文主要介绍AC网络的一些优化算法。
再次回顾和介绍一些基础概念和算法。先看下时序差分算法和优势函数的概念。
TD和优势函数
马尔科夫性质以及贝尔曼等式决定了,值函数可以定义为递归形式:
状态值函数:
动作值函数:
从公式可以看出,当前时刻的状态值函数等于下一个时刻的即时回报和下一个时刻的状态值的和的期望。
假设我们用网络参数化函数V_{\theta}(s) 来估计策略\pi对应的值函数,认为V_{\theta}(s) 是V^{\pi}(s)的近似函数,就可以用时序差分误差作为V_{\theta}(s) 的学习目标(采取动作后的即可回报+采取动作后的状态值函数-当前时刻的状态值函数),也就是td_error。
通过优化td_error的平方值进行参数的优化。以上就是td_error算法。
前文我们也介绍过,时序差分近似函数的特点是低方差但是高偏差(因为不需要累计一次探索过程)。而如果使用MC(蒙特卡罗算法),在一次探索结束后将回报值的累加作为估计量的特点是高方差无偏差。我们希望对TD算法的优化方向是降低偏差,所以结合MC方法,可以不止考虑一步,而是考虑多步来进行近似计算。等一次探索终止,然后对N个估计量进行加权平均,这种方案叫做\lambda-return ,它虽然降低了偏差,但是学习时间上会耗时。
优势函数在前文中也介绍过,它的计算方法是给出动作a的值函数与所有可能动作的值函数均值的差值,也就是在状态s下选取动作a的优势。如果该值大于0,说明动作a优于均值,是好的选择,反之则是差的选择。而s下给出动作a的值函数就是动作值函数Q值,s下所有可能动作的值函数的均值就是状态值V值。
定义优势函数:
结合优势函数和td_error的公式:
所以,可以用td_error作为优势函数的估计值,所以同样此函数存在偏差较大的缺点。
GAE(泛化优势函数)是在以上优势函数的基础上结合了\lambda-return 算法。
我们直接看论文中的公式:
使用K步优势函数:
其中K越大,偏差就越小,但是方差越大。
GAE定义为\lambda 指数下降权重调整的A_k求和:
其中\lambda=0时,相当于td_error,偏差大;
\lambda=1时,相当于K步的td_error求和,方差大。
所以\lambda的作用就是用来调节偏差和方差~
实例代码
上文我们介绍了Actor-Critic,其中梯度更新使用td_error的方式。今天介绍下使用优势函数更新梯度的方案:
探索过程:
# 最大探索次数
for i_episode in range(0, 10000):
state = env.reset()
# 每次探索最大steps
for t in range(10000):
action = policy(state)
state, reward, done, _ = env.step(action)
# 保存每个step的reward
policy.rewards.append(reward)
if done:
break
# 每次探索更新参数
optimizer.zero_grad()
loss = policy.calculateLoss(gamma)
loss.backward()
optimizer.step()
policy.clearMemory()
AC网络结构和loss:
class ActorCritic(nn.Module):
def __init__(self):
super(ActorCritic, self).__init__()
self.affine = nn.Linear(4, 128)
# 预测动作
self.action_layer = nn.Linear(128, 2)
# 预测V值
self.value_layer = nn.Linear(128, 1)
self.logprobs = []
self.state_values = []
self.rewards = []
def forward(self, state):
state = torch.from_numpy(state).float()
state = F.relu(self.affine(state))
state_value = self.value_layer(state)
action_probs = F.softmax(self.action_layer(state))
action_distribution = Categorical(action_probs)
action = action_distribution.sample()
self.logprobs.append(action_distribution.log_prob(action))
self.state_values.append(state_value)
return action.item()
def calculateLoss(self, gamma=0.99):
rewards = []
dis_reward = 0
# 逆序,统计每个时刻的累计折扣reward
for reward in self.rewards[::-1]:
dis_reward = reward + gamma * dis_reward
rewards.insert(0, dis_reward)
# normalizing the rewards:
rewards = torch.tensor(rewards)
rewards = (rewards - rewards.mean()) / (rewards.std())
loss = 0
# 探索序列的action概率,状态值函数, 累计rewards
for logprob, value, reward in zip(self.logprobs, self.state_values, rewards):
# 优势函数:动作状态值函数Q - 状态值函数V(采用动作对比值函数期望的优势)
advantage = reward - value.item()
# actor的目标:优势大的动作学习幅度大
action_loss = -logprob * advantage
# critic的目标:预测value逼近真实reward
value_loss = F.smooth_l1_loss(value, reward)
loss += (action_loss + value_loss)
return loss
def clearMemory(self):
del self.logprobs[:]
del self.state_values[:]
del self.rewards[:]
参考:
https://arxiv.org/pdf/1506.02438.pdf