深度强化学习落地方法七步曲|写在前面 、需求分析、算法选择、动作空间、状态空间、回报函数、训练
大纲
1、写在前面
2、需求分析篇
3、算法选择篇
4、动作空间篇
5、状态空间篇
6、回报函数篇
7、训练篇
正文
一、写在前面
缘起
随着这波人工智能浪潮的兴起,强化学习(Reinforcement Learning,RL)这坛陈年老酒也借势找到了深度学习这个新瓶子,重新吸引了学术界、工业界乃至吃瓜群众的注意力。对于很多人来说,AI闯入他们视野的标志性事件不是2012年AlexNet的横空出世,而是AlphaGo击败李世石引起的轩然大波。可以这样说,深度强化学习(DRL)不仅点燃了世人对AI的热情,而随着CV方向性能瓶颈的出现,作为一个缺点多多、槽点满满的算法框架,DRL正在吸引越来越多的学者前来填坑,从各大AI顶会RL方向文章数量的逐年上升可见一斑。
算法工作者的“知行合一”
如今网上关于DRL的科普文可谓汗牛充栋,图文与公式并茂,paper与代码齐飞。我自知水平有限,无意在DRL科普界班门弄斧。另一方面我认为算法工作者不应只局限在原理层面,看懂很多公式不代表拥有动手能力;也不应满足于跑通toy tasks的demo,知其然不知其所以然,面对实际问题时可能依旧一头雾水。算法工作者的“知行合一”,需要经历从抽象算法原理到广泛动手实践再上升到统一方法论这三个必要阶段,唯有如此才算真正掌握了知识。
我将通过该系列文章,对这两年DRL落地工作中的一些感悟和心得做些粗略总结,若能对读者启发一二则善莫大焉,如有纰漏谬误也真心期待得到大家的批评指正,我深信持续的交流是进步的源泉,也是我写下这些文字的初衷。由于本文的关注点在算法落地上,因此更适合那些有一定强化学习基础的读者,对于尚未入门的新手,强烈建议首先通过其他途径熟悉RL/DRL的基本概念。
源于学术,高于学术
与相对单纯的学术工作不同,DRL落地涉及面相当广。我们的目标是在现实应用中获得实实在在的性能提升,为企业创造肉眼可见的价值,无法自由选择“较弱的baseline”自欺欺人。我们没有像Gym那样现成的虚拟环境可用,需要自己搭建模拟器,定义状态空间、动作空间和回报函数。我们还需要切实考虑算法的实用性,比如模拟器的reality gap是否足够小,对不同应用场景的泛化性如何,模型的训练时间是否可控,inference运算效率能不能跟上,对各种异常、延时和误差鲁不鲁棒等等。套用伟人的句式一言以蔽之,落地来源于学术,又高于学术。
Talk is Not Cheap
我的计划是将从拿到一个新需求到项目落地中的主要环节,分成若干篇来逐一介绍。这些文章里将很少贴公式,也基本不会有代码,所有文字将致力于对方法论的阐述,读者将会看到“so much talk”,但真心希望不要因为我捉急的文字功底而认为它们是 “cheap”的长篇大论。考虑到所涉及的话题较多,因此时间跨度可能比较大,有空的时候我尽量多写。另外由于不能涉及商业机密,必要时会采用通俗的例子加以说明。那么接下来,开始吧~
二、需求分析篇
前言
弘扬中华传统美德,丑话要说在前面。任何机器学习方法都不是包治百病的灵丹妙药,它们也有各自的“舒适圈”,有时候还相当挑剔。强化学习,无论前面带不带“深度”二字,也同样有其鲜明的优势和局限性,务必要具体问题具体分析。不管公众号吹嘘得多么厉害,我们自己要摆正心态,不是所有需求都适合用DRL做,适合用DRL做的需求也未必能超越传统方法。
在我看来,算法工程师的核心能力可以总结成以下三点:
对各种算法本质及其能力边界的深刻理解
对问题内在逻辑的深入分析
对两者结合点的敏锐直觉
一个优秀算法工程师的高光时刻从拒绝不合理的需求开始,其他的都是后话。不经慎重评估而盲目上马的项目不仅是对资源的巨大浪费,更让每个参与者陷在深坑中痛不欲生。知道一种算法不能干什么与知道它能干什么同样重要,对DRL而言,即使在最理想的外部条件下,也有其绕不过去的七寸——泛化无能。这是DRL的基本原理决定的,任何在这一点上提出过高要求的应用都不适合用DRL解决。
DRL的过拟合天性
DRL解决的是从过去经验中学习有用知识,并用于后续决策的问题。有别于纯视觉应用,DRL不仅仅满足于识别和定位,而是要根据这些信息采取针对性的行动以获取最大长期收益。从本质上说,DRL就是一种依赖过拟合的算法,说白了就是通过暴力搜索把其中的成功经验记下来,并用以指导后续决策。别嫌露骨,别怕尴尬,岂不闻学术界某大牛的辛辣讽刺仍余音绕梁——强化学习是唯一被允许在训练集上测试的算法。由于缺乏直接监督信号用于训练,DRL还特别“费数据”,以至于需要专门的模拟器源源不断地产生数据供其挥霍。好不容易训出来的policy在训练环境用得好好的,换个环境立马歇菜。
等等,不是说好了DNN有泛化能力吗?ResNet明明能在一张没见过的图片中识别出阿猫阿狗的呀。这是因为任务层次不同,泛化的定义和要求自然也不同。视觉识别层面的泛化可以理解为深度网络学习到了通用的高层语义信息,以至于只要看到类似的像素结构就能与高层语义对应起来。这个层次的泛化能力DRL也可以有,可惜远远不够。我们前边说过,DRL是要根据识别到的信息做出决策以最大化长期收益的,具体地,DRL拟合了一个特定环境、特定reward函数和当前policy下,从特定输入状态(state)到最终收益(episode结束时)的Value函数,再反过来根据这个函数去优化policy,使其输出的action最大化该Value函数。这是一个交替更新的过程,Value函数和policy你中有我我中有你,直到抵达某种纳什均衡。假设探索足够充分,我们可以认为最终的Value函数只由环境特性和reward函数决定。
OK,整理一下DRL的逻辑链条:Value函数过拟合环境特性和reward函数,Policy又过拟合Value函数。一朝天子一朝臣,一种环境一种policy,环境换了,policy废了。在训练环境中agent看到家猫,发现喂食可以得到高回报,于是用policy记了下来,在测试环境中,家猫换成野猫,虽然识别到的还是猫,agent继续喂食,然后就被猫抓了……相信看到这里,你就能明白为什么DRL被允许在训练集上测试了,也明白为什么DRL泛化无能了,因为这个level的泛化要求太TM高了!即使是人类在缺乏相关经验的情况下也会踩坑,但人类能做到吃一堑长一智,甚至从此主动避开所有可能有危险的动物,这得益于人类的两大核心优势:高效学习(只需极少样本)和终身学习(举一反三、融会贯通),而现阶段DRL却只会低效地死记硬背,训练也是一锤子买卖,这是真正的智能对人工智能的降维碾压。
适用DRL的五大特征
于是我们产生了两个问题:1.是不是DRL就真的一点泛化能力都没有?2.这样的DRL到底有没有实用价值?关于第一个问题我建议读者去了解一些Meta-RL(元强化学习)方向的工作,利用DNN的表征能力,agent能够利用有限的环境探索不断在线学习该环境的特性和reward函数,并据此调整policy的输出使其表现出对新环境的适应能力。但目前也仅能解决一类相似任务间的泛化问题,而代价是训练难度进一步提升,数据效率进一步下降,愿景很美好,实用有点早。第二个问题才是目前我们最关注的,换个问法,到底什么样的任务适合用DRL解决呢?答曰:场景固定,目标明确,数据廉价,过程复杂,自由度高。依次解读如下:
场景固定
场景固定是指决定系统动态演化趋势的主要因素保持恒定。听起来可能有点抽象,举个例子,agent在环境中遇到一条峡谷,跳过去的过程中有30%的概率被落石击中,这里的30%就属于这类因素,无论在训练和工作的时候都不能改变。用符号表示:状态s=面对峡谷,动作a=跳过去,下一个状态s'的概率分布p(s'|s,a)是明确的,即安全着陆70%,壮烈牺牲30%,这个概率在RL中叫状态转移概率,很多paper又称其为环境的model,对于任何s,a和s',p(s'|s,a)都明确而恒定的决策过程又称为Markov Decision Process或MDP,RL的理论基础即建立在MDP之上,Value函数和policy就是通过隐式(model-free)或显式(model-based)地对环境model建模得到的。model变了,policy就废了,上边举的家猫和野猫的例子就是这个道理。另一个不满足场景固定的典型例子是DOTA更换地图,基于训练地图得到的局势演化预期在新地图里不成立了。又比如临时要求围棋棋盘里某几个位置不准落子,AlphaGo大概率是要跪的。你要是还不理解,就记住:训练环境尽可能做到与工作(测试)环境相同。
目标明确
目标明确很好理解,任务要达到何种效果清晰具体,最好可以量化。工业界的需求一般都是优化某个指标(效率、能耗、胜算等),基本满足这个条件。目标越明确,设计优质的reward函数就越容易,从而训练得到更接近预期的policy。
数据廉价
数据廉价对RL至关重要,毕竟挥霍数据是RL与生俱来的属性,没办法。我们知道视频游戏领域很容易满足这个条件,所以我们最常听说DRL在XX游戏里碾压、吊打、秒杀人类玩家。然而这个要求对牵涉到硬件的应用却相当不友好,Google可以用7台KUKA iiwa机器人(单价80万rmb体会一下)日夜不停跑上几个月训练抓取技能,其他公司怕是连8千的设备撞坏了都心疼,那就只剩下模拟器这一条路了。所谓模拟器,就是将真实场景中的各种物理模型(即上文提到的model)在软件环境中仿真,从而生成无限量的高仿数据。这里有一个reality gap的问题,即这些仿真model与真实世界的误差,如果太大则训练出的policy无法直接应用。一个逼真的模拟器也是要花功夫(钱)的,像MuJoCo这样的优秀仿真平台收费也是合情合理的。
有心的朋友已经发现,对模拟器精度的要求其实与上文的“场景固定”逻辑上是一致的,之所以分开介绍,是因为廉价还包含了另一层意思——采样速率。AlphaGo和OpenAI Five在宣传的时候动不动就说他们的agent学习了相当于人类XX万年的经验,显然没有高速模拟器是不可能做到的。总之,如果非要做硬件相关的应用,先尽最大努力做出逼真的高速模拟器吧!
过程复杂
如果说前三个特征决定了“能不能”,那么接下来两个特征决定了“值不值”。我们用DRL的目的无非是看中了其处理复杂场景的能力,人类看不透,DRL来凑。如果任务太简单,依靠规则和启发式就能解决问题了,相当于拿到了“解析解”,还用神经网络拟合个什么劲儿。这里介绍一个技巧,请熟悉业务流程的甲方人员结合Domain Knowledge,分析一下阻碍性能提升的主要瓶颈在哪里,如果对方回答是过程太复杂难以掌握规律或其他类似的答复,那就说明DRL值得一试。
自由度高
自由度高指的是选择空间大、限制少,我们人类之所以有“选择困难症”,正是因为选择太多了,这时候DRL的优势就体现出来了,通过大量探索总能拟合出不错的value函数指导policy做选择。自由度越高,DRL优势越明显,自由度越低,越有利于规则。因此在决定用DRL之前,一定要认真评估任务场景是否有足够的优化空间,千万不要拎着锤子找钉子,否则即使训出了policy,性能也不如传统算法,白忙活一场。
总结
关于需求分析就先写到这里,基本涵盖了需求评估的主要方面,如果经仔细了解后发现不满足以上五个特征,就要小心了。这时候要么直接向对方提出不可行,要么在大需求里找出符合以上要求的子任务用强化学习解决,一样可以改善总体性能,切不可盲目追求大而全。好了,万里长征走完了第一步也是最重要的一步,接下来我们就假设已经拿到了一个适合又值得用DRL解决的任务,讨论如何选择合适的算法。
三、算法选择篇
前言
2021年2月15日更新:考虑到这篇文章写作时间较早,这里统一更新算法选择方面的建议:对于连续控制任务,推荐SAC、TD3和PPO,三种算法都值得试一试并从中择优;对于离散控制任务,推荐SAC-Discrete(即离散版SAC)和PPO。至于TD3和SAC的详细介绍,网上资料很多,暂时就不写了,有机会再说。
虽然每年RL方向的paper满天飞,但真正具有普遍实用价值的突破性工作实在不多,大多数还是在经典框架基础上的改进和扩展。DRL常规武器库里的存货主要还是老三样:DQN,DDPG和A3C,它们是深度学习时代最成熟、最能体现智慧结晶的三个DRL框架,你可以在GitHub上找到无数相关代码,有OpenAI,DeepMind和Nvidia这些大公司的,也有个人爱好者的。对于DRL初学者,它们是最佳的敲门砖;对于算法研究者,它们是最厚实的“巨人肩膀”;对于算法工程师,它们是最顺手的试金石。你完全可以把三个框架都放到项目模拟器上跑一跑,看哪个效果好就用哪个。当然,这三个算法框架都有各自的特点和适用domain,结合对项目的分析,是可以提前评估最合适的算法的。
强化学习——探索和利用的平衡游戏
总体来说,强化学习是一个探索(Exploration)和利用(Exploitation)的平衡游戏,前者使agent充分遍历环境中的各种可能性,从而有机会找到最优解;后者利用学到的经验指导agent做出更合理的选择。两者之间可以说是相爱相杀的关系:
充分的探索才能带来有效的利用,从而使RL走在正确的道路上。对于那些难度特别高的任务,改进探索策略是性价比最高的手段,比如AlphaGo使用蒙特卡洛决策树征服了围棋,Go-Explore利用状态回访打爆了Montezuma's Revenge
充分的利用才能探索到更好的状态,agent往往需要掌握基本技能,才能解锁更高级的技能。就好像小孩先要学会站起来,才能学会走,然后才能学会跑。这种从易到难、循序渐进的思想在RL中也很受用,著名的Curriculum Learning就是由此而来
过量的探索阻碍及时的利用。如果随机探索噪声强度过高,已经学到的知识会被噪声淹没,而无法指导agent解锁更好的状态,导致RL模型的性能停滞不前
机械的利用误导探索的方向。如果刚刚学到一点知识就无条件利用,agent有可能被带偏,从而陷入局部最优,在错误道路上越走越远,在训练早期就扼杀了最好的可能性
强化学习的训练过程其实就是从以探索为主到以利用为主的过渡过程,训练早期通过广泛试错找准一个方向,然后沿着该方向一路试探下去直到达到最优。请牢牢记住这“两点一线”,因为这是所有RL算法的主要内容,任何RL算法都能以此为切入点进行解构,有助于不断加深对算法的理解。接下来我就结合实际经验,谈谈对三个主流DRL框架的一些浅见。
DQN
DQN是借助AlphaGo最早成名的深度强化学习算法,其核心思想是利用Bellman公式的bootstrap特性,不断迭代优化一个Q(s,a)函数,并据此在各种状态下选择action。其中Q(s,a)函数拟合的是一对状态-动作的长期收益评估,该算法没有显式的policy。DQN探索和利用的平衡靠的是一种称为ε-greedy的策略,针对最新的Q(s,a)函数和当前的输入状态s,agent做决策时以概率ε随机选择action,而以1-ε的概率选择使Q(s,a)最大的action,随着ε从大到小变化,DQN也相应地从“强探索弱利用”过渡到“弱探索强利用”。
DQN的原理使其天然地适合离散动作空间,也就是action可以穷举,比如走迷宫的agent只允许前后左右4个动作,下围棋的AlphaGo只允许19*19=361个落子位置(实际还要排除已经落子的网格点)。这是一个重要的特征,如果你手上是一个连续控制任务,action在某区间内有无数种可能,那就不适合用DQN了。当然,你也可以选择把区间离散化,这样就可以应用DQN了,也曾有paper报告这样做在某些任务中可以比连续控制取得更好的性能。
DQN属于off-policy方法,所谓off-policy是指用于计算梯度的数据不一定是用当前policy采集的。DQN使用一个叫replay buffer的FIFO结构,用于存储transition:(s,a,s',r),每次随机从buffer中拿出一个batch用于梯度计算和参数更新。Replay buffer是稳定DQN训练的重要措施,对历史数据的重复使用也提高了其数据利用率,对于那些数据比较“贵”的任务,比如Google的抓取应用(见需求分析篇),这一点非常重要,事实上Google除了replay buffer,还专门搞了个数据库,把之前存储的另一个抓取应用采集的数据拿出来做预训练,精打细算到了极致,真是比你有钱,还比你节约~
DQN的缺点挺多,有些是RL的通病,比如对超参数敏感,我在训练篇会详细介绍;另外利用Bellman公式的bootstrap特性更新Q值的方式自带bias,外加计算目标Q值时使用同一个网络评估和选择动作(见下式),DQN容易被overestimation问题困扰,导致训练稳定性较差,近些年学术界有不少工作是围绕这一点做出改进(比如Double DQN)。此外,DQN还有off-policy方法的通病,对历史数据的重复利用虽然可以提高数据效率,但有个前提条件是环境model不能发生变化,single agent任务较易满足这个条件,但multiagent场景就未必了,对任意agent而言,其他agent也是环境的一部分,而他们的学习进化会改变这个环境,从而使历史数据失效,这就是MARL领域著名的环境不稳定问题,除非replay buffer内的数据更新足够快,否则off-policy方法的性能往往不如on-policy方法。
DDPG
针对DQN无法处理连续控制任务的缺点,DDPG在DQN的基础上做了改进,引入了一个输出连续action的显式policy,与Q函数组成Actor-Critic结构,更新policy网络的梯度完全来自于Q网络,目标是最大化当前的Q函数。Q函数的更新与DQN类似,只是计算s'状态下目标值时放弃了max操作,而采用当前policy网络的输出π(a|s')。DDPG名字里的第一个D是Deterministic的缩写,意思是确定性的,这是有意与正宗Actor-Critic方法(如A2C/A3C等)区分开,后者policy输出的是action的概率分布,而DDPG输出的就是确定性的action。正因为如此,DDPG采用了独特的探索方式,即在action输出直接加上一个noise,该noise的强弱决定了探索力度,本质上相当于以当前action为中心形成了一个概率分布,每次更新都使policy向该分布中更好的方向演化,直到action达到了最优,此时对应分布内其他方向都是更差的方向,policy输出也就稳定在最优action附近了,从而实现了探索和利用的平衡。
能用于连续控制任务自然是招人喜欢的,毕竟实际控制任务的变量往往都是连续取值的,比如角度、位移、速度、加速度、电流、电压等等。学者们把DDPG用在MuJoCo上,解决了很多连续domain的任务,后来也有人把它用到真实的软体章鱼机器人上,用两只触角实现了向前运动。然而,在连续区间上找到最优的确定性action输出本身是一件非常困难的事,导致DDPG在action维度较高的复杂任务中表现不佳,比如KUKA iiwa机器人有7个自由度,使得探索空间一下大了很多,训练难度陡升。同时policy网络的梯度完全来自于Q网络,Q函数的拟合误差都直接传导给了policy,致使DDPG的训练稳定性也不足。在Google的抓取应用中,干脆抛弃了独立policy网络,做决策时随机在区间里取16个点输入Q网络,然后选择Q值最大的那个作为action,实验结果表明如此粗糙的做法却大大提升了训练稳定性,且性能显著优于DDPG,有点尴尬……
总结一下,如果我们面对的问题是连续控制任务,action维度又不高,可以尝试用DDPG解决,但也不要忘了离散化动作空间并用DQN训练得到更高性能的可能性。如果action维度很高,那还是别用DDPG的好。如果数据很“贵”不得不用off-policy方法的话,那就向Google学习,拿掉policy网络,直接用Q网络+启发式搜索选择action。如果数据廉价又追求高性能,我推荐使用PPO框架——个人比较偏爱的一种框架。
2021年2月15日更新:上述建议已经过时了,对于连续控制任务,推荐优先使用SAC,后者在训练稳定性、收敛速度和性能方面都是目前的SOTA,作为off-policy算法数据效率也相对较高,SAC作者尝试直接用实体机器人采样并在几个小时内成功收敛。
A3C→A2C→PPO
在成熟版SAC出现以前,PPO曾带给我最多的成功经验。PPO从A3C的同步版本A2C的基础上演化而来。
A3C能够充分利用多核资源,在不同CPU上并行运行不同的环境种子,显著提升了训练稳定性、收敛速度以及最终性能。A3C支持多种action概率分布,如果action空间是DQN那样的离散集合,可以用Categorical分布;如果是像DDPG那样的多维连续分布,可以用Multivariate Gaussian分布,此外A3C还支持伯努利分布,如果action的每一维都是非此即彼的二值选项,或者one-hot向量太长想改用二进制表示,那就是它了。可见,A3C在通用性上是显著优于DQN和DDPG的,几乎所有任务都能拿A3C跑一跑。此外,A3C作为on-policy方法,每次更新policy的梯度都由当前policy采集的样本计算,这使得A3C在MARL任务里对环境不稳定性的抵抗能力比DQN和DDPG更强。
A3C将多核环境中计算的梯度进行异步(Asynchronous)聚合,然后统一更新主网络并将新参数分发到各环境。这种方式的运行效率较高,但计算的梯度可能与当前主网络参数在时间上“错位”,有可能损害算法性能。因此,学术界更多地沿用了A3C的同步梯度聚合版本A2C。PPO就是在A2C的基础上利用clip操作进一步限制了每次梯度更新的幅度,从而显著提升了训练稳定性。和很多paper的实验结果一致,我在实际应用中发现PPO在连续控制任务里性能显著优于DDPG,对超参数的敏感度也比DDPG低,因此训练起来更加得心应手。因此,我推荐大家在解决连续任务时首选PPO,DDPG的优先级往后放就是了。对于具有离散动作空间的任务也值得用A3C跑一下,跟DQN比一比。
2021年2月15日更新:考虑到这篇文章写作时间较早,这里统一更新算法选择方面的建议:对于连续控制任务,推荐SAC、TD3和PPO,三种算法都值得试一试并从中择优;对于离散控制任务,推荐SAC-Discrete(即离散版SAC)和PPO。至于TD3和SAC的详细介绍,网上资料很多,暂时就不写了,有机会再说。
其他算法
以上三个DRL框架是基础,大多数情况下都至少能得到一个“能用”的policy。然而,也不应奢望它们能解决一切问题。DRL领域是个大坑,里边有太多需要解决的问题和值得挖掘的方向,比如:高难度探索,稀疏reward,数据效率,训练稳定性,快速适应新环境等等,类似MARL这样的子领域还有自己特有的问题,如环境不稳定性,scalability等等。算法工作者一定要保持开放的态度,及时跟踪学术界的新趋势新方法。
每当算法性能遇到瓶颈,首先要沉下心来分析关键制约因素在哪里,如果是上述这些普遍意义上的问题造成,那就去相关方向最新paper中寻找灵感。比如探索不够充分时,可以用count-based exploration或者parameter noise来加强探索;DQN训练不稳定时,可以尝试Double-DQN,或者孪生网络,每次选择较小Q值计算目标值,从而抑制overestimation;DQN或DDPG数据效率不够时,可以用prioritized replay buffer;MARL里为了改善环境不稳定问题,可以尝试DIMAPG,……。问题无常势,算法无常形,群众智慧是无穷的,博采众长才能攻无不克。
关于算法选择就先写到这里,不同算法在训练时还有各种各样的trick和注意事项,我在训练篇里再详细介绍。
四、动作空间篇
前言
在将DRL应用于实际项目时,可能最轻松愉快的部分就是动作空间定义了。倒不是因为这项工作简单,而是agent的控制方式往往早就定死了,留给我们发挥的空间很小,就好像我们无法决定DOTA里允许多少种操作,也无法改变一台机器人的关节数量和各自的角度范围,Gym用户甚至从来都不用为这个问题操心,action空间有多少维,连续还是离散,各种domain早就都定义好了,我们根据这些性质判断任务的难度,仅此而已。选择困难症患者表示松了一口气有木有~~~当然咯,如果运气足够好,agent提供了多种控制选项并允许我们自由选择时,一定要珍惜这种机会。
对动作空间的三个要求
完备性
动作空间首先要提供实现预期目标的可能性,避免在任务解空间中出现无法触及的“状态盲区”,尤其是要保证高性能区域的充分可达性。这一方面要求动作空间要具有功能完备性,比如一辆汽车必须具备加减速、转弯和刹车等基本功能才可以实现导航和防撞任务;另一方面还要求动作空间具有时效完备性,即使一辆汽车具备了正常行驶所需的全部功能,但如果这些功能的响应速度过慢,或者决策周期过长,都会严重影响高速行驶下的突发状况应对能力,因此动作的作用频率必须满足特定任务所需的最低要求。
高效性
动作空间应该尽可能简单高效,从而有效降低训练难度和提升算法性能。一方面,可以将连续动作空间化整为零,在满足基本控制精度的前提下将其转化为离散动作空间,这样可以显著压缩解空间维度,提高探索效率;另一方面,可以根据实际情况,将一些基本动作进行有机组合构成宏动作,就好像实况足球游戏中的马赛回旋、牛尾巴、油炸丸子等久经考验的高级摆脱技巧,让算法依靠自由探索“撞到”并掌握它们的难度极高,但如果能将这些技巧直接作为常备选项,由算法学习如何合理运用它们,将起到事半功倍的效果。
合法性
还有一点值得注意,在DRL应用中并不是所有action在任何state下都有效,比如AlphaGo就不能在棋盘上已经被占据的位置落子,骑自行车时也最好别倒蹬车,自动驾驶车辆遇到行人时绝对不能撞上去。对于特定状态下规则不允许出现的action或者引发严重后果的action,我们应该直接屏蔽掉。DRL与其他AI算法一样,都属于统计学范畴,我们在理解policy输出时也应该使用概率思维,即使agent学会在99.99%的情况下输出合法action,但仍存在0.01%的可能性输出非法action,与其寄希望于DRL完全学会遵守规则,不如加一层“硬保险”来得靠谱。
上述非法动作屏蔽机制是动作空间设计不可或缺的环节,在屏蔽非法动作的基础上,通常还应该在状态空间中标识出当前可用的合法动作,从而使算法在学习过程中意识到某状态下特殊规则的存在,并学会主动遵守规则,每次都从合法动作中做选择。这也是状态空间和动作空间协同设计的经典场景。
结语
关于动作空间的内容不多,DRL算法工作者的主战场在其他方面,比如下一篇我们将要介绍的重头戏——状态空间设计。
五、状态空间篇
1. 前言
DRL的状态信息代表了agent所感知到的环境信息,以及因自身的action带来的变化。状态信息是agent制定决策和评估其长期收益的依据,而状态设计的好坏直接决定了DRL算法能否收敛、收敛速度以及最终性能,兹事体大,不可不察。通常在一些公共平台,如Gym,大部分domain的状态空间都是现成的,学者们在上边比的是谁的算法收敛快、性能好;然而,在实际项目中,状态空间设计工作却要自己来,根据我的个人经验,增加一个优秀的新状态信息所带来的性能提升明显高于其他方面的工作(如调参),性价比非常高,因此状态空间的优化工作几乎贯彻项目始终。
大家注意到我多次使用“设计”这个词,这不就是特征工程(feature engineering)嘛,9102年都快过完了,怎么还搞这一套?直接上深度神经网络呀!真要那么简单就好了……把所有原始信息一股脑堆砌起来,让神经网络去挑选其中有用的成分并学习它们与决策间的相关性,原理上是没毛病的,可端到端的DRL学习效率实在不太给力,比有监督学习差老远了,即使经过大量训练神经网络能够最终提取到有用信息,因为训练时间的延长也会导致算法实用性的下降。更糟糕的是,一些不相关的干扰信息还会起到反作用。因此,要想在可控时间内得到比较好的policy,的确需要人为筛选出一些好的状态信息,可以是raw information,也可以是经过二次加工的信息,帮助神经网络更轻松准确地建立起决策相关性。
2. 状态设计的四个步骤
那么具体该如何做呢?我把状态空间设计的精髓总结成以下4个步骤:任务分析,相关信息筛选,统一性考虑,效果验证。
2.1 任务分析
任务分析是状态设计的灵魂,好的状态信息建立在对任务逻辑的深入理解之上。客户提出最终目标,优秀的算法工程师需要把这个目标进一步分解,研究该目标的本质是什么,要实现它涉及到哪些重要环节,每个环节有哪些影响因素,每个因素又由哪些信息体现。对任务逻辑的深入分析也有助于我们设计优秀的回报函数(reward),并反哺状态空间的设计。对一个复杂任务的理解,除非天赋异禀或者相关经验丰富,一般都是要经过一段时间的摸爬滚打后才会深入到一定程度,期间还可能不断推翻之前的错误认知,更伴随瞬间顿悟的喜悦,因此要保持足够的耐心。
为了便于说明,这里引入一个简单任务场景:在一个遍布障碍物的平面区域内有若干辆小车在随机位置待命,现在要求它们以最短时间行驶到各自的终点位置停下来,期间避免与障碍物或者其他小车发生碰撞。我们可以把上述目标分解成三部分:1.达到终点,2.避免碰撞,3.用时短。到达终点要求小车知道自己在哪,还要知道终点在哪;避免碰撞要求小车知道附近障碍物的位置,自己和周围其他小车的位置及运动状态;用时短要求小车少绕路,行驶速度快,尽量避免减速和刹车。
2.2 相关信息筛选
带着以上分析,我们就可以进入下一个环节——相关信息筛选。顾名思义,就是在所有可用信息中找出与任务目标、子目标有关的那些。我们都知道RL任务逻辑最终是以回报函数(reward)为载体呈现的,而RL算法优化的则是该reward系统下的长期累计收益。神经网络的作用是将原始状态信息经过层层非线性提炼后转化为与长期收益高度关联的形式,并进一步指导生成action决策。理想情况下,状态空间应该完全由筛选出的相关信息组成。某个状态信息所代表的事件在越短时间内得到反馈,神经网络就越容易学会如何对其进行加工并建立起决策相关性。按照这个反馈时间的长短,我们还可以粗略地将这些相关信息分为直接相关信息和间接相关信息。
2.2.1 直接相关信息
直接相关信息不仅对DRL算法学习很友好,在有对口reward奖励/惩罚项的前提下,对算法工作者来说也更容易设计。事实上,DRL的状态空间设计往往和reward设计同时进行,为了达到某个目的需要增加一项奖励/惩罚,并相应增加一个或多个直接相关状态信息,帮助模型识别现象与反馈之间的因果关系,这一设计理念很直观也很有效。
2.2.2 间接相关信息
间接相关信息指的是reward中没有即时联动项的状态信息,其所代表的事件需要一段时间后才得到反馈。相对于直接相关信息,DRL利用它们建立决策相关性的难度更高,学习效率更差。比如下图中的游戏画面,agent要通关必须先吃钥匙,假如reward没有专门设置“吃钥匙”的奖励项,那么吃钥匙的好处要等到通关的时候才能体现出来。
再比如小车要学会到达终点,当前位置坐标、朝向、速度、加速度、终点坐标、周围障碍物的分布似乎都与到达终点这一目标有关,假如我们暂时没有针对这个目标做更细化的credit assignment,而只设置了到达终点奖励,那么这些信息就都属于间接相关信息,agent只有经过充分探索后才能发现某时刻这些信息的变化与最终达到终点之间的联系。
间接相关信息通过某些手段可以转化为直接相关信息,从而提高DRL的学习效率。最简单的方法是对任务目标做更详细的credit assignment并增加相应的reward奖励/惩罚项,如果某状态信息恰好与之即时联动,相应状态信息就成为了直接相关信息。还以小车为例,如果在reward中增加靠近终点奖励或远离终点惩罚,那么小车的朝向(配合小车当前坐标和终点坐标)就成为了直接相关信息。更多关于reward函数设计的内容我在下一篇中再详细介绍。
2.2.3 相关信息预处理
无论是直接相关还是间接相关,原始信息都要经过神经网络的提炼才能转化为action输出,提炼难度与学习效率和最终性能呈反向相关。如果我们提前对原始信息做些二次加工,人为提炼出与学习目标更相关的因素,相当于替神经网络干了一部分活儿,虽然不那么elegant,但往往能收到奇效。举个极端例子,直接告诉agent钥匙的相对坐标在哪儿,一定比神经网络通过原始图像更容易学到吃钥匙的操作。由于强化学习的优化目标是折扣累加的长期收益,这使得reward起作用的方式较为间接,无法像有监督学习那样为神经网络的feature extraction提供很好的指导,这也是DRL训练效率低下的根本原因。因此,我们在状态空间上多下一点功夫,DRL学习的难度就降低一点。在资源有限的情况下这很有可能就是训不出来和训得出来的区别,也有可能是性能不达标与性能达标的区别。
2.3 统一性考虑
当我们已经筛选出了所有相关信息,接下来该以何种形式使用它们呢?把他们拉成一组向量塞到神经网络里行不行?当然可以,但那样做只能得到适用于当前特定场景的policy。比如状态信息中包含了其他小车的信息,则训练出的policy只适合特定数量小车的任务,假如车数增加或减少,输入向量维度随之变化,policy就没法用了。因此,我们必须合理设计状态信息使其对环境主要因素的改变有最起码的兼容性,我把它称之为统一性考虑。具体地,这里的统一性又包含形式统一和逻辑统一。
2.3.1 形式统一
为了保证输入向量长度恒定,我们需要找到一种统一形式把不同信息填到对应的位置。比如小车周围装了一圈测距雷达,按固定顺序输出一维距离向量,那么无论把小车放到什么地方,这些信息所代表的含义也不会变;或者采用imagelike的状态表示方式,把地图信息网格化,无论是作为二维channel或拉成一维向量,都能保持外在形式的统一。
针对上述第二种方案,为了将信息离散化到网格点上,不可避免地会带来精度损失。但在实际应用中,只要网格尺寸合理,这样的精度损失是可以接受的。事实上,离散化操作本身会在一定程度上降低学习难度从而带来性能的提升,有paper报告在Montezuma's Revenge游戏里通过离散化agent和环境的信息,DRL模型性能不仅没有下降,反倒提升了30%多。
2.3.2 逻辑统一
再说回上一节的自动充电任务,如果输入的是绝对电量,而不包含低电量阈值(预警电量),DRL模型需要通过大量探索,根据当前电量与是否被惩罚的经验去摸索出预警电量是多少,并用于指导action的生成。这个隐性阈值会固化到网络参数中,如果客户后续希望提升预警电量,policy就又要用新阈值重新训练了。为了避免这种情况,我们可以把绝对电量改为相对电量(绝对电量/预警电量),能够直接反映当前电量与预警电量的关系,即使预警电量被改变也不影响模型的使用,因为此时固化到网络参数中的知识不再是某个电量阈值而是比例阈值。
2.4 效果验证
当我们设计好状态空间或对原状态空间进行修改后,接下来需要通过实验验证其是否达到预期效果。验证方法可以分为三类:模仿学习验证,直接验证和缺省验证。
2.4.1 模仿学习验证
如果项目已经有一个较好的baseline,可以搭建一个policy网络,专门模仿该baseline在各种状态下的action,如果状态中包含了正确决策所需的相关信息,那么得到的policy性能就会越接近baseline。考虑到有监督学习的高效性,这是验证状态信息有效性的一种较快方式,尤其适用于项目初期一片懵懂的时候。
2.4.2 直接验证
如果没有这样的baseline,那就只能用直接验证了,即用DRL训练一个policy并验证其效果。为了提升效率,也可以只比较训练中途(固定步数、固定样本量)的性能,因为很多时候好状态和差状态的won-lost关系在较早的时候就确定了,当然这必须建立在对特定任务和特定算法训练过程较为熟悉的基础上,在DRL训练中,早期的性能优势无法保持到最后的情形也时有发生。另外可以优先选择收敛速度较快的DRL算法(先不考虑绝对性能),从而快速验证新状态相对旧状态的改进效果。
2.4.3 缺省验证
当我们已经训练得到一个不错的policy时,可以用缺省的方式验证每个状态信息的作用大小,即正常输入其他信息,而将目标信息取合理区间内的定值(如区间中点),测试性能损失的百分比。损失越大说明该状态信息越关键,反之则说明作用越边缘化,有时候甚至会发现性能不降反升,说明该信息有干扰作用,还是去掉的好。缺省验证的意义在于,剔除那些无用或起反作用的状态,为进一步优化关键状态和弱作用状态提供指导。
3. 总结
与学术研究不同,在DRL落地工作中,状态空间设计是如此的重要,所以我用了很长的篇幅探讨了其中各种细节。此外,尽管我已经十分克制,但仍然不得不引入了大量关于回报函数(reward)的描述和设计理念,这是因为在实践中,状态空间和回报函数的设计几乎是水乳交融的,很难做到泾渭分明,往往修改了其中一个,另一个也需要相应做出改变。在下一篇中,我将集中介绍回报函数的设计,当然难免也会涉及到一些状态空间设计的内容,总之,一起服用效果更佳~
六、回报函数篇
1. 前言
回报函数(reward)设计在DRL应用中是极其重要的一环,通过将任务目标具体化和数值化,reward就如同一种特殊语言,实现了目标与算法之间的沟通,算法工作者在这里面承担了翻译的角色,翻译的好坏体现了其对任务逻辑的理解深度,决定了agent最终是否能学到期望的技能,并直接影响算法的收敛速度和最终性能。结合上一篇的内容,我们知道DRL算法中reward负责引导神经网络挖掘状态信息中的决策相关因素并经过提炼后用于action的计算生成。既然reward设计这么重要,想必分析起来又会是像状态空间那样的长篇大论吧。AI时代有了深度神经网络,也不缺数据和算力,难道这点人工就不能省下来吗?
2. 非要手工设计吗?
鉴于强化学习算法对优秀reward函数设计的依赖,学术界提出了很多方法改善这一状况。比如逆向强化学习,利用expert demonstration(专家示范)学习到reward函数,再用这个reward函数训练RL策略。此外,还有一大堆模仿学习的方法,干脆抛开reward直接拟合专家策略。以上方法的前提是要有专家数据,不具备普适性,这里就不多说了。
近年来学术界有个趋势,希望通过深度神经网络自动学习reward函数,从而代替手工设计。其中一篇比较有代表性的工作[1],在传统Actor-Critic框架的基础上,又增加了一个Reward网络,输入当前的状(state)和动作(action),输出这一步的reward值。Actor和Critic网络都依据最新的reward网络输出进行优化,而reward网络则依据人类(supervisor)的喜好用有监督的方式进行更新,具体方法是周期性地采集一些episode片段并成对地让人类观看,后者反馈更喜欢哪个片段,再由reward网络拟合这个二分类问题。这篇paper的思想我非常欣赏,现实生活中确实存在很多难以分解和量化的目标,比如让agent学会后空翻,reward就很难设计;有些目标虽然是本身是量化的,但过于笼统难以分解成具体的reward,此时这种方法就很有吸引力。
可惜啊~~~,我们在现实中遇到的需求不大可能是第一种情况,比起“不能做→能做”,工业界更喜欢“能做→做得更好”,一般都会给出一个明确的指标用来最大化或最小化。假如这个指标能通过少量采样统计出来或体现出优劣关系,那就适合采用这种方法自动学习reward函数,而且不需要人的介入,全程自动化进行。但如果这个指标必须通过大量采样才能统计出来或者获得可靠的优劣关系,该方法的效率就非常低了,这种情况下就得老老实实地手工设计reward,走传统DRL路线了。
3. 主线reward和稀疏回报问题
当我们拿到一个任务目标,往往能够简单分析就能找出与该目标紧密联系的主线事件,比如小车到达终点的任务中“到终点”就是这样的事件,拳皇里“KO”也是这样的事件,超级马里奥中“通关”还是这样的事件。此时我们就有了第一个reward项,我把它称之为主线reward,一般是正奖励,当主线事件发生时即反馈给agent。理论上,只要有主线reward就可以用强化学习算法进行训练了。在很多简单任务中,agent在探索过程中靠误打误撞就能以一定概率遇到主线事件,通过正反馈尝到甜头后通过更新policy逐渐提升得到奖励的概率直至收敛。可是当问题稍微复杂一些,通过随机方式探索到主线事件(正样本)的概率变得很低,而强化学习算法本身的数据效率不高,只靠这些少得可怜的正样本,算法难以收敛或收敛很慢。下面我引用伯克利RL大神Pieter Abbeel的课程CS294-40中的例子具体说明。
在上图中agent从最左端起始位置S处出发(state=1),目标是到达最右端的G位置(state=5),允许的动作包括向左一格和向右一格。在图中的reward系统下,agent只有到达G才能得到1分的奖励(主线reward),其他状态都没有任何反馈,那么仅靠随机探索agent是很难到达G的,因为中间缺乏有效信号来指导agent向正确的方向前进。在强化学习中,这一类问题被称作稀疏回报问题(Sparse Reward Problem),一直都是DRL领域研究的热点。显然,只定义了主线reward的任务几乎都是稀疏回报问题,对数据效率低下的RL算法而言,学习难度是很大的。
当稀疏回报问题遇上高难度探索(Hard Exploration)问题,DRL算法收敛更是难上加难,几乎是不可能完成的任务。比如让机器人学会打开盒子,抓起木块放到盒子里,然后再把盒子盖上,如果只在成功完成这一系列动作的时候才给出奖励,那么用一般的DRL算法和探索策略根本不可能学会目标技能,因为正样本产生的概率跟闭着眼用针尖扎到平面上一点的概率差不多,可以认为是0。
针对稀疏回报问题,学术界提出了很多方法,比如通过鼓励agent探索未见过的状态,提高正样本利用率,或者干脆用遗传算法或进化策略代替RL学习policy网络。这些方法不在本篇的讨论范围内,我们关心的是如何通过reward设计本身来规避稀疏回报问题,并尽可能提高训练效率和最终性能。
此外,联系上一篇状态空间设计的内容,由于主线事件通常难以一蹴而就,大部分状态信息相对于主线reward也就都属于间接相关信息,这也从另一个角度解释了为什么在稀疏回报下算法训练难度高。
4. 目标分解和辅助reward
既然只有主线reward不行,我们接下来就要将原始任务目标进一步分解成子目标,并分别给予合理的奖励或惩罚,从而达到引导agent趋利避害提高主线事件发生概率的目的。学术界一般称该过程为credit assignment,credit意译过来就是功劳,说的是某个子目标在达成总目标的过程中起了多大作用,是正向作用还是负向作用。这些子目标对应的reward可以称之为辅助reward,它们使reward不再稀疏。通常情况下,为了保证主线奖励的核心地位和吸引力,各种辅助reward的绝对值都设得相对较小,以免喧宾夺主。
4.1 目标分解实例
Agent在环境中探索时需要获得反馈,即刚刚的决策好不好,反馈越及时学得越快,理想情况是每一步都有反馈。还以小车导航到终点的应用为例,除了抵达终点+10分,如果每次靠近终点也+1分,那么小车在抵达终点之前就学会主动靠近终点,这样探索到抵达终点的概率也大大提高了,DRL算法收敛速度自然会加快。
除了抵达终点,小车还要避免与障碍物和其他小车发生碰撞,我们还要对碰撞事件做出惩罚。为了使agent更好地学会避免碰撞,我们除了对已经发生的碰撞事件给予惩罚,还可以再增加一个预防式的靠近惩罚,并利用状态空间里的直接相关信息——与最近邻居的距离,提高算法学习效率,具体可以参考状态空间篇。
辅助reward的设计建立在对任务逻辑的深刻分析和理解之上,有很多细节都会对最终目标的实现产生正向或负向的影响,值得我们深入挖掘。比如,为了使小车尽快到达终点,就要求少绕路,而绕路的典型表现是转弯多,于是可以增加对转弯的惩罚。类似这样的链式思考有助于找到更好的辅助reward,帮助降低学习难度和提升最终性能。
此外,由于将最终目标分解成了子目标,在设计对应辅助reward时往往很容易找到与之即时联动的直接相关状态信息,或者相关性较强的间接相关信息。事实上,我们每设计一个reward项,就应该回过头去检查状态空间中是否包含了直接或间接相关信息,已经包含的信息是否足够直接高效,有没有改进的空间。
4.2 杜绝异常行为
OK,让我们来捋一捋目前的reward项:抵达奖励,靠近终点奖励,碰撞惩罚和转弯惩罚。Reward项一多,我们就要特别注意它们之间的相对大小。首先,应该避免某个(些)reward项的绝对值过大,以至于淹没其他reward项的影响,必要时应使用系数加以调控;其次,应该避免reward项的不合理取值及多项reward之间的不合理相对大小,导致agent学到异常行为。因为不合理reward造成的常见异常行为主要包括三种类型:鲁莽、贪婪和胆怯,怎么感觉我在说炒股呢~~~
4.2.3 鲁莽
鲁莽行为指的是reward中漏掉了针对某个不希望出现的事件的惩罚项或者惩罚力度太小,被其他reward项盖过,导致agent无法学到主动规避该事件或者权衡利弊后仍然选择接受该事件的惩罚以换取更大收益。比如下图中金币搜索任务中,设计者忘了给予进入岩浆的惩罚,结果机器人为了尽快得到金币而甘愿“赴汤蹈火”;在小车导航的例子中,碰撞惩罚相对于远离惩罚过小,小车可能为了尽快到达终点宁愿撞到其他小车上也不愿意绕远。
4.2.1 贪婪
靠近终点奖励使reward变稠密了,但这样做就够了吗?我们说过RL追求的是长期收益,事实上对小车来说收益最高的选择不是尽快抵达终点,而是不断重复“靠近-远离”的动作,如此一点点地累加,收益远超过抵达终点的一锤子买卖!很显然,agent钻了reward设计漏洞的空子,变得不思进取,贪得无厌。为了防止这种情况发生,我们还要对原地不动或远离终点的行为进行惩罚,而且相对于靠近奖励,扣分太少也不行,否则agent仍然会发现钻空子是划算的。一劳永逸的办法是,将靠近终点的正向奖励改成微小惩罚,绝对值小于原地不动或远离惩罚,这样做的好处是不仅不给agent钻空子的机会,而且还能督促小车尽快向终点行驶。
再比如在蒙特祖玛的复仇中,要想通关(主线事件,+10)必须先吃到钥匙,为了鼓励agent吃钥匙我们还可以为这个子目标提供专门的奖励(+1)。这种情况下房间只有一把钥匙还好,但如果到处都是钥匙呢?Agent很可能会变得乐不思蜀,一直留在房间里找新的钥匙,忘记通关这回事了。如果真是这样,那就不如不设吃钥匙奖励,只保留主线奖励,由Agent自己去发掘通关与吃钥匙之间的内在联系。
实际上,除了主线reward应该提供正向奖励以外,其他辅助reward最好都设置为惩罚项。除非某个子目标与主线事件之间存在强关联,而且该子目标的达成是一次性的或者数量可控,否则不应轻易设置额外奖励项,因为这样很容易诱导agent学习到短视的贪婪策略,只捡芝麻,不要西瓜。
4.2.2 胆怯
与贪婪相反的另一个异常行为是胆怯,如果惩罚项很多且绝对值相对于主线reward太大,那么agent在探索过程中会收到大量负反馈,从而变得畏首畏尾,学习到各种意想不到的“绥靖”策略。比如在小车到终点的例子中,假如碰撞惩罚和转弯惩罚绝对值过大,agent有可能宁愿选择原地不动,这是因为训练初期policy很差,需要经历大量转弯和碰撞后才可能出现主线事件(到达终点),而收到的负反馈完全湮没了主线奖励,因此在agent看来原地不动的长期累计收益暂时不比到终点差,尽管只是暂时的,但agent很可能陷在这个局部最优里出不来了。
在上述情况下,你会发现只需要将惩罚项绝对值减小,突出主线奖励的影响,其他什么也不用干,DRL模型就能顺利收敛了。当然,还可以适当降低折扣因子,让agent变成“近视眼”,更多关注眼前利益,忽略长期的负收益期望(靠后的负反馈都被折扣掉了),只要agent“迈开腿”出来探索,就有更大可能遇到主线事件,并在主线reward的奖励下学习到目标技能。折扣因子的设置技巧我将在训练篇中详细介绍。
4.3 Reward Shaping
把任务目标分解得足够细,又避开了上述各种坑,这样就完美了吗?并没有~~~如果你读过Andrew Ng在1999年发表的关于reward shaping的paper,你就会发现还可以让RL算法收敛得更快一些。Reward shaping技术的证明这里就不赘述了,喜欢手推公式的朋友可以去读原著。我大概说下原理,在原有reward基础上增加一项shaping reward,该项代表某种势能函数,与最终目标的差距决定了势能大小。对于我们上一节举过的例子,如果把reward修改为下图中的形式,agent每向右移动一格都会获得奖励,且离G越近奖励越高,那么agent就很容易被引导到G位置,从而大大加速算法收敛。
5. Optimal Reward Problem
看到这里你可能会问,针对特定任务,比如小车导航到终点,是否存在一组最优reward使得DRL算法在同等条件下收敛最快、性能最高?答案是肯定的,但要想找到它是困难的,该问题在学术界被称为Optimal Reward Problem(ORP),解决方案包括暴力搜索[2]、基于在线策略梯度的PGRD[3]、分层强化学习[4]、遗传算法[5]、Bayes方法[6]等等,有兴趣的朋友可以找来相关paper读一读,相关参考文献我列到最后。
6. 总结
总结一下,reward设计的原则是:尽可能稠密(最好每步都有反馈),能够反映任务目标/子目标逻辑,与状态空间相呼应,控制好各项取值和相对大小,避免异常行为,适时采用reward shaping。当算法选择好,动作空间定义好,状态空间和回报函数都设计好,接下来就该进入训练环节了。
参考
^Christiano, P. F.; Leike, J.; Brown, T. B.; Martic, M.; Legg, S.; and Amodei, D. 2017. Deep Reinforcement Learning from Human Preferences. Neural Information Processing Systems: 4299-4307.
^Sorg, J.; Singh, S. P.; and Lewis, R. L. 2010. Internal Rewards Mitigate Agent Boundedness. International Conference on Machine Learning: 1007-1014.
^Sorg, J.; Lewis, R. L.; and Singh, S. P. 2010. Reward Design via Online Gradient Ascent. Neural Information Processing Systems: 2190-2198.
^Bratman, J.; Singh, S. P.; Sorg, J.; and Lewis, R. L. 2012. Strong Mitigation: Nesting Search for Good Policies within Search for Good Reward. Adaptive Agents and Multi Agents Systems: 407-414.
^Niekum, S.; Barto, A. G.; and Spector, L. 2010. Genetic Programming for Reward Function Search. IEEE Transactions on Autonomous Mental Development 2(2): 83-90.
^Hadfield-Menell, D.; Milli, S.; Abbeel, P.; Russell, S.; and Dragan, A. D. 2017. Inverse Reward Design. Neural Information Processing Systems: 6765-6774.
七、训练篇
为了保证DRL算法能够顺利收敛,policy性能达标并具有实用价值,结果有说服力且能复现,需要算法工作者在训练前、训练中和训练后提供全方位一条龙服务。我记得GANs刚火起来的时候,因为训练难度高,有人在GitHub上专门开了repository,总结来自学术界和工业界的最新训练经验,各种经过或未经验证的tricks被堆砌在一起,吸引了全世界AI爱好者的热烈讨论,可谓盛况空前。在玄学方面,DRL算法训练有得一拼。但毕竟在科研领域没有人真的喜欢玄学,只有久经考验的一般化规律才能凝结成知识被更多的人接受和推广。本篇接下来的内容融合了许多个人经验和各种参考资料,算是在DRL训练“去玄学”化上做出的一点微不足道的努力。
1. 训练开始前
1.1 环境可视化
如果条件允许,开始训练前最好先可视化一个随机环境,观察是否会出现你希望的状态(即上一篇里的主线事件)。如果靠随机选择action都能以一定概率探索到目标状态,那说明该任务难度比较低,心里就可以更有底;如果从来不会出现目标状态,说明该任务难度较高,需要在状态空间和reward函数设计时特别下功夫,从而更好地引导agent向目标状态前进。
1.2 数据预处理
注意reward只能进行rescale,而不能整体平移(减去均值)。回报函数中各项reward的符号以及它们之间的相对大小唯一确定了回报函数的实际功能,各项reward的整体缩放对其没有影响,但整体平移会改变这种相对大小,也就改变了回报函数的功能。事实上,哪怕是clip操作也在一定程度上存在这种问题,但通常影响不大。
2. 训练进行中
2.1 拥抱不确定性
终于要开始调参了!如果你做过CV项目,会发现相对来说DRL训练的不确定性更高,可复现性更差。这是因为DRL算法不仅超参数多,而且对它们非常敏感,这里贴张图给大家感受一下。
上图是三个DRL算法(纵向)在五个Atari游戏(横向)中的得分随学习率变化的趋势。可以看到,以10倍为单位,高性能所对应的学习率区间普遍很窄,要达到最优性能真得靠地毯式搜索。其实原作者本意是想通过这张图表明他们的方法对超参数变化抵抗力较好……,况且这还只是针对学习率这一个超参数。
当我们刚开始尝试用DRL算法解决一个全新问题时,性能好坏甚至都是其次,能否收敛才是最关键的。牛逼闪闪的OpenAI Five都听说过吧?其团队成员在接受采访时承认,他们第一次将训练跑起来后因为心里实在没底,干脆全体度假去了,回来后打开屏幕发现竟然收敛了,上帝保佑!
尽管DRL对超参数如此敏感,也没有必要过分悲观。当我们在心理上接受了这一事实,并开始潜心研究时,就会发现DRL训练还是有迹可循的,尤其是随着实际经验的积累和对算法本质理解的不断深入,将每个超参数的作用都了然于胸,那么训练出优秀policy的可能性就会大很多。接下来我以DQN,DDPG和PPO为例,介绍一下其中的主要超参数和调参技巧。
2.2 DRL通用超参数
有些超参数不是某个算法所特有,而是DRL普遍使用的,而且作用原理和设置依据都差不多,我放到最前面集中介绍。典型的通用超参数包括折扣因子、网络结构和学习率。
2.2.1 折扣因子
2.2.1.1 作用原理
折扣因子通常以符号γ表示,在强化学习中用来调节近远期影响,即agent做决策时考虑多长远,取值范围(0,1]。γ越大agent往前考虑的步数越多,但训练难度也越高;γ越小agent越注重眼前利益,训练难度也越小。我们都希望agent能“深谋远虑”,但过高的折扣因子容易导致算法收敛困难。还以小车导航为例,由于只有到达终点时才有奖励,相比而言惩罚项则多很多,在训练初始阶段负反馈远多于正反馈,一个很高的折扣因子(如0.999)容易使agent过分忌惮前方的“荆棘丛生”,而宁愿待在原地不动;相对而言,一个较低的折扣因子(如0.9)则使agent更加敢于探索环境从而获取抵达终点的成功经验;而一个过低的折扣因子(如0.4),使得稍远一点的反馈都被淹没了,除非离终点很近,agent在大多数情况下根本看不到“光明的未来”,更谈不上为了抵达终点而努力了。
2.2.1.2 选取方法
总之,折扣因子的取值原则是,在算法能够收敛的前提下尽可能大。在实践中,有个经验公式1/(1-γ),可以用来估计agent做决策时往前考虑的步数。根据对特定任务的分析,合理选择γ值,避免“近视”和“远视”。比如可以根据观察或统计agent到达终点所需的步数分布,选择合适的步数使得agent在该步数内的探索下有一定概率到达终点(正样本),注意这个概率越高训练难度就越小,然后利用经验公式把该步数换算成γ即可。
2.2.1.3 Frame Skipping
上述折扣因子的选择方法并非无往不利,有时我们会面临这样的窘境:无法设置合理的γ在“近视”和“远视”间找到满意折中,常见于“细粒度”复杂任务。复杂决定了agent需要看得很远才能做出合理决策,而“细粒度”指agent决策间隔很短以至于一段较长的episode只对应较少的agent状态变化。仍以小车导航为例,0.1s的决策间隔显然满足了实时防碰撞的机动性要求,但也大大延长了episode长度,即到达终点所需步数。如果agent平均需要1min才能到达终点,那就要求向前考虑1min/0.1s=600步,按照经验公式计算合理的折扣因子γ≈1-1/600=0.998,如此高的折扣因子+如此长的episode,训练难度可想而知。
假如我们在保证足够机动性的前提下适当延长决策间隔,比如0.5s,中间4帧重复上一次决策的action不变,相当于跳了几帧达到“快进”效果,从而使episode长度大大缩短,训练难度也直线下降,即使采用较高折扣因子也能顺利收敛。通过这个例子我们也可以知道,在DRL中agent“看得远”表面上指的是向前考虑的步数多,实质上是指agent向前考虑的系统动态演化跨度大。
2.2.2 网络结构
DRL算法中的网络结构也属于超参数,然而与CV任务不同,DRL绝不应该片面追求网络的复杂化,否则你会发现训练根本无法收敛。对于网络结构的选择,DRL有自己的规矩——契合状态,够用就好。前者针对网络类型,后者针对网络深度。
2.2.2.1 网络类型
网络类型的选择主要取决于状态空间设计,如果状态信息是向量式的,即一组拉成一维的标量,比如位置、角度、速度等,那就适合采用全连接(MLP)网络;如果状态信息是imagelike的,比如图像,或者其他以二维形式重组的信息,就适合采用卷积神经网络(CNN)。实际应用中往往同时包含这两种状态信息,因此网络类型也可以既有CNN也有MLP,处理完各自对应的输入信息后,在高层通过concat操作汇集在一起,再通过若干层全连接,最后输出action或Q/V值。
对于on-policy算法,episode形式的数据天然适合采用RNN来挖掘更多时序信息,但同时也会显著提高训练难度,用与不用取决于决策对时序相关性的依赖程度。换句话说,如果之前的经验对当前决策很有参考意义(比如Dota)就适合用RNN,反之仅依靠即时信息做应激式决策就足以应付就没必要用RNN。实践中经常采取折中方案,将最近几个step的原始状态信息叠加到一起作为当前时刻的实际状态信息输入policy,既可以挖掘一定范围内的时序信息,又避免增加训练难度。
2.2.2.2 网络深度
至于网络深度,千万不要认为越深越好,虽然深层网络的表征能力更强,但训练难度非常高,更适合有监督训练。DRL算法由于数据效率低下又缺乏直接监督信号,并不擅长以end-to-end的方式训练过深的网络,如果还同时采用了RNN结构,那就是相当不擅长了。除非你有DeepMind或OpenAI那样的硬件资源,否则还是现实点好。其实多读几篇DRL方向的paper就会发现,所谓deep往往只是2-3层MLP或4-5层CNN,虚张声势的背后就是这么知趣。
当然,如果任务逻辑和状态信息确实非常复杂,浅层网络不足以提供所需的特征提取和加工能力,那么可以考虑适当加深网络,但仍应以够用为准则,不可矫枉过正。我曾经试过把已经work的3层MLP改成10层,发现根本就不收敛或收敛极慢,后来在各层中加了类似ResNet的跳线,勉强收敛,但相同训练量下性能还不如3层网络。如果输入状态信息是ImageNet那样的自然图像,可以像视觉检测应用那样,先用有监督方式预训练一个backbone,然后再放到DRL里finetune。
2.2.3 学习率
从整个AI技术来看,学习率是如此的平淡无奇,相关调参技巧早就被研究透了。毕竟DRL算法里的学习率也遵循同样的基本法——大了收敛快,稳定性差,且后期影响性能;小了收敛慢,浪费时间。学习率常用的淬火操作也同样可以应用到DRL中。当然,对于不同DRL算法而言,学习率也可能有各自的特点,如有必要,我会在下文介绍DRL特色超参数的时候顺带多说几句。
2.3 DRL特色超参数
2.3.1 DQN
DQN的特色超参数主要有:buffer size,起始训练时间,batchsize,探索时间占比,最终epsilon,目标网络更新频率等。
Buffer size指的是DQN中用来提高数据效率的replay buffer的大小。通常取1e6,但不绝对。Buffer size过小显然是不利于训练的,replay buffer设计的初衷就是为了保证正样本,尤其是稀有正样本能够被多次利用,从而加快模型收敛。对于复杂任务,适当增大buffer size往往能带来性能提升。反过来过大的buffer size也会产生负面作用,由于标准DQN算法是在buffer中均匀采集样本用于训练,新旧样本被采集的概率是相等的,如果旧样本或者无效样本在buffer中存留时间过长,就会阻碍模型的进一步优化。总之,合理的buffer size需要兼顾样本的稳定性和优胜劣汰。顺便说一句,针对“等概率采样”的弊端,学术界有人提出了prioritized replay buffer,通过刻意提高那些loss较大的transition被选中的概率,从而提升性能,这样又会引入新的超参数,这里就不做介绍了。
起始训练时间的设置仅仅是为了保证replay buffer里有足够的数据供二次采样,因此与batchsize有直接关系,没啥可说的。Batchsize指的是从replay buffer中二次采样并用于梯度计算的batch大小,和CV任务中的设定原则基本一致,即兼顾训练稳定性和训练速度,也没啥好说的。
探索时间占比和最终ε共同决定了DQN探索和利用的平衡。ε-greedy策略在训练开始的时候,随机选择action的概率ε=1,探索力度最大;随着训练进行ε逐渐线性下降直至达到最终epsilon保持恒定,之后DQN的训练将以利用为主而只保留少量探索。因此,最终ε取值在区间[0,1]内靠近0的一端。探索时间占比指的是ε从1下降到最终ε的时间占总训练时间的比例,在(0,1)内取值,用来调节以探索为主到以利用为主的过渡。通常来说,复杂任务的探索时间占比应设得大一些,以保证充分的探索;最终ε不宜过大,否则影响模型最终阶段“好上加好”的性能冲刺,因为最好的状态往往是在足够好的Q网络指导下才能探索到的,训练后期过强的探索干扰了习得知识的利用,也就阻碍了性能的进一步提升。
标准DQN引入了一个延迟更新的目标网络用来计算Q的目标值,避免Q网络误差的“自激效应”,并借此来提高训练稳定性。目标网络更新频率就是用来控制这个延迟程度的,时间到了就把Q网络的参数整个复制过来。通常情况下根据具体问题,参考Q网络的更新周期设定,比如Q网络每1个step更新一次,目标Q网络可以设定每500个step更新一次。
2.3.2 DDPG
DDPG的特色超参数主要包括:buffer size,batchsize,目标网络软更新参数τ,探索噪声等。其中很多超参数与DQN类似,比如buffer size和batchsize,这里就不重复介绍了。
DDPG也使用了目标网络(目标Q网络和目标Policy网络)稳定训练,不同的是DDPG的目标网络与主网络更新频率相同,稳定效果来自于软更新(soft-update),即(1-τ)*target + τ*main,τ取很小的值(DDPG paper中建议0.001)限制每次更新的幅度。
DDPG值得特别介绍的是探索噪声及其参数。由于policy网络输出确定性action,DDPG的探索依靠在输出action空间叠加噪声来实现。可选的噪声类型主要包括Gaussian噪声和DDPG paper推荐的ou噪声(Ornstein-Uhlenbeck),后者相对于前者主要是增加了噪声强度逐渐衰减的功能。这两种噪声的主要参数是噪声方差,方差越大探索力度越强。虽然论文推荐使用ou噪声,但我在实践中发现ou噪声并不一定比Gaussian噪声效果好,还是要看具体任务。
后来DeepMind又提出了adaptive parameter noise,抛弃了在输出层叠加噪声的方法,转而采用在policy网络靠近输出的若干层网络参数上叠加噪声,优点是探索更充分,毕竟参数噪声的影响范围更大,而且可以根据实际情况自适应调节探索力度。类似地,我发现参数噪声在有些任务上效果不错,但在另一些任务中不如传统噪声。综上所述,关于不同噪声的优劣没有确定性结论(局限于我的个人经验),具体选择哪种噪声,还要实际试过才知道。
此外,值得一提的是Q网络和policy网络采用了不同的学习率,且一般Q网络的学习率比policy网络大一个数量级,比如前者用1e-3,后者用1e-4。这样做的原因是,用于更新policy网络的梯度完全来自于Q网络,两者地位不是对等的。
2.3.3 PPO
作为on-policy方法,PPO与前两种DRL框架有很大不同,无论是算法原理还是超参数设置。PPO的特色超参数包括:采样环境数量,episode长度,entropy系数,V网络系数,GAE factor,PPO cliprange等。
并行采样的环境数量越多,整体的探索效率越高,绝对收敛时间越快,该参数的设置主要取决于可用的硬件资源。
PPO的训练基于episode(或trajectory),将其中每个中间state到episode结束时的Return作为目标值拟合一个V网络,并用V网络作为baseline指导policy网络的更新。为了便于训练,通常每个环境都采集固定长度的episode并返回主进程中拼成一个batch。Episode越长,每次计算梯度时的数据量越大,但消耗内存也越多。Episode长度通常取4096, 2048, 1024等2的次幂,原因是更新网络参数时整个batch还会再分成minibatch(2的次幂比较好分),遍历若干个epoch,从而提高数据利用率,注意minibatch不能太大,否则有可能导致“学不动”的现象。在实际应用中,除了考虑内存开销,episode长度选取也跟任务难度息息相关。以小车导航为例,训练刚开始时agent可能需要探索很久才能幸运地抵达终点,episode长度最好能囊括整个探索过程,这样中间状态与理想状态(到终点)间的演进关系就很容易学习到。当然,episode不可能无限长,如果探索难度实在太高,那也只好提前终止探索,把截断的部分放到下一个episode中。
PPO算法的loss由三部分组成:policy loss,value loss和entropy loss。其中entropy loss项的系数是一个非常重要的超参数,对收敛速度和最终性能有直接影响。我在算法选择篇介绍PPO的探索-利用平衡时,说过随着训练进行policy输出的action分布的variance会越来越小,反映到统计指标上就是entropy越来越小。这本来是一个自然发生的过程,不需要人的干预,然而DRL训练早期往往受到各种local minima的干扰,容易陷入“拣了芝麻丢了西瓜”的怪圈。为了避免模型过早迷失方向,PPO加入了entropy loss用于强迫policy输出不那么“尖锐”的action分布,从而起到加强探索的效果。Entropy系数负责调节这种“强迫”力度,合理的系数既能确保训练早期充分探索从而使模型向正确方向前进,又能使模型在训练中后期充分利用学到的技能从而获得高性能。对于不同任务,最优entropy系数往往各不相同,需要若干次试错才能找到。比如在训练开始后policy entropy快速下降说明模型陷入了局部最优,根本没学到有用技能,这时就应该提升entropy系数;如果训练很长时间policy entropy仍然未下降或者下降缓慢,说明模型探索过头了,学到的知识被随机性淹没,无法进一步用来提升性能,此时应该适当降低entropy系数。
V网络系数是PPO loss中value loss项的系数,通常取0.5(policy loss系数默认是1),在实践中不太需要修改。
由于on-policy算法对数据的使用方式是“现采现用,用完就扔”。为了防止policy跑偏,在错误道路上越走越远,需要通过特定方法限制其每次参数更新的幅度。PPO与更早的TRPO类似,核心思想都是针对更新前后policy输出的KL散度设定阈值,但PPO通过一个简单的clip操作大大简化了运算,兼顾了效率和性能。PPO相关的参数主要是cliprange,通常取略大于0的小数,代表使policy更新前后KL散度控制在1-cliprange到1+cliprange之间,超出该范围的梯度直接被忽略(相当于对应数据被弃用)。Cliprange越小训练越稳定,越大越节省数据。一般在训练早期取较小的值,比如0.2,保证训练平稳进行;到训练后期可以适当放大,因为此时policy已经足够优秀,所采集数据中正样本比例非常高,可以放心利用。此外,PPO使用了GAE来估计advantage,相应增加了一个超参数GAE factor,用于在bias和variable之间寻求平衡,也是在(0,1]内取值,一般都默认取0.95。
2.4 给DRL初学者的建议
以热门平台作为切入点。热门平台维护频率高,关注度高,遇到问题容易找到解决方案。比如在OpenAI Gym平台上找些简单的任务多练手,baselines里提供了大量参考代码。使用现成代码训练的最大好处是可以排除bug的干扰,把注意力都放在调参上,这对于初学者非常重要。
不要迷信默认超参数。在实际应用中可以先采用参考代码里的默认超参数,但一定要记住它们不是万能的。可以用tensorboard显示出各种训练曲线,理解其中主要曲线的含义和作用,并根据这些曲线判断超参数设置是否合理,应该朝哪个方向调整。
重视首次成功经验,学会简化问题。实践多了你会发现,对于特定Domain和特定算法,最优超参数组合分布都各有特点,最困难也最关键的是首次训练,一旦有了成功经验,后续根据观察结果做些微调就能得到高性能模型了。如果原任务训练难度太高,可以先尝试做适当简化,待成功收敛后再逐渐恢复任务难度。比如要实现任务目标A,必须满足条件B,C,D…,可以先暂时去掉对C,D,…的要求,只保留B,这样探索到A的概率就会显著提升,算法训练难度直线下降,待算法收敛并获得一定的经验后再逐步恢复所有条件。
保持耐心。DRL训练本来就挺慢的,很多时候除了等待什么都不用做。除非你对相关Domain的算法训练流程已经很熟悉,否则不要轻易断定算法不收敛,可以等等再说。也不要整天一动不动地盯着屏幕发呆,既浪费时间,更浪费生命,间歇式检查一下就好。
3. 训练收敛后
当你发现policy已经可以如预期的那样完成任务了,先不要忙着喜形于色,你还需要做些检查和分析工作以确保policy性能达到了最优。以A3C为例,你至少应该:
1. 观察Value网络对Returns拟合的精度如何,value loss是否还有进一步下降的空间?
2. 观察entropy是否处在合理范围内,相对于action维度是否过高或过低?
假如policy输出10维categorical分布,其entropy有两种极端情况:(1) 完全随机,每个维度概率均为0.1,此时entropy最大等于10*[-0.1*log(0.1)]=2.3;(2) 完全确定,其中一维为1.0其余都是0.0,此时entropy最小等于0。整个训练过程,entropy从2.3开始逐渐下降,当训练收敛后,entropy应该稳定在较低水平。如果太高则说明policy对决策信心不足,如果不是任务本身太复杂那就是entropy系数过大造成的,应该适当降低该系数增加exploitation的力度,很有可能继续提升模型性能。当然,entropy很少能降到0,除非是极其简单的任务。
4. 总结
经过前后近一个月零零星星的整理,这篇又臭又长的训练篇终于快要结束了,连我自己都觉得枯燥透顶,如果有哪位读者能坚持读到这里,我敬你是个勇士!我也时常怀疑写这些东西到底有没有意义,毕竟包括DRL在内的深度学习调参技巧往往琐碎而不成体系,很难总结得面面俱到,更何况新算法还在源源不断地涌现,旧的知识经验正在迅速“贬值”,就像现在有了Soft Actor-Critic,谁还用DDPG啊。最重要的是,假如读者不经过亲身实践,直接看这些干巴巴的总结,作用真心不大。对我自己来说,就权当备忘吧~
事实上,当你通过广泛阅读和动手实践,对各种DRL算法原理有了深入理解,对各种超参数的作用了然于胸,自然而然就会形成自己的调参方法论。只要算法收敛,性能达标,项目验收,调参的细节没那么重要。此外,调参工作毕竟只停留在“术”的层面,而我们应该追求的是算法之“道”,孰轻孰重每个人都要心里有数。祝愿每一个算法工程师最终都能做到“调尽千参,心中无参”。