前回の続き。
ある升に発生した利きの価値は、王様からの距離に反比例するのですか?それとも何らかの減衰曲線になるのですか?
optimizer曰く、「利きの価値は距離に反比例します」
1 2 3 4 |
int our_effect_value [9] = { 1024, 496, 297, 272, 184, 166, 146, 116, 117}; int their_effect_value[9] = { 1024, 504, 357, 320, 220, 194, 160, 136, 130}; // ↑optimizerの回答 // 変数名、紛らわしかったのでちょっと変えました。 |
前回書いたように、王様への利きは、味方の利きの価値は68(歩の価値を90とする)でした。王様への相手の利き(王手)の価値は、96でした。
これが、王様から1升離れると、味方の利きの価値は、496/1024 , 2升離れると 297/1024 というように減っていくとoptimizerは言っています。
同様に、王様から1升離れると、敵の利きの価値は、504/1024 , 2升離れると 357/1024 というように減っていくとoptimizerは言っています。
おおよそ距離に反比例することがわかりました。
問) 上記のoptimizerの出力から察するに、王様から1升離れたところにある味方の利きの価値は歩の価値の何%ですか?
答) 68×496/1024 ≒ 32.9 , 歩の価値は90だから、32.9 / 90 ≒ 36.6%。
何故、利きの価値が王様からの距離に反比例するのか考えてみよう
さて、optimizerから得られた答えに対して、その合理的な解釈を考えるのが我々の次の仕事です。合理的な解釈を見つけ出してこそ、次の段階に思考を推進することができるのですから。
今回、optimizerは「利きの価値は距離に反比例する」…かのようなデータを返してきました。王様のいる場所(升)によっても事情は違ってくるかも知れません。99(左下)の升にいる先手玉と、59の升にいる先手玉、91の升にいる先手玉。それぞれ寄せやすさが異なることは皆さんご存知ですよね。すなわち、王様のいる升によって、各升の利きの価値は異なるはずです。
しかし、玉がどこにいるかは問わず、そこを均してみると、おおよそ距離に反比例する、ということです。この記事で「均す」と書いているのは、紛らわしい表現かもしれませんが、それが「平均」の意味ではないことをここで注意しておきます。
optimizerは実際に対局させて勝率が最大化した値を返しますから、実際の対局で現れた局面において、パラメーターをその値にしたほうが強かった、ということにすぎません。決して、すべての玉の移動できうる升(9×9=81升)での利きの価値の平均ではありません。入玉模様の将棋は5%程度しか発生しないでしょうし(もっと強いプレイヤーならばもう少し発生します)、先手の玉が51にいる局面の存在確率は全体の0.1%あるかないかでしょうし。そういう意味では、入玉の時の利きの価値は、ここからはかけ離れているかも知れません。
なので、optimizerが返してきた答えが「(利きの価値が)距離に反比例する」かのように見えたとしても、それは一つの側面にすぎませんし、反比例の関係があるかのように見えたからと言って、本当に反比例の関係があるとは限らないのです。もっと複雑な現象が、ある方向から見た時に、たまたま反比例の関係のように見えているだけかも知れません。
だから、あなたは注意深く、現象を観察して、合理性のある解釈を行う必要があります。
ただ、どういう風に数理モデリングをしたとしても、「(玉の位置を均すと)利きの価値は玉からの距離に反比例する」ということを否定するモデルは(高い確率で)誤りだということです。この問題は、あとで何度も出てきます。
「利きの価値は玉からの距離に反比例する」ことに対する合理的な説明
いま、王様を利きで円形に包囲することを考えましょう。この円の中心は王様であり、半径はrとします。そうすると円周は2πrであり、rに比例した数だけ利きつきの升が必要になることがわかります。
半径が1であれば、円周は2πです。これが1升だとしましょう。
半径がrであれば、円周は2πrですから、これはr升だということになります。
すなわち、半径rの円で王様を包囲しようと思うと半径1のときのr倍の利きのある升が必要となり、その利きつきの升の価値は、半径1のときの1/rしかないことになります。
そんなわけで、利きの価値が王様からの距離に反比例するというのは、このように説明できなくはないです。
これが正しいとは限りませんが、そこそこ合理性があるので、このように現段階では理解しておきましょう。
今回のモデルに基づいて、評価関数を書いてみよう!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
// 王様からの距離に応じたある升の利きの価値。 int our_effect_value [9]; int their_effect_value[9]; void init() { for (int i = 0; i < 9; ++i) { // 利きには、王様からの距離に反比例する価値がある。(と現段階では考えられる) our_effect_value [i] = 68 * 1024 / (i + 1); their_effect_value[i] = 96 * 1024 / (i + 1); } } Value compute_eval(const Position& pos) { auto score = pos.state()->materialValue; for (auto sq : SQ) { // この升の先手の利きの数、後手の利きの数 int effects[2] = { pos.board_effect[BLACK].effect(sq) , pos.board_effect[WHITE].effect(sq) }; for (auto color : COLOR) { // color側の玉に対して auto king_sq = pos.king_square(color); // 筋と段でたくさん離れているほうの数をその距離とする。 int d = dist(sq, king_sq); int s1 = effects[ color] * our_effect_value [d] / 1024; int s2 = effects[~color] * their_effect_value[d] / 1024; // scoreは先手から見たスコアなので、colorが先手の時は、(s1-s2) をscoreに加算。colorが後手の時は、(s2-s1) を加算。 score += color == BLACK ? (s1 - s2) : (s2 - s1); } auto pc = pos.piece_on(sq); if (pc == NO_PIECE) continue; // 盤上の駒に対しては、その価値を1/10ほど減ずる。 auto piece_value = PieceValue[pc]; score -= piece_value * 104 / 1024; } return pos.side_to_move() == BLACK ? score : -score; } |
評価関数本体は21行です。(コメント行、空行を除く)
テーブル化について
さきほどのoptimizerから返ってきたテーブルをそのまま使ったほうが良いのでは?という意見もあるかと思います。
あるいは、「玉のいる升ごとに利きの価値が違うと思うなら、effect_value[king_sq(王様のいる升)][sq(利きのある升)]のような二次元テーブルを用意して、機械学習(強化学習等)をさせてしまえば良いのでは?」みたいな意見もあるかと思います。
まあそれはその通りなのですが、パラメーター数が増えると皆さんが自作するときにそれらのパラメーターを最適化するのが面倒でしょうから、ここではなるべくパラメーター数は控えめにいきます。(探索部によっても、最適なパラメーターの値は変わりますから、探索部に合わせてパラメーターを調整する必要はあります。)
ただ、「反比例する」ということは割り算が必要になるので、その部分は事前に計算して配列にその値を持っています。割り算には、乗算の30倍以上時間がかかるのが普通ですので、そこを事前に計算して配列に格納しておくのは、まあ常識的な対応かと…。
いずれにせよ、この連載では、将棋に潜む法則性を発見して、そして何故そのような法則性があるのかを考察して、それを元に評価関数をブラッシュアップしていくことで、従来にない性質の評価関数を生み出すことをその目的としています。
なので、巨大なテーブルを用意して機械学習(強化学習)でまとめてパラメーターのチューニング!みたいなことはしません。そうやってしまうと、パラメーターの意味がわからなくなってしまうからです。
これどのくらいの強さなの?
さきほどのコードの評価関数で、駒得のみの評価関数から+R200程度上がります。
「そんなに?」と驚いた方も多いでしょう。
しかし、こんなに上がるのは最初の最初だけです。あとはほとんど上がりません。むしろ度々下がります。😥
やねうら王の探索部 + 駒得のみの評価関数の場合、Ryzen Threadripper 3990XでR2300~2400程度なので、今回のコードをその評価関数に用いた場合、R2500~2600程度だと思います。プロ棋士にはまだまだ届きませんが、奨励会レベルではあるかと思います。
20行程度で書ける評価関数のなかでは最強クラスの評価関数と言えるかもしれません。
駒得評価関数の将棋ソフトよりは、断然ソフトの指し手が面白くなるので、評価関数をどう書くかお困りの将棋AIビギナーの方々は、上のコードを参考に書いてみてはいかがでしょうか。(丸コピペも可)
基準ソフトについて
この連載では駒得評価関数をベースに改造していますので、この元となったソフトと比較して(自己対局させて)、どれだけ強くなったかどうかを判定します。
ところが、この方法には問題がいくつかあります。
まず、駒得評価関数ベースのソフトは、持ち時間が倍になっても他のソフトのように(Elo ratingで)R200も上がりません。R2000を超えたあたりからは、持ち時間が倍になってもおそらくR50すら上がりません。序盤で作戦負けになって、全く勝てなくなるからです。
この連載で出てくる新しい評価関数ベースのソフトだと、持ち時間が2倍になるとR100増えるとしたら、持ち時間を2倍にするごとに駒得評価関数とはR50ずつ差が開いていくことになります。さすがに、持ち時間を2の32乗倍(42億倍)にして、「駒得評価関数の+R1750強くなった!」みたいなことは言わないにしても、持ち時間でそこまで棋力差が生じるのであれば計測用として適切でない意味があります。
そこでまあ、2スレ1秒で駒得評価関数と対局させた時のR差を計測してこの記事に書くことにします。2スレ2秒とか4秒とかですと、場合によっては倍以上のR差があるかも知れませんが、そういう事情なので仕方ないですね。
あと、R差が200以上になってきますと、正確なレーティング計測を行うために対局回数を増やさないといけなくて、計測が困難になってきます。また、将棋はわりと頓死するゲームなのでR200強いソフトにR200強いはずのソフトが、最初のソフトと比較すると+R300ぐらいしか強くなっていない(ように計測結果が出る)ことは多々あります。
そんなわけで、基準ソフトと+R100以上の差がついたら、その強くなったほうのソフトを新たな基準ソフトとして計測に用いていきます。
あと、駒得評価関数のエンジンは序盤がすこぶる弱いので、互角局面集などを用いて対局させてしまうと(すでに中盤に差し掛かっていたりするので)、あまり不利にならない意味はあります。かと言って、定跡なしに初手から対局させると同じような進行になって正確な棋力計測ができなくなります。そこで仕方なしにやねうら王の互角局面の24手目から開始しています。
これらの理由により、この連載で「この改良で+R50」とか書いてあるものを適用しても、元の駒得評価関数のエンジンに対して+R30ぐらいにしかならないことは考えられます。(その逆もありえます) まあ、そのへんは参考程度ということで御容赦願いたい次第です。
次回予告
次回は、「利きの価値は玉からの距離に反比例するって嘘やったん?」です。お楽しみに!
胸が高鳴る次回予告ぅ!
ところで駒の価値も利きの価値も固定値のようですけど、するとたとえば「いま桂馬の価値が高い」のような局面でも、桂馬の価値が上乗せされるわけではないということですよね。桂馬を取るべきと言う局面評価は、「探索」によって補正されていくということなのでしょうか。(以前記事にされていた、1手先も読まない大局観だけのAIは、桂馬を取ろうとしない?そんなわきゃない気もします)
この例に限らず、固定値が登場するたびに「それって動的に変えられないものだろうか?」と考えてしまいます。コストが高くなりすぎるんですかね。
桂馬の価値が高くなる局面は、例えば、桂馬で両取りがかかる局面ですが、桂馬を持っていなくとも両取りがかかる形自体は潜在的に悪い形と言えると思いますし、KPP型であれば、PPが桂馬で両取りのかかる形に対しては暗黙的にペナルティが含まれていると思いますよ。(将棋って、「将来、桂を入手されたときに嫌だからこの形は避ける」のように終盤を想定した駒組みを序盤でしますから…)
そうか、別の評価軸(KPP)でしかるべきペナルティが与えられれば、桂馬自体の価値を上下させずともよいわけですね。
あと、「1手先も読まない」というのはわたしの誤解で、「自分の次の指し手しか読まない」ということですかね。それであれば、「相手の二枚の金の位置と自分の駒台の桂」という三駒関係を積極的に選択できると。
コンピュータが振り飛車指さないのはやっぱり手損な側面が大きいんですかね
しかし、玉を相手の大駒から遠ざける
という考え方が振り飛車なら、
将来の戦地(飛び交う駒の利き)から
王を遠ざけるということは
別な側面から見ると
合理的な判断なのかもですね
そう考えると、振り飛車は
「駒の利きに最も重点を置いた戦法」
ということで、「捌きとは駒の利き」
ということなのか???
今回の評価関数では「将来の利き」までは見れてないですね…。探索するので、depth 20で読むなら、20手先あたりでの利きは(部分的に)見ていることにはなりますが、それで足りてるのか足りてないのか…。
的を得た質問や意見は出来ないけれど、
物凄く興味があることなので最後まで記事にしてほしいです。
いつも応援してます。
過去には欲しいものリストにあるものを贈りつけた事もあります。
頑張って下さい。
ありがとうございます > 欲しい物リスト
そういや、Qhapaqさんに私の欲しいものリストにあったキシリトールガム詰め合わせ×6セットをプレゼントしてもらったので、これを噛み噛みしながら記事を書いています。キシリトールガム駆動ライティングですね。← なんじゃそりゃ~。
角さんかな?
デフォルトで1手で最低√2、マンハッタンで2も進める角さんが当てはまらないのかな?
この龍の紋章が目に入らぬか(チャ〜チャラララ〜)とか言いながらひっくり返る角さんが。
筋の差と段の差の大きいほうを距離と定義しているので角さんの移動も1ずつなのだ。
>駒得評価関数のエンジンは序盤がすこぶる弱いので~(中略)~同じような進行になって正確な棋力計測ができなくなります。
駒得評価関数は、定跡を使わないと初手から16歩~15歩~17香~18飛とかやって一気に大損してしまうので、なかなかピンとくる公平そうな対局条件が難しいイメージがあります。
そうですねー。