OpenSpielで始める強化学習その① ~OpenAI Gymとの比較~

# 初めに
## OpenSpielとは
https://github.com/deepmind/open_spiel

DEEPMINDが作成した研究者向けの強化学習フレームワークのOSSです。マルチエージェントRLの研究の促進や研究の再現性の向上を目的としています。
主にPythonとC++で書かれていますが、GoやSwift、Juliaもサポートされています。
深層強化学習はTensorFlow1、2、JAXとPytorchに対応しています。

## 想定される読者
自分のように研究者ではないが強化学習について勉強してみたいけど何使えばいいんだっけという人向けに書いています。

## 環境
```bash
$ sw_vers
ProductName:    macOS
ProductVersion: 11.4
BuildVersion:   20F71

$ python3 -V
Python 3.9.5
```

## インストール方法
```
pip install open_spiel
```
でインストールされます。ビルドするため8分程度かかるかと思います。
環境によっては、うまくいかないかもしれないので[公式ドキュメント](https://github.com/deepmind/open_spiel/blob/master/docs/install.md)を参考にしてください。

8月中旬に1.0.0へのMajor updateが予定されていて、それ以降はでものの数秒でインストールされるようになるはずです。

## 基本的な使い方
主に二つあるのでそれぞれ紹介します。
### ①OpenAI Gymに近い使い方
```python
import pyspiel
from open_spiel.python import rl_environment
from open_spiel.python.algorithms import random_agent
# 登録されているゲームを読み込みます
env = rl_environment.Environment('go(komi=0.0,board_size=5)')
# 環境を初期化します
time_step = env.reset()
agents = [random_agent.RandomAgent(player_id, env.action_spec()['num_actions']) for player_id in range(2)]
# ゲームが終了するまで動かします
while not time_step.last():
    current_player = time_step.observations["current_player"]
    # エージェントのstepでactionを決定
    agent_output = agents[current_player].step(time_step)
    # 環境を進めます
    time_step = env.step([agent_output.action])
```

### ②公式で紹介されている方法
```python
import random

import pyspiel
import numpy as np

# 登録されているゲームを読み込みます
game = pyspiel.load_game('go(komi=0.0,board_size=5)')
# 新しいゲームを始めます
state = game.new_initial_state()
# ゲームが終わるまで動かします
while not state.is_terminal():
    legal_actions = state.legal_actions()
    # チャンスノード
    if state.is_chance_node():
        outcomes_with_probs = state.chance_outcomes()
        action_list, prob_list = zip(*outcomes_with_probs)
        action = np.random.choice(action_list, p=prob_list)
        # 環境を進めます
        state.apply_action(action)
    else:
        # 各プレイヤーの行動
        legal_actions_mask = state.legal_actions_mask()
        prob_list = [legal_actions_mask[action] / sum(legal_actions_mask) for action in legal_actions]
        action = np.random.choice(legal_actions, p=prob_list)
        # 環境を進めます
        state.apply_action(action)
```

今回はGymとの比較のため前者の方法で記載していきたいと思います。

# OpenAI Gymとの違い
まず「強化学習をpythonで」と聞くと真っ先に思いつくのがOpenAI Gymだと思います。
ここでは違いを簡単に比較していきたいと思います。
## 提供されているゲーム
```python
>>> import gym
>>> len(gym.envs.registry.all())
859
```

```python
>>> import pyspiel
>>> len(pyspiel.registered_games())
78
```
Gymは強化学習のシミュレーション用プラットフォームだけあって提供されているゲームがそれこそ桁違いにあります。公式リンクは[こちら](https://github.com/openai/gym/wiki/Table-of-environments)。
OpenSpielの一覧は[こちら](https://github.com/deepmind/open_spiel/blob/master/docs/games.md)です。
GymはAtariを代表とした主に一人用のゲーム、OpenSpielは主に複数人用のゲームを提供しているためかゲームは全く被っていません。


## ゲームの実行速度
被っているものがなかったので[Gym公式が記載](https://github.com/openai/gym/blob/master/docs/environments.md#gymgo-the-board-game-go)していた外部パッケージを使用して比較します。

```python
import gym
go_env = gym.make('gym_go:go-v0', size=5, komi=0)
for _ in range(100):
    go_env.reset()
    done = False
    while not done:
        action = go_env.uniform_random_action()
        state, reward, done, info = go_env.step(action)
```
`2.07 s ± 102 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)`

```python
import pyspiel
from open_spiel.python import rl_environment
from open_spiel.python.algorithms import random_agent
env = rl_environment.Environment('go(komi=0.0,board_size=5)')
for _ in range(100):
    time_step = env.reset()
    agents = [random_agent.RandomAgent(player_id, env.action_spec()['num_actions']) for player_id in range(2)]
    while not time_step.last():
        current_player = time_step.observations["current_player"]
        agent_output = agents[current_player].step(time_step)
        time_step = env.step([agent_output.action])
```
`164 ms ± 1.89 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)`

OpenSpielではゲームはC++で書かれたものをPythonバインディングしているので、Gymと比較して10倍程度早いという結果になりました。

## 実装されている手法

Gymはシミュレーションプラットフォームのため、手法の提供しておらず、外部パッケージを用いる必要があります。
例えばDQNを使用したい場合、[公式のリンク](https://github.com/openai/gym/blob/master/docs/agents.md#dqn)からパッケージをインストールするか、
[keras-rl](https://github.com/keras-rl/keras-rl)を用いて自分でネットワークを書かないといけません。

OpenSpielでは、多くの手法が提供されており、例えば`Alpha Zero`も何も考えずに使えるようになっています。
比較のためDQNを使用する場合を記載すると

```python
import pyspiel
from open_spiel.python import rl_environment
from open_spiel.python.algorithms import random_agent
from open_spiel.python.pytorch import dqn
env = rl_environment.Environment('go(komi=0.0,board_size=5)')
time_step = env.reset()
agents = [dqn.DQN(player_id,
                  env.observation_spec()['info_state'][0],
                  env.action_spec()['num_actions']) for player_id in range(2)]
while not time_step.last():
    current_player = time_step.observations["current_player"]
    agent_output = agents[current_player].step(time_step)
    time_step = env.step([agent_output.action])
```

もちろんDQNの引数でNNの定義を変更することも可能です。
その他すぐに使える手法としては公式で[一覧](https://github.com/deepmind/open_spiel/blob/master/docs/algorithms.md)があります。


## まとめ
1. 自分が強化学習したいゲームを提供している環境がある方を使うのが良さそう
2. Gymの外部パッケージのゲームとOpenSpielのゲームだったらOpenSpielの方が高速なのでOpenSpielの方が良さそう
3. いろいろな手法を比較したい場合OpenSpielの方が良さそう



# 最後に
目隠しチェスでの[DEEPMIND主催のコンペ](http://higcompetition.info/)も開催されているみたいです。

次回は[ばかなおうじさんの記事](https://qiita.com/bakanaouji/items/f70d7948931c96d94ef8)を元にしてOpenSpielを使用したCFRの実装を深ぼるところを記事にしたいと思います。
(追記)[②も書いたのでぜひ読んでください！](https://qiita.com/asgasw1127/items/758f2ab2f987f3373422)