今回、やねうら王は公開しているバージョンからほとんど強くすることが出来なかった。(KPP_KKPT型 評価関数でelmo+R170、探索部で+R20程度。elmo+rezero8 = relmo8がelmo+R120程度なので+R70ぐらいしか強くなっていない。) 私の本業のほうが忙しかったこともあるが、それを差し引くとしても、課題に対する取り組み方とか、方針の立て方とか、色々反省すべき点は多い。他の開発者が同じ轍を踏まぬよう、その原因をざっと書いておきたいと思う。
1. 開発用のPCの問題
開発に使っているPCはXeon 2698のdual(40C80HT)が6台である。元は5台であったが、1台が故障して、修理している間にもう一台購入した。5月の段階では、メモリは32GBで頑張っていたが、結局1台は256GB、残り5台を96GBに増設した。ストレージは、サーバー機に1TBのSSD、3TBのHDD×3という構成である。6台のPCはJenkinsで管理しており、教師局面などはサーバー機の共有フォルダに直接生成するにしている。
昨年から丁度メモリが倍ぐらいの値段になっている時期であったため、メモリの購入を躊躇う余り、評価関数の改造が遅れた。そのため、評価関数を拡張するというのが後手後手になった。
2. 探索の伸び
探索はStockfishに追随するだけで強くなると言っていた時代はとうに終わっており、冬の時代が到来していた。前回、WCSC27の直前に私が長い時間で探索パラメーターを調整したが、それで将棋のゲームツリーにはかなり適合した探索部になっていたのだと思う。探索部でほとんど強く出来なかった。やねうら王のライブラリ作者であるから、探索部は他のライブラリ勢(ライブラリ利用者)よりは深く理解しているわけだが、そのアドバンテージが何も生きなかった。
今回のSDT5、やねうら王をライブラリとして使用しているチームは多いが、PR文で探索部を改良出来たというチームは1チームも無い。「どういじっても弱くなる」と悲嘆に暮れる声ばかりだ。軍靴に踏み躙られた大地が忿怒し、恵みなど未来永劫お前たちには与えぬと作物を実らせないのにも似ている。
— やねうら王 (@yaneuraou) October 11, 2017
3. 教師局面生成の問題
教師局面を数百億局面生成しておき、そこから学習をさせたり異なる形の評価関数の学習をさせたりしなければならなかった。しかし、depth 8で生成した教師では、elmo+R40ぐらいにしかならず、depth 10や12で生成しなければならなかったが、これにとても時間がかかった。(結局、家のPCではdepth 10では20億局面程度しか生成できていない。最後、AWSを利用して100億生成した。)
思えば、この待ち時間が丸々無駄であった。このへんがAWS/GCPを利用する場合との差であり、このように教師生成に関して瞬間的に大きな計算資源が必要になるので、このときに6台しかないという状態ではとても太刀打ちできない。こちらが1ヶ月かけてやっていることをAWSを借りれば1日で完了するのだから、その差は歴然である。
AWS/GCPを利用しようとしなかったのは私の判断のミスである。AWS/GCPを利用していれば、メモリの増設なんてする必要がなかったし、大きなメモリを使っての実験も出来たはずである。メモリがないばかりに、三角配列化やミラー化など複雑なソースコードを書く必要があった。このへんもいたずらに工数が増えた原因である。
4. 教師局面のシャッフルの問題
やねうら王で取り扱う教師局面は1局面40バイト(32バイト+付随情報)であり、100億局面で400GBとなる。この局面をシャッフルするのに、いったんテンポラリに書き出して、それを結合するような処理を行なうので、元の教師ファイルの3倍のストレージが必要である。そうなると1TBのSSDだと100億局面のシャッフルがギリギリである。(ギリギリ出来ない) そこでHDDに移動させてからシャッフルするのだが、USB3.0でぶら下げている関係もあって、100億局面のシャッフルに16時間程度要する。
シャッフルするのに3倍のストレージが必要なのがおかしく、本当はテンポラリファイルを削除しながら結合すれば2倍で済むのだが、手抜き実装なのが良くなかった。しかし、それが仮に2倍で済んでいたとしても、1TBのSSDでは、200億局面程度しか扱えない。このへんが限界であった。
今回、評価関数を拡張することになったのだが、評価関数の特徴因子の数が10倍になれば、教師も10倍程度なければうまく学習できないと予想される。そう考えると1000億ぐらいは教師局面が欲しいところであるが、ストレージ的な限界から、その実験が出来なかった。(depth 6ぐらいで1000億生成するなどして実験すべきであった。)
5. 私のスキルセットの問題
私がAWSを利用することに億劫だったのは、Windowsのオンデマンドインスタンスは高すぎるということだ。Linuxのスポットインスタンスと比べると10倍ぐらいの単価となる。もっとも、その安いはずであるLinuxのスポットインスタンスの料金にしても、手元のPCの電気代と比べると3倍程度はする。AWSの場合、機材代金(イニシャルコストやメンテナンスコスト)が0円なのだから、電気代の3倍というのは実際は安いのかも知れないが、これが3倍×10(Windows・オンデマンドインスタンス)=30倍だとさすがに手を出そうという気になれない。(なれなかった)
Linuxのスポットインスタンスでどうにか賄えれば良かったのだが、インスタンスを100個ぐらい同時に立ち上げてうまくそのへんを操作する自信がなかった。(Linuxは門外漢なので…) 実際、やってみるとAWS+Linux+スポットインスタンスを扱うのは簡単だったのであるが、やったことのないことに対しては工数の見積りがいい加減になるし、仕方のない意味もある。
世間ではプログラマーというとExcel、Wordに始まりゲームプログラミングからWebプログラミングまで出来ると思われているかも知れないが、実際は得意な分野と不得意な分野がある。自分のスキルセットと掛け離れた分野は習得に時間がかかる。だから、時間がないときはどうしても避けてしまいがちである。
6. 自戒を込めて
いまや将棋ソフトはオープンソースである。公開されているソースコードを深く理解し、自分のなかに取り込み、改良していける開発者だけが、次の時代を築いていくのだと思う。他の開発者のツイッターやブログをこまめにチェックし、かつ、自分でも情報を積極的に発信し、他の開発者と情報を交換できるようなコミュ力がある開発者以外は、遅かれ早かれ淘汰されていくのではないかと思う。その淘汰されていくなかに、私も含まれているかどうかはわからない。
私はブログやツイッターで情報を発信するのは得意だが、しかしGitHubの扱いに慣れてないし、今回の件で言うとAWSの操作やLinuxのコマンドすらままならない状態であったのが敗因でもある。DeepLearningを用いた将棋ソフトが台頭してきた昨今、統計分野や機械学習分野を避けては通れない。
ライブラリ勢は、巨人の肩に乗って(ライブラリを使って)独自の成果を出すのは大変かも知れないが、巨人は巨人で歩くことすらままならないところまで来ているのだ。
本年度のやねうら王の開発関連の主な出費
PC関連 150万円+α
電気代 60万円
AWS代 20万円
打ち合わせのための交通費、会議費等 20万円
私の人件費 プライスレス何故このような罰ゲームに参加させられているのか、わけわからん。
いやまあ、開発していて楽しい部分がないと言うと嘘になるのだけども。— やねうら王 (@yaneuraou) November 17, 2017
うちは荒いシャッフルだけ自分で作りました。広義の無課金勢ということでAWSには手を出してませんが
教師局面のシャッフル、効果が微妙なのでなくてもいいような気もしていますが、それでR20ぐらい損したら馬鹿らしいので、やっぱりやってる派です。(´ω`)
大量データを扱う時にはインデックスをシャッフルして学習時の局面読み込み処理を修正する方が楽そうに思うのですがどうですか?
1つの局面データが40バイトしかない状況ですが、それに対するindexは、8バイト必要でしょうから(100億局面なので4バイトに収まらない)、indexだけで8バイト×10GB(100億)=80GBもメモリが必要になるのでは…。
周期がデータ数に近い程度の乱数生成でシードを指定するだけで良いと思います。余った分は捨てるしかないですが……。多重にサンプリングして良いなら単に乱数でいいですけど。
あ、返信でない所に書いてしまいました。すみません。
前半の意味がよくわかりません…。(´ω`)
今一度、前提条件を確認しておきますが、
教師データ : 100億局面(1局面40bytes×100億=400GB)が、ストレージ上にある
PC : workに使えるのは4〜8GB程度
I/Oがボトルネックになりかねないので教師データを1passないし2pass程度で、ランダムシャッフルされた状態(完全なシャッフルでなくとも可)でmini-batch size(100万局面)ずつ取得したい
です。
まとめて(シーケンシャルに)読み込みたいという所は考えに入っていませんでした。意味のない事言ってしまって申し分けないです。
1レコードずつの読み込みで良いとう状況があるとして一応解説しておきます。インデックスをシャッフルするというのは、100億〜2^37弱という事で、周期が2^37とかの疑似乱数列を線形合同法なりなんなりで生成し上限を越える値の時は捨てれば擬似的に実現できます。その乱数生成法の初期値(シード)を指定すれば後は読み込む順にまわしていけば良いんじゃないかな、という意見でした。nスレッドで回すなら開始位置を1つずつずらしてそれぞれのスレッドで(上限値以下の有効なものをカウントして)n個先の乱数を生成すれば良いですし。
ああ、意味わかりました(`・ω・´)b
簡易的なインデックスのシャッフルだけならこんな手もあります。
重複しない1対1の変換が保障されてるそれ自体は単純な変換、
(例えば奇数を掛ける、定数XOR、ROTやビット入替えみたいな奴)
を複数組み合わせてセットにして、それを数回まわす関数。10行位で書けます。
前に某所で実装したときは4byteでカウンタ1,2,3,…入力に対して、
出力は(NISTやDIEHADみたいな乱数検定に通る程度にはランダムな)
周期2^32の準乱数になってたです。ちょっと遅目ですが。
LZ4 とかの極端に速いアルゴリズムで圧縮してディスクに書き出すとどうなんですかね?
元の教師局面データがすでにハフマン符号で256bitに圧縮されているので、LZ4ではほとんど小さくならないような…。