前段时间自己手搓了一个简单的赛车小游戏,用来做深度学习实验。
算法选的是DDPG,这个在连续动作上的效果比较好。要是不知道DDPG是啥,可以去看看莫凡的教学。
游戏设计
游戏的控制很简单,aswd四个键前后左右,后面简化了一下,前进和刹车共用一个w键,
按下加速,到最大速度为止,松开减速,就像特斯拉的那种操纵一样。
参数设计
围绕车子一周设定了10个距离探测器,尤其是斜角的位置,放了两个。
然后再上传速度和角速度就行了。
场地
手工拼接了一个包含了所有地图原件种类的图作为训练场地。一旦这个图跑通了,后面就算随机拼接也是可以一直跑的。
训练
一开始就这样拿去训练,结果车子原地打转,因为没有前进方向,后面不得不又在道路中央加了个导航用的箭头
就是画圈的那个白色的长条,用它的方向来指示道路方向,运行时隐藏。
同时传入速度的向量改成相对于前进方向的垂直和水平分量(Vz, Vx),注意这里z是前后,x是水平
一开始我的奖励公式是:
<math xmlns="http://www.w3.org/1998/Math/MathML"> R e w a r d = − ( ( 1 − V z ′ ) 2 + V x ′ 2 ) × a 2 × d 2 Reward = - ( (1-V_z')^2+V_x'^2) \times a^2 \times d^2 </math>Reward=−((1−Vz′)2+Vx′2)×a2×d2
这里面 <math xmlns="http://www.w3.org/1998/Math/MathML"> V x ′ V_x' </math>Vx′和 <math xmlns="http://www.w3.org/1998/Math/MathML"> V z ′ V_z' </math>Vz′分别是相对于道路方向水平和垂直方向的速度,并且相对于最大速度做了归一化,a是车子与前进方向的夹角除以180度后的归一化值,d是车子与道路中心的归一化距离。因为奖励是负的,所以只要各个评分项越小,奖励越大。设计初衷就是想让车子尽量在道路中间开, 并且沿着道路方向前进。
一开始这个奖励公式收敛极快,才十几回合就学会直走了。然而在开始学习过弯的时候问题出现了。这个公式下,AI学过弯很慢,一个120度弯学了172回合才学会,换个角度大点的弯又得从头学。
而且随着训练次数的增加,AI开始越来越倾向贴着道路右侧开,到500回合的时候连直道都开不好了,遇到弯也不拐,到最后干脆直接一开始就右转加速撞墙,直到慢慢的停原地不动开始摆烂。
也就是说,出现了很多人提到的,随着训练次数增多,不但没收敛反而越学越差,开始劣化。
我不得不换个奖励公式
参考了一下网上其他人的公式,换成了
<math xmlns="http://www.w3.org/1998/Math/MathML"> R e w a r d = − ( ( 1 − V z ′ ) C o s θ + V x ′ S i n θ ) 2 − a 2 − d 2 Reward = - ( (1-V_z') Cos\theta + V_x' Sin\theta)^2 - a^2 - d^2 </math>Reward=−((1−Vz′)Cosθ+Vx′Sinθ)2− a2− d2
依旧是 <math xmlns="http://www.w3.org/1998/Math/MathML"> V x ′ V_x' </math>Vx′和 <math xmlns="http://www.w3.org/1998/Math/MathML"> V z ′ V_z' </math>Vz′分别是相对于道路方向水平和垂直方向的速度,并且相对于最大速度做了归一化,a是车子与前进方向的夹角除以180度后的归一化值,d是车子与道路中心的归一化距离。
这里引入了 <math xmlns="http://www.w3.org/1998/Math/MathML"> θ \theta </math>θ,就是车子前进方向与道路方向的夹角。
这个式子是对前面那个的优化,用相加代替相乘,这样就不会因为一个因素变成0而导致整个结果为0。在这种情况下,贴边前进是要扣分的。
这次学习直行没有之前那么快了,平均50-60回合才学会,然而就是学不会转弯,同时依旧存在训练一两百回合之后开始出现擦边、越学越差的问题。跑了一晚上,弯道没学会,反而连一开始学会的直走都不会了。只会开局右转撞墙,然后停在原地摆烂。
无奈,我只好把其他人的公式照抄过来试试,同时增加神经元的数量,又把Actor网络多加了一层
<math xmlns="http://www.w3.org/1998/Math/MathML"> R e w a r d = V z C o s θ − V x S i n θ − ∣ d ∣ × ∣ V x ∣ Reward = V_z Cos\theta - V_x Sin\theta - |d|\times|V_x| </math>Reward=VzCosθ−VxSinθ−∣d∣×∣Vx∣
这是个正向奖励的公式,速度没有做归一化, <math xmlns="http://www.w3.org/1998/Math/MathML"> V x V_x </math>Vx和 <math xmlns="http://www.w3.org/1998/Math/MathML"> V z V_z </math>Vz就是相对于道路方向水平和垂直方向的原始速度,θ是前进方向与道路方向的夹角,d是归一化了的车与道路中央的距离。公式最后一项就是对于车子远离道路中央的行为施加惩罚。
这个式子的目的就是使车子沿道路方向的速度尽量大,同时避免横向远离道路中央.
不得不说神经元多了AI也变得聪明许多。开始训练后,学习直线前进的速度跟之前那个差不多,也是平均50-60回合才会,但是不会再剧烈的左右晃动了,但同时依旧个弯道困难户,一开始运气好一下子开过了好几个弯,之后就一直撞墙,开始越学越差,最后退化成开局右转擦边撞墙。
这时我打算把整个过程的奖励和神经网络的loss画出来看看。
结果这一看才发现,loss在一开始的一百回合内慢慢变小之后,又开始渐渐变大,critic网络的loss在一开始几个回合之后便不再有大的波动。
然后是奖励曲线,由于撞墙会直接给0分奖励,所以整个奖励图就像个心电图,每一回合从零分开始再到撞墙零分结束为一个周期,一直重复。
(这里之前的曲线图删了,所以没有了)
不过也不是没有发现,就是每次撞墙前的reward居然是正的,而且整个回合属于是从起步开始reward就在逐渐变大,直到撞墙那一刻突然归零。
这就表示对于AI来说,起步右转恰恰是获得更高奖励方式,目前给的奖励公式居然在鼓励起步右转撞墙。这显然是不对的。
罪魁祸首就是因为从零加速的过程中因为速度增加而追加的奖励,大于了因为偏离道路中央而施加的惩罚。所以整体的奖励是在增加的。
针对这个问题我又把公式改了又改,依旧不理想,要么最后又变成开局撞墙,要么学不会转弯。
经过一段时间的思考,我突然想起,可以用车上的距离传感器的数据算奖励
具体是这样的,取所有传感器中距离最小那个值。距离越小说明越容易撞墙,得扣分,越大表明越靠近路中央,得给与奖励。然后为了不让AI停在原地,再加上之前的垂直方向速度奖励:
<math xmlns="http://www.w3.org/1998/Math/MathML"> R e w a r d = V z C o s θ + ∣ d ∣ Reward = V_z Cos\theta+ |d| </math>Reward=VzCosθ+∣d∣
这里 <math xmlns="http://www.w3.org/1998/Math/MathML"> V z V_z </math>Vz是车子相对于道路方向垂直方向上的速度, <math xmlns="http://www.w3.org/1998/Math/MathML"> θ \theta </math>θ角度是车子前进方向与道路方向的夹角,d是所有传感器中最小的那个距离。
之前查资料的时候有看到大佬的说法是,奖励规则越简单越好,所以其他限定条件就不加了。只要保证靠越近道路边缘分数越低就行,防止AI又变成之前只会开局右转的样子。
这次训练明显学习直线的回合变多了,直到第80次左右才学会开直线,然而,AI却怎么也学不会转弯,虽然有时运气好,突然能转过去一次,但AI好像并不会去吸取这次的经验。
本着奖励规则越简单越好的原则,这次我干脆把其他条件都删了
这次的公式非常简单:
<math xmlns="http://www.w3.org/1998/Math/MathML"> R e w a r d = f × d Reward = f \times d </math>Reward= f×d
d就是距离传感器中最小的那个值,为了防止车屁股贴路边也扣分,只取车前方的那几个传感器的数据。f的值只有1和0,车停下来就是0,只要有速度就是1,防止AI停路中间摆烂。
这次终于训练成功了!
附上训练视频
www.bilibili.com/video/BV1QA...
Git源码: github.com/DarkAngelZT...
以及最后炼丹成功的统计图
(横轴episode,纵轴value)
AI终于开完全部赛道的那一刻,我只想起一个词:大道至简,Less is more。有些时候规则制定太多反而什么都学不会。