【CSDN 编者按】AI 风暴影响已然持续,AI 依旧是行业内炙手可热的词汇,因此,想进军 AI 领域的你,是否做好了充足的准备呢?

英文标题:To Learn Deep Reinforcement Learning, Start Here

原文链接:https://medium.com/applied-data-science/how-to-train-ai-agents-to-play-multiplayer-games-using-self-play-deep-reinforcement-learning-247d0b440717

本文为已获作者翻译授权,转载请注明出处。

作者 | David Foster   译者 | 弯月

出品 | CSDN(ID:CSDNnews)

作者:David Foster,Applied Data Science 的联合创始人。英国剑桥三一学院的数学硕士学位,华威大学运筹学硕士学位。著作《生成式深度学习》(Generative Deep Learning)。

简介

 

在本文中,我们将向你展示如何结合强化学习AI的三个关键思想:

  • 如何通过自我博弈来训练智能体(比如AlphaZero);

  • 如何在多人游戏中应用强化学习;

  • 如何在自定义环境中应用强化学习。

我们将逐步介绍图中的各个部分,以了解如何在Python中构建自我博弈AI的每个组件。


概要

 

首先我们来介绍一些概念,并看看以下这部分图。

这个图是一个3人游戏的环境,其中包含两个AI玩家和一个人类玩家。图中的虚线箭头表示实体之间的信息传递(比如,动作从智能体传递到环境,奖励从环境传递到智能体)。实线箭头表示实例化要求(比如,AI智能体需要通过策略网络根据当前观测返回策略)。

智能体(agent)指的是根据当前状态,在环境中执行动作的实体。智能体可以是AI,通过策略网络实例化(策略网络的作用是将当前游戏状态转换为策略);可以是机器人,根据一组简单的硬编码规则玩游戏;也可以是人类玩家。

策略是所有可能动作集上的概率分布。我们希望找到一种方法来训练策略网络,以确保它输出的策略能够在这个环境中尽可能地战胜人类玩家和其他强大的AI玩家,即最大化环境返回的平均奖励。

首先,我们来看看如何在单人游戏的环境中训练策略网络,然后再看看在多人游戏环境中需要哪些改动。


单人游戏的PPO

 

PPO(Proximal PolicyOptimization,近端策略优化)是OpenAI 于2017年开发的一种先进的强化学习算法。有关PPO的详细介绍,请参见这篇文章https://towardsdatascience.com/introduction-to-various-reinforcement-learning-algorithms-part-ii-trpo-ppo-87f2c5919bb9

下图展示了如何使用Stable-Baselines库,将PPO模型连接到单人游戏环境中。

图:训练PPO模型掌握单人游戏的系统示意图

相应的代码如下:

import gym
 
from stable_baselines import PPO1
from stable_baselines.common.policiesimport MlpPolicy
from stable_baselines.common.callbacksimport EvalCallback
 
env = gym.make('Pendulum-v0')
model = PPO1(MlpPolicy, env)
 
# Separate evaluation env
eval_env = gym.make('Pendulum-v0')
eval_callback = EvalCallback(eval_env,best_model_save_path='./logs/',
                            log_path='./logs/', eval_freq=500,
                            deterministic=True, render=False)
 
model.learn(5000,callback=eval_callback)

这里的策略网络就是一个简单的MlpPolicy神经网络,由Stable-Baselines包提供。

每完成500个时间步长,回调函数就会评估一次智能体,如果取得了更高的成绩,就将模型的权重保存到文件中。

在单人游戏的训练中,我们要解决的问题很简单,即在环境中获得最大的回报。你可以将其视为智能体与环境本身的对抗,而且这个环境在整个训练过程中不会发生变化。

下面我们来看看,在多人游戏中实现自我博弈时需要改动的地方。


多人游戏的PPO

 

在多人游戏中,问题就没有那么简单了,因为必须要考虑与你对抗的对象。打败随机行动的玩家很容易,但这并不意味着在环境中达到了完美的程度。

我们必须不断比较网络的当前版本与之前的版本,希望智能体不断寻找新的策略来战胜新对手,从而逐渐成为更强大的玩家。

这是自我博弈的关键,且已成功应用于AlphaZero和OpenAI Five等智能体,OpenAI Five使用PPO作为强化学习的引擎,攻克了Dota 2游戏。

下图展示了如何建立一个系统,通过自我博弈PPO,训练神经网络玩多人游戏。

图:系统示意图,训练PPO模型通过自我博弈掌握多人游戏。

与单人游戏相比,多人游戏最大的不同在于,游戏环境打包到了一个“Selfplay wrapper”(自我博弈打包器)中。

这个打包器有三个重要的作用:

  1. 在每次重置环境时,随机加载某个先前的版本,作为正在接受训练的网络的下一个对手。

  2. 通过策略网络采样,逐个挑战对手。

  3. 延迟向PPO引擎返回奖励,直到轮到其他代理。

从本质上来说,自我博弈打包器将多人游戏环境转换成了不断变化的单人游戏,因为在每次游戏结束后,它都会创建并加载新的对手。

了解延迟的奖励机制也很重要。假设你正在玩3人纸牌游戏。如果你是第一个出牌的玩家,那么你的动作不会立即产生奖励。你需要等一等,看看另外两名玩家的出牌情况,才能知道你是否赢了。

为了处理这种延迟,在将奖励返回给接受训练的PPO智能体之前,自我博弈打包器必须记录该智能体的动作,以及所有其他对手的动作。

加载之前的网络、记录对手的动作以及延迟奖励的结合就是自我博弈的秘诀!


环境打包器

 

下面我们来看一看自我博弈环境打包器中的三个函数,这些代码片段都来自selfplay.py文件(其中包含SelfPlayEnv类)。这里我们假设我们正在玩多人游戏,每一场游戏结束都会给出最终的奖励(例如,赢得游戏为1,否则为0)。

reset
 
class SelfPlayEnv(env):
   # ...
   
   def reset(self):
       super(SelfPlayEnv, self).reset()
       self.setup_opponents()
 
       if self.current_player_num != self.agent_player_num:  
            self.continue_game()
 
       return self.observation

首先,我们像往常一样重置游戏(第5行),然后设置下一场游戏的对手(第6行)。默认情况下,选择对手的方式为:80%来自当前最佳网络,20%随机选自之前的网络。这种方式可以降低智能体过度拟合最新的最佳模型,并导致旧模型表现不佳的可能性。

接下来,我们检查是否轮到了我们正在训练的智能体(第8行),可能我们的智能体还没开始游戏!如果不是,则调用continue_game方法(第9行)……

continue_game
 
class SelfPlayEnv(env):
 # ...
 
   def continue_game(self):
       while self.current_player_num != self.agent_player_num:
            self.render()
            action =self.current_agent.choose_action(self, choose_best_action = False,mask_invalid_actions = False)
            observation, reward, done, _ =super(SelfPlayEnv, self).step(action)
            logger.debug(f'Rewards: {reward}')
            logger.debug(f'Done: {done}')
 
            if done:
                break
 
       return observation, reward, done, None

这个方法负责完成对手的回合,直到再次轮到我们正在训练的智能体。首先,我们要求智能体选择一个动作(第7行)。这是根据策略分布随机选择的,而不是选择“最佳”动作,因为我们希望智能体面对各种情况,并学会利用一些小动作来防御对方的“高招”。

我们将选择的动作传递到多人游戏环境(第8行)。这段代码处理了游戏状态的变化,比如更改current_player_num,返回done的布尔值,并以列表的形式返回每个玩家的reward。

如果done为true(第12行),则尽早退出while循环(第13行),对手采取了结束游戏的动作。一旦轮到正在训练的智能体或游戏结束了,我们就退出这个函数。

step
 
class SelfPlayEnv(env):
 # ...
 
   def step(self, action):
       self.render()
       observation, reward, done, _ = super(SelfPlayEnv, self).step(action)
       logger.debug(f'Action played by agent: {action}')
       logger.debug(f'Rewards: {reward}')
       logger.debug(f'Done: {done}')
 
       if not done:
            observation, reward, done, _ =self.continue_game()
 
       agent_reward = reward[self.agent_player_num]
       logger.debug(f'\nReward To Agent: {agent_reward}')
 
       if done:
            self.render()
 
       return observation, agent_reward, done, {}

最后,我们需要打包多人游戏的step函数。

首先,我们将选定的动作传递给处理游戏状态变化的多人游戏环境(第6行)。

接着,我们检查游戏是否已结束done(第11行)。如果还没有结束,则调用continue_game方法(第12行),正如上面的介绍,处理对手的回合,直到再次轮到我们的智能体。

最后,我们给智能体选择奖励(第14行)。由于最终的奖励值(1 = 赢,0= 输)可能发生在对手回合之后,因此step方法必须等到所有对手的回合都完成,轮到自己才能将奖励值返回给智能体(第20行),这一点很重要。

恭喜你!学完这些内容,你就知道如何构建能够通过自我博弈PPO,训练多人游戏的系统了。


回调

 

回调函数是该系统的另一个部分,在通过自我博弈训练智能体时,这部分也需要一些小的调整。

在单人游戏中,在训练期间,每次超过这个阈值,回调都会记录环境中的最佳分数,并保存新版本的模型。通过这种方式,我们就能建立一个越来越复杂的智能体仓库,并且随着时间的推移,成为最佳模型所需的阈值也会不断增加。

在多人游戏中,我们需要从当前保存的最优智能体中选择一部分,然后再随机从之前的智能体中选择一部分,并通过二者的组合来评估智能体,因为只有这样才能避免过度拟合。只有当某个智能体击败一系列的对手,并持续打破阈值,才能证明它就是最新的“最优”智能体。

注意,在自我博弈的回调中,阈值不会随着时间的推移而提高,就像单人游戏一样。相反,随着时间的推移,环境本身会越来越复杂,因为越来越多的智能体被当作对手加载了进来。因此,评判新冠军的阈值可以保持不变。

完整的fullSelfPlayCallback类在这里:

https://github.com/davidADSP/SIMPLE/blob/main/app/utils/callbacks.py


Tensorboard

 

我们可以通过Tensorboard图表(由Stable Baslines库自动创建),了解训练过程的状态。

图:Tensorboard的输出,在训练的过程中创建。

需要观察的几个重要的图表如下:

episode_reward /discounted_rewards

这些图展示了智能体在整个训练过程中获得的平均奖励。通常,你应该看到图中的曲线逐步上升,直到达到阈值分数。每次保存完新的策略网络后,曲线就会突然下降,因为这时智能体必须在环境中击败更强的对手。

loss / policy_gradient_loss / value_function_loss /entropy_loss

这些术语与PPO网络优化有关,它们都是为了最大化以下目标函数:

在Tensorboard中,实际上你看到的各项都是相反数,因为神经网络的训练过程就是最小化问题,所以每一项都是相反数,也就是说这些图表示的是“损失”。

policy_gradient_loss(策略梯度损失)针对的是CLIP项。PPO算法会设法提高CLIP,具体做法是:如果某个动作与值函数预测的结果相比更有利,则提高选择该动作的概率;否则,如果该动作与值函数的预测相比更为不利,则降低选择该动作的概率。但是,这个值是裁剪过的,为的是防止该策略偏离当前策略太远。

value_function_loss就是VF项。VF关系到值函数的准确性,因此PPO会试图减少这个损失,因此VF前面加上了负号。

entropy_loss就是S项。S与策略的熵有关,熵越高,策略越随机。PPO算法会设法提高这个值,以鼓励模型探索。但是,当然CLIP会平衡这个值,以确保最终选择产生有益结果的动作。

注意事项!

通常,你应该看到value_loss随时间逐渐下降,尽管每次保存新网络时,这个值会上升,因为之前的评估对新智能体无效。随着智能体越来越确定自己的行为,随机性降低,entropy_loss应该逐渐向上浮动(熵下降)。

对于一个好的策略来说,随着时间的流逝,熵系数应该趋近于零,从而由policy_gradient_loss占主导地位。如果刚开始的时候,动作没有足够的随机性,那么该模型就有坍缩的风险,因为它已经找到了自认为是制胜法宝的策略,但实际上它只是没有充分探索对手的动作,无法认识到自己学习到的是一个弱策略。


自定义环境

 

你可以将存储库中现有的环境作为模板,构建自己的环境。一般这种结构遵循标准的OpenAI Gym框架,然后再通过一些其他的方法和属性,确保你的环境能够与自我博弈环境打包器相结合。到这里为止,我们只实现了Discrete动作(比如在轮到你的时候,从n个动作中选这个一个)。


自定义网络

 

另外,你还需要为策略网络定义一个自定义结构,为此你需要考虑动作空间的大小,以及观测输入的维度。

例如,在玩井字棋之类的网格游戏时,你可以将观测作为三维数组输入(高度、宽度、特征数),并编写卷积层来处理这个输入。在玩寿司派对(Sushi Go)之类的棋牌游戏时,可以使用密集层。

这个网络完全可以自定义,对于PPO,你只需要输出策略头(pi)和值(vf)头。也可以将legal_actions向量作为附加输入传递进去,对策略头进行屏蔽,从而将无效的动作设置为零(0)。具体的做法,请参见这里https://github.com/davidADSP/SIMPLE/blob/main/app/models/sushigo/models.py

图:寿司派对的自定义策略网络,无效动作被屏蔽


评估

 

我们可以通过一个比赛,评估自我博弈训练出来的智能体,每个智能体都与其他智能体一起玩一定的次数。最后就会得到一个平均奖励的矩阵,如下所示,这是通过多个玩家的卡牌游戏寿司派对来训练一组智能体得到的结果。

图:每个小格代表智能体P1在寿司派对的游戏中,与两个智能体P2的副本对战了50场后,获得的平均奖励。奖励为:第一名 = 1,第二名 = 0,第三名 = -1。所有智能体都接受了自我博弈PPO的训练。

我们可以清楚地看到,随着训练的进行,智能体在不断提高,假设 i < n,则平均值model_n超过了所有的model_i。矩阵对角上红/蓝色的分割线充分说明了这一点。

在尝试了最近的模型之后,我发现它已经开发出了类似于人类的复杂策略,例如阻挡其他玩家,使用筷子挑选最好的两张手牌,而且在赢面很小的情况下不选择某些牌。我玩了一会儿寿司派对,发现根本不是对手,这个模型已经达到了“超人”的水平。


总结

 

在本文中,我们探讨了如何通过自我博弈的强化学习来建立训练自定义多人游戏的过程。

你可以利用SIMPLE项目,构建自己的强化学习智能体。我非常期待看到你们的成果!



☞“面向对象就是一个错误!”☞乐视视频 App 图标改为“欠 122 亿”,网友:我在别家分红包,却在你家随份子!
☞30 行代码实现蚂蚁森林自动“偷”能量
☞计算机科学界至今未解决的四大难题
Logo

20年前,《新程序员》创刊时,我们的心愿是全面关注程序员成长,中国将拥有新一代世界级的程序员。20年后的今天,我们有了新的使命:助力中国IT技术人成长,成就一亿技术人!

更多推荐