第五回将棋電王トーナメントのお礼と本大会でのQhapaqの戦略

第五回将棋電王トーナメントを視聴してくださった皆様、並びに、大会に参加した開発者の皆様にお礼申し上げます。

Qhapaq_conflatedは並み居る強豪を圧倒的幸運(とひと握りの努力)で倒し、5位入賞という大変名誉な結果を残すことが出来ました。

 

本体のうpの遅延ですが、大会実況と大会前後の情報戦で自宅の貧弱ネットワークが死に、有給を使いすぎたことに依る仕事のしわ寄せでネット環境(と書いて無料wifi付きの吉野家)にいくタイミングがないので本当に申し訳ないですがもう暫くお待ちください。

 

以下、御礼に組み合わせて大会中Qhapaqが考えていたことを雑に書き下していきます。

 

・四駒、爆死するんじゃね?

KPPTの時代は終わりだ、と開発者たちが圧倒的開発力を見せる中で、Qhapaqは四駒爆死読みでほぼ全てのリソースをKPPTにつぎ込んでいました。科学者の癖に。

理由としては学習リソースが確保できる気がしなかったのと、ponanza_chainerレベルの化物がNNで成し遂げたレートが2000程度で、depth 1のelmo搭載型やねうら王がレート1000前後は行く(技巧depth1=700から推定)ことを加味すると、四駒の表現力ボーナスは大したことがないと睨んでいたからです。

後出しジャンケンが半端ないですが、一応この読みが当たったお陰で、リソース差の壁をほんの少し埋めることが出来ました。

 

・時間攻めと河童パーク定跡

timemanに対する効果の測定は困難です。ponderを加味しなければならないため、レート測定が大変面倒なことになります。ただ、私自身はtimemanは投資に値するものだと思っていました。

wcscの時に、対戦回数が少ない(100回も戦ってない) and 評価関数が同じなため持ち時間差やponderの差が響きやすいとは言え、定跡+timemanを搭載したqhapaqは搭載していないqhapaqに7割弱勝っていた(定跡搭載vs定跡offの持ち時間一定対局は6割弱)ので、今回も時間攻めによる暗殺をやる気満々でした。

今回は定跡狙撃よりも時間攻めに重きをおき、評価値が悪くない、定跡に当たりにくそうな展開として、真やねうら王が愛顧していた38銀型(後手は62銀型)を河童パーク定跡とし、twitter上でリソースを集りながら作成を急ぎました。

 

・マッチングミス

1試合目を勝っていたのに負けにされてしまった関係で、2戦目のマッチングがずれてしまい、計らずもソルコフを損する展開になってしまいました。しかし蓋を開ければQhapaqが最初の2戦で戦った相手は大健闘をしてくれ、ソルコフが増えた結果Qhapaqは不幸にも黒塗りの高級山(aperyにponanzaが鎮座する超厳しい山。師匠曰く、決勝トーナメントの強制収容所)に送られてしまいました。損した言ってサーセン

 

・河童パークは爆死したのか?

予選リーグの最後の3局、Qhapaqは後手を引き続けた上に、33金(41銀、32金の状態で角を33にあがり同角成、同金となった展開)戦法を繰り返し、一度も良くなることなきまま敗れ続けました。これはQhapaqの評価関数の脆弱性(Qhapaqはそもそも33角成をあまり良い手と思っていない)に起因するものでした。

予選当日、私はこの戦略を酷く後悔していましたが、よくよく考えると読み太やnozomi相手には後手を引きながら上手く戦えています。

河童パークの脆弱性は先手が純粋に飛車角先を伸ばし続けた時(定跡offでやりやすい手)に起こることですが、もしかしたら、これまでの定跡狙撃合戦の影響で読み太やnozomiは急過ぎる戦いを仕掛けないよう(単純に飛車角の歩を伸ばすことを避けるよう)に調整していたのかも知れません。

即ち、定跡読み定跡としては機能していたとも言えるのです。

 

・1日目夜の攻防

1日目終了時点で2日目の山のどうしようもない手強さにsan値がダダ下がりでした。しかし、順位の期待値を上げるべく夜を削って策を練りました。まずはponanza対策を諦めました。評価関数、探索の双方で負けている可能性が高かったからです。ponanzaは定跡を使ってこないので、そこ狙撃できればチャンスはありますが、それを用意する時間もありませんでした。

そして、相手をaperyに絞り、aperyの弱点となりうる部分を列挙しました

・やねうら王の方が1日目時点では探索部が強そう

・時間配分はワンチャンスある

・nozomiとのテスト対局での頓死など逆転負けをすることがある

・定跡はあまり積んできてなさそう

以上を踏まえ、探索の差で稼げる小さな利益を積み重ねること、一局面の読みの精度の差で取り返しのつかない展開になることを避けることを目指しました。具体的には

・玉の危険度が絡みやすい横歩

・角の睨み合いが続く角換わり

・決着までが長引きやすい相掛かり

を狙いました。この判断には大昔に千田先生が書かれていた記事を参考にしました。具体的には、wcsc27の定跡を12手だけ使う(評価関数の質が変わった手前、狙撃は狙えないと思った)ことにしました。

 

・2日目は只管祈る

1時間対局でのtimemanをテストするのはほぼ不可能なので、2日目は只管祈っていました。平岡さんに「強くならない独自性は独自性じゃないという風潮は嫌ですが、自分についてはライブラリで楽してる以上、独自性に強さを求めていきたいし、元のライブラリより強くならないなら出ないという心つもりでやってます」とイキリ発言をする以外は大体祈ってました。

2日目も1日目同様に、兎に角後手ばかり引きましたが、aperyを倒し、ponanzaに対しても一瞬だけ形勢を取り戻し、やねうら王を倒しと謎の躍進を見せました。定跡をしっかり用意してるであろうやねうら王に横歩の入り口で定跡を切らす真似をするのは死ぬほど怖かったですが、Qhapaqが飛車を変な場所に動かしたお陰で早い段階で定跡を外れてくれた(これもよもしたら脆弱性なんじゃ)ため、無事に時間攻めをすることが出来ました。

 

・振り返ると振り駒運も悪くなかった

5位決定戦がelmoと聞いた時は「apery、ponanza、やねうら王、elmoって、参加ソフトのレートを上から4つ並べたのとほぼ同じじゃね」と思うと同時に「此処だけはなんとしても先手を引きたい」と考えていました。というのも、elmoもまた定跡巧者であり、先手で此方を狙撃できる定跡を沢山持っていると考えられたからです。昨日の戦いから後手パーク定跡は通用しないし、wcscの後手定跡で横歩にしてしまったら、そこから一方的に時間を削られかねなかったのです。

elmoは兎に角当たりたくない相手でしたが、同時に当たる価値がある相手でもあります。というのも、最終試合ならネタばらしをしても次の対局で対策されることがないので、ネタばらしをし放題だからです。

 

私「tkzwさーん、timemanどうしました?」

瀧澤さん「slowmoverを80 90にしました。そちらは?」(瀧澤さんより訂正いただきましたthx)

私「MoveHorizonを128にしてslowmoverは110にしてます」

 

インタビューなどで偉そうにいろいろ語りましたが、実はqhapaqのtimemanはやねうら王に比べ2バイトしか差がありません。方方の理論研究から、optimum timeを一定に保ちながら、150手前後で時間を使い切るようにするのがベストだと導出していたからです(この2バイトの改造で、なぜ上記の理想的状態に持ち込めるかの説明はまた今度...の予定)

 

2バイトで独自性とはこれ如何にですが、導出には20年近く蓄え続けた数理パワーと机の前での数十分の悶絶が伴っていますので許してください。保証は出来ないけど、少し強くなってると期待してます(強くなってなくても、他に強くなった部分はあるし、勝てたからいいんだよ!

 

・狼ヘッド

狼ヘッドはsdt4でたぬきのきぐるみを着た変な人を倒すために、ドンキのハロウィンセールの売れ残りから買った獲物なのですが、着けてると色々と縁起が良いのと、対局開始と終局時にこれを装備して挨拶してたことから、正装=狼ヘッドという図式が成り立ってしまったので、定番化してしまいそうです。

喋るときに不便だし、全く私の顔が残らないのも癪なので、早く色褪せるとか穴空くとかしないかなと思いつつ、ちょっと保存状態の悪そうな棚に閉まってあります。

 

・入賞スピーチの焼き直し

ライブラリ勢の活躍やponanzaの引退のお陰でsdtのゲームバランスに疑問の声が上がるのも然るべきことだと思います。また、Yorkieの敗退やマッチングミスなど、運営に対する不満もゼロではないかも知れません。私自身は、ライブラリ勢が嫌いなら嫌いで良いと考えています。ただ、ライブラリ祭りが嫌いだからとsdtを観るのをやめてしまう前に、それを声に出して欲しいとお願いしたいです。sdtは見てくれる人があってのイベントであり、見てくれる人のためにルールが組まれます。皆様の声にはsdtを作る力があるし、sdtが続くことは、コンピュータ将棋の発展にも重要だと信じています。

 

今後共、Qhapaq。いや、コンピュータ将棋をよろしくお願いします。

(棋力)ゼロから始める将棋ソフトを使った戦型探索(解説編、居飛車編)

2017年の升田賞は千田翔太六段が受賞しました。

将棋ファンには今更ではありますが、千田先生はコンピュータ将棋を用いた将棋研究の第一人者でもあります。受賞の原動力となった角換わり腰掛け銀4二玉・6二金・8一飛型は、2015年頃からコンピュータ同士の対戦で多用される(プロの棋譜でも65歩型の持久戦では見られたのですが、今ほどメジャーな形として扱われていなかった)ようになった戦型です。ソフトの棋譜から先見性を見出し、実戦で活用し一つの技術に昇華させるのは並大抵ことではなく、千田先生の代わりとなる人はそう居ないことは疑う余地はないです。

のですが......将棋ソフトを最も深く理解しているであろう将棋ソフト開発者勢としては

狙ってみたい、二匹目のどぜうを!!

と思ったりもするわけです。

とはいえ、今の時代、棋譜を並べてソフトで点数や指し手を解析するような研究は将棋ガチ勢の大半がやっていることでしょう。そこで、開発者ならではの技として、棋譜ではなく、評価関数自体を可視化することによる戦型探索に挑戦してみることにします。

 

【そもそも、4二玉・6二金・8一飛型をソフトはどう思ってるの?】

まずは予備実験として、msd賞受賞作品である4二玉・6二金・8一飛を解析してみます。現在のソフトの主流である、3駒系の評価関数では、玉の位置と駒二つの並びに点数をつけることで盤面の価値を測定しています。4二玉・6二金・8一飛の並びは玉、駒、駒の3つから成り立っているため、駒の並びそのものに点数が突いているはずです。

というわけで、elmo+100程度のレートを持つという評価関数を用いて、4二玉・6二金・8一飛の駒の並びの点数を求めてみましょう。

結果:32点

金底の歩の点数が60点前後であることから、極めて雑な(駒の並びの良さが評価値に比例する、他の駒との相互作用を無視する)近似をすれば、この構えは、将棋入門テキストに必ず含まれる超良形の半分くらいの価値があることになります。

上述の近似だけだと、少々説得力に欠けるので、比較実験として、昔の評価関数でも同じ解析を行ってみます。

2016年8月時点でのaperyの評価関数では28点、2016年6月時点でのqhapaq(大樹の枝野評価関数とほぼ同じレート)では8点となりました。2015年の電王トーナメントでのponanzaあたりから、4二玉・6二金・8一飛が多用され始めたことと(一応)一貫性が取れており、3駒(KPP)の高い形を新戦型として模索するという方法が(希望的観測では)可能と言えそうです。

 

【レッツ、評価値の高い駒の並びを探してmsd賞を狙おう】

というわけで、最新鋭のqhapaqの評価関数(開発者の意地で強いと言われる某評価関数より強いのを用意したはず)を用いて4二玉・6二金・8一飛並に点数の高い3駒の並びを模索し、千田先生の二匹目どじょうを目指しに行きたいと思います。

88や68の玉を相手にKPPの値を取得、ランキングにしていきます。序盤戦略ということで駒の位置を4段目以下とします。更に、KPPに相手の駒を含むものも加味してしまうと、29の相手の龍、39の金と相手の大駒を取れる形ばかりが出てきて目ムワなので、KPPの構成要素を自分の駒に固定します。

..........出てこない。4二玉・6二金・8一飛に匹敵する高得点の並びがっ!

 

32点以上の価値を持つ要素自体は沢山あります。金底の歩を始めとした、終盤の受けの手筋や馬や龍を使った受けは32点以上の価値を生み出しています。しかし、それは序盤戦術ではありません。

それならば、と、玉の位置69に変えて検索。すると......

 

おおっ、玉の位置が69、金の位置を59としたときのKPPには32点以上の並びがあるではないかっ!

このような書き方をすると、「点数が高い駒の並びから戦型を探すのはわかるけど、それなら棋譜を見て判断するのと差がないんじゃね?」と思われるかも知れません。

しかし、それは違います。69玉周りの囲いの点数が高くても、elmo+R100クラスのソフトは(私が知る限りでは)以下のような手はほぼ指しません。

f:id:qhapaq:20171005003626p:plain

(初手から、26歩、84歩、25歩、85歩、78金、32金、69玉)

この局面の評価値は-50〜0程度で先手が悪いという判断です。コンピュータの推奨手は38銀や24歩であり、何れも+50ちょいの評価値が出るため、並列化などで評価値にブレが出るとは言え、候補には上がってくれないのです。

しかし、この玉形の価値は戦いが進むことで発揮されます。69玉型は金駒をよせることで高い守備力を発揮できるので、この時点での評価値が悪くても将来性はあるのです。

ここから金を59、銀を48に動かし、必要に応じて48の銀を繰り出したり、79の銀を68に動かしたり......

 

さて、将棋ファンの方は即座にツッコミを入れられることでしょう。それ、中原囲いじゃねと

 

そのとおりです。数理パワーがあっても、棋力が足りないとこの手の罠に引っかかります。(なお、私は気づくのに数時間かかりました

 

さて、残念ながら中原囲いを使う作戦は既存戦型を大きく上回る勝率は達成できなかったようです(勝率は大体50%前後。先手のアドを加味するとやや悪い)。ただそれでも、上記局面から展開される棋譜はなかなかに面白く有意義なものだったと思いました。

 

・例:中原囲いからの浮き飛車

f:id:qhapaq:20171005005322p:plain

(初手から、26歩、84歩、25歩、85歩、78金、32金、69玉、86歩、同歩、同飛、87歩打、84飛、48銀、72銀、76歩、42玉、36歩、74歩、35歩、75歩、同歩、73銀、59金、14歩、26飛、64銀)

この局面での評価値はほぼプラスマイナス0。ここから飛車先を切ってから角交換をし、桂馬と角で33の地点に総攻撃を仕掛けて先手が優勢となりました。

 

この他にも駒組が飽和してから、銀を68、角を79に引いて嬉野流に組み替えるなど面白い棋譜が量産されるので、戦型マニアのフレンズは試してみては如何でしょう。もしかしたら、新しい戦型が見つかるかも知れません。

 

【まとめ 棋力は必要だった】

msd賞を狙おうとかいいながら、思いっきり過去のmsd賞受賞作品を再開発してしまいました。やはり戦型の大半は賢人たちによって試されているというわけです。

しかし、「ソフトは指さないけど実はいい形」というのを発見する上で、指し手や盤面全体の評価値だけでなく駒の並びの点数自体に注目してみるという戦略は意味がありそうです。

 

【次回、ゼロから始める将棋ソフトを使った戦型探索 振り飛車編】

駒の並びをベースにソフトは指さないけど実はいい形という戦略を用いるなら、居飛車よりも定跡なしでは殆ど指してもらえない振り飛車こそ、開拓の余地があるのではなかろうか(そして、ここまで工数を使った以上、簡単に負けを認めたくない)というわけで、次回は振り飛車での戦型探索を行ってみます。

先手中飛車を含めても、今のソフトの振り飛車の勝率はとても悪い(先手でも50%どころか45%も怪しい)ですが、果たしてコレを上回る戦型は見つかるのでしょうか。

 

# 今手元で回している新戦型は26-21で勝ち越していますが....果たしてコレは誤差なのでしょうか。

羽生三冠の通算勝率を5割にするのってプロ棋士のルール的に無理じゃね?

皆様知っての通り、羽生三冠はとても強いです。将棋連盟のホームページによるとこの記事の執筆時点で通算成績は1379-552だそうです。長く現役を続け、かつ、タイトルに近づくほど相手が強くなる中でこの成績をキープするには並々ならぬ努力を続けていることと思われます。

 

通算100タイトル、永世七冠など将棋ファンなら羽生三冠がどこまで記録を打ち立てるかに目が離せないと思われますが、ふと、逆に、ここから羽生三冠が全力で将棋をサボった場合、果たしてどこまで成績を下げられるのかが気になりました。

 

勝率を5割に戻すためには、現時点で827連敗しなければいけないそうです、果たしてそんなことは可能なのでしょうか......

 

注:以下、そんなに真面目に計算はしていません。興味を持たれた方は是非、ご自身で計算してみましょう(あと、結果教えて欲しいですw)

 

【愚直に連敗すると勝率5割になる前に引退してしまう】

一番愚直な戦略として、出る対局全てで負けてもらうことにします。玲瓏(ファンサイト)を見た所、羽生三冠は年間50-60局程度対局しているようです。これに全て負け続ければ、15年後に勝率が5割になります。将棋はゲームの性質上、全棋士の勝ち負け数の和は同じになるはずなのですが、15年間負け続けても大丈夫なほどの勝ち越しって一体何なのでしょう。

しかし、名人戦で連敗を続けるとA級棋士の場合、9年でフリークラスに落ちてしまいます。そして、60歳の時点でフリークラスに居ると強制的に引退となってしまいます。

羽生三冠は現在46歳。此処から先の全ての試合を負け続けたとしても、14年後には引退となってしまいます。負けると対局数も減るという致命的な補正を無視してもなお、勝率5割になるには後1年足りないのです。

負け続けても勝率が5割になる前に引退になってしまうという言葉の響きがパないですが、兎に角ただ負けるだけではダメなのです。

 

【真の敵は寿命】

単純な対局ボイコットでは勝率5割にならない以上、名人戦だけはフリークラスにならないようにしながらできるだけ多くの対局で負け続けることを考えます。

後1年余分に負ければいいかといえばそうでもありません。なぜなら、クラスが落ちるにつれて対局数が減ってしまうからです。ひふみんの此処数年の成績から推察するに、凡そ年20局程度まで減ってしまうようです。

できるだけ多くの対局で負けるために、意図的に一部の試合に勝つという手は使えないでしょうか。

現在の将棋タイトルはトーナメント戦やリーグ戦からなります。トーナメントであれば、初戦で負けるのが勝率を下げる上では最適解です。王将リーグなどは入れれば負け星を稼げるのですが、これをやるには相当な数のトーナメント型の予選を勝ち越さなければならず、結局予選のトーナメントで負けるしかありません。試合を勝ち進むことで負け星を増やすことはどうやら難しそうです。

更に、前述のようにフリークラス行きを避けるためには、C2リーグで4勝程度はしなければなりません。以上を纏めると、4-16程度の負け星で800超もある勝ち星を埋め立てていく必要があります。

A級からC2に行くまで全負け(この間にどれだけ対局が組まれるかは定かではありませんが、此処では平均30局(恐らく多く見積もり過ぎ)とし240連敗出来たと仮定します)、C2+刺青x2になってからはフリークラス行きを避けるように4-16の星をキープするとしましょう。

827 = 240 + 12 * X。 勝率5割になるにはX+8年の歳月が必要です。X=49。即ち57年後に勝率が5割になるようです。103歳まで対局を続けて勝率がやっと5割になるって、何を言ってるか解らないと思いますが、ありえなくもないと思えるのは私だけでしょうか。

 

永世竜王ループ】

改めて見ると、特に厄介なのは強制引退を避けるためにC2で4勝しなければならないことです。年間対局の半分を占める順位戦で半分ぐらいの試合に勝たねばならぬのは勝率を下げる上でとてつもなく邪魔なのです。仮にこの縛りがなければ(年間負け星を20稼げれば)、X=30となり、84歳で目標を達成することができます。

そこで、フリークラスの規定を見直すと、とてつもなく良いことが書いてありました。

 

今泉健司四段、 フリークラスからC級2組へ昇級|将棋ニュース|日本将棋連盟

より引用

 

【フリークラスからC級2組への昇級規定】フリークラスからC級2組への昇級規定は以下のうち一つを満たした場合です。ただし、「年間」は4月1日から翌年3月31日まで。宣言によるフリークラス転出者は除きます。
1.年間対局の成績で、「参加棋戦数+8」勝以上の成績を挙げ、なおかつ勝率6割以上。
2.良い所取りで、30局以上の勝率が6割5分以上であること。
3.年間対局数が「(参加棋戦+1)×3」局以上。ただし、同じ棋戦で同一年度に2度(当期と次期)対局のある場合も1棋戦として数える。
4.全棋士参加棋戦優勝、タイトル戦挑戦。

 

棋戦優勝。これです。

例えば今年の竜王戦で優勝して永世竜王になってもらうとしましょう。この場合、来年はチャンピオンとして竜王決定戦にだけ出ることになります。ここで4-3で竜王を防衛してもらいます。

するとどうでしょう。全棋士参加棋戦優勝の称号が手に入るではないですか! 相対的な負け星をわずか一つ削るだけで!!!

 

改めて纏めると

1.竜王竜王じゃなくても、チャンピオンによる防衛戦の概念がある名人以外の棋戦の王者なら良い)になる

2.他の棋戦は全て負ける

3.竜王だけ4-3で防衛し続ける

4.フリクラに落ちても竜王防衛の力でC2にすぐ戻れる

5.年間20程度、相対的な黒星を稼ぐ

6.これを84歳ぐらいまで続ける

 

寝言は寝て言えと言いたくなる超がつくほどの糞ゲーですが、上記二つの戦略に比べればきっと現実的でしょう。

 

通算勝ち星800超えの壁は厚かった!!

 

【勝率といえば】

現在コンピュータ将棋で流行しているelmo式の学習は、深く読んだ評価値と局面の勝率を教師に浅い読みの評価値を補正していこうという戦略です。評価値が低いのにコンピュータの読み筋にしたがって指すと勝ててしまう様な局面の評価値をちゃんと高くすることがelmo式の肝ともいえます。

実は(という程でもないけど)elmo式の効果は適当な盤面を初期値に勝率を計測することで可視化することが出来ます。下のグラフはaperyの学習機に付属された局面の評価値と、そこから戦わせた時の勝率のグラフです。

f:id:qhapaq:20170829225323j:plain

評価値0付近にエラーが大きいことが解ります。恐らく、相手玉の詰む詰まないを正しく読めていないのでしょう。

f:id:qhapaq:20170829225557j:plain

これがelmoだとこうなります。短い持ち時間ではqhapaqはelmoよりも強いのですが、盤面全体のブレの小ささではelmoが上を行っており、これが長い持ち時間での強さの秘訣になっていると思われます。

という現象を理想的にはrelmoやまふ関数などで検証するべきなのですが、面倒なので手が出せてないです orz

 

さて、こうした将棋学習理論の詳細やコンピュータ将棋の最先端のぶつかり合いが見たい人は、是非、電王戦もよろしくお願いします(私もQhapaqとして参戦します)

(言い訳程度のコンピュータ将棋要素)

ゼロから創る chainerrl を使ったディープラーニングもどき

注:今回の記事は完全にプログラマ向けの解説記事です

ソースコードの閲覧、ダウンロードは此方からどうぞ
GitHub - qhapaq-49/chainerrl_test: chainerrlを使ったスタンドアロンな強化学習のサンプルです

【前回の記事(tensorflow版)】
ゼロから創る tensorflow + reinforcement learningを使ったディープラーニングもどき - qhapaq’s diary

【今回の記事と合わせてオススメしたい記事】
ChainerRLで三目並べを深層強化学習(Double DQN)してみた - Qiita
# 正直、本稿よりも此方の記事のほうが良く出来ています。

【今回作りたいもの】
前回に引き続き、今度はchainerrlでニューラルネット+強化学習を組んでみました。
今回も200行程度で動くスタンドアロンな強化学習のサンプルを公開、紹介していきます。ゲームAI開発者の必須スキル(?)である
ディープラーニング+強化学習の雰囲気と簡単な実装を本稿で感じていただければ幸いです。

強化学習の詳しい導入や解説は前回の記事をご覧ください。

【本稿で扱うゲームのルール(再掲載)】
本稿ではニューラルネットで動く競りゲームのAIを作ります。競りゲームとは

・初期所持金10のプレイヤー2人が4回の競りを行う
・各競りでは1〜4の勝利点がランダムで出てくる。同じ勝利点は二度出ない。
・競りで買値を宣言できるのは一度だけ。高値を宣言した側が勝利点を得られる。引き分けの場合の再試合はなし
・4回の競りの後で勝利点が高いほうが勝利

というゲームです。私が風呂に入りながら勝手に考えたゲームです。

【使い方】

step.0 学習/対戦のモード選び

# 学習 or 対戦
Learning = False

の部分を編集して学習させるか対戦させるかを決めます

step.1 読み込むモデルを決める

# 学習させる場合の初期値(になってるか怪しい
sagent.loadAgent("")

のダブルクオテーションの中に所定のモデルファイル(初期状態では無いはずなので空文字にしてください)
を書き入れることで、エージェントの初期値を決定します。


step.2 対戦相手のモデルを決める

env = seriEnv()
# 学習させる場合の対戦相手
env.loadEnemy("random")

のダブルクオテーションの中に所定のモデルファイル(初期状態では空文字かrandom(全ての手をランダムにする)を選んでください)
を書き入れることで、対戦相手の初期値を決定します。以下の学習(対戦)ではこのモデル相手の勝率を最適化(測定)します


step.3 学習の場合、保存するモデル名を決める

if Learning == True:
    # この名前で保存する
sagent.agent.save('model2')

のクオテーションの中に学習結果を保存する際の名前を書き入れてください。

以上の準備を済ませたら

python3 serichainer.py

とでもすれば動きます(本コードはpython3での動作は確認してますが、他の環境での動作は解りません)


【chainerで強化学習するときにハマりやすいポイント】

ChainerRLで三目並べを深層強化学習(Double DQN)してみた - Qiita
を合わせて読むことを強くオススメします。

PFNのサイトに17/08/22時点に書かれていない大事なポイント(かつ最もハマり易いポイント)はexplolerの独自設定です。サイトではお約束のgymの流用がなされており、最低限どんな機能を含んでいればよいのかがハッキリしません。が、必要な実装自体はシンプルです。以下、公開したコードより抜粋

# step1 ランダムムーブ用のクラスを創る
# explorer用のランダム関数オブジェクト
class RandomActor:
    def __init__(self):
        pass
    def random_action_func(self):
        # 所持金を最大値にしたランダムを返すだけ
        return random.randint(0,10)

# 本当はrandom_action_func(self, money)と所持金を引数にしたかった
ra = RandomActor()


# step2 メンバ関数を引数としたexplolerを定義する
self.explorer = chainerrl.explorers.ConstantEpsilonGreedy(epsilon=0.05, random_action_func = ra.random_action_func)

# step3 dqnagentにぶち込む
# explolerの自作以外はだいたいコピペでも動く。パラメタや設定がベストか否かは...知らぬ
self.agent = chainerrl.agents.DoubleDQN(self.q_func, self.optimizer, self.replay_buffer, self.gamma, self.explorer,replay_start_size=500, phi=self.phi)

このとき、random_action_funcに引数がつくと、どうもエラーが出て動かないようです。また、紹介したqiitaのコードはdoubledqnの定義の際に不要な引数がついているため、そのままだと動きません。
# コード中の update_frequency=1, target_update_frequency=100 を消さねばならぬ。
この辺の処理をしないとうまく動かない理由はいまいちわからないのですが、取り敢えずtensorflowよりも短い行数で曲がりなりにも学習するサンプルを創ることが出来ました。

【あとがき:なんでこんなものを態々公開したか】
chainerも布教されるべきだと思ったからです。三目並べを見つけた時には「私の記事いらなくね」と思いましたが、此方は不完全情報ゲームの学習機だったり、三目並べの補足情報もあったほうが良い(事実、三目並べのコピペだけでは機能しなかったわけですし)するので存在意義は見いだせたかなと思います。

チュートリアルよりも高度な実装をtensorflow, chainerと有名なDNNパッケージで作り比べた感想は「tensorflowよりchainerの方が楽とか、kerasの方がもっと楽とか言うけど、実装で最も工数を喰うのはチュートリアルをはみ出た、解説記事を見つけ難い状態での謎解きであり、中級者的な使いやすさは教えを請える相手が何を使っているか(教えを請う人が居ないならどれだけまともなデバッグログを吐くか)でほぼ決まる」というものです。三目並べの記事のお陰でchainerはtensorflowに比べ地力で書くコードの数は少なかったですが、引数やらなんやらの問題を自力で解読した結果、tensorflowと使った工数には大差はありませんでした。

chainerはtensorflowに比べればコミュニティーが小さいですが、chainerにしか出来ないことも沢山あります。tensorflowに慣れ親しんだ人も、chainerの練習はしておくと将来的に得なのでは....ないでしょうか。

ゼロから創る tensorflow + reinforcement learningを使ったディープラーニングもどき

注:今回の記事は完全にプログラマ向けの解説記事です

ソースコードの閲覧、ダウンロードは此方からどうぞ
GitHub - qhapaq-49/tf_reinforcement: tensorflowを使った簡単(300行弱)なreinforcement learning

【今回作りたいもの】
囲碁やポーカーのAIで度々注目されているディープラーニングを使った強化学習。時代の先端を走るゲームAI開発者的には是非覚えておきたいスキルの一つです。といっても、強化学習の動作原理自体は下記の図のようにシンプルなものです。本稿では下記図の流れを一通り搭載したスタンドアロンで動く強化学習ルーチンを紹介します(上述のgithubのコードを見ながら読まれることをオススメします)。

f:id:qhapaq:20170818162212p:plain

【本稿で扱うゲームのルール】
本稿ではニューラルネットで動く競りゲームのAIを作ります。競りゲームとは

・初期所持金10のプレイヤー2人が4回の競りを行う
・各競りでは1〜4の勝利点がランダムで出てくる。同じ勝利点は二度出ない。
・競りで買値を宣言できるのは一度だけ。高値を宣言した側が勝利点を得られる。引き分けの場合の再試合はなし
・4回の競りの後で勝利点が高いほうが勝利

というゲームです。私が風呂に入りながら勝手に考えたゲームです。

【使い方】
Step1. ランダムムーブで教師データを作る

# 何方のエージェントも外部モデルを使わない
playout = seriGame("","")
# 対戦回数 : 30000 ログファイル名:hoge、player1にランダムムーブをさせる:True、player2にランダムムーブをさせる:True
playout.playout(30000,"hoge",True,True)

によってランダムムーブの対局を行わせて教師データを作ります。
なお、教師データは勝った方の手を良い手としてその手の採択率を上げるというシンプルな作りになっています。

Step2. 学習させる

sa = seriAgent()
sa.loadnofile()

# 教師データ名:hoge、ステップ数:200、保存するモデル名 model/test_model
sa.learnbycsv("hoge",200,"model/test_model")

Step1で作られた棋譜を元に勝った時の手の採択率を上げるように学習します。


Step3. 学習させたモデル同士を戦わせて棋譜を作る(以下、Step1-3を繰り返す)

# player1、player2のモデルを指定
playout = seriGame("model/test_model","model/test_model")

# 読み込ませたモデル同士を戦わせる
playout.playout(30000,"hoge2",False,False)

【コード中の特に重要(かつ、tensorflowにちなんだ)部分の解説】

1.ネットワークの定義

    def design_model(self):
        
        graph_t = tf.Graph()
        with graph_t.as_default():
            # 入力:X、隠れ層:W1,B1,H1,W2、出力:Y
            X  = tf.placeholder(tf.float32, [None, glob_inpXdim])
            W1 = tf.Variable(tf.truncated_normal([glob_inpXdim, self.hiddendim], stddev=0.01), name='W1')
            B1 = tf.Variable(tf.zeros([self.hiddendim]), name='B1')
            H1 = tf.nn.relu(tf.matmul(X, W1) + B1)
            W2 = tf.Variable(tf.random_normal([self.hiddendim, self.outdim], stddev=0.01), name='W2')
            B2 = tf.Variable(tf.zeros([self.outdim]), name='B2')
            Y = tf.nn.softmax(tf.matmul(H1, W2) + B2)
    
            tf.add_to_collection('vars', W1)
            tf.add_to_collection('vars', B1)
            tf.add_to_collection('vars', W2)
            tf.add_to_collection('vars', B2)

            # 学習時の教師データ:t
            t = tf.placeholder(tf.float32, shape=[None, self.outdim])

            # 交差エントロピーの宣言 and 学習機(adam)
            entropy = -tf.reduce_sum(t*tf.log(tf.clip_by_value(Y,1e-10,1.0)))
            learnfunc = tf.train.AdamOptimizer(0.05).minimize(entropy)

            # これらのパラメタを外から弄れるようにする(これ理解正しいの?)
            model = {'X': X, 'Y': Y, 't' : t, 'ent' : entropy, 'learnfunc' : learnfunc}
            self.sess = tf.Session()
            self.saver = tf.train.Saver()
            self.sess.run(tf.global_variables_initializer()[f:id:qhapaq:20170719231154p:plain][f:id:qhapaq:20170818173131p:plain])

この処理によってネットワークの構築、ネットワークを使って実際に計算する部分(session)と
計算結果を保存するためのsaverが定義できます。

この辺の処理は解りにくい(そして私の理解も怪しい)ですが、graphが住所、variablesが家の設計図、sessionが建築士と考えると良いでしょう。上記関数は特定の住所(graph)に対し、どういったネットワーク(variable)が入るかを指定したものを、建築士(self.sess)に渡したと解釈できます。graphにぶら下げる形でvariableとsessを定義するのがミソ(with graph_t.as_default())のようです。建築士に住所を指定して教えこむことで、複数のsessを動かすときに変な衝突の発生を避けられるようです。


2.ネットワークの書き込み、読みこみ
TensorFlowでmodelを保存して復元する - test.py
を合わせて読むことをオススメします(本作のコードの元にもなってます)

    def learnbycsv(self, fname, stepnum,outname):
        # 学習部
        data = pd.read_csv(fname,header=None, dtype=float)
        x = data.iloc[:,0:glob_inpXdim]
        y = data.iloc[:,glob_inpXdim:63]

        # 学習
        for i in range(stepnum):
            self.sess.run(self.ln, feed_dict={self.X : x, self.t : y})
            #print(i)
            if i%20 == 0:
                ent = self.sess.run(self.ent, feed_dict={self.X: x, self.t : y})
                print("epoch " + str(i)+" ,entropy = " + str(ent))
        # データの保存
        self.saver.save(self.sess, outname)

saverはsessを呼び出し、sessからgraphの内部変数を引っ張りだすことで学習結果を保存してくれます。

逆にデータの読み出しは

    def loadfile(self,fname):
        self.model = self.design_model()
        self.X, self.Y, self.t, self.ent, self.ln  = self.model['X'], self.model['Y'], self.model['t'], self.model['ent'], self.model['learnfunc']
        print("load agent from : " + fname)
        self.saver.restore(self.sess, fname)

でいけます。

3.雑なまとめ
これらの関係を図にするとこんな感じになります。
f:id:qhapaq:20170818173131p:plain


【あとがき:なんでこんなものを態々公開したか】
一言で言えば、このコードを書いた際に教えて欲しかったことを教えてくれる記事がなかったからです。tensorflow+reinforcement leaningの記事は沢山あるのですが、その多くはgymなどの洗練されすぎたパッケージを使っていて、「結局、これを自前のゲームに適用するにはどうすんのよ」という問の答えは自力で探す必要がありました。更に悪いことに、多くの教材はmnistのように外部から教師を引っ張ってきて、一度学習させたらオシマイとなっており、複数の学習モデルの比較や、強化学習といった学習結果を次の学習にフィードバックさせる類の学習への適用は困難でした。特に、学習データの読み書きや、複数のsessionの切り替え、誤差関数の自作については悲しいくらいに記事が少なく(または、サンプルコードやっつけたらオシマイの記事に埋もれてしまい)、数学に関係ない実装面でのエラー潰しに酷く苛々させられたものです。

これに対し、本コードは300行未満のシンプル、かつ、スタンドアロン(tensorflowやnumpy以外の外部パッケージは使ってない)なコードでありながら、複数セッションの呼び出しやデータの読み書き、思考部と対戦部の切り分けとクラス化といった基本的な機能が搭載されています。本コードを動かし、読み解くことで、皆様もtensorflowを使った強化学習に取り組めるのではないかと期待しています。

# batch normalizationやcnnなどの高度な機能は一切積んでいませんが、そのへんの技はググればすぐ出てくるので、ggrksなのです

しかし同時に、本稿はtensorflow歴一週間にも満たない野良開発者の雑コードの解説であり、至るところに間違いもあると思います。悪く言えば未完成なのですが、よく言えばインタラクティブな記事だと思っています。間違いやアドバイスがありましたら、教えていただけると幸いです。


【謝辞】
本稿の執筆に当たり、コンピュータ囲碁開発者の方々にアドバイスをいただきました。
これらのアドバイスがなかったら、恐らくこの記事は完成しなかったでしょう。心よりお礼申し上げます。

レート測定エンジン ELQを使って君も棋力解析をしよう!

藤井四段と羽生三冠のどちらが強いか、流行の人工知能に聞いてみました - qhapaq’s diaryで用いたレート解析機をやっとの思いでリリースいたしましたので、此処に報告いたします。ソフト自体はかなり前にできていたのですが、皆様の利用に耐える形になるのに凄く時間がかかりました......

 

DLはこちらから(ソースコード付き)

Release レート測定器ELQ · qhapaq-49/qhapaq-bin · GitHub

 

レート測定器ELQの使い方

0.ELQの動作環境

ELQはC#で開発されています。windows上での動作を確認していますが、他OSでの動作は確認しておりません。

 

1.将棋guiを用いた棋譜解析

まず最初に、ELQのkif_exampleのように、解析したいプレイヤー(プロ棋士、ソフト、etc)の棋譜を先手、後手別に分別して保存してください。ELQには棋譜コメントから先手後手を判別する機能はついていません。

分別が終わったら、解析したい棋譜将棋guiの"連続解析"を用いて解析します。手元のバージョンでは対局タブから棋譜解析から連続解析が出来ました。指定したフォルダにkif、csaファイルといった将棋guiに対応した棋譜ファイルを置くことで、連続で解析を行うことができます。解析にはそこそこ時間がかかります。ラノベを読むなどしてお待ちください。

kif_example/fujiis/fujiis_sente/ 内のkifファイルのように、kifファイル内に指し手とソフトの評価値が用意されたら準備OKです。

 

2.ELQを用いて仮想エージェントを創る

kifファイルが保存されたフォルダを指定して、「指し手データ作成」ボタンを押してください。保存するcsvファイルの名前を聞かれるので、適当な名前を付けてあげましょう。

注:ELQは指定したフォルダ内のkifファイル"のみ"を"全て"読み込む仕様になっています。将棋guiの解析の保存形式がkif以外だったり、元の棋譜ファイルがkifファイルだったりすると不具合の原因となりますのでご注意ください。

注2:将棋guiは日本語でログが保存される関係で、テキストエンコーディングの愛称が問題になってきます。ELQはshift_jis対応ですが、他のエンコーディングへの対応は明示的にはしていません。将棋guiのデフォルト設定で問題なく動いていることは確認していますが、ご注意ください。

 

3.仮想エージェント同士で対局させる

step2で作ったエージェントをレート測定のタブ上で戦わせることが可能です。細かい動作原理はソースコードを参照していただけると幸いですが、雑に言えば「各プレイヤーについて、局面の良さに毎の指し手の統計データを取り、それにしたがって局面の評価値を動かすエージェントを作り、その勝率を測定」することでレートを測っています。

藤井四段と羽生三冠のどちらが強いか、流行の人工知能に聞いてみました で用いたレート測定用のパラメタは評価値カーネル200、ノイズ20です。これは羽生三冠ととある棋士(双方とも対局数が多く年単位でレートがあまり動いていない)のレート差を際限するように調整した結果です。

 

4.棋風を解析する

step2で作ったcsvファイルをelq_templateに入れることで、棋風の解析が出来ます。一例として、藤井聡太四段のデータを可視化してみましょう。

f:id:qhapaq:20170719231154p:plain

横軸は局面の評価値、縦軸は悪手を指す確率(厳密にはソフトが評価する手を指す前と後での勝率の差分の変化量の平均)です。値が大きいほど悪い手を指す傾向が強いです。青が藤井四段、赤が対局相手です。

こうして見比べると、藤井四段と対局相手とでは200-1000点程度の自身が有利な局面での手の質に大きな差があることが解ります。藤井四段の29連勝には逆転勝ちの棋譜も少なくないですが、改めて数値化すると、藤井四段は自分が有利なときはミスをせず、相手が有利なときにミスを誘うような手を指すのがとても上手であると言えます。加えて、相手のミスを誘う勝負手はソフト的にはベストな手ではないことが多いにも拘わらず、藤井四段の不利な局面での悪手率は対局相手と比べてもあまり高くないのは驚愕です。

 

藤井四段旋風に乗っかって藤井四段の棋譜ばかり解析していたので、他の棋士のデータがあまりないのは心苦しいですが、本ソフトを使って皆様も棋風解析を楽しんでくれれば幸いです。本研究ではやりませんでしたが、戦型別の棋風測定なども有意義であると思います。

 

おまけ、本ソフトの名前の由来

本ソフトの正式名称は「ねうら王の学習がまだ終わらないから作ったelmo-qhapaq理論型レート測定器」略して「山田エルク」です。

どういうわけか某大作ノベルの人気キャラの名前にとても良く似ていますが、1億%偶然です。やる気が出たからコードを書きました。レート測定部分の変数添字が逆だったなどのバグを発見した際はダビデ像を添えてご報告いただければ幸いです。

REsolve MUtationエンジンの使い方

ゼロから始める評価関数分解のやり方紹介です。

githubのドキュメントがあまりに貧弱すぎたので、幾つかの具体例を以って使い方をもう少し細かく解説したいと思います。

 

注:この関数がパクリじゃないか的な議論をするためにREMUを使うことは推奨しません。また、私自身がそういった用途での解析に協力することもありません。KPPTという同じ評価軸を使ってる以上、強い関数がある程度似るは仕方がないことですし、ちょっと数理を理解してる人ならバレないように関数をコピペすることは容易だからです。

 

本体:

Release 評価関数分解機 REsouve MUtationエンジン · qhapaq-49/qhapaq-bin · GitHub

sseバージョンも作りました

https://t.co/6I2Sypa5nC

 

1.評価関数フォルダと実行ファイルを揃える

YaneuraOuが置かれているフォルダと同じフォルダに評価関数を並べます。このとき、evalフォルダにダミーのKPPT評価関数を置くようにしてください。これをやらないと、コマンド実行直後に「open evaluation file failed」というコメントが出て計算に失敗します。

 

f:id:qhapaq:20170717001919p:plain

こんな感じ(binディレクトリの上半分しか写ってませんが、下にはYaneuraOu-by-gcc-remu.exeが居ます)

 

2.evalresolveコマンドを実行する

例えば

test evalresolve elmo_ep8_55 elmo epoch8

と打ち込んでみましょう。elmo_ep8_55はelmoとリゼロepoch8を5:5で混ぜた関数です。このコマンドはelmo_ep8_55をelmoとepoch8で分解するという意味です。

 

3.解析結果を読み解く

出力結果一例(evalフォルダにはwcsc27のqhapaqの評価関数が入ってます)

 

test evalresolve elmo_ep8_55 elmo epoch8
info string Eval Check Sum = 702fb2ee5672156 , Eval File = Qhapaq(WCSC27)
REsolve MUtation engine target = elmo_ep8_55, reference = elmo,epoch8,
prodmatrix
7.60975e+13,1.06684e+13,
1.06684e+13,2.91226e+13,
converge in 5 loop
result : elmo_ep8_55 = 0.49981 x elmo + 0.499613 x epoch8 + 0.00111753(diff ratio)

 

解析結果は最後の行です。

elmo_ep8_55 = 0.49981 x elmo + 0.499613 x epoch8 + 0.00111753(diff ratio) とは

「elmo_ep8_55の評価関数はelmoを0.49981倍したものと、epoch8を0.499613倍したものに、0.00111753 x 未知の関数(雑に言えば、学習によって生じた差分)を加えたもの」という意味です。

未知の関数の大きさ(KPPTのパラメタの2乗和)は解析元の評価関数(例の場合は、elmo_ep8_55の2乗和)に等しいです。今回はelmoとepoch8を混ぜただけの関数なのでdiff ratioは極めて小さい値になっています。やねうら王を使った単純な合成を施した関数であれば、個数や割合が未知であっても大体分解できます。分解元の評価関数に漏れがなければ、diff ratioが極めて小さい値になるかと思います。

評価関数の合成し過ぎで迷子になった時にお使いいただければ幸いです。

 

4.学習の効果を可視化する

wcsc27のqhapaqを分解してみます

test evalresolve qheval-wcsc27 elmo epoch8

qheval-wcsc27 = 0.865984 x elmo + 0.0192829 x epoch8 + 0.311523(diff ratio)

qhapaqとelmoの内積は割と大きいです。どちらも浮かむ瀬を元にしているからでしょう。

 

これにelmo絞りを加えるとこうなります(この関数は公開してないです)。

test evalresolve 0621-yz elmo epoch8

0621-yz = 0.860074 x elmo + 0.0660107 x epoch8 + 0.403802(diff ratio)

ちょっとepoch8の要素が増えるのと同時に、何らかの差分が加わっているように見えます。

リファレンスにqhapaqを加えて再分解

test evalresolve 0621-yz elmo epoch8 qheval-wcsc27

result : 0621-yz = 0.0249868 x elmo + 0.0474157 x epoch8 + 0.964326 x qheval-wcsc27 + 0.282281(diff ratio)

どうやらqhapaqに何らかのdiffが加わった形とするほうがより正確なようです(qhapaqの成分があんまり減ってなくて驚いた)。この関数のレートはelmoと同じぐらいなのですが、どうやらelmoとは別路線の関数になってるようです。

 

余談:さらなる応用

定跡ごとに雑巾を作って、どのぐらい関数の形が変わるかを見てみる、強い評価関数の組成比を見て、それに近づくように学習や合成を調整するなどの工夫ができると思います。使い方は無限大ですよ!(多分