美文网首页Unity探路营
Unity3d开发跳一跳AI(ML-agents)全纪录

Unity3d开发跳一跳AI(ML-agents)全纪录

作者: 洪智 | 来源:发表于2018-01-30 15:29 被阅读75次

    本文首发于“洪流学堂”微信公众号。
    洪流学堂,让你快人几步!

    跳一跳介绍

    最近微信上非常火的一个小游戏,相信大家都已经玩过了。

    玩法

    • 小人跳跃的距离和你按压屏幕的时长有关,按屏幕时间越长,跳的越远
    • 跳到盒子上可以加分,没跳到盒子上游戏结束
    • 连续跳到盒子中心可以成倍加分

    开发历程

    阅读此文章需要有一定的Unity3d基础和unity-ml-agents基础。
    文中有任何纰漏欢迎指正。

    用Unity开发跳一跳

    先参照微信原版用Unity3d开发了简版的跳一跳。

    视频教程 https://edu.csdn.net/course/detail/6975
    源码工程 https://github.com/zhenghongzhi/Unity-JumpJump

    PPO中一些名词解释

    Experience

    每一次states,action及输出称为一次experience

    episode_length

    指的是每一次游戏一个agent直到done所用的步数

    batch size

    进行gradient descent的一个batch

    buffer size

    先收集buffer size个数据,然后再计算进行gradient descent

    Number of Epochs

    会对buffer size的数据处理几遍,比如为2,就会把这些数据处理2遍

    Time Horizon

    被加入experience的条件是agent done或者收集到time horizon个的数据量才加入,为了搜集到更全的可能性,避免过拟合

    模型迭代

    迭代1

    一开始模型建立很困惑,官方的demo里并没有这种给一个输入以后,需要一段时间等结果才出来进行下一步的例子。

    如果使用2个按键分别表示按下和抬起的事件,并且训练出按键之间的联系,训练起来会很慢而且有可能无法拟合。

    一开始尝试用Player类型的brain,结果发现无法实现,只能用两个键的方式。然后使用Heuristic来直接模拟按键的时长,即action只有一个值,一个连续型的float,代表按空格的时长。
    这样的话只能去掉小人的缩放和台子的缩放效果。(后注:现在想来其实是有一个对应关系的,可以直接通过参数设置出来,但是没有过渡的动画。)

    这个模型用5个值作为state,分别是小人位置坐标的x,小人位置坐标的z,下一个盒子位置的x,下一个盒子位置的z,下一个盒子的localScale的x(x和z相同)

    在最开始的时候一直在考虑如果小人在空中的时候,action还一直输入,可能会在神经网络中建立出来奇怪的联系。所以本次迭代在小人在空中的时候如果给了大于0的action的时候给一个惩罚,reward -= 1(这种想法其实是错误的)

    开始训练...

    迭代1-1

    经过2个小时的训练发现效果很不好
    禁止输入应该是一个规则,而不应该惩罚玩家,即使玩家输入游戏也不应该有反馈,所以去掉了小人在空中时候的惩罚。

    另外用Academy中的Frame to Skip参数,设为30,来减少无用的输入。

    开始训练...

    迭代1-2

    游戏出现了卡死的情况,游戏中的判定有BUG,导致可以一直接收action并且没有获得reward。
    修复BUG...开始训练...

    迭代1-3

    训练效果一直不好,又重新将unity-ml-agents里面的文档都看了一遍,特别参照best practice将参数重新调整了一遍开始训练。
    训练了一个晚上,大概8个小时,效果依然不好...

    迭代2

    本来想着这么简单的模型,训练出来一定的成果再用curriculum的方式重新训练对比以下,看来只能直接上Curriculum看看效果了...
    定义了如下的Curriculum文件

    {
        "measure" : "reward",
        "thresholds" : [10,10,10,10,10,10,10,10,10,10,10,10,10,10,10],
        "min_lesson_length" : 2,
        "signal_smoothing" : true, 
        "parameters" : 
        {
            "max_distance" : [1.1,1.1,1.1,1.1,1.5,1.5,1.5,1.5,2,2,2,2,3,3,3,3],
            "min_scale" :    [1,1,0.5,0.5,1,1,0.5,0.5,1,1,0.5,0.5,1,1,0.5,0.5],
            "random_direction":[0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1]
        }
    }
    

    这里需要注意的是,thresholds的值要比下面parameters里面的值少一个,因为最后一个lesson是会一直训练下去的,没有threshold

    迭代2-1

    忽然游戏崩了,发现log是场景里collider太多了
    这次训练的效果还不错,但是游戏里还是有bug,导致box生成太多了
    将box的生成方式改为对象池的方式,性能能优化不少,也应该不会有刚才的bug了

    迭代2-2

    嗯,这么训练确实不错,tensorboard里的cumulative_reward一直在增长,但是训练了一段时间以后想到一个可能存在的问题,由于第一个max_distance是1.1,和最小distance是一样的,也就是说下一个盒子总是距离现在的盒子1.1米,可能会有过拟合的问题,而且threshold貌似有点太大了,一直没有切换到下一个lesson

    于是赶紧停掉训练,将第一个max_distance改成了1.2重新训练

    迭代2-3

    发现了不会切换lesson的问题,赶紧去查,在issues里面发现了答案,只有global_done为true时才会切换,需要将academy设为done或者设置一个max steps
    这次将每次game over的时候academy设置为done
    后来发现这样其实是有点问题的,write_summary之后cumulative_reward会清空,如果刚好这之后的cumulative_reward波动出来大的值,那么就会跳过lesson,这样随机性很大。怪不得官方的demo用的是max step 50000

    迭代2-4

    经过30小时的训练,第一版终于训练出来了,但是结果很不理想

    image.png

    如图:

    1. 第一个过程就是开始设置max_distance是1.1的时候,增长的很快
    2. 修改为1.2之后,又训练了一段时间,发现不会切换lesson,然后改了bug
    3. 后面lesson切换的很快
    4. 最后训练了很久,但是cumulative_reward也没到1

    于是开始反思模型上的问题,要简化模型,优化Curriculum。

    迭代3

    将state重新定义为2个,一个是小人与下一个盒子之间的距离,一个是下一个盒子的size
    Curriculum也重新定义为:

    {
        "measure" : "reward",
        "thresholds" : [5,5,5,5,5,5,5,5,5,5,5,5,5,5,5],
        "min_lesson_length" : 2,
        "signal_smoothing" : true, 
        "parameters" : 
        {
            "max_distance" : [1.2,1.2,1.2,1.2,1.5,1.5,1.5,1.5,2,2,2,2,3,3,3,3],
            "min_scale" :    [1,0.9,0.7,0.5,1,0.9,0.7,0.5,1,0.9,0.7,0.5,1,0.9,0.7,0.5],
            "random_direction":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
        }
    }
    

    由于现在state中是小人与下一个盒子之间的距离,而生成与跳跃方向又是游戏系统定的,所以random direction就没有什么用了。
    而且问了能加快训练速度,重构了游戏和场景,使得可以有很多游戏instance可以一起训练,去掉random direction可以避免游戏之间发生冲突


    image.png

    迭代3-1

    出现了两次Info/cumulative_reward突然剧烈下降并且再也回不去的情况,虽然这两次发生的时候都进行了别的工程webgl的编译,但是应该还是参数的问题

    1. 怀疑是layer为1的锅,虽然模型很简单,因为并不是线性关系那么简单,所以把layer加为2重新训练
    2. 看来不是layer为1的锅。怀疑原地跳的话-0.1的惩罚有点太大了,改为-0.01重新训练试一下
    3. 再次训练还是出现了剧烈下降,这次直接删掉原地跳的惩罚,再试验看看是什么问题
    4. 还是会出现剧烈下降,试着寻找问题的原因
    5. 修改了以下内容:
      • 去掉normalization,改为手动normalization
      • academy max steps改为1000

    还是不行!!!!!!!!!!!!!!!

    开始怀疑是多个instance同时运行出现的问题,但是没找到多个instance同时运行的逻辑问题

    经过反复思考,得到是游戏机制问题。小人跳跃时不应该接收输入,否则的话前一个step的输入得到的reward值会是0,无法对应起来。之前设置的skipfram30确实有问题,无法将states,action和reward对应起来

    2018年1月27日23点19分
    但是经过debug发现,有超过200帧还无法接收下次输入的情况,高达5000帧,这样就没法用skip frame这个值了,训练速度会太慢太慢,怀疑程序有bug,调试中。。。

    发现高达5000帧时,有player掉到地面以下的情况,可能是物理穿透,,加上一个y位置的判断,如果y的值小于1,那么表示小人穿透了地面gameover,并且将ground的box厚度改大

    而且发现frame skip 有bug,还是我没理解对,竟然每帧都会执行step
    Edit:
    I find in Academy.cs line:345 that following code is not surrounded by if (skippingFrames == false).
    Am I misunderstanding something?

    AcademyStep();
    
    foreach (Brain brain in brains)
    {
         brain.Step();
    }
    

    在github上发了个issuse,寻求建议
    重新调试测试,用代码的方式检测skipframe的最佳值,设为了200
    是否跳到盒子上端的检测方式改为法线检测
    并且将上述的代码加到了if判断里面

    迭代3-2

    2018年1月28日
    今天早上起来发现模型还是不行,而且游戏里还有bug
    14点29分
    在github上发了个issuse,作者给了个建议,可以使用unsubscribe和resubscribe的方式来让小人在空中的时候不接受action
    但是测试后发现不行,因为动态unsubscribe和resubscribe的话,agents数量是动态变化的,python端上次输入和这次输出的信息,可能长度不相等
    输入的时候可能有10个小人的信息,但是输出的时候只有8个或20个

    修复了游戏里的bug:跳到了下一个盒子侧面,又和当前的盒子发生了碰撞,会造成逻辑问题

    最终解决方案还是得用frame skip,这次游戏运行稳定了,通过统计找到一个最佳值是137,而且小人每一步都不会超过137帧。将场景中game instance的数量设置为100个,layer 1,hidden units 32
    开始训练

    发现一个报错,game instance数量太多,导致socket buffer超出的问题,复现了几次,出现问题时收到的buffer长度都是1460或者1460的倍数。然后意识到这是由于socket的包分包所致

    路由器有一个MTU( 最大传输单元),一般是1500字节,除去IP头部20字节,留给TCP的就只有MTU-20字节。所以一般TCP的MSS为MTU-20=1460字节。

    还是会出现剧烈下降的问题,应该是learning rate的问题,learning rate太大了,降低learning rate到1e-4
    终于训练成功了,但是跳到中心的几率不高,感觉需要修改reward,给跳到中心加分多一些 试试中心+1,台子上+0.1试试

    迭代3-3

    2018年1月29日13点21分
    终于训练出比较成功的模型了,当前设置了小人最多跳100步,所以cumulative_reward最好也就是100

    Step: 349000. Mean Reward: 88.70098911968347. Std of Reward: 10.67634076995865.
    Saved Model
    Step: 350000. Mean Reward: 89.22838773491591. Std of Reward: 9.210647049058082.
    Saved Model
    Step: 351000. Mean Reward: 89.44072978303747. Std of Reward: 12.138629221972357.
    Saved Model
    

    其实所有东西都搞对以后训练的速度还是挺快的,大概到200K步的时候Mean Reward就能到70了

    后记

    搞完以后尝试修复socket分包的问题,修复完创建PR的时候发现:

    development-0.3的分支中已经修复这个问题

    哈哈,白费功夫了~不过对python的socket通信以及粘包有了更深的了解

    总结

    这个游戏也算是开创了一个mg-agents的游戏类型:需要等待一些时间才能得到reward的情况。
    虽然最终是用skip frame解决的,但是中间做了很多的探索,也发现了ml-agents的一些局限。
    贡献:在GitHub上提了一个pull request(解决了env含有子目录时export_graph的bug)和2个issue(一个是寻求这种类型游戏的建议——作者回复会跟进这种类型游戏的支持;一个是unity端解析socket buffer的json失败的问题,自己修复了这个问题,不过development-0.3的分支中已经修复这个问题)。
    自己在Unity3d的一些细节开发,增强学习、ml-agents的使用和理解上也有不小的进步。
    共同交流,共勉~


    image.png

    交流群:492325637
    关注“洪流学堂”微信公众号,让你快人几步

    image.png
    image.png

    相关文章

      网友评论

        本文标题:Unity3d开发跳一跳AI(ML-agents)全纪录

        本文链接:https://www.haomeiwen.com/subject/knjczxtx.html