書籍「将棋AIで学ぶディープラーニング」のプログラムコードをGoogle Colaboratoryで動かす + GPUで学習と対局も

# 書籍「将棋AIで学ぶディープラーニング」のプログラムコードをGoogle Colabで動かす、学習と対局も

- 以前は
 - 「[書籍「将棋AIで学ぶディープラーニング」のプログラムコードを動かす](/kfjt/items/8553cfe6a61dd04b2d0b)」にて、写経部分をコピペできるようにしました。
 - 「[書籍「将棋AIで学ぶディープラーニング」のプログラムコードをCPUで動かす](/kfjt/items/cb5d8b567dbf830bbaf2)」にて、CPUで動くように書きかえました。
- 今回は
 - **Google Colab**で動かします。

## 一番伝えたい事

- 「[ColaboratoryでのGoogle Driveへのマウントが簡単になっていたお話 - Qiita](/k_uekado/items/45b76f9a6f920bf0f786)」が凄く嬉しかった。
- ですが、いいねが伸びてない、検索上位に上がって来てないので、活用記事を書いて支援したい。

## やること
1. ローカル端末で資材を集める
1. Googleドライブと同期する
1. Google ColabでGoogleドライブをマウント
1. 環境つくり
1. 動作確認
1. 学習
1. 対局

## ローカル端末で資材を集める
- python-dlshogi on Githubをクローンする。
 - `git clone https://github.com/TadaoYamaoka/python-dlshogi.git`

## Googleドライブと同期する

章題のとおり。

## Google ColabでGoogleドライブをマウント

`Go to this URL in a browser:`が一度だけでてきます。これまでのイディオムだと二度必要だったので楽になりました。

```python:Colab_cel_1
from google.colab import drive
drive.mount('/content/drive')
```

ここでは、**My Drive**が空白含みで扱いづらいのでリンクはってみました。

```python:Colab_cel_2
%cd /content
!ln -snf '/content/drive/My Drive/' MyDrive
```

やってて気づきましたが、一つのセルに一行で書くだけなら、`cd hoge`や`ls`などが`%cd`や`!ls`としなくて良くなりました。楽ちんです。

## 環境つくり

- chainerとpython-shogiをいれます。

```python:Colab_cel_3
!curl https://colab.chainer.org/install | sh -
!pip install python-shogi==1.0.8
```

GPUが有効なことも確認しましょう。

```python:Colab_cel_4
import chainer
print('GPU availability:', chainer.cuda.available)
print('cuDNN availablility:', chainer.cuda.cudnn_enabled)
```

お目当ての**python-dlshogi**をありがたくインストールします。

```python:Colab_cel_5
%cd /content/MyDrive/python-dlshogi
!mkdir log
!pip install --no-cache-dir -e .
```

## 動作確認

- ためしにpolicyプレイヤを動かします。

```python:Colab_cel_6
# !python -m pydlshogi.usi.usi_policy_player
from pydlshogi.usi.usi import *
from pydlshogi.player.policy_player import *

player = PolicyPlayer()
usi(player)
```

- つぎのテキスト入力を順次して動作することを確認します。
 - Colabっていつから対話できるようになりましたか？楽ちん。

1. `usi`
 1. 戻りの最終行は`usiok`
1. `setoption name modelfile value /content/MyDrive/python-dlshogi/model/model_policy_value_resnet`
 1. 戻りはない
1. `isready`
 1. 戻りは`readyok`
1. `position startpos`
 1. 戻りは`lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1`
1. `go`
 1. 戻りの最終行は`bestmove ....`
1. `quit`

## 学習

### ローカル端末で資材を集める

powershellで準備

- **wdoor2016.7z**をダウンロードする
- **kifulist_test_100.txt**と**kifulist_train_1000.txt**を編集する

```powershell:get-kifu.ps1
# Googleドライブのとこで作業
pushd $googledrive

# 棋譜
$homepage="http://wdoor.c.u-tokyo.ac.jp/shogi/"
$filehost=(wget $homepage|%{$_.Links}|?{$_.href -match "downloads"}|?{$_.href -match "2016"}).href
$filename=$filehost.split("/")[-2]
$fileref=(wget $filehost|%{$_.Links}|?{$_.href -match $filename+"$"}).href
$uri = New-Object System.Uri $filehost;
$downloadUrl = New-Object System.Uri ($uri,  ($fileref[0]).Replace('&amp;','&'))
wget -uri $downloadUrl -OutFile $filename
$kifuarchive = $filename

# 7zip
$homepage="https://sevenzip.osdn.jp/"
$downloadpage=(wget $homepage|%{$_.Links}|?{$_.innerText -eq "ダウンロード"}|?{$_.href -match "download"}).href
$filehost=(wget $downloadpage|%{$_.Links}|?{$_.innerText -eq "ダウンロード"}|?{$_.href -match "7za"}).href
$filename=$filehost.split("/")[-2]
$fileref=(wget $filehost|%{$_.Links}|?{$_.href -match $filename+"$"}).href
$uri = New-Object System.Uri $filehost;
$downloadUrl = New-Object System.Uri ($uri,  ($fileref[0]).Replace('&amp;','&'))
wget -uri $downloadUrl -OutFile $filename
$7zaarchive = $filename

# Expand zip
Expand-Archive $7zaarchive
$7zaexe = (Get-Item $7zaarchive).BaseName+"/7za.exe"

# Expand 7z
& "$7zaexe" x "$kifuarchive"

# del zip 7z
rmdir -recurse (Get-Item $7zaarchive).BaseName
del $7zaarchive
del $kifuarchive

# kifulist_*.txt
$kifulists=Get-Childitem python-dlshogi|?{$_ -match "kifulist_.+\.txt"}|%{$_.fullname}
# "D:\2016\" to "/content/MyDrive/2016/"
foreach($kifulist in $kifulists) {
  (Get-Content $kifulist) | %{ $_ -replace "D:\\2016\\", "/content/MyDrive/2016/"} | Set-Content $kifulist
}
# crlf to lf
foreach($kifulist in $kifulists) {
  [string]::Join("`n",(Get-Content $kifulist))  | Set-Content $kifulist
}
```

### Googleドライブと同期する その２

章題のとおり。

### policy学習

```python:Colab_cel_7
%run -i train_policy.py kifulist_train_1000.txt kifulist_test_100.txt --eval_interval 100 --log log
```

## 対局の準備

- `python-dlshogi/model/model_policy`が更新されています。
- これを使い対局しましょう。
- とりあえずpolicy対policyです

### 将棋サーバ

- shogi-serverを取得

```python:Colab_cel_8
%cd /content/MyDrive/
!git clone git://git.sourceforge.jp/gitroot/shogi-server/shogi-server.git
%cd /content/MyDrive/shogi-server
%mkdir player-logs
!apt-get install --no-install-recommends ruby
```

- shogi-serverをデーモン実行

```python:Colab_cel_9
%cd /content/MyDrive/shogi-server
!ruby shogi-server --daemon . \
 --pid-file ./shogi-server.pid \
 --player-log-dir ./player-logs \
 test 4081
```

### クライアント実行スクリプトをつくる

- playerをpythonモジュール実行する、というスクリプト

```python:Colab_cel_10
%%writefile /content/MyDrive/usi_policy_player
python3 -m pydlshogi.usi.usi_policy_player
```

```python:Colab_cel_11
%%writefile /content/MyDrive/usi_search1_player
python3 -m pydlshogi.usi.usi_search1_player
```

- playerを二つ登録する、というスクリプト
    - [`shogi-server/bin/usiToCsa.rb`はクライアントの実装例](https://ja.osdn.net/projects/shogi-server/wiki/UsiToCsa)

```python:Colab_cel_12
%%writefile /content/MyDrive/start_shogi_game
ruby /content/MyDrive/shogi-server/bin/usiToCsa.rb --host localhost --port 4081 --id pydlshogi1 --password pydlshogi1p --gamename=test1 --hash=12000 --keep-alive=60 --options="modelfile=/content/MyDrive/python-dlshogi/model/model_policy" /content/MyDrive/usi_policy_player &
ruby /content/MyDrive/shogi-server/bin/usiToCsa.rb --host localhost --port 4081 --id pydlshogi2 --password pydlshogi2p --gamename=test1 --hash=12000 --keep-alive=60 --options="modelfile=/content/MyDrive/python-dlshogi/model/model_value" /content/MyDrive/usi_search1_player &
echo start shogi game
```

## 対局する

```python:Colab_cel_12
!chmod +x /content/MyDrive/usi_policy_player
!chmod +x /content/MyDrive/usi_search1_player
!chmod +x /content/MyDrive/start_shogi_game
!/content/MyDrive/start_shogi_game
```

- 出力は次の通り
- 対局は正常に終わっていますが
- input()についてEOFErrorがあります、これは諦める
- ディレクトリ`shogi-server/yyyy/mm/dd/`にcsaファイルが出来ます

```sh:正常に対戦が終わった様子
start shogi game
Starting engine...  /content/MyDrive/usi_policy_player
Starting engine...  /content/MyDrive/usi_search1_player
Connecting to localhost:4081...
Login...  test1 pydlshogi1,xxxxxxxx
LOGIN:pydlshogi1 OK
Wait for a game start...
Connecting to localhost:4081...
Login...  test1 pydlshogi2,xxxxxxxx
LOGIN:pydlshogi2 OK
Wait for a game start...
game crated test+default-1500-0+pydlshogi2+pydlshogi1+20181009022757
game crated test+default-1500-0+pydlshogi2+pydlshogi1+20181009022757
#RESIGN
#LOSE
game finished.
#RESIGN
Traceback (most recent call last):
  File "/usr/lib/python3.6/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/lib/python3.6/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/content/drive/My Drive/python-dlshogi/pydlshogi/usi/usi_search1_player.py", line 5, in <module>
#WIN
game finished.
    usi(player)
  File "/content/drive/My Drive/python-dlshogi/pydlshogi/usi/usi.py", line 3, in usi
    cmd_line = input()
EOFError: EOF when reading a line
Traceback (most recent call last):
  File "/usr/lib/python3.6/runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "/usr/lib/python3.6/runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "/content/drive/My Drive/python-dlshogi/pydlshogi/usi/usi_policy_player.py", line 5, in <module>
    usi(player)
  File "/content/drive/My Drive/python-dlshogi/pydlshogi/usi/usi.py", line 3, in usi
    cmd_line = input()
EOFError: EOF when reading a line
```

## 活用する

- 繰り返し対戦できるようにして云々
- csaファイルを活用して追加の学習を云々
- ビジュアルで対局内容を確認したいときは「[JS将棋盤](http://www.geocities.jp/shogi_depot/JS/index.html)」 でcsaファイルを再生してはいかがでしょうか

## オリジナルPlayer作成のとっかかり

- python-shogiはCSAクライアントです
- python-dlshogiはUSIクライアントです
- python-dlshogiを参考にディープラーニングとか無しでランダムに合法手を返すプレーヤーは次のように簡単にできます
- 新規にGoogle Colabを開いて次の４セルだけ実行しましょう。

- まずpython-shogi取得

  ```python:Colab_cel_ex1
!pip install python-shogi==1.0.8
  ```
- BasePlayerをパクります

```python:Colab_cel_ex2
import shogi

class BasePlayer:
    def __init__(self):
        self.board = shogi.Board()

    def usi(self):
        pass

    def usinewgame(self):
        pass

    def setoption(self, option):
        pass

    def isready(self):
        pass

    def position(self, moves):
        if moves[0] == 'startpos':
            self.board.reset()
            for move in moves[2:]:
                self.board.push_usi(move)
        elif moves[0] == 'sfen':
            self.board.set_sfen(' '.join(moves[1:]))
        # for debug
        print(self.board.sfen())

    def go(self):
        pass

    def quit(self):
        pass
```

- ランダムプレーヤーを定義、各メソッドの中も最低限にしたつもり

```python:Colab_cel_ex3
import random

class RandomPlayer(BasePlayer):
    def __init__(self):
        super().__init__()

    def usi(self):
        print('id name policy_player')
        print('usiok')

    def setoption(self, option):
        pass

    def isready(self):
        print('readyok')

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

        moves = [move for move in self.board.legal_moves]        
        bestmove = random.choice(moves)
        print('bestmove', bestmove.usi())
```

- usiクライアントを定義、プレイヤーを呼んでる

```python:Colab_cel_ex4
player = RandomPlayer()

def usi(player):
    while True:
        cmd_line = input()
        cmd = cmd_line.split(' ', 1)

        if cmd[0] == 'usi':
            player.usi()
        elif cmd[0] == 'setoption':
            option = cmd[1].split(' ')
            player.setoption(option)
        elif cmd[0] == 'isready':
            player.isready()
        elif cmd[0] == 'usinewgame':
            player.usinewgame()
        elif cmd[0] == 'position':
            moves = cmd[1].split(' ')
            player.position(moves)
        elif cmd[0] == 'go':
            player.go()
        elif cmd[0] == 'quit':
            player.quit()
            break
```

- いざ実行、`usi`→`setposition startpos`→`go`とかすると`bestmove`が返ってきます
- さらに`setposition ～`と`go`で対局が進みます
- または`go`だけ連続で渡せばランダムなbestmoveが返ってくることが確認できます
- 適当に`quit`して終わりましょう

```python:Colab_cel_ex5
usi(player)
```

- これで分かる通り、３ファイルあれば取り合えず対局できそうです
  - `BasePlayer`をパクる
  - `自前Player`をつくる
  - `usi`クライアントをつくる
- ロジックを挟むポイントは、
  - 自前Playerの`go`の中身を良い感じに`board`から情報をとって、`bestmove`を決定するだけです
  - ランダムの例ならこの部分を変更します

```python:Colab_cel_ex3抜粋
 moves = [move for move in self.board.legal_moves]
 bestmove = random.choice(moves)
```

- この基本を押さえて`python-dlshogi`のコードを見ると、ディープラーニングの成果をどのように呼び出しているか理解しやすいと思います

## 書籍紹介を再掲載
![将棋AIで学ぶディープラーニング](https://book.mynavi.jp/files/topics/88752_ext_06_0.jpg)
- [マイナビBooks](https://book.mynavi.jp/ec/products/detail/id=88752):出版元([サポートサイト](https://book.mynavi.jp/supportsite/detail/9784839965419.html)←ここに正誤表あります)
- [Amazon](https://www.amazon.co.jp/dp/4839965412)


おしまい