ML2.1 DQL 学习和实践

1. 资料

2. Q Learning 的思路

强化学习的目标是:获得一个智能体 Agent ,当把环境信息告知 Agent 以后,Agent 可以做出”好”的决策。

有一些关键词:环境信息、“好”决策

  • 为了建模环境信息和交互影响,可以简单粗暴利用马尔科夫链来建模整个交互场景。
    • 每个状态 state 都可以被独立描述(不依赖历史和未来状态)
    • 特定 state 下做出特定的行为 action 后,会概率转移到某其他 state
    • 基于马尔科夫链可以极大简化建模的难度。虽然也有其限制
      • 很多现实问题的环境并非是独立的,要强行独立则需要给 state 加入历史信息,可能会导致状态空间非常大
  • 而为了评估”好”决策,则需要有价值评分机制

基于马尔科夫链和价值度量,我们可以建立一个 Q 值函数,用以度量各个 state 下各个 action 的 期望价值

\(Q(s, a) = \mathbb{E} \left[ \sum_{t=0}^{\infty} \gamma^t r_{t+1} \mid s_t = s, a_t = a \right]\)

  • s 表示状态
  • a 表示动作
  • \(r_{t+1}\) 是下一时刻的奖励
  • \(\gamma\) 是折扣因子,用来平衡当前奖励和未来奖励的重要性,0-1 之间,越小越看重当前奖励

不过我们最后更关心的 Q 值是在最优策略选择下的 \(Q^* \),如果获得了这么一个 \(Q^* \),那对于任意 state 我们只需要选择使 \(Q^* (s,a)\) 最大的那个 action 即可。即

\(Q^*(s, a) = \max_{\pi} \mathbb{E} \left[ \sum_{t=0}^{\infty} \gamma^t r_{t+1} \mid s_t = s, a_t = a, \pi \right]\)

  • 其中 \(\pi\) 表示 Agent 的策略,对应于在各个 state 下会选择的 action。\(a=\pi(s)\)

由于我们并不知道 \(Q^* \) 函数的真实函数形式,我们需要去寻找它。参考机器学习思路:

  1. 先建立一个带参数的 Q 函数 \(Q(s,a,\theta)\) ,其中 \(\theta\) 是待定的参数(组)。
  2. 通过类似随机梯度下降的方法去更新参数 \(\theta\),使 \(Q(s,a,\theta)\) 逼近 \(Q^*(s, a) \)
  3. 目标值:\(r_t + \gamma \max_{a’} Q(s_{t+1}, a’)\)
  4. 向目标值靠近的更新方法:\(Q(s_t, a_t) \leftarrow Q(s_t, a_t) + \alpha \left( r_t + \gamma \max_{a’} Q(s_{t+1}, a’) – Q(s_t, a_t) \right)\)
    • 不断用微小步伐向样本目标靠近(随机梯度下降)

3. Table Q Learning

3.1 思路说明

Q 本身是一个 状态,动作 -> 价值 (s,a -> v) 的函数,如果 s 和 a 的可能性都足够少,那完全可以建立一个 (s,a -> v) 的表去直接记录映射,如

States \ Actionsa1a2a3
s1v11v12v13
s2v21v22v23
s3v31v32v33

其参数组 \(\theta\) 最简单粗暴的设定方式,就是和表大小一致,每个 v 对应一个 \(\theta\)

即 \(Q(s,a,\theta) = \theta(s,a)\)

对应的参数更新方法 \(\theta(s_t, a_t) = \theta(s_t, a_t) + \alpha \left( r_t + \gamma \max_{a’} \theta(s_{t+1}, a’) – \theta(s_t, a_t) \right)\)

  • 需要不停和环境交互获得 \(s_t, a_t, r_t, s_{t+1}\) 的经验信息
  • 在尝试并获得经验过程中,尝试时的 action 选择策略也很重要,常用有
    • ε-greedy 策略:这是最常见的选择策略之一。在 ε-greedy 策略中,智能体以 ε 的概率选择随机动作(探索),以 1− ε 的概率选择当前最优的动作(利用)。ε 通常从较高的值(如 1.0)开始,逐渐降低到一个较低的值(如 0.01),以便在训练初期有更多探索,后期逐步收敛到利用。
    • Softmax 策略:将 Q 值转换成概率分布来选择动作,Q 值越高被选中概率越大。
    • Thompson Sampling(汤普森采样):估计各个动作的回报的概率分布,并基于分布进行采样。
    • … 其他策略

3.2 实践

用 OpenAI Gym 的经典强化学习环境 冰湖 来尝试 Table Q Learning。

简而言之:控制小人在 4*4 的冰湖上走动。目标是走到右下角拿奖励。冰湖上有洞,掉洞就死。可以选择上下左右走,但选择并不一定对应于上下左右,可能由于滑动导致行为和结果不一致(有干扰)。

我们在让 Agent 学习的过程中,采用 ε-greedy 策略进行探索。

详细实现过程的 Jupyter Notebook 见:https://github.com/Raytto/my_ml_study/blob/main/deep_q_study/s1_regular_q_agents.ipynb

训练结果:

基本能稳定在 65% 左右的胜率,考虑到冰湖场景下由于滑动天然就非必胜,基本上 70% 左右的成功率就是最好策略了。

4. 基本的 Deep Q Learning

4.1 思路说明

当 Q 函数的参数都是离散型且很有限变量时,用上面的 table 方式表示 Q 函数就非常直观方便。

但是如果 state 和 action 的可能性都非常多,取两个值域的笛卡尔积可能构成非常庞大的集合,甚至如果 state 或 action 是连续变量时,构成的集合是无限大的。这种情况下则无法用 table 表示了。

这种情况下最方便的做法是采用深度神经网络(DNN)。

  • 根据通用逼近定理,当 DNN 神经元足够多且层数足够时,可以拟合任意连续函数
  • 当 DNN 结合 ReLU 等非线性函数后,可以拟合任意分段连续的函数

即用 DNN 来拟合我们的 Q 函数,如果 DNN 的各连接参数为 \(\theta\),则可简单表示为 \(Q(s,a,\theta) = N(s,a,\theta)\)

不过由于 DNN 的最后一层可以是多个输出神经元。如果 action 是离散的,则可以让最后一层的神经元和 action 的各个可能性一一对应。

  • 这样仅需要一次 state 的输入即可得到此 state 下各个 action 的 Q 值。由于决策时确实需要知道所有 action 的 Q 值才行。所以这样建模后使用会比较方便

这样一来,则可表示为 \(Q(s,a,\theta) = N(s,\theta)[a]\)

  • 取 action 对应的的那个输出结果

学习时采用同样思路的更新方法:\(N(s_t,\theta)[a] \leftarrow N(s_t, \theta)[a_t] + \alpha \left( r_t + \gamma \max_{a’} N(s_{t+1})[a’] – N(s_t)[a_t] \right)\)

  • 不过不能像之前 Table Q Learning 那样,直接对 \(\theta\) 进行更新了。需要用反向传播,同步更新 DNN 所有参数。
    • 事实上,table Q Learning 可以视作单层单分支的神经网络,反向传播的梯度都全作用到一个 \(\theta\) 参数上。

4.2 实践

这次用 OpenAI Gym 的经典强化学习环境 小车和杆 来尝试 CartPole-v1。

很经典的控制问题:一个小车上面有一根杆,小车只能在一条线上左右移动。目标是尽可能使上面的杆保持平衡不倒,平衡时间越长分越高(500为上限,会强制结束一次测试)

我们在让 Agent 学习的过程中,继续采用 ε-greedy 策略进行探索。不过 Q 函数替换为简单的 DNN 进行拟合。

详细实现过程的 Jupyter Notebook 见:https://github.com/Raytto/my_ml_study/blob/main/deep_q_study/s2_deep_q_agents.ipynb

训练结果:

  • 可见在初期训练中,Agent 实力确实在上涨。但到一个阶段(也仅仅40分,上限500分)后就反而越来越差。

可能的原因

  • 模型是用一个个样本进行训练的,可能导致新的单一样本对模型产生极大的参数干扰

4. 优化 Deep Q Learning 的表现

4.1 尝试添加 DropOut

参考 GPT 实现中使用过的 dropout 方法,训练中随机抛弃一部分神经元。以减少过拟合的可能性,提升模型的稳定性。

详细实现过程的 Jupyter Notebook 见:https://github.com/Raytto/my_ml_study/blob/main/deep_q_study/s3_deep_q_agents_dropout.ipynb

  • 仅修改了 DNN 部分,对每一层神经元都添加了 dropout

训练结果:

可见有 dropout 确实比没 dropout 的基础版好了不少

  • 最高达到120分
  • 没有在某个时间后一直趴在低分区

但依旧不够好

  • 没达到500的最高分左右,代表还是不能驾驭 CartPole 场景
  • 训练曲线不稳定,不收敛

4.2 尝试添加记忆回放

为了进一步增强模型的稳定性。减少经验对参数的严重干扰。一种常用的方式是基于批量的经验进行学习。

  • 对批次中各个样本求参数梯度后求平均。更高概率抵消掉噪音性质的梯度,留下对学习有价值的梯度。

但为了做到这一点,需要把历史经验保存下来,并每次学习的时候随机取一部分历史经验。

详细实现过程的 Jupyter Notebook 见:https://github.com/Raytto/my_ml_study/blob/main/deep_q_study/s4_deep_q_agents%20mem.ipynb

  • Agent 中添加 memory 以记录经验
  • 添加 sample_memory 以每次随机选取一批经验用于学习
  • 修改 learn 方法,每次学习的时候基于一批次的经验(给每个数据添加一个 batch 维度,从单数据变成张量)

训练结果:

可见模型收敛性上确实好了不少,效果明显。但分数依旧不算很高(仅90左右)。

首先能想到的是 DNN 神经元不够,没能拟合场景需要的 Q*

4.3 尝试扩大模型规模

先尝试把模型每一层的神经元数量从128提升到256

详细实现过程的 Jupyter Notebook 见:https://github.com/Raytto/my_ml_study/blob/main/deep_q_study/s5_deep_q_agents%20256.ipynb

训练结果:

模型扩大到256后,结果确实好了一些,提升很有限。

继续尝试扩大,每一层加到512个神经元,并且加一层 LayerNorm 尝试稳定训练过程。

详细实现过程的 Jupyter Notebook 见:https://github.com/Raytto/my_ml_study/blob/main/deep_q_study/s6_deep_q_agents%20512_norm.ipynb

训练结果:

确实表现又有进一步,但并不太满意:

  • 首先小车平衡单节的杆的问题本身也不算太复杂,却需要依赖如此多神经元,学习和实际推理效率不高。
  • 学习曲线上依旧不是很稳定,甚至随时间还略有变差的趋势

4.4 尝试 Clipped Double Q-Learning

根据 Clip Double Q Learning 的论文:https://proceedings.mlr.press/v80/fujimoto18a/fujimoto18a.pdf

目前我们的 Q Learning 存在一个比较大的问题:

  • 学习过程中,由于取 max Q 的操作,天然容易高估 Q 值
    • 每次经验对 Q 的采样可能偏高也可能偏低,由于取 max ,则学习过程中偏低的会被忽略,偏高的会被关注,进而导致整体上 Q 值的高估。

Clipped Double Q 可以一定程度缓解高估的问题。

思路如下:

  • 同时采用两个用于估计 Q 的 DNN 神经网络
  • 在更新 Q 的过程中,选择下一步 max Q 时,对每个 a 而言,选用 min(Q1(s,a),Q2(s,a))。这样除非两个 Q 都高估,否则不会受高估影响
  • 在策略选择时,由于没有更新那么严格,可以和学习时一致取 min,也可以取平均

详细实现过程的 Jupyter Notebook 见:https://github.com/Raytto/my_ml_study/blob/main/deep_q_study/s7_clip_double_q.ipynb

训练结果:

分数提升明显,已经可以达到500(最高值)了。

但是依旧有不收敛的问题。

Clip Double Q 论文中也有提到,采用 Clip Double Q 的方法能减少高估,但也并非完全消去高估的影响

4.5 尝试传统 Double Q Learning

论文见:https://cdn.aaai.org/ojs/10295/10295-13-13823-1-2-20201228.pdf

传统 Double Q Learning 也是为了缓解高估 Q 值的问题。

核心实现方式是:用一个网络来选取最优的行为,但用另一个网络来获取此行为的 Q 值进而更新

具体实现:https://github.com/Raytto/my_ml_study/blob/main/deep_q_study/s8_double_q.ipynb

训练结果:

可见 Double Q 表现已经挺不错的了。

4.6 尝试 Duel Q Learning

Duel Q Learning 论文:https://arxiv.org/pdf/1511.06581

核心思路:拆分 Q 值为当前状态的价值和动作价值的和

  • 很多强化学习场景下,确实就是很多 state 比其他 state 更有优势,即忽略 action 的情况下 state 也一般是有独立价值的。拆分开可以让 state 的价值独立训练,更稳定不受干扰。

\(Q(s, a) = V(s) + \left(A(s, a) – \frac{1}{|A|} \sum_{a’} A(s, a’)\right)\)

  • \(\frac{1}{|A|}\)是为了求s下所有action的价值平均。之所以减去平均值是为让 V(s) 可以更纯粹地表达状态的价值。且避免数据过分波动让 Q 值更稳定。有利于学习。

具体实现:https://github.com/Raytto/my_ml_study/blob/main/deep_q_study/s9_duel_q_agents.ipynb

训练情况:

虽然也不完美收敛,但看起来还是可以接受。

5. 渲染训练过程和测试

可以用 gym 自带的渲染方法查看训练过程和训练后的 Agent 玩游戏表现。

主要需要在创建环境时添加 render_mode=”human”

env = gym.make("CartPole-v1",)

即可实时看到运行过程。

不过要渲染的话,程序不能跑在 Jupyter Notebook 中,需要用普通的 py 文件运行。

代码详见:https://github.com/Raytto/my_ml_study/blob/main/deep_q_study/s10_render.py

  • 除了添加渲染逻辑以外,还加入了模型参数的保存和加载
  • 运行前需要设置模式变量,如:mode = “render_test_model”
    • if mode == “train_new” :从新开始训练一个模型
    • if mode == “train_continue” : 接着上次的训练继续训练模型
    • if mode == “render_test_model” : 用训练好的模型尝试推理看表现

除了小车杆场景以外,个人还尝试了下 gym 中的 Acrobot-v1 场景

  • 双节的连杆,控制第一根连杆,尝试将第二节杆的端点尽快甩到目标高度以上
  • 花的时间越久分数越低

用 Dueling Q Learning 也可以达到 -70 分左右(基本还算不错的表现)

代码详见:https://github.com/Raytto/my_ml_study/blob/main/deep_q_study/s11_render%20acrobot.py

6. 总结

Q Learning 整体具有一些优势

  • 方便理解和实践
  • 可以利用离线数据来学习
  • 结合了深度学习的优势:可以直接从图像像素中提取信息

但也有一些劣势:

  • 对超参比较敏感,没选好模型可能就表现很差
    • 如学习率、batch size、神经网络大小、epsilon 递减方式 等
  • 不稳定,容易发散
    • 即使用 Dueling Q Learning 学 CartPole,随学习次数增加,分数依旧会有一些波动
  • 延迟奖励情境下不方便学习
    • 部分场景下,奖励可能很久才出现,而 Q Learning 的折扣因子可能导致远期奖励被极大削弱

发表评论