将棋AIで学ぶディープラーニング on Mac and Google Colab

AI

[価値ネットワーク]
第10章6〜9

1手探索のAI(search1_player.py)

合法手フィルタリングの場所

合法手フィルタリングの場所が方策ネットワークのときよりも前に来ている。目的は?使用メモリ削減?そのせいで方策ネットワークのときと変数の意味が変わっていたりするので注意。(leagal_logitsとlogitsとか)

features.append(make_input_features_from_board(self.board))

make_input_features_from_boardの出力:先手の駒が有る場所、先手の持ち駒、後手の駒が有る場所、後手の持ち駒
[(9×9の行列),
(9×9の行列), ・・・が(18+4+4+4+4+2+2)個,
(9×9の行列),
(9×9の行列), ・・・が(18+4+4+4+4+2+2)個]
featuresにはこの配列が合法手の数だけ追加される。[[この配列],[この配列],・・・,[この配列]]。
合法手フィルタリングした後に実施しているので要素数は合法手の数。例えば初手だったら要素数30。

y.data

y.dataの一例

[[-0.04460792]
 [ 0.02167853]
 [ 0.04610606]
  ・・・
 [-0.09904062]]

y.data.reshape(-1)

 [-0.04460792  0.02167853  0.04610606 -0.10492548 -0.22675163 -0.23193529
  -0.06671577  0.02509898 -0.02109829 -0.05519588 -0.05578787 -0.03609923
  -0.11021192 -0.10877373 -0.04065045 -0.01540023 -0.0336022  -0.03805592
   -0.03325626 -0.02194545 -0.08399387 -0.13204134 -0.2106831  -0.24970257
   -0.18735377 -0.08184412 -0.15573277 -0.00548664 -0.0353202  -0.09904062]

合法手フィルタリングした後に実施しているので要素数は合法手の数。上記は初手の局面でprintしてみた例である。初手は合法手が30手なので要素が30個。

for i, move in enumerate(legal_moves):

enumerateはインデックスと値を返す
方策ネットワークではインデックスを取得するためにmake_output_labelをしていた。
その方が原理的な意味はわかりやすい。ただし記述が多くなる。
価値ネットワークではインデックスを取得するためにenumerateを使っている。
原理的な意味はわかりづらいが記述は簡単。やってることの意味は同じだと思われる。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# 環境設定
#-----------------------------
import socket
host = socket.gethostname()
# IPアドレスを取得
# google colab  : ランダム
# iMac          : xxxxxxxx
# Lenovo        : yyyyyyyy

# env
# 0: google colab
# 1: iMac (no GPU)
# 2: Lenovo (no GPU)

# gpu_en
# 0: disable
# 1: enable

if host == 'xxxxxxxx':
    env = 1
    gpu_en = 0
elif host == 'yyyyyyyy':
    env = 2
    gpu_en = 0
else:
    env = 0
    gpu_en = 1

# 戦略
# 'greedy': グリーディー戦略
# 'boltzmann': ソフトマックス戦略

algorithm ='boltzmann'

#-----------------------------

import numpy as np
import chainer
from chainer import serializers
import chainer.functions as F
if gpu_en == 1:
    from chainer import cuda, Variable

import shogi

from pydlshogi.common import *
from pydlshogi.features import *
from pydlshogi.network.value import *
from pydlshogi.player.base_player import *

def greedy(logits): # 引数に指定したリストの要素のうち、最大値の要素のインデックスを返す
                    # logitsとはニューラルネットワークでは活性化関数を通す前の値のこと
    return np.argmax(logits)
    # 方策ネットワークではlogits.index(max(logits)) としていた。同じ意味。少しづつ記述を簡略化していっているのか?

def boltzmann(logits, temperature):
    logits /= temperature # a /= b は a = a / b の意味
    logits -= logits.max() # a -= b は a = a - b の意味。 マイナスの値になる。最大値は0。
    probabilities = np.exp(logits) # x =< 0 のexp関数
    probabilities /= probabilities.sum()
    return np.random.choice(len(logits), p=probabilities) # choice(i, p=b)は0~i-1までの数値をbの確率でランダムに返す

class Search1Player(BasePlayer):
    def __init__(self):
        super().__init__()
        if env == 0:
            self.modelfile = '/content/drive/My Drive/・・・/python-dlshogi/model/model_value'
        elif env == 1:
            self.modelfile = r'/Users/・・・/python-dlshogi/model/model_value' # 学習して作成した価値ネットワークモデル
        elif env == 2:
            self.modelfile = r"C:\Users\・・・\python-dlshogi\model\model_value"
        self.model = None

    def usi(self): # GUIソフト側:起動後にUSIコマンドを送信する。USI側:id(とoption)とusiokを返す。
        print('id name search1_player')
        print('option name modelfile type string default ' + self.modelfile)
        print('usiok')

    def setoption(self, option):
        if option[1] == 'modelfile':
            self.modelfile = option[3]

    def isready(self): # GUIソフト側:対局開始前にisreadyコマンドを送信する。USI側:初期化処理をしてreadyokを返す。
        if self.model is None:
            self.model = ValueNetwork()
            if gpu_en == 1:
                self.model.to_gpu()
        serializers.load_npz(self.modelfile, self.model)
        print('readyok')

    def go(self):
        if self.board.is_game_over():
            print('bestmove resign')
            return

        # 全ての合法手について
        #  合法手フィルタリングの場所が方策ネットワークのときよりも前に来ている。
        #  目的は?使用メモリ削減?
        #  そのせいで方策ネットワークのときと変数の意味が変わっていたりするので注意。(leagal_logitsとlogitsとか)
        legal_moves = []
        features = []
        for move in self.board.legal_moves:
            legal_moves.append(move)

            self.board.push(move) # 1手指す

            features.append(make_input_features_from_board(self.board))
            # make_input_features_from_boardの出力:先手の駒が有る場所、先手の持ち駒、後手の駒が有る場所、後手の持ち駒
            # [(9x9の行列),
            # (9x9の行列), ・・・が(18+4+4+4+4+2+2)個,
            # (9x9の行列),
            # (9x9の行列), ・・・が(18+4+4+4+4+2+2)個]
            # featuresにはこの配列が合法手の数だけ追加される。[[この配列],[この配列],・・・,[この配列]]。
            # 要素数は合法手の数。例えば初手だったら要素数30。

            self.board.pop() # 1手戻す

        if gpu_en == 1:
            x = Variable(cuda.to_gpu(np.array(features, dtype=np.float32)))
        elif gpu_en == 0:
            x = np.array(features, dtype=np.float32)

        # 自分の手番側の勝率にするため符号を反転
        with chainer.no_backprop_mode():
            y = -self.model(x)

            if gpu_en == 1:
                logits = cuda.to_cpu(y.data).reshape(-1) # reshape(-1)で1次元配列にする
                probabilities = cuda.to_cpu(F.sigmoid(y).data).reshape(-1)
            elif gpu_en == 0:
                logits = y.data.reshape(-1) # ちなみにy.dataの要素数は、合法手の数。例えば初手なら30個。
                probabilities = F.sigmoid(y).data.reshape(-1)
                # y.dataの一例
                # [[-0.04460792]
                #  [ 0.02167853]
                #  [ 0.04610606]
                #   ・・・
                #  [-0.09904062]]
                #
                # y.data.reshape(-1)
                #  [-0.04460792  0.02167853  0.04610606 -0.10492548 -0.22675163 -0.23193529
                #   -0.06671577  0.02509898 -0.02109829 -0.05519588 -0.05578787 -0.03609923
                #   -0.11021192 -0.10877373 -0.04065045 -0.01540023 -0.0336022  -0.03805592
                #    -0.03325626 -0.02194545 -0.08399387 -0.13204134 -0.2106831  -0.24970257
                #    -0.18735377 -0.08184412 -0.15573277 -0.00548664 -0.0353202  -0.09904062]
                #   要素数は合法手の数。上記は初手の例で合法手が30手なので要素が30個。

            for i, move in enumerate(legal_moves):
            # enumerateはインデックスと値を返す
            # 方策ネットワークではインデックスを取得するためにmake_output_labelをしていた。
            # その方が原理的な意味はわかりやすい。ただし記述が多くなる。
            # 価値ネットワークではインデックスを取得するためにenumerateを使っている。
            # 原理的な意味はわかりづらいが記述は簡単。やってることの意味は同じだと思われる。
                # 確率を表示
                print('info string {:5} : {:.5f}'.format(move.usi(), probabilities[i]))
                print(y.data)
                print(y.data.reshape(-1))

        if algorithm == 'greedy':
            # ①確率が最大の手を選ぶ(グリーディー戦略)単純に確率が最大の要素を返す。
            selected_index = greedy(logits)
        elif algorithm =='boltzmann':
            # ②確率に応じて手を選ぶ(ソフトマックス戦略)確率が大きい要素をランダムに返す。
            selected_index = boltzmann(np.array(logits, dtype=np.float32), 0.5)

        bestmove = legal_moves[selected_index]

        print('bestmove', bestmove.usi())

対局

価値ネットワークだけ採用して1手探索だけ行うAI。弱すぎますね。

終局図

コメント

タイトルとURLをコピーしました