MountainCar-v0环境
GYM是强化学习中经典的环境库,现在就有DQN来解决里面经典控制场景MountainCar-v0问题。
概述
汽车位于一维轨道上,位于两个“山”之间。目标是驶向右侧的山峰;但是,汽车的发动机强度不足以单程通过山峰。因此,成功的唯一途径是来回驱动以建立动力。我们的任务就是让这个无动力的小车尽可能地用最少的步数去到达右侧的终点。episode 结束条件是小车到达右边的小黄旗或者超过200次迭代。
Observation
Nun | Observation | Min | Max |
---|---|---|---|
0 | position | -1.2 | 0.6 |
1 | velocity | -0.07 | 0.07 |
小车的Obs数据有两个维度,一是位置,二是速度。小车的初始位置是-0.6到-0.4;初始速度是0。
Action
Nun | Action |
---|---|
0 | push left |
1 | no push |
2 | push right |
Reward
每一步的reward都是-1,而终点的reward是0.5。
DQN
在迷宫这样的简单环境下,环境中的状态是可数的,所以Q-learning和Sarsa这种基于有限Q表格的解决方法是非常高效的。但当我们遇到一些非常复杂的环境,如机器人控制时,我们的Agent是会遇到无限多种状态的,这种情况是Q表格方法所不能解决的,这时候基于神经网络方法的DQN就有用武之地了。
原理
为了解决表格型方法在存储和查找效率上都受局限的问题,DQN用神经网络来近似代替Q表格,简单的说就是构造一个值函数,无论是哪个状态,都可以快速提高值函数运算得到下一步的动作。这样等于把Q表格扩展到了无限大,但存储空间却没有跟着增大而且查找效率也提高了。可以这样认为,其实DQN就是用神经网络代替Q表格的Q-learning方法。
两大创新点
经验回放
经验回放简单的来说就是讲Agent获得的数据选一批送入一个容器中,每次训练都会在里面选取一小批样本来进行网络的更新,而且容器中的数据是不断更新的,这些数据可以类比成人的经验。
士兵在前方不断的进行试错,将所获得的经验教训汇总到军师那,好让军师制定更加正确的作战方案。这就是经验回放的通俗比喻,是一个典型的off-policy策略。这样就打乱了样本的关联性并且提高样本的利用率(不会用完即弃),增强了网络的鲁棒性。
固定Q目标
训练神经网络需要定义loss函数,DQN也不例外。如下图所示,Q真实值使用Q_target来表示,而Q_target的值是变化的,如果强行用Q_target作为真实标签,这就会造成网络训练的不平稳。
DQN用固定Q目标的方法来解决这个问题,DQN将网络拷贝一份,称为target网络。target网络每隔若干个回合都会进行复制,其输出的Q值就作为Q的真实值与Q预测值进行运算得出网络的loss,以此来更新Q网络。
整体实现思路
代码介绍
DQN
Agent
def predict(self, obs): # 选择最优动作
obs = np.expand_dims(obs, axis=0)
pred_Q = self.fluid_executor.run(
self.pred_program,
feed={'obs': obs.astype('float32')}, #网络输入数据定义
fetch_list=[self.value])[0] #网络输出定义
pred_Q = np.squeeze(pred_Q, axis=0)
act = np.argmax(pred_Q) # 选择Q最大的下标,即对应的动作
return act
def learn(self, obs, act, reward, next_obs, terminal):
# 每隔200个training steps同步一次model和target_model的参数
if self.global_step % self.update_target_steps == 0:
self.alg.sync_target()
self.global_step += 1
act = np.expand_dims(act, -1)
feed = {
'obs': obs.astype('float32'),
'act': act.astype('int32'),
'reward': reward,
'next_obs': next_obs.astype('float32'),
'terminal': terminal
}
cost = self.fluid_executor.run(
self.learn_program, feed=feed, fetch_list=[self.cost])[0] # 训练一次网络
return cost
def sample(self, obs): #随机选取动作
sample = np.random.rand() # 产生0~1之间的小数
if sample < self.e_greed:
act = np.random.randint(self.act_dim) # 探索:每个动作都有概率被选择
else:
act = self.predict(obs) # 选择最优动作
self.e_greed = max(
0.01, self.e_greed - self.e_greed_decrement) # 随着训练逐步收敛,探索的程度慢慢降低
return act
上面是Agent类动作选取和训练函数,其中动作选取函数与Q-learning方法所用的选取方法是一致的。
训练效果
这个问题不算太复杂,如果用复制的网络模型训练,其效果可能适得其反。我用了三个全连接层+relu,通过修改学习率、隐藏层size等超参数,最终模型通过2000个episode 大致可以收敛到reward等于负100左右。tanh和relu效果差不多,relu的效果会稳定一点。
5000个episode 的效果直观上感觉会好一点。