1. 资料
- Udemy Deep Q Agents 课程:https://www.udemy.com/course/deep-q-learning-from-paper-to-code/?couponCode=LEARNWITHUSNOW
- OPEN MLSYS 强化学习介绍:https://openmlsys.github.io/chapter_reinforcement_learning/index.html
- 知乎白话强化学习:https://www.zhihu.com/column/c_1215667894253830144
- Double Q Learning 论文:https://cdn.aaai.org/ojs/10295/10295-13-13823-1-2-20201228.pdf
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^* \) 函数的真实函数形式,我们需要去寻找它。参考机器学习思路:
- 先建立一个带参数的 Q 函数 \(Q(s,a,\theta)\) ,其中 \(\theta\) 是待定的参数(组)。
- 通过类似随机梯度下降的方法去更新参数 \(\theta\),使 \(Q(s,a,\theta)\) 逼近 \(Q^*(s, a) \)
- 更新方法:\(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 \ Actions | a1 | a2 | a3 |
s1 | v11 | v12 | v13 |
s2 | v21 | v22 | v23 |
s3 | v31 | v32 | v33 |
其参数组 \(\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。
- “Frozen Lake”(冰湖) 介绍:https://www.gymlibrary.dev/environments/toy_text/frozen_lake/
简而言之:控制小人在 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
训练结果:
确实表现又有进一步,但并不太满意:
- 首先小车平衡单节的杆的问题本身也不算太复杂,却需要依赖如此多神经元,学习和实际推理效率不高。
- 学习曲线上依旧不是很稳定,甚至随时间还略有变差的趋势