欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

用强化学习制作游戏AI

程序员文章站 2022-03-31 19:08:24
...

前言

本人最近在研究强化学习方式制作游戏AI,目前项目还在进展当中,训练效果只能说是马马虎虎,可能在细节方面处理得不是很好,现在先趁着有空记录一下近阶段的学习情况吧,如果自己的研究能给大家提供一点灵感那最好不过了。

关于AI制作的方向

传统算法方式

传统算法这边不是我这次研究的重点,但确实是一个很经典的方向,很多游戏AI都是使用传统算法加以实现,比如NPC一定范围内随机走动之类的。
说是说传统算法,它的算法类型还是很多的,比如一些最短路径算法、随机分布算法之类的。
一般来说就是将游戏情况抽象成有限状态机,然后根据不同的状态施加不同的算法或者执行不同的动作,具体的话我就不深入了,传统算法网上讲解还是很多的。

强化学习方式

其实很多时候我们的机器学习也可以利用一些传统算法的,但是我这次研究的主题是强化学习方式来做AI,所以其他方式我就不谈了(毕竟没做过,不能误人子弟。。)。
用强化学习制作游戏AI
先来说一下最近的新闻,OpenAI制作的Dota2游戏AI战胜了一群半职业选手,震惊众人。我也是很兴奋啊,竟然已经智能到能够协同作战了,人工智能的时代已经拉开的序幕!!
有兴趣的小伙伴也可以去了解一下OpenAI(马斯克创立的)和它的游戏环境gym(传送门),这对于入门级别的选手来说是非常有帮助的(其实我是强烈推荐的)。

DQN简介

这里只是简单介绍一下DQN,更加细节的方面就不加以赘述了。
最初的强化学习方法叫Q-Learning,它会维护一张Q表,我们会根据Q表在不同的状态选择不同的动作,同时最后根据执行动作之后的获得的奖励,反过来更新Q表(这两点是强化学习的核心,也表明其能力必须由不断的探索来加强)。
贴上伪代码(来自莫烦Python):
用强化学习制作游戏AI
之后由DeepMind(没错,就是AlphaGo的创造者)提出了一种结合深度学习和强化学习的方式,就是DQN(Deep Q Network),我们的Q表不再是一张二维表,而是利用神经网络对状态值与动作进行构建。
用强化学习制作游戏AI
很明显,其实简单情况下,Q-Learning可以也可以用,但是DQN是为了解决现在更复杂的情况,有更多的state(状态)、更多的action(动作),因此我们采用神经网络计算得出state的值,这样就不用维护一张很大的Q表了。

几种优化方式

说到优化我还真是有点羞愧,因为我自己的项目现在还没优化好呢,不过也就周末有空了,只能趁热先抓紧写一下了。

  • Double DQN:利用DQN的新老两个神经网络,对内部参数进行更新,解决过估计(overestimate)问题。
  • Prioritized Experience Replay :有时候正向奖励的样本会比较少,比如有时候只有游戏成功才给奖励,这时我们可以通过提高正向样本的优先级提升训练的速度。
  • Dueling DQN:Dueling DQN是将最终的输出结果差分为动作价值和状态价值,会减少有时训练陷入的局部最优。

我的项目

由于某些原因,代码和游戏目前不能开放,也请大家谅解。
先看一下规则。

游戏规则

这是一个回合制战棋类游戏,大致规则如下:

  1. 我方敌方随机出生在地图上(12*12),地图上一开始会产生墙(不会消失)
  2. 回合制,其实是一个路径规划问题,一回合走的格子不限制,我方走到敌方附近四个格子可以进行攻击,但是这回合之前走过的格子不能重复走(Action可以抽象为九个:移动到附近八个格子或者不动结束回合)
  3. 会产生毒圈,每隔五回合收缩一层,进去会有伤害;同时每五回合还会产生道具,吃了攻击防御会有加成,道具不吃当回合失效

其实大家玩过《火焰之纹章》就很容易想象那个场景了,只不过我这么是一对一,并且没有那么复杂的技能,但是对于我这个新手来说这样难度的AI实现已经是挺累的了。
用强化学习制作游戏AI
我们要做的其实就是尽可能多攻击到对面,然后躲到一个相对安全的区域进行防守等待下回合进攻。

实现方式

我们之前说到了强化学习方式,但是强化学习方式也是有很多的,如果入门的话也很推荐莫烦Python的网站。我有很多东西都是从上面学到的。

我最终选取的方式是DQN及其改进方法,因为我的动作是离散的(只有八个方向可以移动,这点比较关键),比较符合应用场景。
如果你的动作是连续的,可以考虑将其转化为离散的动作,或者选用其他方式比如Policy Gradients或者Actor-Critic这些强化学习方法。

我们一般会写成如下三个结构:
1. 游戏环境——执行动作、返回游戏状态、可视化游戏界面(可选)等
2. 神经网络——构建神经网络、进行动作选择
3. 交互模块——调用上面两个模块,控制训练过程以及轮数

只要按照一定的规则写成上面三个模块,会让我们的神经网络复用性大大提高。我参照的是gym模块的方式,将游戏环境进行了构建,这块莫烦教程也有涉及。但是学习的话,我感觉侧重应该在神经网络那一块。

游戏环境

游戏环境是整个过程中最最复杂的一块,因为环境的设计也会很大程度上影响你的训练效果。我的问题初步排查也在这里,因为代码写得还不是那么规范,且所有项目基本都是一个人完成,编写的时候出现很多bug,后期排查了很久,虽然现在能运行了,但是还是有点问题。这一块如果用gym是不需要编写的,这也是推荐新手用gym的原因,抓住学习的重点,而不是死在第一步。。。

初始化

初始化的话,一般会有一些属性,当我们创建对象的时候,这些属性就跟着生成了。我就设置了很多属性(具体还是要看游戏是怎么样的),比如我方位置、我方血量、敌方位置、敌方血量等。这些属性在之后的判定是需要用到的,除非真的是完全靠图像来处理。

观测状态

OpenAI发了一篇很有名的论文《Playing Atari with Deep Reinforcement Learning》,他们利用像素游戏的图像,获取动作和指定奖励之后,让机器自动打像素游戏,比如超级马里奥之类的。也有很多大佬做了flappy bird的AI,反正就很有趣啦,可以多去看看。

我根据这个得到了一些启发,既然是像素图像,简单了解过数字图像处理的人应该知道,计算机里面存储的图像一般都是一个个数值,然后由很多像素组成,彩色图的话一般会有RGB三个轨道,如果是灰度图的话就只有一个轨道数值在0~255区间内。

这款游戏我无法直接获取游戏图像,因此我通过后台传来的数据给计算机生成了一个简单的图像。比如人物就标记为8,敌人标记为9,空地标记为0等,这样就形成了一个12*12的观测值,最后通过这个观测值进行训练。

执行动作

这一步也是关键的一步。首先我们要清楚,到底有几个动作,因为游戏里面的动作是不可能通用的吧,比如我这个游戏可以有九个动作,另一个游戏说不定就只有四个方向可以走,所以我们要根据具体情况进行编写,并且我们还要记得编写一轮训练结束的条件是什么。

与此同时,执行动作之后,游戏环境的状态还会发生变化,这里游戏环境编写最最复杂的一块,我之前的错误大多在这里,比如移动一步之后英雄的位置就肯定会发生变化、道具有没有被吃掉等等,因为我们还需要返回一个新的观测值,供AI继续进行判断。

奖励设置

其实如果其他模块什么问题的话,AI之间的真正差距就在这里了。这里其实也没有什么理论。反正我觉得有一句话挺对的,会打游戏才会做游戏。同理,会打游戏才会做AI。当然考虑的情况越多,当前AI可能会有更好的表现,但是参数的调整也是很有必要的,否则容易陷入局部最优之中。

神经网络

构建

神经网络我是采用tensorflow实现的,讲道理自己不是那么强啦,还是借鉴了一下别人的*进行修改的,毕竟单纯靠自己进行论文复现的话还是挺复杂的,当然也不失为一种锻炼,我因为开发比较急促,所以直接改了*(大家不要学啊)。
用强化学习制作游戏AI
最终我采用了Dueling DQN的结构,首先将图片经过两轮的卷积池化输出到第一个全连接层,然后第二个全连接层就是DuelingDQN的核心,它将单纯的输出转变为状态价值和动作价值的和。编完之后,可以用tensorboard观察一下有没有构建错误。

两个神经网络

我们会构建两个网络target_net和eval_net,eval_net用于预测选择动作并时时更新的,target_net是不是时时更新的。一段时间后,eval_net会将自己的数值重新给target_net。
我们进行梯度下降的对象,其实是这两个网络的均方差,用自己以前的状态进行调整。

交互模块

环境和网络构建好之后,我们就可以进行交互了,其实这一步之前写好也没关系,就没多少行代码。我们获取观测值之后选取动作,然后获得新的观测值,过一定回合结束。同时会有一轮游戏结束的判断。

def run_maze():
    step = 0    # 用来控制什么时候学习
    for episode in range(300):
        # 初始化环境
        observation = env.reset()

        while True:
            # 刷新环境
            env.render()
            # DQN 根据观测值选择行为
            action = RL.choose_action(observation)
            # 环境根据行为给出下一个 state, reward, 是否终止
            observation_, reward, done = env.step(action)
            # DQN 存储记忆
            RL.store_transition(observation, action, reward, observation_)
            # 控制学习起始时间和频率 (先累积一些记忆再开始学习)
            if (step > 200) and (step % 5 == 0):
                RL.learn()
            # 将下一个 state_ 变为 下次循环的 state
            observation = observation_
            # 如果终止, 就跳出循环
            if done:
                break
            step += 1   # 总步数

    # end of game
    print('game over')
    env.destroy()

结语

总结了一波,其实还是挺菜的,争取让我的AI能够战斗吧,也希望这篇文章能给你的思路有一点点启发。


参考:
1.莫烦Python
2.http://www.siyuzhou.com/2018/04/11/基于-deep-qdqn-的快速避障路径规划/