Open Spiel:强化学习工具库
介绍了Open Spiel的安装和使用,以及部分代码的解析
简介
OpenSpiel 是一个用于通用强化学习研究以及游戏中的搜索 / 规划的环境和算法集合。OpenSpiel 支持 n 人(单智能体和多智能体)零和、合作以及一般和博弈的游戏,这些游戏可以是一次性的和序列性的、严格轮流行动的和同时行动的、完全信息和不完全信息的,它还支持如(部分可观测和完全可观测的)网格世界以及社会困境等传统的多智能体环境。OpenSpiel 还包含用于分析学习动态以及其他常见评估指标的工具。游戏被表示为程序性的扩展式博弈,并带有一些自然的扩展。其核心应用程序编程接口(API)和游戏是用 C++ 实现的,并提供了 Python 接口。算法和工具既有用 C++ 编写的,也有用 Python 编写的
对应的github地址为https://github.com/google-deepmind/open_spiel
安装
先clone前面的git工程https://github.com/google-deepmind/open_spiel
进入clone下来的open_spiel目录下
安装python的依赖
系统版本是Ubuntu 22.04及以上执行下面的指令
Python |
如果是更低的版本执行下面的指令
Python |
安装open_spiel库
使用pip指令即可安装open_spiel库
Plain Text |
项目结构
open_spiel 目录下的直接子目录大多是 C++ 代码(integration_tests 和 python 目录除外)。open_spiel/python 目录中有类似的结构,包含与之对应的 Python 代码。
一些顶级目录比较特殊:
- open_spiel/integration_tests:针对所有游戏的通用(Python)测试。
- open_spiel/tests:C++ 通用测试工具。
- open_spiel/scripts:对开发有用的脚本(用于构建、运行测试等)。
例如,对于 C++ 代码而言:
- open_spiel/:包含游戏抽象的 C++ 应用程序编程接口(API)。
- open_spiel/games:包含游戏的 C++ 实现代码。
- open_spiel/algorithms:在 OpenSpiel 中实现的 C++ 算法。
- open_spiel/examples:C++ 示例代码。
- open_spiel/tests:C++ 通用测试工具。
对于 Python 代码来说:
- open_spiel/python/examples:Python 示例代码。
- open_spiel/python/algorithms/:Python 算法代码。
基础例子
可以进入open_spiel\open_spiel\python\examples目录下,里面有许多官方例子
example.py
代码解析
下面是一个最简单例子的完整代码。该例子是创建了一个tic tac toe(井字棋)的玩法,使用随机选择的方式,进行对局。井字棋的玩法逻辑是两名玩家顺序在3*3的格子下棋,当其中一方的三个棋子形成连线时获胜。若格子填满时没有玩家形成连线则平局
python |
game = pyspiel.load_game(FLAGS.game_string) 是创建了一个新的游戏
state = game.new_initial_state()获取初始状态
state.is_terminal()是判断游戏是否结束
每个state有三种类型的节点 chance node,simultaneous node和decision node
chance节点是概率节点,返回的是一个action和概率的列表。这个是属于玩家不能控制的节点。一般是由游戏本身来决定的。比如玩家执行了一次roll骰子的操作。chance节点就是action为[1,2,3,4,5,6],概率为[1/6,1/6,1/6,1/6,1/6]。需要根据概率来选择节点。
simultaneous节点是一个同步节点,需要所有玩家都进行操作
decision节点是决策节点,需要当前玩家进行操作
在决定好相关的操作之后,执行state.apply_action(action)。游戏会进入下一个状态。
输出
下面是游戏输出:"."代表未填的格子,"x"代表玩家1的操作,"o"代表玩家2操作,当格子填满的时候,或者其中一个玩家填的格子形成三个的连线时,对局结束。形成3个连线的玩家得1分,对手得-1分。平局则双方得0分
yaml |
rl_example.py
代码解析
下面是一个关于tic tac toe的强化学习的例子
python |
env = rl_environment.Environment(FLAGS.game, **env_configs)由于强化学习,所以这里创建了一个代表环境的env,参数是游戏名
time_step = env.reset()重置环境
time_step.step_type.last()判断是否结束
time_step.observations代表当前状态
observations["legal_actions"][cur_player]为指定玩家可执行的操作
env.step(actions)执行动作,进行状态转移和给出奖励
输出
yaml |
mcts.py
代码解析
下面是一个蒙特卡洛搜索树算法的一个例子。让一个蒙特卡洛搜索的机器人和其他机器人对打。完成若干场tic tac toe的对局
python |
bot=mcts.MCTSBot()就是根据参数创建一个mcts的机器人
action = bot.step(state)就是机器人根据当前的state获得一个操作
state.apply_action(action)然后state再根据操作更新状态
输出
下面是一个让一个蒙特卡洛树搜索机器人和一个随机机器人进行一百场对局的输出。可以看到最终的结果是mcts净胜95场
yaml |
部分关键逻辑解析
state的概率事件和clone相关逻辑解析
搜索类算法
下面是async_mcts.py的中随机评估器(RandomRolloutEvaluator)的部分代码
该函数的返回值是prior, value。
- prior:动作的概率列表,每个元素是 (动作, 概率) 的元组
- values:状态的价值估计。
如果现在是chance节点(is_chance_node为True),就会直接采用state的动作概率数组chance_outcomes。如果不是概率节点,则会对所有的可执行的操作,给予相同的概率
之后会复制当前的状态,作为working_state,然后在这个working_state下随机进行操作,直到对局结束。将结束时的得分作为当前的预估值。
python |
RL_Environment
RL_Environment在step的时候,会执行一个对额外事件的采样_sample_external_events
python |
采样的逻辑里,如果遇到的是chance_node,就会用当前的随机事件采样器进行采样
python |
默认的概率事件的采样器ChanceEventSampler,会直接使用state.chance_outcomes的动作概率列表,随机进行操作
python |
因此在默认的RL环境下,不太需要关心change_node和chance_outcomes
自定义游戏的流程
如果想要直接使用open spiel中的一些智能体和算法,需要按照特定的规则自定义游戏,相关文档可以查看下方页面中的Add a game部分的内容
这里我们仅介绍添加新游戏最简单、最快捷的方法。理想情况下,你首先要了解通用的应用程序编程接口(API)(请参阅 open_spiel/spiel.h)。这些指南主要针对用 C++ 开发的游戏;对于用 Python 开发的游戏,过程类似,特殊注意事项会在步骤中注明。
1. 选择模板游戏
在 open_spiel/games/(或 open_spiel/python/games/)中选择一个游戏作为模板进行复制。推荐的模板游戏如下:
- 对于无随机事件的完全信息游戏,可选择井字棋(Tic - Tac - Toe)和突破棋(Breakthrough)。
- 对于有随机事件的完全信息游戏,可选择双陆棋(Backgammon)或猪游戏(Pig)。
- 对于同时行动的游戏,可选择古夫棋(Goofspiel)和推角力棋(Oshi - Zumo)。
- 对于不完全信息游戏,可选择勒杜克扑克(Leduc poker)和吹牛骰子(Liar’s dice)。
在接下来的步骤中,我们以井字棋为例。
2. 复制头文件和源文件
将 tic_tac_toe.h、tic_tac_toe.cc 和 tic_tac_toe_test.cc 复制为 new_game.h、new_game.cc 和 new_game_test.cc(如果是 Python 游戏,则复制 tic_tac_toe.py 和 tic_tac_toe_test.py)。
3. 配置 CMake
- 使用 C++ 开发时:
- 将新游戏的源文件添加到 open_spiel/games/CMakeLists.txt 中。
- 将新游戏的测试目标添加到 open_spiel/games/CMakeLists.txt 中。
- 使用 Python 开发时:
- 将测试添加到 open_spiel/python/CMakeLists.txt 中。
- 在 open_spiel/python/games/__init__.py 中导入新游戏。
4. 更新 C++/Python 样板代码
- 在 new_game.h 中,重命名文件顶部和底部的头文件保护宏。
- 在新文件中,将最内层的命名空间从 tic_tac_toe 重命名为 new_game。
- 在新文件中,将 TicTacToeGame 和 TicTacToeState 重命名为 NewGameGame 和 NewGameState。
- 在 new_game.cc 文件顶部,将短名称改为 new_game,并包含新游戏的头文件。
5. 更新 Python 集成测试
将新游戏的短名称添加到 open_spiel/python/tests/pyspiel_test.py 中的预期游戏列表里。
6. 验证复制的游戏
此时,你应该得到了一个换了名称的井字棋副本。它应该能够编译,测试也应该能够运行。你可以通过重新编译并运行示例程序 build/examples/example --game=new_game 来验证。注意:Python 游戏不能使用此示例程序运行,需使用 open_spiel/python/examples/example.py 代替。
7. 实现新游戏逻辑
现在,修改 NewGameGame 和 NewGameState 中函数的实现,以体现你新游戏的逻辑。大多数 API 函数从你所复制的游戏中应该很容易理解。如果不清楚,每个被重写的 API 函数在 open_spiel/spiel.h 的基类中都有完整的文档说明。
8. 测试新游戏
在构建游戏的过程中,若要测试游戏功能,你可以使用 open_spiel/tests/console_play_test.h 中的 ConsolePlayTest 进行交互式测试。至少,测试应该包括一些随机模拟测试(可参考其他游戏的测试示例)。注意:Python 游戏不能使用 ConsolePlayTest 进行测试,但 C++ 和 Python 游戏都可以使用 open_spiel/python/examples/mcts_example 在控制台与人类玩家一起进行测试。
9. 代码检查
使用代码检查工具检查你的代码,使其符合 Google 的风格指南。
- 对于 C++ 代码,使用 cpplint。
- 对于 Python 代码,使用 Google 风格指南中的 pylintrc 配置 pylint 进行检查,也可以使用 YAPF 对 Python 代码进行格式化。
10. 重新编译和测试
完成上述操作后,重新编译并再次运行测试,确保所有测试都能通过(包括你新游戏的测试!)。
11. 添加游戏流程文件以捕捉回归问题
运行 ./open_spiel/scripts/generate_new_playthrough.sh new_game 生成一个随机游戏流程文件,集成测试将使用该文件来防止出现回归问题。open_spiel/integration_tests/playthrough_test.py 会自动加载这些游戏流程文件,并将其与新生成的游戏流程进行比较。
如果你进行了影响游戏流程的更改,请运行 ./scripts/regenerate_playthroughs.sh 来更新这些文件。
支持的游戏和算法
支持的游戏
https://github.com/google-deepmind/open_spiel/blob/master/docs/games.md
支持的算法
https://github.com/google-deepmind/open_spiel/blob/master/docs/algorithms.md
OpenSpiel的核心接口
下面是openSpiel的一些核心接口以及其对应的描述
https://github.com/google-deepmind/open_spiel/blob/master/docs/api_reference.md
核心方法(Core)方法
方法 |
Python |
C++ |
描述 |
deserialize_game_and_state(serialized_data: string) |
Python |
C++ |
从序列化的对象数据中重构出一个元组,包含(游戏对象,游戏状态对象)。 |
load_game(game_string: str) |
Python |
C++ |
根据指定的游戏字符串返回一个游戏对象。 |
load_game(game_string: str, parameters: Dict[str, Any]) |
Python |
C++ |
根据指定的游戏字符串和参数值返回一个游戏对象。 |
registered_names() |
Python |
C++ |
返回库中所有游戏的短名称列表。 |
serialize_game_and_state(game: pyspiel.Game, state: pyspiel.State) |
Python |
C++ |
返回游戏状态以及创建该状态的游戏的字符串表示形式。 |
状态(State)方法
方法 |
Python |
C++ |
描述 |
action_to_string(player: int, action: int) |
Python |
C++ |
返回指定玩家的动作的字符串表示形式。 |
apply_action(action: int) |
Python |
C++ |
将指定的动作应用到当前游戏状态。 |
apply_actions(actions: List[int]) |
Python |
C++ |
将指定的联合动作(每个玩家的动作)应用到当前游戏状态。 |
chance_outcomes() |
Python |
C++ |
返回一个由(动作,概率)元组组成的列表,代表随机事件的结果分布。 |
current_player() |
Python |
C++ |
返回当前行动玩家的玩家 ID。 |
history() |
Python |
C++ |
返回从游戏开始以来所有玩家采取的动作序列。 |
information_state_string() |
Python |
C++ |
返回代表当前玩家信息状态的字符串。 |
information_state_string(player: int) |
Python |
C++ |
返回代表指定玩家信息状态的字符串。 |
information_state_tensor() |
Python |
C++ |
返回一个浮点数列表,代表当前玩家的信息状态。 |
information_state_tensor(player: int) |
Python |
C++ |
返回一个浮点数列表,代表指定玩家的信息状态。 |
is_chance_node() |
Python |
C++ |
如果当前状态是随机事件节点,则返回 True,否则返回 False。 |
is_simultaneous_node() |
Python |
C++ |
如果当前状态是同时行动玩家节点,则返回 True,否则返回 False。 |
is_terminal() |
Python |
C++ |
如果当前状态是终止状态(游戏已结束),则返回 True,否则返回 False。 |
legal_actions() |
Python |
C++ |
返回当前玩家的合法动作列表。 |
legal_actions(player: int) |
Python |
C++ |
返回指定玩家的合法动作列表。 |
observation_string() |
Python |
C++ |
返回代表当前玩家观察信息的字符串。 |
observation_string(player: int) |
Python |
C++ |
返回代表指定玩家观察信息的字符串。 |
observation_tensor() |
Python |
C++ |
返回一个浮点数列表,代表当前玩家的观察信息。 |
observation_tensor(player: int) |
Python |
C++ |
返回一个浮点数列表,代表指定玩家的观察信息。 |
returns() |
Python |
C++ |
返回回报列表(从游戏开始累计的奖励):每个玩家对应一个值。 |
rewards() |
Python |
C++ |
返回中间奖励列表(自玩家上次行动以来获得的奖励):每个玩家对应一个值。 |
serialize() |
Python |
C++ |
返回游戏状态的字符串表示形式,可用于从游戏中重构该状态。 |
游戏(Game)方法
方法 |
Python |
C++ |
描述 |
action_to_string(player: int, action: int) |
Python |
C++ |
返回指定玩家动作的(与状态无关的)字符串表示形式。 |
deserialize_state(serialized_data: str) |
Python |
C++ |
从序列化的状态字符串中重构游戏状态。 |
information_state_tensor_shape() |
Python |
C++ |
信息状态张量应被视为的形状。 |
information_state_tensor_size() |
Python |
C++ |
状态的信息状态张量函数返回的列表大小(值的数量)。 |
max_chance_outcomes() |
Python |
C++ |
游戏中随机事件节点的不同随机结果的最大数量。 |
max_game_length() |
Python |
C++ |
任何一局游戏的最大长度(以游戏树中访问的决策节点数量衡量)。 |
max_utility() |
Python |
C++ |
游戏在任何一次游玩(回合)中可达到的最大效用(回报)。 |
min_utility() |
Python |
C++ |
游戏在任何一次游玩(回合)中可达到的最小效用(回报)。 |
new_initial_state() |
Python |
C++ |
返回游戏的一个新的初始状态(注意:可能是一个随机事件节点)。 |
num_distinct_actions() |
Python |
C++ |
返回游戏中(与状态无关的)不同动作的数量。 |
observation_tensor_shape() |
Python |
C++ |
观察张量应被视为的形状。 |
observation_tensor_size() |
Python |
C++ |
状态的观察张量函数返回的列表大小(值的数量)。 |
创作不易,如果觉得这篇文章对你有所帮助,可以动动小手,点个赞哈,ღ( ´・ᴗ・` )比心

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。
更多推荐
所有评论(0)