TadaoYamaokaの開発日記

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

UbuntuでBoostをビルドしてBoost.PythonとBoost.Numpyを使う

Ubuntu 16.04 LTSのaptでインストールできるBoostのバージョンは1.58なのでBoost.Numpyが使えない。
そこで、最新バージョンのBoostをソースからビルドした。

手順は、
Boost Getting Started on Unix Variants - 1.66.0
を参照した。

ソースダウンロード

wget https://dl.bintray.com/boostorg/release/1.66.0/source/boost_1_66_0.tar.bz2

解凍

tar --bzip2 -xf boost_1_66_0.tar.bz2

Pythonインストール

Boostをビルドする前にPythonをインストールする。
pyenvでAnaconda3 5.0.1をインストールしてデフォルトに設定しておく。
参考:Ubuntu 16.04 LTSにChainerをインストールする - TadaoYamaokaの開発日記

$ git clone git://github.com/yyuu/pyenv.git ~/.pyenv
$ echo 'export PYENV_ROOT="${HOME}/.pyenv"' >> ~/.bashrc
$ echo 'export PATH="${PYENV_ROOT}/bin:$PATH"' >> ~/.bashrc
$ echo 'eval "$(pyenv init -)"' >> ~/.bashrc

再ログイン

$ pyenv install --list
$ pyenv install anaconda3-5.0.1
$ pyenv versions
$ pyenv global anaconda3-5.0.1
$ pyenv versions

Ubuntu 16.4 LTSのgccより、Anacondaがコンパイルされたgccが古いとリンク時にpython3のライブラリの競合が起きる。
Anaconda 4.2.0のpython3を使うとリンク時に以下のエラーが発生した。

g++ -std=c++11 -I/home/xxx/.pyenv/versions/anaconda3-4.2.0/include/python3.5m -o obj/a.o -c a.cpp
g++ -o bin/a obj/a.o -L/home/xxx/.pyenv/versions/anaconda3-4.2.0/lib -lboost_python3 -lboost_numpy3 -lpython3.5m -std=c++11
//usr/local/lib/libboost_python3.so: `std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_create(unsigned long&, unsigned long)@GLIBCXX_3.4.21' に対する定義されていない参照です
//usr/local/lib/libboost_python3.so: `std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_append(char const*, unsigned long)@GLIBCXX_3.4.21' に対する定義されていない参照です
//usr/local/lib/libboost_python3.so: `std::runtime_error::runtime_error(std::runtime_error const&)@GLIBCXX_3.4.21' に対する定義 されていない参照です
//usr/local/lib/libboost_python3.so: `std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_construct(unsigned long, char)@GLIBCXX_3.4.21' に対する定義されていない参照です
//usr/local/lib/libboost_python3.so: `std::range_error::range_error(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)@GLIBCXX_3.4.21' に対する定義されていない参照です
//usr/local/lib/libboost_python3.so: `std::runtime_error::runtime_error(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&)@GLIBCXX_3.4.21' に対する定義されていない参照です
//usr/local/lib/libboost_python3.so: `std::__cxx11::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> >::_M_construct(unsigned long, wchar_t)@GLIBCXX_3.4.21' に対する定義されていない参照です
//usr/local/lib/libboost_python3.so: `std::__cxx11::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> >::_M_create(unsigned long&, unsigned long)@GLIBCXX_3.4.21' に対する定義されていない参照です
//usr/local/lib/libboost_python3.so: `std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::compare(char const*) const@GLIBCXX_3.4.21' に対する定義されていない参照です
collect2: error: ld returned 1 exit status
Makefile:12: ターゲット 'bin/a' のレシピで失敗しました
make: *** [bin/a] エラー 1

最新のAnaconda3 5.0.1を使用するとコンパイルされたgccのバージョンはUtuntuより新しいが、エラーなしでリンクできた。

Anaconda3 4.2.0のgccのバージョン
$ python
Python 3.5.2 |Anaconda 4.2.0 (64-bit)| (default, Jul  2 2016, 17:53:06)
[GCC 4.4.7 20120313 (Red Hat 4.4.7-1)] on linux
Anaconda3 5.0.1のgccのバージョン
$ python
Python 3.6.3 |Anaconda, Inc.| (default, Oct 13 2017, 12:02:49)
[GCC 7.2.0] on linux
Ubuntu 16.4 LTSのgccのバージョン
$ gcc --version
gcc (Ubuntu 5.4.0-6ubuntu1~16.04.6) 5.4.0 20160609

Boost.Pythonを有効化

ホームディレクトリに「user-config.jam」を作成し、以下のように編集する。

using python : : /home/xxx/.pyenv/versions/anaconda3-5.0.1/bin/python : /home/xxx/.pyenv/versions/anaconda3-5.0.1/include/python3.6m : /home/xxx/.pyenv/versions/anaconda3-5.0.1/lib ;

:で区切られた4つ目はインクルードディレクトリを指定し、5つ目はライブラリディレクトリを指定している。
なお、user-config.jam内では環境変数は使えない。

参考:Configuring Boost.Build - 1.66.0

ビルド

cd boost_1_66_0/
./bootstrap.sh
sudo ./b2 install

/usr/local/include/boost/
/usr/local/lib/
にヘッダーとライブラリがインストールされる。

ビルドをやり直すには、

sudo ./b2 clean

を実行してから、やり直す。

Boost.PythonとBoost.Numpyを使用したソース作成

以下ようなソースを作成する。
a/a.pyで作成したNumpyのndarrayをa.cpp側で取得して、ndarrayから値を取り出して表示する。

a.cpp
#include <iostream>
#include <boost/python/numpy.hpp>

using namespace std;

namespace py = boost::python;
namespace np = boost::python::numpy;

void call_py_a()
{
	// Boost.PythonとBoost.Numpyの初期化
	Py_Initialize();
	np::initialize();

	// Pythonモジュール読み込み
	py::object a_ns = py::import("a.a").attr("__dict__");

	// 関数取得
	py::object py_a = a_ns["a"];
	auto ret = py_a();
	np::ndarray data = py::extract<np::ndarray>(ret);
	float *val = reinterpret_cast<float*>(data.get_data());
	cout << val[0] << endl;
}

int main()
{
	call_py_a();

	return 0;
}
a/a.py
import numpy as np
def a():
    return np.array([123], dtype=np.float32)
setup.py
import setuptools

setuptools.setup(
    name = 'a',
    version = '0.0.1',
    author = 'a',
    packages = ['a'],
    scripts = [],
)

Pythonモジュールとしてインストールしておく。

pip install -e . --user

コンパイル

以下のようなMakefileを作成する。

Makefile
CC = g++
CFLAGS = -std=c++11
LDFLAGS = -lboost_python3 -lboost_numpy3 -lpython3.6m
INCLUDE = -I $(PYENV_ROOT)/versions/anaconda3-5.0.1/include/python3.6m
LIB = -L$(PYENV_ROOT)/versions/anaconda3-5.0.1/lib

target = bin/a
sources = a.cpp
objects = $(addprefix obj/, $(sources:.cpp=.o))

$(target): $(objects)
	@[ -d bin ] || mkdir -p bin
	$(CC) -o $@ $^ $(LIB) $(LDFLAGS) $(CFLAGS)

obj/%.o: %.cpp
	@[ -d obj ] || mkdir -p obj
	$(CC) $(CFLAGS) $(INCLUDE) -o $@ -c $<

all: $(target)

clean:
        rm -f $(objects) $(target)

コンパイルする。

make

実行時に共有ライブラリのパスを設定

コンパイルした実行可能ファイルを実行する際には、共有ライブラリのパスを環境変数LD_LIBRARY_PATHに設定する。

export LD_LIBRARY_PATH=/usr/local/lib:$PYENV_ROOT/versions/anaconda3-5.0.1/lib:$LD_LIBRARY_PATH

実行

$ bin/a
123

成功すれば、「123」と表示される。

音声スペクトルモニター(Audio Spectrum Monitor)のバージョンアップ

久しぶりに音声スペクトルモニター(Audio Spectrum Monitor)をバージョンアップしました٩( 'ω' )و
play.google.com

ベータ版で公開したままにしていた機能を正式にリリースしました。

  • ピッチホールド機能を追加
  • waveファイルをインポートする機能を追加(44.1kHz/16ビットのみ)

ピッチホールド機能は、ドラムのチューニングに使いたいという要望を受けて追加しました。
設定画面から有効にできます。
音が高い方、もしくは低い方のピッチを一定時間キープします。

waveファイルのインポートは複数の要望をもらっていたので対応しました。
mp3とかもインポートできた方がよいのでしょうが、とりあえずwaveのみです。

もう少し短い間隔で要望に対応しないとな...( ¨)

Android Studio 3にアップデート

将棋ソフトの開発ばかりしていたので、しばらくスマホアプリの更新ができていませんでした。
要望はちょくちょくもらっていたのですが、返信もおこたってました・・(・´ω・`)ゞ
時間の使い方を見直して、アプリ開発にも少し時間をとることにしようと思います。

簡単そうな要望を取り込もうと、久しぶりにAndroid Studioを立ち上げたついでにAndroid Sudioをver3にアップデートしました。
ver3でgradleの設定があちこち変わっていて設定の修正だけで結構時間を使ってしまいました。

変更が必要だった設定を残しておきます。

build.gradleのdependenciesの設定

compileが廃止されimplementation に変更された。
compileはまだ使用可能。

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:24.2.1'
    adImplementation 'com.google.android.gms:play-services-ads:9.8.0'
}

flavorDimensions

ビルドバリアントの設定にflavorDimensionsの設定が必須になった。
flavorDimensionsが不要であれば、とりあえず1つ定義しておけばよい。

    flavorDimensions "release"
    productFlavors {
        ad {
            applicationId "xxxxxxad"
            buildConfigField "boolean", "AD", "true"
            dimension "release"
        }
        noad {
            applicationId "xxxxxxnoad"
            buildConfigField "boolean", "AD", "false"
            dimension "release"
        }
    }

依存ライブラリアップデート

ver3とは関係ないが、appcompat-v7とplay-services-adsのアップデートも行った。
ライブラリはアップデートしておかないとサポートが打ち切られるのでリリースのたびに更新するようにしている。

最新のバージョンがいくつなのか調べるには、

    implementation 'com.android.support:appcompat-v7:+'
    adImplementation 'com.google.android.gms:play-services-ads:+'

のようにバージョンを「+」にして、とりあえずプロジェクトを更新して、ProjectビューでExternal Librariesでバージョンを調べて、「+」の部分を固定バージョンに書き換えるとよい。
minSdkVersion 9にしているので、最新のバージョンが使えないので、

    implementation 'com.android.support:appcompat-v7:24.+'
    adImplementation 'com.google.android.gms:play-services-ads:9.+'

のようにメジャーバージョンを最新バージョンから1つずつ減らしてエラーが出ないバージョンを調べた。

将棋でディープラーニングする その52(自己対局で教師局面生成)

AlphaZeroと同様の方式で、自己対局で教師局面を生成するプログラムを作成した。

自己対局の仕様

以下の仕様はAlphaZeroと同じとした。

  1. 自己対局を行う双方のプレイヤーは、同一のモデルを使用する。
  2. プレイアウトは固定プレイアウト数

以下の点は変更した。

  1. 開始局面は、Aperyと同じようにroots.hcpからランダムで選択し、1手ランダムムーブを行う。
  2. 常に、ルート局面でのプレイアウト数が最大の手を選択する(グリーディー戦略)。

1点目は、AlphaZeroでは、初期局面から一定の手数まではルートノードの合法手のプレイアウト数に応じた確率分布に従って指すことで序盤の手をばらけさせている。
Aperyと同様にroots.hcpを使用した方が手をばらけさせることができるので、roots.hcpを使うように変更した。
教師局面がroots.hcpの質に影響してしまうので、roots.hcpは十分なバリエーションを持たせる必要がある。

2点目は、1点目と関連するが、AlphaZeroは序盤は確率分布に応じて手を選択するが、一定の手数以上になるとグリーディー戦略に切り替える。
roots.hcpを使うので始めからグリーディー戦略とした。

教師データの仕様

AlphaZeroの教師データは、ルートノードの合法手のプレイアウト数に応じた確率分布となっているが、グリーディー戦略の場合、選択した1手のみがあればよいので、教師局面はApery及びelmoと同様にhcpeフォーマットとした。
elmo_for_learnで生成したデータと同じフォーマットなので、これまで使用していた学習プログラムを変更しないで学習に使用できる。

ランダム性

AlphaZeroでは、ルートノードでは方策ネットワークの確率分布にディリクレノイズを加えている。
同様にルートノードではノイズを加える。
以前にdlshogiにディリクレ分布を使うと弱くなってしまったので、dlshogiで使っているのと同じノイズ方式とした。
dlshogiでは、一定の確率で選択した1手の方策ネットワークの確率に対して
{ \displaystyle
p \leftarrow (p + 1) / 2
}
という操作を行い、確率をあげている。

ハッシュ再利用

前の手番で探索した結果は、ハッシュに残っているが、ハッシュを再利用することで探索を高速化できる。
しかし、上述の通りルート局面にはノイズを加えているが、ハッシュに残っている局面にはノイズが加わっていないので、ハッシュは再利用しないことにした。

自己対局の効率化

自己対局を1局ずつ行うのでは、マルチスレッドで実行しても、少ないプレイアウト数ではMCTSの性質上、ほとんどのスレッドが同一局面を調べてしまう。
教師局面を大量に生成するためには、プレイアウト数はあまり増やせない。
そこで、1つの対局は1スレッドで実行するようにし、複数の対局を並列で実行するようにする。

各対局の探索でニューラルネットワークの計算を行うが、それらはキューにためてミニバッチで処理することで、ボトルネックとなるニューラルネットワークの計算を効率化する。

実験結果

以上のように実装した自己対局プログラムで教師局面の生成を行った。

32スレッドで実行して、プレイアウト数を1000とすると、1秒間で平均2.62個の教師局面が生成できた。

AlphaGo Zeroでは1回のイテレーションで、25,000局を実行している。
囲碁の平均手数を200とすると、1イテレーションで、5,000,000局面を生成している。

AlphaGo Zeroと同様に1イテレーションを5,000,000局面とすると、1イテレーションの実行には、530時間=22日かかる。

これでは時間がかかりすぎなので、プレイアウト数と1イテレーションの局面数を減らして、モデルを強くできるか今後実験してみたい。

↓自己対局のソースを公開しました。
https://github.com/TadaoYamaoka/DeepLearningShogi/tree/master/make_hcpe_by_self_playgithub.com

C++のログ出力ライブラリ

電王トーナメントのときに将棋所のログ出力が同期処理で非常に遅いこと知らずに時間切れ負けを起こしてしまった。
その後、速度優先でログ出力を行わないように修正したが、ログ出力がないと探索の状況がわからず不便である。

そこでログをファイルに出力するようにして、「tail -f」などで監視できるようにしたい。
マルチレッドで同期してログ出力すると速度低下していまうため、速度低下しないようにしたい。
自分で作るのも大変なのでオープンソースのライブラリがあれば、それを利用したい。
以前にGoogleのglogを試したことがあるがビルドが大変だったので、ヘッダーのみのライブラリがよい。
また、WindowsでもLinuxでも使用できるものがよい。

要件を満たすライブラリがないか調べたところ、spdlogというライブラリを見つけた。
spdlog/include/spdlog at master · gabime/spdlog · GitHub

spdlogは、欲しい要件をすべて満たしている。

  • ヘッダーのみで使用できる。
  • 非同期でログ出力が可能。CPUがIO待ちのときに出力するので速度低下を心配しないでログ出力できる。
  • スレッドセーフ
  • Windows(Visual Studio)でもLinux(gcc)でも使用できる。

以下に、spdlogを使って非同期のログ出力を試した内容を記述します。

インストール

GitHubReleasesから最新のソースをダウンロードする。
ソースのincludeディレクトリをプロジェクトのソースディレクトリにコピーする。

使い方

スレッドセーフで非同期出力を行うには以下のように記述する。

ロガー作成
#include "spdlog/spdlog.h"

auto loggersink = std::make_shared<spdlog::sinks::stdout_sink_mt>();
auto logger = std::make_shared<spdlog::async_logger>("mylogger", loggersink, 8192);

spdlog::async_loggerの引数の"mylogger"はロガー名、8192はキューのサイズを指定している。

main関数などで作成した場合、他の関数からロガーを取得するには、

auto logger = spdlog::get("mylogger");

のようにロガー名を指定して取得する。

ロガーをグローバル変数として作成してしまってもよい。

ファイルに出力するには、stdout_sink_mtの代わりに、simple_file_sink_mtを使えばよい。

auto loggersink = std::make_shared<spdlog::sinks::simple_file_sink_mt>("fileName.txt");

標準出力にしておいて実行時にリダイレクトしてもよい。

ログ出力
logger->info("hello");

値を埋め込む場合は、

logger->info("va1={}, val2={}", val1, val2);

のように記述する。

非同期のロガーを使う場合、プログラムの終了時に

spdlog::drop_all();

を呼び出す。

ログレベル

ログレベルには、critical, error, warn, info, debug, traceがある。

出力するログレベルを変更するには、

logger->set_level(spdlog::level::trace);

のように指定する。

リリース版でデバッグログとトレースログをプリプロセッサで条件コンパイルするには、

logger->debug("debug val={}", val);

と書くところを、

SPDLOG_DEBUG(logger, "debug val={}", val);

と記述する。

デバッグ版でデバッグ用ログ出力の条件コンパイルを有効するには、SPDLOG_TRACE_ONとSPDLOG_DEBUG_ONマクロを定義する。

#define SPDLOG_TRACE_ON
#define SPDLOG_DEBUG_ON
ログ出力フォーマット

デフォルトでは、以下のようにログ出力される。

[2018-02-10 17:22:05.778] [mylogger] [info] hello

フォーマットを変更するには、

logger->set_pattern("[%Y-%m-%d %H:%M:%S.%e] [%l] %v");

のようにする。

パターンの指定の仕方は、
3. Custom formatting · gabime/spdlog Wiki · GitHub
を参照。

改行コード

Windowsで標準出力にログ出力すると、改行コード"\r"が2個出力される問題があった。
改行コードを変更するには、

#define SPDLOG_EOL "\n"

のようにする。
Windowsでは、ファイル出力時に自動的に"\r"が付くので改行コードは"\n"を指定する。

第28回世界コンピュータ将棋選手権申し込み

しばらく忙しかったのでコンピュータ将棋の開発できていませんでした。
ぼちぼち再開します。

12月くらいにブートストラップのバグを修正してelmoの深さ8で生成した局面を使って学習をやり直しましたが、強さはあまり変わりませんでした。

5.8億局面くらいでtest accuracyがサチります。
f:id:TadaoYamaoka:20180131225027p:plain:w300
f:id:TadaoYamaoka:20180131225038p:plain:w300

定跡なしで1手3秒でGPSfishと互角くらい。
f:id:TadaoYamaoka:20180131225545p:plain:w300

学習局面の質を上げないとこれ以上強くするのは無理そうです。

次は、elmoの局面で学習したモデルから開始して、AlphaZeroと同じような自己対局で学習局面を生成することを試そうと思っています。
AlphaZeroのようにスクラッチからの学習ではないのでうまくいくかはわかりませんが。

ということで、これからがんばる状況ですが第28回世界コンピュータ将棋選手権に申し込みました。

今回はコンピュータ囲碁からの参加者もいてディープラーニング勢も増えて楽しみですね。
Crazy Shogiは、AlphaZeroのアルゴリズムを実装したということで興味があります。

Ubuntu Serverを後からDesktopにする

先日Ubuntu Serverを使うとCUDA+cuDNN+Chainerが動かせたという記事を書きましたが、以前にUbuntu DesktopでうまくNvidiaのドライバをインストールできなかった原因がわかりました。

Ubuntu Desktopでうまくインストールできなかった原因は、ドライバインストール前は、カーネルパラメータにnomodesetを付けて起動しないとコンソールモードにできないためでした。
CUDAのInstallation Guide for Linuxに記載がありました。
起動時にGRUBの設定で、カーネルパラメータにnomodesetを付与すれば、Desktopでもインストールできるようですが、Ubuntu ServerでNvidiaドライバとCUDAをインストールした後で、Desktopにした方が簡単です。

後から、Desktopにするには、

$ sudo apt-get install ubuntu-desktop

で、Desktopをインストールします。

GUIモードで起動するようにするには、

$ sudo systemctl set-default graphical.target

を実行します。

GUIで起動した後に、手動で日本語環境の設定が必要です。
System SettingsからLanguage Supportを選んで、言語サポートをインストールするか聞かれるのでインストールします。
キーボード入力に使うIMシステムを「fcitx」に変更します。
再ログインすると、日本語環境になります。
かな漢字変換は、「半角/全角」キーで切り替えできます。

ChainerのMNISTサンプルを動かしてみましたが、コンソールモードで実行するのと、実行時間に違いはありませんでした。
X Serverを動かすことでGPUの速度が低下するということはなさそうです。