置換表に保持する指し手の削減

 Miacisでは一つの局面に対応する置換表エントリが持つ変数として、以下のようなものを保持している。(hash_table.hppから一部抜粋し見やすく改変)

struct HashEntry {
    int32_t sum_N;         //探索回数の合計
    int32_t virtual_sum_N; //バーチャルロスの合計

    //以下vectorとなっているものは全てmovesのサイズと同じサイズになる
    std::vector<Move> moves;          //行動
    std::vector<Index> child_indices; //各行動を取った後の置換表エントリに対応するインデックス
    std::vector<int32_t> N;           //各行動を探索した回数
    std::vector<int32_t> virtual_N;   //バーチャルロス
    std::vector<FloatType> nn_policy; //ニューラルネットワークの出力方策

    ValueType value;    //価値。漸進的に更新され、常にこのノードを根とする部分木内の価値の平均となる

    bool evaled; //ニューラルネットワークによる評価が行われたかを示すフラグ

    std::mutex mutex; //排他制御用のmutex

    //ハッシュ管理用の変数
    int64_t hash;       //局面に対応するハッシュ値
    uint16_t turn_number; //手数。ハッシュ値だけでは衝突頻度が高いので
    uint16_t age;       //置換表のageと照らし合わせて現在の情報かどうかを判断する変数
};

 ここでコメントにもある通り、std::vectorの形式で保持しているものは、この局面の合法手数だけ要素を持つことになる。基本的に将棋は終盤になると(持ち駒が増えると)平均合法手数が大きくなるため、長時間の対局を行ったとき終盤でメモリが溢れそうになってしまった。

 ねね将棋では上位16手のみを保存していると聞いたので、真似してそのような機能を実装した。なお、Miacisでは詰み探索を置換表に直接書き込む都合上、ルートにおいては合法手を全て展開し、それ以降の局面のみ、ニューラルネットワークのPolicy確率が大きい順に上位N手のみを残すようにした。

対局

  • Miacis : 1手1秒
  • YaneuraOu/Kristallweizen(4Thread, 定跡オフ) : 1手0.25秒
保持する指し手 対局数 勝数 引き分け数 敗数 勝率 相対Eloレート(±1σ)
全て 1000 535 2 463 53.6% 25.11±22.73
上位16手 500 303 0 197 60.6% 74.79±33.24

 「全て」の方の結果は少し前のものなので今の実行バイナリと完全に同じものではないとは思われるが、性能的にはほぼ変わっていないはずであり、そのときの結果よりも今回上位16手に絞った場合のほうがやや勝率が高かった。誤差の範囲内かもしれないが、性能が落ちているということはなさそうなので今後も上位16手に絞ることにする。

 上位16手に絞って性能が上がる要因があるとすれば、UCB選択で合法手を全てループするのが高速化されることが考えられる。実際に10秒の探索を10回測定して平均値を出してみると、

保持する指し手 初期局面でのNPS ある中盤局面でのNPS
全て 15685.8 13977.5
上位16手 16262.9 15806.4

となった。ややNPSが上がるメリットと、N手以降に良い手があり見落とすデメリットのバランスを見てNを決めれば良さそうだ。

【コンピュータオセロ2】カテゴリカル分布の有効性

 Miacisで用いている手法の主張点は「評価値出力をスカラーではなく、各値になる確率を示すカテゴリカル分布にすることで性能が上がり、探索にも有効活用できる」というところにある。簡単な説明はWCSC30アピール文書を参照。

 そもそも着想の元ネタの一つであるCategorical DQNAtariゲームで効果を上げたものでもあり、これは基本的にゲームによらず有効であると考えられる。よってオセロでも実験することで有効性を確認する。(なぜこれを第一回でやらなかったのか)

実験

 最終層の出力を

  • スカラーの場合1ユニット
  • カテゴリカル分布の場合51ユニット

としたモデルで強化学習を実行する。

 カテゴリカル分布を適切に学習するためには \lambda \lt 1としてTD( \lambda)を行う必要がある。TD( \lambda)の説明は、信頼性の高い英語文献としてはSutton & Bartoの『Reinforcement Learning: An Introduction 2nd Edition』を引くのが正しそう。下からPDFも見れる。

 日本語の解説は、

など。

 同じ条件で実験するため、スカラーの場合もカテゴリカル分布の場合も同じ \lambda(今回は0.75)を使う。(これによりスカラー≠AlphaZeroである。AlphaZeroは教師信号として常に最終結果を使うので \lambda = 1の場合と同値)

実験結果

f:id:tokumini:20200501162240p:plain

 あ、あれ、スカラーの方が強い……!? しかも単に運が良かったという以上に明確な差がありそう……。学部の卒論時点では3層の小さい全結合ネットワークを使い、そのときはオセロでも効果があったのだが、今回将棋と同じように残差ブロックを基本とした畳み込みニューラルネットワークにしたところ逆転してしまった。今回の手法は将棋に限っただけの性能向上なのかもしれない……。

 そもそも将棋でも本当に効果があるのか自信がなくなってきた。冒頭に挙げたスライドに載っている結果はちょっと古いものであり、対戦相手がGikou2 (depth 9)と、やや弱い段階での実験となっている。さらに言えば、このスライドの結果はできるだけAlphaZeroに従ったものであり、つまり \lambda = 1とした結果なので、実は性能向上の寄与はTD( \lambda)の \lambda \lt 1としたところが大きいのかもしれない。こんな単純なことを確認し忘れていたとは……。慌てて将棋の方でもスカラー(TD(0.75))学習を回し始めたが、結果が出るのは2週間後。この手法で論文を書くのは難しそうな雰囲気がしてきたなぁ……。

【コンピュータオセロ1】指し手選択の温度について

背景1

 手法の有効性がゲーム依存ではないことを主張するため、そして実験サイクルを高速に回せるという利点があるため、オセロでも実験をしていく。

 オセロは将棋より簡単だろうと勝手に判断してやや小さめのネットワークで学習をさせている。具体的にはチャンネル数を128→64、ブロック数を10→5にしている。さらに盤面の拡張も左右反転だけでなく盤面の回転も導入しているため、生成速度が非常に速い。0.5Mステップの学習が18時間ちょいで終わる。対戦による評価も含めておおよそ1回の実験が1日で終わるため、様々な条件を探索しやすい。

背景2

 将棋の方では、そこそこ強いパラメータが得られているものの、穴熊を学習できず弱いという問題が発生している。

 実際、学習データ生成における自己対局で穴熊のような局面は一切現れていない。

 これは強化学習における探索が不足している問題だと思われる。もっと実践的に言えば、指し手のランダム性が低すぎるということになる。

 ランダム性の入れ方として、AlphaZeroでは30手までについて探索回数を正規化した割合で行動選択をしている。しかし、この方法では何度か探索した結果悪いと判明した手についてもある程度の確率が生じてしまう点が気になる。

 Miacisでは、ターン数の制限は入れず、探索した結果得られた各行動の価値にソフトマックス関数をかけた値を確率として行動選択をしている。このとき、ソフトマックス関数には温度を導入している。

 ある局面を(800回)探索し、行動 iの価値が Q_iであるとする。このとき行動 iを選ぶ確率を、温度 tを用いて


p_i = \frac{\exp{(Q_i / t)}}{\sum_{j} \exp{(Q_j / t)}}

とする。 tは大きくすればランダム性が上がり、小さくすれば最大価値を持つ行動を選ぶ確率が高くなる。今まで将棋では t = 0.01で行っていたが、今回はこれについて調査してみる。

背景:補足

 将棋における調査

学習

 AlphaZeroと同様の強化学習を行う。学習のコードはほぼ将棋と共通となっている。温度を t = 0.01, 0.025, 0.05, 0.1の4通りで試した。

対戦相手

 オセロでの対戦相手としてはEdaxを用いる。

 対局条件を揃えるためにMiacis側でダウンロードスクリプト対局スクリプトを書いている。

学習結果

f:id:tokumini:20200430145233p:plain

 オセロと将棋では事情が違うかもしれないが、温度が t = 0.025, 0.5の方が今までの t = 0.01より良い結果となった。将棋でももう少し大きい温度で実験し直してみるべきかもしれない。

Miacis WCSOC2020版

 世界コンピュータ将棋選手権のオンライン大会(WCSOC)に向けて、これ以上レートが伸びそうにないのでここで結果をまとめておきます。

実行ファイル

 Windows向け実行ファイルをGitHubで公開しています。

 CPU版

 GPU

 CPU版はNPS50くらいしか出ないのでほぼお試し用です。GPU版もWindowsではなぜかNPSが低く、手元では3,000くらいしか出ませんでした(コンパイル時に最適化を上手くかけられていない気がします)。Ubuntu 18.04では16,000くらい出るのでUbuntu推奨です。Ubuntuでの実行ファイルは……自分でビルドしていただく方針で(README.md参照)。

最終的に行った実験

学習

 ランダムに初期化したパラメータからAlphaZeroと同様の形式で強化学習

項目
バッチサイズ 512
学習ステップ数 2,000,000
データ生成速度 112.2 局面 / 秒
1ステップあたりの生成量 128 局面
学習率 0.025(1Mステップ時点で1/10、1.5Mステップ時点でさらに1/10)
リプレイバッファサイズ  2^{20} 局面
1局面の探索回数 800
マシン 2080ti × 2搭載
学習時間 672時間32分 (=28日)

学習結果

 検証損失として、floodgate2015年の棋譜からレート2800以上同士の対局者による棋譜のみを抽出し、Policyでは指し手に対する交差エントロピーValueでは勝敗を1,-1として自乗誤差を計算

f:id:tokumini:20200427131800p:plainf:id:tokumini:20200427131809p:plain
左:Policy損失 右:Value損失

対局

 CPU:Intel i7-6700K @4.00GHz, GPU RTX 2080ti

 Miacis側は1手1秒、YaneuraOu/Kristallweizen(4Thread, 定跡オフ)は1手0.2秒で対局。引き分け、入玉などのルールはWCSC30のルールに準拠。

f:id:tokumini:20200427131204p:plain

 こうしてみると1Mステップ時点でもまだ伸びているので学習率を減衰させるのが早すぎたかもしれない。

 YaneuraOu/Kristallweizen(4Thread, 定跡オフ)側も1手1秒にして500局対局した場合

対局数 勝数 引き分け数 敗数 勝率 相対Eloレート
500 94 0 406 18.8% -254.2

教師データ生成時(800探索)の性能

要約

 教師データ生成時のレートはfloodgate換算で

  • Miacis:2700程度
  • やねうら王:2800〜2900程度?
  • AobaZero:3000程度

 また探索バッチサイズはできれば1で生成するべきだとわかった。

背景

 AobaZeroが800回の探索でKrist_483_473stb_16t_100mに勝ったという話が出ている。

 800回の探索というのは教師データ生成における設定であり、これが強ければ強いほど質の高いデータを生成できるので最終的な性能も上がると考えられる。

 Miacisは128ch、10ブロックというやや小さめのネットワークを使っており、その分学習が早く終わったりNPSが高くなることに利点を見出している。しかし800回の探索については性能が低めになっていると思われるため、今回はその性能を測定した。

実験1

 対戦相手にはYaneuraOu/Kristallweizen(1スレッド 100Kノード)を用いた。細かい条件は違うかもしれないが、おおむねfloodgateのKrist_483_473stb_100k(レート2787(2020/04/11時点))と同じと考えて良いのではないかと思われる。

 対局結果は次のようになった。

対局回数 勝数 引き分け数 敗数 勝率 相対Eloレート
1000 413 7 580 41.65% -58.6

 つまりMiacisの教師データ生成時のレートは2700程度と考えられる。

 NNUE系のプログラムが一般的にどの程度の探索量で教師データを生成しているのかは知らないのだが、たとえばやねうら王公式サイトでは

depth 18 + 100kだとかdepth 18 + 200kだとか打ち切りノード数は探索部に合わせて現実的な時間に収まるように調整している。

 とあるので、Krist_483_473stb_100kのレート2787と同じくらいか、200kつまり2倍なのでレート+100程度と思われる(depth18はもっと強いのかもしれない?)。

 またAobaZeroのp800は

とおおむねレート3000近くある。

 こうして比較してみるとやはりMiacisの教師データ生成時のレートは低めであり、もう少し改善するべきだと考えれる。

実験2

 また、Miacisではデータ生成を高速化するために、学習時の探索についてもバッチサイズを4にして学習している。(32局を並列で対局するので、GPUが一度に計算する最大の局面数は128)

 バッチサイズを1より大きくすることが性能に悪影響を与えるのかどうかについても検証した。実験1と同様の対戦相手に対して探索バッチサイズのみを変えてそれぞれ1000局対局を行った。

f:id:tokumini:20200411100312p:plain

 探索バッチサイズを大きくするほど性能が落ちている。バッチサイズが大きいときにはバーチャルロスなどの関係で最善読み筋以外にも探索が割り当てられやすく、最善読み筋が伸びきる前に800回へ達してしまうためだと考えられる。

 探索バッチサイズを32などにすれば大きく性能が落ちるだろうとは思っていたが、4でもレートが25ほど落ちてしまうとは意外だった。

 並列対局数を4倍にすれば探索バッチサイズ1でも同じ生成速度になるはずだが、並列対局数を増やすとその分だけ置換表を保持する必要があるのでより多くのCPUメモリが必要になる。

余談

 現在、大学は立入禁止になっているため、パソコンが落ちると再起動させる手段がない。使用メモリ量を増やしてパソコンが固まるなどすると困るので正直いじりたくない。レート-25には目をつぶるか……。

損失と棋力の関係

動機

 Miacisでは強化学習の最中にも定期的にfloodgateの棋譜を用いて検証損失の計測をしている。対局には計算コストがかかるため、検証損失の値から大雑把にでも性能が把握できると嬉しい。よって今回はこれらの関係を調査した。

使用した結果

 基本的にはこの記事の設定通り。

 上の結果そのものではなくバッチサイズを256として学習し直したもの。

  • Eloレート
    • Miacis0.5秒、YaneuraOU/Kristallweizen(1スレッド)0.1秒による計測
  • 検証損失 : floodgate2015年の棋譜、レート2800以上の対局のみについて計測

 Eloレート、Policy損失、Value損失の順番に並べると次のようになる。

f:id:tokumini:20200403095031p:plainf:id:tokumini:20200403095043p:plainf:id:tokumini:20200403095051p:plain

 だいたいグラフの概形が同じなのでおおむね相関がありそうということはわかる。

 横軸へ損失、縦軸にEloレートを取ってグラフを描いてみる。左がPolicy損失、右がValue損失を横軸にしたもの。

f:id:tokumini:20200403095924p:plainf:id:tokumini:20200403100029p:plain

 直線で近似していいものかよくわからないが、とりあえずそれぞれGoogle スプレッドシートの機能で直線を書き込んでみた。係数はPolicy:-2135、Value:-8649ということになり、Policy損失が0.01小さくなればレートが21上がる、あるいはValue損失が0.01小さくなればレートが86上がるということを意味していると思われる。

 ついでに横軸にPolicy損失、縦軸にValue損失を取ってみると次のようになり

f:id:tokumini:20200403100356p:plain

だいたい4倍の比になる。これが上の0.01あたりのレートの上昇分の比と同じくらいということになる(まぁ同じデータをプロットしているので当然のことだが)

結論

 まとめると、少なくとも今回の試行については

  • Value損失が0.01下がる
  • Policy損失が0.04下がる
  • Eloレートが86上がる

のような関係があると思われる。

 たとえばネットワークを大きくすることを考えると、表現力が上がることにより性能が上がるメリットと、計算が重くなりNPSが落ちるデメリットのトレードオフがある。NPSが1/2になると以前の調査から

だいたいレートが100落ちると考えられる。つまり今回の結果から考えると、NPSが1/2になるような巨大ネットワークを使う場合、もとのネットワークに比べてValue損失が0.01、Policy損失が0.04小さくなる程度では全体としての性能は上がらないと予想できる。

 正直Value損失を0.01も下げるのは結構大変な気がするうえに、NPSが落ちるということは学習時間も大きくなるということなので、実験サイクルが回るのも遅くなる。本当に性能だけを目指すなら巨大ネットワークを使った方が良いのかもしれないが、比較実験をすることを考えると今以上に大きなネットワークはあまり使いたくない。

Google Compute Cloudでの探索実験

 8GPUインスタンスを使って探索速度を検証した。

 まず20CPU、8GPU(Tesla V100)のインスタンスを使って探索速度を検証した。

f:id:tokumini:20200330160250p:plain

 早い段階で頭打ちになっている。8GPU使うときのGPU利用率を見てもあまり高くなっていない(1GPUなら50%くらいは行く)。

f:id:tokumini:20200330160409p:plain

 1GPUでも探索中は20CPUを満遍なく使っている。

f:id:tokumini:20200330160612p:plain

 なんだかんだCPUがボトルネックになっていそうではあるので増やしてみた。(別に探索中に立てるスレッド数を増やすわけではないので意味はないと思ったが検証のため)。

f:id:tokumini:20200330165333p:plain

 20CPU→40CPUにしたときはそこそこ上がったのでお!と思ったが、60CPUにしたらまた下がったので、なにかマシンの影響だったのかもしれない。CPUの情報を見ておけばよかったか。

 最後に、探索中バックアップするときのmutexロックを外してみたところ、8GPUでNPSが46371まで上がった(約1.5倍程度)。やはり排他制御でかなり詰まっているようだ。

考察

 そもそも現状の並列化を振り返ると、次のような想定をしている。

f:id:tokumini:20200330172414p:plain

 こうやってGPUをフルに使うのが理想なのだが、現状はGPUの方が空いている時間が多く、おそらく次のようになっている。

f:id:tokumini:20200330172552p:plain

 CPUを増やす方法として、このキューを3つにするというのは実装しているのだが、3つのキューを順繰りに計算していくことになると最善読み筋を伸ばすのに時間がかかるのでかえって弱くなってしまいそうな気がしている。

 フラットに図を眺めてみれば、単純にキューを埋める部分を複数スレッド化すれば良いのではないかと思えた。むしろなんで今までそれを思いつかなかったのか。実装してみます。

思考時間とレートの関係(2)

 上の調査に便乗してMiacisでも調べ直した。以前(8ヶ月前)の調査は

 結論としては今回も変わらず、「思考時間2倍でレート+100ちょい」という感じ。

 以下対戦相手はYO/Kristallweizen(Thread4・0.2秒)。

Miacis 1手1秒

対局数 勝数 引き分け数 敗数 勝率 相対Eloレート
1000 461 0 539 46.1% -27.2

 (本筋とは関係ないが前回に比べて、探索中のバッチサイズを32から64にすることで持ち時間1秒でもレート低下が抑えられた。)

Miacis 1手2秒

対局数 勝数 引き分け数 敗数 勝率 相対Eloレート
500 315 0 185 63.0% 92.5

 レート上昇分は119.7

MuZero

 以前もちらっと触れたが、MuZeroの論文中で学習したシミュレータと真のシミュレータでの比較グラフが掲載されている。(囲碁についての結果なので将棋でも単純比較はできないかもしれないが)

f:id:tokumini:20200322134440p:plain
Figure 3 A 囲碁に関してのグラフ

 オレンジ線の方が真のシミュレータでの結果なのでそっちを見ることとして、これの1秒〜2秒の変化ではR+100くらいだろうか。大雑把に1秒でR:4800、50秒でR:5400と読み取れば持ち時間50倍でレート+600なので、2倍分で考えると \frac{600}{\log_2 50} = 106.3。まぁこれくらいしか上がらないものなのかなという印象。

対局結果メモ

 ここ数日で回していた対局結果をメモしておく。対局結果は全てMiacis側から見たもの。マシンはCPUの周波数約3.6GHz、GPUは2080ti。YO/Kristallweizenは4スレッド、定跡オフ。

ベースライン

 Miacis側 0.5秒、YO/Kristallweizen側 0.1秒。

対局数 勝数 引き分け数 敗数 勝率 相対Eloレート
1000 453 0 547 45.3% -32.8

持ち時間二倍

 Miacis側 1秒、YO/Kristallweizen側 0.2秒。

対局数 勝数 引き分け数 敗数 勝率 相対Eloレート
1000 416 0 584 41.6% -58.9

 0.5秒/0.1秒での対局に比べてレートが30ほど落ちた。今までの調査でも多少わかっていたことだが、Miacisは持ち時間を増やしたときの性能の伸び方があまり良くない。

定跡について

 Miacis側 0.5秒、YO/Kristallweizen側 0.1秒で対局。

定跡オン

 standard_book.dbを利用。手数を無視して局面の同一性を判定し、一局面に複数の手が登録されている場合、ランダムに選択する。

対局数 勝数 引き分け数 敗数 勝率 相対Eloレート
250 85 0 165 34.0% -115.2

 やや対局数が少ないが、レートが80程度落ちてしまった。

定跡オン(初手、二手目飛車先固定)

 定跡オンでの対局を眺めていると、standard_book.dbは振り飛車が多めに見えた。Miacisは振り飛車側を持つのが苦手と思われるので初手と二手目だけについては飛車先を伸ばす手に固定して居飛車に固定するようにした。

対局数 勝数 引き分け数 敗数 勝率 相対Eloレート
250 98 0 152 39.2% -76.2

 少しだけレートは伸びたが、250局の範囲では誤差内くらいか。

定跡オン(真やねうら王 定跡)

 居飛車固定でも弱かったことからstandard_book.db自体がやや良くない定跡である可能性を考えて、真やねうら王定跡を使うようにした。念の為初手、二手目は飛車先固定にした。

対局数 勝数 引き分け数 敗数 勝率 相対Eloレート
250 123 0 127 49.2% -5.6

 定跡オフでのベースラインに比べてもややレートが伸びている。持ち時間の消費も抑えられる分もあるのでfloodgate等では有効そう。

教師あり学習による事前学習

 再現性をある程度担保したいので、一度強化学習で学習したパラメータを初期値として再び強化学習をやるようなことはあまりしたくない。かといって毎回ランダムパラメータから学習しているのも効率が悪いと思えたので、自分の中で折り合いのつく点として、教師あり学習である程度事前学習を行ってから強化学習をすることを試してみた。

結果

教師あり学習

 まず教師あり学習を0.1Mステップ(学習時間は4時間弱ほど)回した時点では、Policy損失が1.656799、Value損失が0.701600となった。これは今までの強化学習

での最終的な損失と比べると

手法 Policy損失 Value損失
教師あり学習 1.656799 0.701600
強化学習 2.020327 0.702119

となり、強化学習より小さい値となっているが、これをそのまま対局させても強くない(おおむねレート-300程度である)ことがわかっている。floodgateのデータを「2016年~2018年は学習データ、2015年は検証データ」と分けているとはいえ、データに相関が高いので汎化性能が高くないままに損失の数値は低くできてしまうということだろうか。

 この教師あり学習で得られたパラメータを初期値として強化学習を行った。

強化学習:損失

f:id:tokumini:20200229181159p:plainf:id:tokumini:20200229181201p:plain
左:Policy損失 右:Value損失

 事前学習によってPolicy損失、Value損失ともに僅かながら改善されているが、そこまで極端な差が出るというわけでもなかった。よく見ると一番最初の検証時点(0.1Mステップ)での損失は教師あり学習での最終的な損失より大きい値となっている。教師あり学習で得た初期値があまり有効ではなく、学習をやり直している雰囲気を感じる。

強化学習:各ステップでの性能測定(Miacis側0.5秒)

f:id:tokumini:20200229181345p:plain

 0.5Mを超えたあたりからはほとんど差がなくなっている。事前学習にはあまり意味がなさそうだ。

おまけ

 floodgateには振飛車棋譜をちらほらあるようで、そこから教師あり学習を行ったため強化学習の初期には飛車を振る序盤がそこそこ現れていた。

f:id:tokumini:20200229184301p:plainf:id:tokumini:20200229184311p:plain

 しか序盤が対抗形風味だとしてもその後囲いあう展開にはならず、さっさと大駒を交換して乱戦模様になっていることが多い。教師あり学習で「振飛車っぽい指し方」を学習できているわけではなく、序盤数手の段階で飛車を左に置く行動の割合が少し多いだけというように感じられた。また、結局学習が進むと居飛車のみになっていた。

 棋譜の保存は学習データ生成中の100局につき1局の頻度であり、さらにそこから数十局を雑に目視で確認しただけなのではっきりした検証ではない。雑談程度に。