どーも! まじすけです🎉 今回は最近話題の強化学習、DQNに挑戦してみました。 以前「AINOW」というAIのキュレーションメディアにてDQNについての記事を書いたので、よろしければ見てみてくださいm(_ _)m ainow.ai 今回はこのDQNを使って3色オセロのトリコロールを学習しました!
トリコロールとは?
トリコロールは青・赤・白の3色で行うボードゲームです。 青と赤それぞれの裏面が白になっています。
ひっくり返るルールはオセロと同じで、縦・横・斜めで自分の色のコマ同士で挟むことでひっくり返ります。 ただ記憶力が高くなければ白がひっくり返った時に何色になるかを覚えることができません。特に本プログラムでは初期配置の白コマの裏面はランダムになっています。なので、最初の時点ですでに運ゲー要素があります。
DQNの学習
今回はこちらのサイトを参考に実装しました。 機械学習の理論を理解せずに tensorflow で オセロ AI を作ってみた 〜導入編〜 – Qiita まずはオセロとDQNのプログラムをこちらからダウンロードしてみてください。
$ git clone https://github.com/sasaco/tf-dqn-reversi
DQNはAI(エージェント)が状況を把握して行動します。行動に対する報酬”Q値”をエージェントに与えることで、より適した行動を取れるようになります。初めはランダムに行動した結果を保存しますが、勝った時に得られる報酬を参考にそれぞれの一手に対するQ値が定まります。 本プログラムではDQNのエージェント同士を戦わせて学習していきます。 まずは黒ターンと白ターンのAIを用意します。
import copy
from Reversi import Reversi
from dqn_agent import DQNAgent
if __name__ == "__main__":
n_epochs = 100
env = Reversi()
playerID = [env.Black, env.White, env.Black]
players = []
players.append(DQNAgent(env.enable_actions, env.name, env.screen_n_rows, env.screen_n_cols))
players.append(DQNAgent(env.enable_actions, env.name, env.screen_n_rows, env.screen_n_cols))
ここから学習の記述になります。コマを置く、勝敗が決まる、というオセロの一連の流れをn_epochs回リピートして行動のモデルを保存します。 後にAIと戦う際に学習したAIが後攻になるので、後攻の勝敗の結果のみ保存します。
for e in range(n_epochs):
env.reset()
terminal = False
while terminal == False:
for i in range(0, len(players)):
state = env.screen
targets = env.get_enables(playerID[i])
if len(targets) > 0:
for tr in targets:
tmp = copy.deepcopy(env)
tmp.update(tr, playerID[i])
win = tmp.winner()
end = tmp.isEnd()
state_X = tmp.screen
target_X = tmp.get_enables(playerID[i+1])
if len(target_X) == 0:
target_X = tmp.get_enables(playerID[i])
for j in range(0, len(players)):
reword = 0
if end == True:
if win == playerID[j]:
reword = 1
players[j].store_experience(state, targets, tr, reword, state_X, target_X, end)
players[j].experience_replay()
action = players[i].select_action(state, targets, players[i].exploration)
env.update(action, playerID[i])
loss = players[i].current_loss
Q_max, Q_action = players[i].select_enable_action(state, targets)
print("player:{:1d} | pos:{:2d} | LOSS: {:.4f} | Q_MAX: {:.4f}".format(
playerID[i], action, loss, Q_max))
terminal = env.isEnd()
w = env.winner()
print("EPOCH: {:03d}/{:03d} | WIN: player{:1d}".format(
e, n_epochs, w))
players[1].save_model()
実行するとこんな感じにどんどん学習していきます。(結構時間かかります)
DQNとの戦いの実装
次は実際に学習したAIと戦うプログラムです。コマンドライン上でコマンドを書きながらAIとオセロをします。argparseを使用することで、保存しているモデルをエージェントに追加します。
import argparse
from Reversi import Reversi
from dqn_agent import DQNAgent
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("-m", "--model_path")
parser.add_argument("-s", "--save", dest="save", action="store_true")
parser.set_defaults(save=False)
args = parser.parse_args()
env = Reversi()
agent = DQNAgent(env.enable_actions, env.name, env.screen_n_rows, env.screen_n_cols)
agent.load_model(args.model_path)
これ以下のほとんどのコードがオセロの実行プログラムReversi.pyと記述が同じなので、AIの実装部分のみ紹介します。保存されている学習結果を参考に次の手を決めています。
print("*** AIターン● ***")
env.print_screen()
enables = env.get_enables(2)
if len(enables) > 0:
qvalue, action_t = agent.select_enable_action(env.screen, enables)
print('>>> {:}'.format(action_t))
env.update(action_t, 2)
else:
print("パス")
実行すると、このようにAIと戦えます。(画像はトリコロールでの実行結果)
オセロをトリコロールに書き換え
最後にオセロのプログラムReversi.pyをトリコロールに書き換えます。 まずは初期のマスが8*8と大きく時間がかかってしまうので、5*5に変えます。 そしてトリコロール独特のコマである「黒の裏」と「白の裏」を作っておきます。
import os
import numpy as np
import random
class Reversi:
def __init__(self):
self.name = os.path.splitext(os.path.basename(__file__))[0]
self.Blank = 0
self.Black = 1
self.White = 2
self.Blackrev = 3
self.Whiterev = 4
self.screen_n_rows = 5
self.screen_n_cols = 5
self.enable_actions = np.arange(self.screen_n_rows*self.screen_n_cols)
self.reset()
初期のコマの配置の際、センターのコマの色をランダムで決めます。 20~25行目
def reset(self):
""" 盤面の初期化 """
self.screen = np.zeros((self.screen_n_rows, self.screen_n_cols))
self.set_cells(7, self.White)
self.set_cells(11, self.Black)
self.set_cells(17, self.Black)
self.set_cells(13, self.White)
self.set_cells(12, random.randint(3,4))
黒と白以外のコマは「裏」と表記することにします。 40~50行目
def print_screen(self):
""" 盤面の出力 """
i = 0
for r in range(self.screen_n_rows):
s1 = ''
for c in range(self.screen_n_cols):
s2 = ''
if self.screen[r][c] == self.Blank:
s2 = '{0:2d}'.format(self.enable_actions[i])
elif self.screen[r][c] == self.Black:
s2 = '●'
elif self.screen[r][c] == self.White:
s2 = '○'
else:
s2 = '裏'
s1 = s1 + ' ' + s2
i += 1
print(s1)
マスの数を変えたので、コマをおける場所の定義も変更します。 60~80行目
def put_piece(self, action, color, puton=True):
""" ---------------------------------------------------------"""
t, x, y, l = 0, action%5, action//5, []
for di, fi in zip([-1, 0, 1], [x, 4, 4-x]):
for dj, fj in zip([-5, 0, 5], [y, 4, 4-y]):
裏に返す処理の記述です。黒と白だけであれば塗り替えるだけで良いのですが、反転するという動作に変更します。 110~120行目
if puton:
""" ひっくり返す石を場合分けする """
for i in l:
if self.get_cells(i) == 3:
self.set_cells(i, 1)
elif self.get_cells(i) ==4:
self.set_cells(i, 2)
elif self.get_cells(i) ==1:
self.set_cells(i, 3)
elif self.get_cells(i) ==2:
self.set_cells(i, 4)
これでトリコロールに書き換え完了です。
実戦!!
それでは学習数100回のAIと戦ってみました!
負けちゃいましたね… もうちょっと自分の腕を磨いて再チャレンジします。