コンピュータとオセロ対戦33　～勝敗予測AIと対戦～

https://qiita.com/tt_and_tk/items/068f2afde6db637e189f

[前回](https://qiita.com/tt_and_tk/items/a95c5632be0a1f643408)

# 今回の目標
前回作成したモデルとの対戦プログラムを作成します。
# ここから本編
使用するのはこれまでも何度も使ってきたBitBoardクラスと前回作成した学習済みモデルです。
## BitBoard.py
オセロをするための基本クラスを置いたファイル。
中身は省略します。

## net.py
ニューラルネットワークを定義したクラスを置いたファイル。
前回同様、4層全て全結合層で活性化関数はtanhです。

```py
import chainer
import chainer.functions as F
import chainer.links as L

class Net(chainer.Chain):
    def __init__(self):
        n_in = 128
        n_hidden = 128
        n_out = 1
        super().__init__()
        with self.init_scope():
            self.l1 = L.Linear(n_in, n_hidden)
            self.l2 = L.Linear(n_hidden, n_hidden)
            self.l3 = L.Linear(n_hidden, n_hidden)
            self.l4 = L.Linear(n_hidden, n_out)
        
    def __call__(self, x):
        h = F.tanh(self.l1(x))
        h = F.tanh(self.l2(h))
        h = F.tanh(self.l3(h))
        h = self.l4(h)

        return h
```

## osero_deep_AI.py
前回作成したモデルとの対戦を行うためのクラスを置いたファイル。

### インポート、定義部分
盤面複製のためのdeepcopy、予測値がかぶった時のためのランダム、モデル操作のためchainer、盤面をモデルに投げる際の整形のためnumpyをインポートしています。
BitBoardとnetは前述の通り。

```py
from copy import deepcopy
from random import randint

import chainer
import numpy as np

from BitBoard import osero
import net

BLACK = 0
WHITE = 1
```

### コンストラクタ
思考方法に今回のモデルを追加し、モデルとの対戦準備を整えます。

```py
class osero_deep_AI(osero):
    def __init__(self, player=BLACK, read_goal=[1, 1], eva=[0, 0],\
                 player_method=osero.PLAY_WAY["human"]):
        osero.PLAY_WAY["osero_deep_AI"] = len(osero.PLAY_WAY)
        if player == BLACK:
            black_method = player_method
            white_method = osero.PLAY_WAY["osero_deep_AI"]
        else:
            black_method = osero.PLAY_WAY["osero_deep_AI"]
            white_method = player_method
        super().__init__(\
            black_method,
            white_method,
            read_goal=read_goal,
            eva=eva
        )
        self.think.append(self.osero_deep_AI)
        self.model_setup()
```

### model_setup
ニューラルネットワークを呼び出します。

```py
    def model_setup(self) -> None:
        self.net = net.Net()
        chainer.serializers.load_npz("deep_AI.net", self.net)
```

### reshape
盤面を、モデルに投げられる形に変更します。

```py
    def reshape(self, now_board: dict) -> np.ndarray:
        data = []
        if self.turn:
            my = ["b_u", "b_d"]
            opp = ["w_u", "w_d"]
        else:
            my = ["w_u", "w_d"]
            opp = ["b_u", "b_d"]
        for i in range(32):
            data.append(int(now_board[my[0]] & 1 << i))
            data.append(int(now_board[opp[0]] & 1 << i))
        for i in range(32):
            data.append(int(now_board[my[1]] & 1 << i))
            data.append(int(now_board[opp[1]] & 1 << i))
        
        return np.array([data], dtype=np.float32)
```

### predict
盤面から最終結果の予測値を返します。

```py
    def predict(self, now_board: dict) -> float:
        x = self.reshape(now_board)
        return self.net(x).array[0][0]
```

### osero_deep_AI
置ける場所の中から予測値が最大となる場所を探し、その位置に置きます。
なおこのメソッドは、親クラスのcheckメソッドにより、そのターンに置ける場所が一か所でもある場合のみ呼び出されます。そのため最低一か所は置ける場所があるという前提で作られています。

```py
    def osero_deep_AI(self) -> None:
        max_score = -100.
        line_ans, col_ans = [-1], [-1]
        place_num = 0
        
        for i in range(8):
            for j in range(8):
                if not self.check(i, j, self.bw, self.turn):
                    continue
                board_leaf = deepcopy(self.bw)
                self.put(i, j, board_leaf, self.turn)
                score = self.deep_AI_think(\
                    board_leaf,
                    not self.turn,
                    1,
                    self.read_goal[self.turn]
                )
                if score > max_score:
                    max_score = score
                    place_num = 0
                    line_ans = [i]
                    col_ans = [j]
                elif score == max_score:
                    place_num += 1
                    line_ans.append(i)
                    col_ans.append(j)
        
        if place_num:
            place = randint(0, place_num)
            line_ans[0] = line_ans[place]
            col_ans[0] = col_ans[place]
        
        print("line:\t%d\ncol:\t%d" % (line_ans[0]+1, col_ans[0]+1))
        self.put(line_ans[0], col_ans[0], self.bw, self.turn)
```

### deep_AI_think
再帰的に目的のターン数まで進み、その段階での予測値を返します。

```py
    def deep_AI_think(self, now_board: dict, turn: bool,\
                      num: int, read_goal: int) -> float:
        if num == read_goal:
            return self.predict(now_board)
        
        score = 0
        place_num = 0
        for i in range(8):
            for j in range(8):
                if not self.check(i, j, now_board, turn):
                    continue
                place_num += 1
                board_leaf = deepcopy(now_board)
                self.put(i, j, board_leaf, turn)
                score += self.deep_AI_think(\
                    board_leaf,
                    not turn,
                    num + 1,
                    read_goal
                )
        
        if place_num:
            return score / place_num
        else:
            return self.predict(now_board)
```

### play
試合を行います。

```py
    def play(self) -> None:
        can, old_can = True, True
        
        self.printb()
        
        can = self.check_all()
        while can or old_can:
            if can:
                if self.turn:
                    print("black turn")
                    self.think[self.black_method]()
                else:
                    print("white turn")
                    self.think[self.white_method]()
                self.printb()
            self.turn = not self.turn
            old_can = can
            can = self.check_all()
        
        self.count_last()
```

### 実行部分
オブジェクトの作成と試合実行を行います。
引数playerを変更することで黒と白どちらでプレイするかを変更でき、player_methodを変更することでAIと対戦するときの思考方法を変更できます。
humanを指定することで自分がAIと対戦でき、randomやnhand、nleastなどに変更することも可能です。

```py
if __name__ == "__main__":
    a = osero_deep_AI(player=BLACK, player_method=osero.PLAY_WAY["human"])
    a.play()
```

# 実際にやってみた
機械学習の時よりも強くなった気はしますが、まだまだ人間に勝てるほどの強さはありません。

# フルバージョン

https://github.com/TTaisei/osero/tree/main/deep_learn
# 次回は
最初予想していたよりはるかに長い時間がかかりましたが、これでランダム・探索法・機械学習・深層学習の四手法でのオセロが完成しました。
それぞれを戦わせ、その強さを客観的に評価してみたいと思います。

[次回](https://qiita.com/tt_and_tk/items/13be9077d3b136c20375)

https://qiita.com/tt_and_tk/items/068f2afde6db637e189f