TadaoYamaokaの開発日記

個人開発しているスマホアプリや将棋AIの開発ネタを中心に書いていきます。

Windows(Bash on Windows)でfastTextを使う

word2vecより高速で学習できて精度がよいというfastTextを試してみました。

環境

学習用データの準備

確認用にコンパクトなデータセットとして、Wikipediaの全ページの要約のデータを使用した。

Index of /jawiki/latest/
から、jawiki-latest-abstract.xml をダウンロードする。

XMLファイルからテキストを抽出するため、
GitHub - icoxfog417/fastTextJapaneseTutorial: Tutorial to train fastText with Japanese corpus
こちらのツールを使用させていただいた。

$ git clone https://github.com/icoxfog417/fastTextJapaneseTutorial.git 
$ cd fastTextJapaneseTutorial
$ mkdir source
$ mkdir corpus

sourceディレクトリにダウンロードしたjawiki-latest-abstract.xmlを格納し、

$ python3 parse.py source/jawiki-latest-abstract.xml --extract

corpus/abstracts.txt
に抽出したテキストが保存される。

形態素解析

日本語のテキストはそのままでは使用できないため、MeCabを使用して単語に分割する。

Bash on WindowsMeCabをインストールする。

$ sudo apt-get install mecab libmecab-dev mecab-ipadic
$ sudo aptitude install mecab-ipadic-utf8
$ sudo apt-get install python-mecab

テキストを単語に分割する。

$ mecab corpus/abstracts.txt -O wakati -o data.txt

単語に分割した結果が、data.txtに保存される。

fastTextのインストール

事前に、make、g++をインストールしておく。

適当な作業ディレクトリで、

$ git clone https://github.com/facebookresearch/fastText.git
$ cd fastText
$ make

fastTextで学習

$ ./fasttext skipgram -input path/to/data.txt -output model -dim 100

「-dim」オプションに次元を指定する。
小さめのコーパスのため最小の100とした。

model.binとmodel.vecが出力される。

学習済みモデルを使用

Pythonから使用するには、gensimを使用する。
gensimはfastTextのモデルの読み込みにも対応している。

$ sudo pip3 install gensim

で、gensimをインストールする。

pythonで以下のスクリプトを実行する。

from gensim.models.wrappers.fasttext import FastText
model = FastText.load_fasttext_format('model')
model.most_similar(positive = ["王様","女性"], negative = ["男性"], topn = 3)

王様 - 男性 + 女性の結果が表示される。

[('おかえり', 0.6713434457778931), ('恋', 0.6410529613494873), ('おんな', 0.6355608701705933)]

Wikipediaの全ページの要約のデータではあまり期待した結果にはならなかった。。。

強化学習の教科書

昨日の日記強化学習の勉強を始めたと書いたが、教科書についてまとめておく。

購入した教科書は、昨日の日記で取り上げた

と、TD学習を発案した著者による

この本である。

強化学習を体系的に記述してあり、本格的に学ぶなら後者の本がよいと思う。ただし、内容は難しい。
前者の本はサンプルプログラムも付いているので、動かしながら学べるのが良いと思う。

ちなみに、後者の本は翻訳本で、原書のSecond EditionのDraftのPDFがオンラインで読める。
Sutton & Barto Book: Reinforcement Learning: An Introduction

翻訳本の元の版の内容を全て含んでいる。
英語での表現を知りたければ翻訳本と対比してみるとよいと思う。

Second Editionでは、AlphaGoのRL policy networkの学習で用いられた手法である、REINFORCEアルゴリズムについても「13.4 REINFORCE with Baseline」で取り上げられている。

ホームページのデザイン変更

わりとどうでも良い話ですがホームページのデザインを変更しました。

はてなブログに記事を書き始めてからほとんど更新しなくなっていますが、90年代のようなデザインを直したいと思っていました。

最近CSSフレームワークの存在を知ったので、人気があるらしいBootstrapを適用してみた。

変更前

f:id:TadaoYamaoka:20170415224928p:plain

変更後

f:id:TadaoYamaoka:20170415225147p:plain

ちょっとモダンになった。
内容はまったく変わっていません。。。

強化学習の勉強

コンピュータ将棋の記事をちょっと書いたこともあり、強化学習について勉強を始めました。

教科書として用いるのは、

この本です。

この本で使用されているサンプルプログラムは、Octaveとg++が使用されいます。
Windowsで最新のOctaveとMSYS2で実行するのに少し苦労したので、実行方法を記しておきます。

環境

三目並べのコードの実行方法

Octaveのインストール

https://ftp.gnu.org/gnu/octave/windows/
から、「octave-4.2.1-w64-installer.exe」をダウンロードしてインストールする。

MSYS2のインストール

MSYS2 download | SourceForge.net
から、「msys2-x86_64-20161025.exe」をダウンロードしてインストールする。

MSYS2
の説明に従ってパッケージを最新にする。

g++インストール

スタートメニューから「MSYS2 MinGW 64bit」を起動する。

$ pacman -S mingw-w64-x86_64-toolchain
:: 17 のパッケージがグループ mingw-w64-x86_64-toolchain にあります:
:: リポジトリ mingw64
   1) mingw-w64-x86_64-binutils  2) mingw-w64-x86_64-crt-git  3) mingw-w64-x86_64-gcc  4) mingw-w64-x86_64-gcc-ada
   5) mingw-w64-x86_64-gcc-fortran  6) mingw-w64-x86_64-gcc-libgfortran  7) mingw-w64-x86_64-gcc-libs
   8) mingw-w64-x86_64-gcc-objc  9) mingw-w64-x86_64-gdb  10) mingw-w64-x86_64-headers-git
   11) mingw-w64-x86_64-libmangle-git  12) mingw-w64-x86_64-libwinpthread-git  13) mingw-w64-x86_64-make
   14) mingw-w64-x86_64-pkg-config  15) mingw-w64-x86_64-tools-git  16) mingw-w64-x86_64-winpthreads-git
   17) mingw-w64-x86_64-winstorecompat-git

選択して下さい (デフォルト=all): 1 2 3 7 9 10 11 12 13 14 15 16

makeコマンドのファイル名がmingw32-make.exeとなっているので、コマンドプロンプトでmake.exeへハードリンクを作成する。

cd /d C:\msys64\mingw64\bin
mklink /H make.exe mingw32-make.exe
FLTKのインストール

FLTKはパッケージマネージャからインストールできないので、
GitHub - msys2/MINGW-packages: Package scripts for MinGW-w64 targets to build under MSYS2.
のビルドスクリプトを使用して、自分でビルドしてインストールする。

「MSYS2 MinGW 64bit」を起動して、適当な作業ディレクトリに移動して、

$ git clone https://github.com/Alexpux/MINGW-packages.git
$ cd MINGW-packages
$ cd mingw-w64-fltk
$ MINGW_INSTALLS=mingw64 makepkg-mingw -sLf
$ pacman -U mingw-w64-x86_64-fltk*.pkg.tar.xz

三目並べサンプルプログラムのビルド

書籍に記載されているサポートページからサンプルプログラムをダウンロードして解凍する。
「MSYS2 MinGW 64bit」を起動して、解凍したディレクトリに移動して、

$ cd sanmoku
$ g++ -fpermissive sanmoku.cpp -o sanmoku.exe -lfltk

サンプルプログラムのコードは古いバージョンのg++でビルドされているためデフォルトオプションではエラーになるので「-fpermissive」オプションを付けている。

sanmoku.exeを実行すると、
f:id:TadaoYamaoka:20170415171548p:plain
このような画面が表示される。

学習の実行

学習のプログラムの実行方法は、書籍の4.9章に記載されている。

スタートメニューからOctave(GUI)を起動して、ファイルブラウザでサンプルプログラムを解凍したディレクトリの「sanmoku」ディレクトリを開く。
コマンドウィンドウで、書籍の通り入力していく。

options.pmode=2
options.epsilon=0.1
options.gamma=0.9
Q=MonteCarloPolicyIteration(10,1000,options)

このまま実行すると、大量に警告が出力されるので、以下の通り編集する。

action_train.mの49行目

if(val==2 & num==1)

if(val==2 && num==1)

に修正する。

MonteCarloPolicyIteration.mの97行目と99行目の日本語部分を

g=xlabel('games');

g=xlabel('rate');

に修正する。

Q=MonteCarloPolicyIteration(10,1000,options)

を実行すると、以下のように結果が表示され、

>> Q=MonteCarloPolicyIteration(10,1000,options)
1) Win=411/1000, Draw=83/1000, Lose=506/1000
2) Win=719/1000, Draw=63/1000, Lose=218/1000
3) Win=786/1000, Draw=45/1000, Lose=169/1000
4) Win=783/1000, Draw=61/1000, Lose=156/1000
5) Win=751/1000, Draw=50/1000, Lose=199/1000
6) Win=850/1000, Draw=38/1000, Lose=112/1000
7) Win=788/1000, Draw=36/1000, Lose=176/1000
8) Win=797/1000, Draw=29/1000, Lose=174/1000
9) Win=821/1000, Draw=52/1000, Lose=127/1000
10) Win=809/1000, Draw=26/1000, Lose=165/1000
Q =

Compressed Column Sparse (rows = 19683, cols = 9, nnz = 388 [0.22%])

  (1, 1) ->  4.9260
  (6, 2) -> -0.57000
  (12, 2) ->  3.1858
  (46, 2) ->  5.4000
  (84, 2) -> -2.6775
  (105, 2) -> -7.5000
  (129, 2) ->  5
  (748, 2) -> -6
  (781, 2) -> -5
  (831, 2) ->  6.3141
  (883, 2) -> -4.5000
  (937, 2) -> -5
  (968, 2) ->  10
  (993, 2) ->  4.0500
  (1158, 2) ->  9
  (1182, 2) ->  10
  (1245, 2) ->  4.5000
  (1344, 2) ->  20
  (1590, 2) -> -20
  (1713, 2) -> -7.5000
  (1717, 2) -> -5
  (1772, 2) -> -5
  (1806, 2) -> -10
  (1808, 2) -> -5
  (1856, 2) -> -5
  (1878, 2) ->  40
  (1896, 2) ->  12
  (1902, 2) -> -11.667
  (1906, 2) ->  10
  (1949, 2) -> -5
  (1988, 2) ->  9

グラフウィンドウが起動する。一度ウィンドウを切り替えないと描画されないので、他のウィンドウをアクティブにしてからグラフのウィンドウをアクティブにする。
f:id:TadaoYamaoka:20170415172533p:plain

対戦の実行

学習した関数を使用して対戦を実行できるが、Octaveで改行コードがCrLfだと、state.txtが正しく読めない。
observe_test.mの19行目を以下の通り編集する。

    [tok,rem]=strtok(rem,":\r\n");
    if (length(tok) == 0)
      break;
    endif

これで、書籍に記載している通りの実行方法で対戦ができる。

test(Q,options)

f:id:TadaoYamaoka:20170415182527p:plain

将棋でディープラーニングする その3(棋譜から学習)

プロの棋譜を使って前回の日記で作成したニューラルネットワークの学習を行った。

棋譜サイトにあった竜王戦棋譜を使用した。

訓練データとテストデータの数は以下の通り。

訓練データ テストデータ
局数 3744 417
局面数 422852 47242

学習には非常に時間がかかるため、学習が進んでいることが確認できたら途中で打ち切ることにした。

はじめ最適化手法をAdamで行ったが学習が進まないので、AdaGradにしたところ学習が進むことを確認できた。
ミニバッチサイズを8として、1ミニバッチサイズの学習を1イテレーションとして、32500イテレーション学習した結果は以下の通り。
f:id:TadaoYamaoka:20170410204331p:plain
accuracyは100イテレーションごとに、テストデータからランダムに抽出した64サンプルで評価している。

グラフを見ると、lossは順調に下がっている。
accuracyは、0.15程度まで上昇しており、15%の精度でプロの指し手を予測している。

lossはまだ下がりそうなのでこのまま学習を進めれば予測精度は上がっていくと思われる。

lossが下がらなくなるまで学習を進めないとはっきりしたことは言えないが、将棋でもディープラーニングを使用して指し手の予測ができそうである。

学習に使用したソースをGitHubに公開した。
github.com

次は、特徴に手番や王手、2歩などを追加した場合に精度が上がるか比較してみたい。

追記

3エポック(158500イテレーション)学習した結果、accuracyは0.21程度まで上昇した。
f:id:TadaoYamaoka:20170410221308p:plain

実行時間は、GeForce1080で35分かかった。

追記2

後手番のときに盤を回転しないで学習していたので、180度回転して学習するようにしたところ、accuracyが0.25くらいまで上がった。
f:id:TadaoYamaoka:20170410234225p:plain

将棋でディープラーニングする その2(ニューラルネットワークの構成)

先日の日記に続き、将棋でのディープラーニングの実装を試す。

今回は、ニューラルネットワークの構成を検討する。

ネットワーク構成

ネットワーク構成は、AlphaGoのネットワーク構成を参考にし、13層の畳み込みニューラルネットワーク(DCNN)とする。
位置について精度が要求されるため、プーリング層は使用しない。

入力特徴

9×9の2値画像を特徴数の枚数分入力する。
特徴は以下の通りとする。

特徴 枚数
自プレイヤーの駒の配置 14
自プレイヤーの持ち駒 38
相手プレイヤーの駒の配置 14
相手プレイヤーの持ち駒 38
空の配置 1

持ち駒は、駒ごとに1枚を割り当て、持ち駒の最大枚数分を割り当てる。
持ち駒がある場合は、すべて1の画像とし、持ち駒がない場合はすべて0の画像とする。

入力特徴数の合計は、105枚となる。

他にも、手番や王手かどうか、2歩の位置なども入力特徴とした方が精度があがると思われるが、一旦最低限の特徴のみで検証を行いたい。

出力

指し手の確率を出力する。
指し手は合法手を絞らず、駒ごとの座標に一つのラベルを割り当て、多クラス分類問題として扱う。
どの駒を移動したかは考慮しない。
ラベル数は、駒の種類が14なので、14×9×9=1134となる。

Chainerでの実装

以上のように設計したネットワーク構成、入力特徴、出力をChainerで実装すると以下のようになる。

import chainer
from chainer import Chain
import chainer.functions as F
import chainer.links as L

import shogi

k = 256
class MyChain(Chain):
    def __init__(self):
        super(MyChain, self).__init__(
            l1=L.Convolution2D(in_channels = None, out_channels = k, ksize = 3, pad = 1),
            l2=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l3=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l4=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l5=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l6=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l7=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l8=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l9=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l10=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l11=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l12=L.Convolution2D(in_channels = k, out_channels = k, ksize = 3, pad = 1),
            l13=L.Convolution2D(in_channels = k, out_channels = len(shogi.PIECE_TYPES), ksize = 1, nobias = True),
            l13_2=L.Bias(shape=(9*9*len(shogi.PIECE_TYPES)))
        )

    def __call__(self, x):
        h1 = F.relu(self.l1(x))
        h2 = F.relu(self.l2(h1))
        h3 = F.relu(self.l3(h2))
        h4 = F.relu(self.l4(h3))
        h5 = F.relu(self.l5(h4))
        h6 = F.relu(self.l6(h5))
        h7 = F.relu(self.l7(h6))
        h8 = F.relu(self.l8(h7))
        h9 = F.relu(self.l9(h8))
        h10 = F.relu(self.l10(h9))
        h11 = F.relu(self.l11(h10))
        h12 = F.relu(self.l12(h11))
        h13 = self.l13(h12)
        return self.l13_2(F.reshape(h13, (len(h13.data), 9*9*len(shogi.PIECE_TYPES))))

フィルター枚数は、AlphaGoでは192だが、将棋は駒の種類が多いため一旦256とした。
フィルター枚数などのハイパーパラメータは実験により調整が必要である。

次回、作成したニューラルネットワークを使用して、棋譜から学習を行う予定。

将棋でディープラーニングする

先日の日記で1行もコードを書かずに、将棋におけるディープラーニングについて考察した。

コードを書いて実験しないとただの机上の空論になるのでコードを書いて検証してみたいと思う。
といっても、将棋のプログラムの実装は行ったことがない。
一からコードを書くのはしんどいので、とりあえず使えるライブラリを探して使用していきたいと思う。

python-shogiのインストール

Pythonディープラーニングフレームワークを使用したいので、Pythonで扱えるライブラリがよい。
Python向けの将棋のライブラリとして、python-shogiが見つかった。
pypi.python.org

pipからインストールが可能になっている。

pip install python-shogi

でインストールできる。

しかし、Windowsでインストールしようとすると、

UnicodeDecodeError: 'cp932' codec can't decode byte 0x81 in position 2239: illegal multibyte sequence

というエラーが表示されて、インストールできなかった。

GitHubからリポジトリをクローンしてきて、setup.pyの25行目を以下の通り編集(「, encoding='utf-8'」を追加)し、

  description = open(os.path.join(os.path.dirname(__file__), 'README.rst'), encoding='utf-8').read()
pip install --no-cache-dir -e .

でインストールした。
GitHubにプルリクエストを送ったので、そのうち修正してもらえるかもしれない。(※2017/4/9 マージしていただきました。)

棋譜スクレイピング

棋譜サイトから棋譜のデータをスクレイピングして集める。
使用した棋譜サイトは、直リンクでは棋譜がダウンロードできず、ボタンクリックをしてCSA形式で棋譜データが表示できるようになっていたので、SeleniumとPhantomJSを使用して棋譜データを抽出した。

from selenium import webdriver
driver = webdriver.PhantomJS(executable_path='C:/bin/phantomjs-2.1.1-windows/bin/phantomjs.exe')

driver.get('https://xxxxxxx')
driver.implicitly_wait(10)
csa_export = driver.find_element_by_id('xxxxx')
csa_export.click()
csa_txt = driver.find_element_by_css_selector('xxxx').text

このような感じに記述することで取得できる。

棋譜の再生

取得したCSA形式の文字列を、python-shogiで再生する。

import shogi
import shogi.CSA

kifu = shogi.CSA.Parser.parse_str(csa_txt)[0]
moves = kifu['moves']

board = shogi.Board()
board.push_usi(moves[0])
board.push_usi(moves[1])
board.push_usi(moves[2])
...

盤の駒は以下のようにして取得する。

board.piece_at(shogi.I4)

持ち駒は以下のようにして取得する。

hand = board.pieces_in_hand
hand_b = hand[shogi.BLACK] # 先手
for p in hand_b.keys():
    print("piece: {0}, num: {1}".format(shogi.Piece(p, shogi.BLACK).symbol(), hand_b[p]))

以上のようにして、教師データの準備ができる。

本日はここまで。
次回、方策のニューラルネットワークを記述する予定。