Hatena::ブログ(Diary)

Bonanzaソース完全解析ブログ

2011-12-18

USIプロトコルへの様々な疑問にお答えします

USIプロトコルへの様々な疑問にお答えしますを含むブックマーク USIプロトコルへの様々な疑問にお答えしますのブックマークコメント

USIに文句を言うだけ言う

http://d.hatena.ne.jp/merom686/20111217


まあ、USIプロトコルがいろいろアレであることは、いまさら言うまでもないことなのですが、なぜこんなプロトコルがコンピューター将棋の標準になっているかと言うと、将棋所という素晴らしいUIが採用したからに他なりません。


その将棋所の作者はコンピューター将棋を自らは作っておらず、何が必要かあまりよくわかっていないのだと私は理解しています。


まず、将棋所のサンプルについているLesserKaiのソースもかなりでたらめなコードです。例えば、将棋所のサンプルのLesserKaiのソースで、時間をparseしているコードは次の部分です。

void USIUtil::ParseAllTimes(const char *buf, int SorE)
{
	string goStr = buf;
	if (goStr.find("infinite") != string::npos) {
		isInfinite = true;
	} else {
		isInfinite = false;
		int pos = goStr.find(" btime ") + 7; // 7 = strlen(" btime ")
		int senteTime = ParseTime(buf + pos);
		pos = goStr.find(" wtime ") + 7; // 7 = strlen(" wtime ")
		int goteTime = ParseTime(buf + pos);
		remainTime = SorE == SELF ? senteTime : goteTime;
		pos = goStr.find(" byoyomi ") + 9; // 9 = strlen(" byoyomi ")
		byoyomiTime = ParseTime(buf + pos);
	}
}

驚くべきことに"byoyomi"という文字列が必ず書かれていることを期待しており、"byoyomi"という文字列が書かれていない場合のことは全く考えてもありません。"byoyomi"という文字列がない場合、findは-1を返し、-1 + 9でpos == 8となり、なんと"btime"の直後の数字を"byoyomi"の数値として解釈してしまいます。


まるで、「"byoyomi"は必ず書くものであり、書かなかったときの動作はどうなっても知らんよ」と言いたげです。


将棋所もこれと同じぐらいひどいコードが書かれていることは想像に難くなく、思考エンジン側から少し変な文字列を送るだけで将棋所がクラッシュするのは、このようなエラーチェックの甘さに起因するものでしょう。


解釈できない文字列が来たら、警告を出した上でデバッグ用のログには書き出すだとか、そういう配慮がないので、思考エンジンのデバッグ時に非常に使いにくいです。将棋所はユーザーフレンドリーではありますが、思考エンジンの開発者フレンドリーでは決してありません。


自分で思考エンジンを書いている人ならば、こんなひどい仕様にはしないと思うのですが。


さて、以下では冒頭で挙げたmerom686さんのUSIプロトコルへの不満に対して私の理解を書いておきます。

ponder(先読み)は、それ自体をUSIの仕様から削除すべきだと思う。

予測読みのやり方は、エンジン作者が考えるべき問題だ。

そのやり方を1種類に決めてGUIがサポートしてはならないと思う。

これは使いやすさ以前の問題だ。

ponderの仕様が非常にまずいことは私が以前書きました。( http://d.hatena.ne.jp/LS3600/20111029 ) 結論としては、ponderを使うなってことです。USIプロトコルから削除すべきは私もそう思います。

setoptionで、エンジン側で設定を保存する必要があるのがおかしい。

ファイルに設定を保存するというのは、それだけで相当な手間である。

USI_PonderとUSI_Hashだけ保存しなくていいというのも一貫性がなく面倒だ。

そもそもハッシュサイズを1MB単位で指定するという仕様も変だ。

1MB単位ではなく1byte単位で指定できるようになっているべきということですかね?

単位がMBになっているのは文字数を少しでも減らす、みたいな意図はあるのかも知れません。

1MB単位ではなく2**N byteのNを指定させろという意味でしたら、それは私は反対です。置換表は、通常探索用・詰将棋探索用など複数の置換表を持つことがあり、それぞれを適当な配分で確保したいことがあるので2**Nしか指定できないと困ります。

position

毎回全ての指し手を書く必要があるのは糞。

N手の将棋でやり取りされるpositionのデータ量がO(N^2)になる時点でキモい。

これはまあ、そうですね。入玉模様で1000手ぐらいの将棋ですと特に…。

USIでは、棋譜をSFENという方式で表す。

これは、USIがUCIチェス用のプロトコル)を元にしているので仕方ないのだが、

日本人は8hがどの升を表すかわからないし、パーサを書くのもプチめんどくさい。

飛車がRookのrで表されるのも、チェスの知識を要求される。

Bonanzaソースコード上で飛車はRookとなってて、Bonanzaソースコードに慣れ親しんでいるとそのへんはどうってことはないですが、升の表記がわかりにくいのは同感です。思考エンジンをデバッグしているときに、すごく困ります。

merom686 「対局ごとに必要な初期化」はgameover直後にするのがいいと思うのです。最初のisreadyでは「起動時」「対局ごと」両方やるとして。

http://d.hatena.ne.jp/merom686/20111217/1324123889#c

対局ごとに必要な初期化はisreadyでやって全然構いませんけどね。isreadyに数時間かかろうと問題ないようなので。isreadyは対局のたびに送られてきますし、連続対戦であればgameoverのあと即座にisreadyが送られてきますので。

go infinite

次にstopが送られてくる前にbestmoveを返してはいけません。

将棋所:USIプロトコルとは

細かいところだけど、不安になるエンジン作者がいたらいけないので。

勝ちを読み切った場合などは当然bestmoveを返していいはず。

返してはいけません。

例えば、次の問題です。(将棋所付属のLesserKaiのソースコードのコメントより抜粋。)

// 人間対エンジンで、エンジンが予想した手を人間が指すとponderhitが送られ、

// その後、まだエンジンが継続して思考している時に「すぐ指させる」ボタンを押すと、

// stopが送られてくる。このように、ponderhitとstopが続けて送られてきた場合、

// ここでは何も返さず、その次のstopに入ったところで手を返す。

実は、上記のコメントに書かれているようなことはうまく実装できません。

ponderhitが起きたときに、ponderのときに指定されていた思考時間設定のまま思考を継続しているわけで、ちょうど思考が終了してbestmoveを返す瞬間にstopが送られてきたからと言って、「何も返さず」というのが無理な話で、「もう返してしまっている」こともありますし、次のstopでもbestmoveを返すのですが、それがそのstopに呼応するbestmoveを返していることにはならないケースがあります。

たとえば次のようなシーケンスを考えてみましょう。

ホスト側     思考エンジン側

go ponder

ponderhit

※ 思考エンジン側はponderhitして、思考が終了したのでbestmoveを返す

       bestmove XXXX

stop

※ ホスト側には思考エンジン側が返したbestmove XXXXが、stopに呼応したもののように見える。

position XXXX

go

       bestmove XXXX

※ 思考エンジン側はstopコマンドが来たようなのでbestmoveを返すが、これが、ホスト側からすればgoコマンドに対する応手のように見える。

とまあ、bestmoveを返して良いコマンド(ponderhit,stop)を思考エンジン側からの応答を待たずにホスト側が2つ続けて送信してしまうのは、プロトコル上の欠陥であり、シーケンス的に見て破綻しています。

要するにponderhitに続けてstopを送ることがあるというのは仕様上の欠陥だと私は思います。


同様の理由により、"go infinite"で「次にstopが送られてくる前にbestmoveを返して」しまいますと、bestmove返すのと同時にstopが送られてくるとホスト側にはstopに呼応するbestmoveのように見え、次の局面図を思考エンジン側に送信するかも知れないですが、思考エンジン側はそのあとstopコマンドを解釈し、bestmoveを再度返しまして、これがホスト側からすると次の局面図に対するbestmoveのように見えます。

ゆえに、いかなるときであっても「次にstopが送られてくる前にbestmoveを返して」は、いけません。


usinewgame

このコマンドの意味するところがよくわからない。

異なる対局を始めるからハッシュをクリアしたほうがいいとか、

対局が始まったからsetoptionなどのコマンドを受け付ける義務がなくなったとか、

いくつか考えられるが、仕様に明示されていないと使えない。

ぶっちゃけ、なくてもいいコマンドなので、その辺りの説明は欲しい。


対局中に(思考中に)setoptionで置換表サイズを変更されても困りますし、ponder有りから無しに変更されても困ります。対局中に、それらのコマンドが来ないことを保証するために、対局中と非対局中とでは別の状態(決定性有限オートマトン的な意味で)でなくてはなりません。usinewgame〜gameoverはそういう意図だと思います。このへんはまあよく考えてあるなぁとは思うのですが。USIのドキュメントに有限オートマトンで状態遷移図を書いて、この仕様についてもう少し明確にしてあったほうがいいような気は私もします。


以上、ざっとUSIプロトコルについての疑問に答えてみました。思考エンジンを実装する人の参考になれば幸いです。

merom686merom686 2011/12/18 13:31 > ハッシュサイズ
「1MB単位か2**N byte単位かそれ以外かはGUIではなくエンジン作者が決めるべき」という意図です。
私が2**N byte以外のケースをよく知らないため、あのような表現になってしまいました。

> isreadyは対局のたびに送られてきますし、
なるほど。同意です。
しつこいようですが、私が言いたかったのはそれならそれで仕様に明記してほしいということです。

> ※ ホスト側には思考エンジン側が返したbestmove XXXXが、stopに呼応したもののように見える。
これは理解できますが、
goより前に来た(この順序は保証できますよね?)stopに反応するのはエンジンが悪いと思います。
また、この問題はgo infiniteに限らずgo btime 0 wtime 0 byoyomi 1000でも起こるのではありませんか?

私の意見は、「go infiniteで、stopが送られてくる前にbestmoveを返してよい」という仕様がよく、
それで問題が起こるようならgo infinite以外の仕様を変えることで対応するべき、というものです。
理由は、そうしないと時間制限なしの検討モードで困るということと、
他のgo ***ではいつbestmoveを返してもいいのにgo infiniteだけ違うのは変だと思うからです。

> usinewgame
なるほど、よくわかりました。

LS3600LS3600 2011/12/18 17:29 > 「go infiniteで、stopが送られてくる前にbestmoveを返してよい」という仕様がよく、

ああ、意味がわかりました。もしUSIプロトコルを作りなおすならばという仮定の話であるなら、それには私は賛成です。

「goに対してbestmoveは一回しか返さない。(stopが何回送られてこようが、goに対して1回bestmoveを返せば、思考エンジン側はそれ以上bestmoveを返してはならない。)」というのを守って、かつ、ホスト側もそのように解釈(思考エンジン側からbestmoveが来るまで思考エンジンに次のgoを決して送らない)」するなら問題はないのです。

USIプロトコルの仕様としてこのことが明記されていないので、将棋所はたぶんそういう実装にはなっていない気がしますし、LesserKaiのソースもコメントを読む限りそういうつもりで書かれていないように見えますし、他のUSI対応のホストもそういう仕様にはなっていない気がします。

ゆえにstopが来る前には勝手にbestmoveは返さないほうが無難というのが私の認識です。

LS3600LS3600 2011/12/18 17:37 > 私が2**N byte以外のケースをよく知らないため、

余談ですが、置換表は2**N byteでないことのほうが多いです。なぜなら、たとえばBonanzaであれば16byte*3 = 48byteが1エントリーであり、このエントリーへのindexが2**Nになるようにするため(index = hash & 0xffffff;のように書きたいため)、置換表自体のサイズは2**N byteにはなりません。

ゆえに指定された置換表サイズに収まるようなNの値を計算するようなコードが必要になります。

あとはまあ、通常探索用の置換表とdfpnの置換表は確保するサイズを4:1にしようかだとかまあ何かそういうことをするので、結局ユーザーは「1GBに収まる範囲で自由に使って」というようにアバウトに上限を指定し、思考エンジン側は、そのサイズに収まるように最大サイズの置換表を確保するような動作になるのではないでしょうか。

merom686merom686 2011/12/18 18:47 > go infinite
簡単に勝ちを読み切れる局面をLesserkaiでgo infiniteさせてみました。
すぐに読み切りましたが、将棋所には思考が完了したことを知る術がありません。
(ユーザーはCPU使用率や読み筋の様子でわかるので中断ボタンを押しますけど)
これがUSIの仕様に沿った動作であることはわかりました。
「stopが来る前には勝手にbestmoveは返さないほうが無難」な理由もわかりました。

では、このような仕様になっている理由をどう考えているのですか?
このエントリで言及されている問題は、制限時間付きのgoでも同様に起こるので、
理由になっていないと思います。
「理由はないけど仕様がそうなっている」というのならそれで構わないのですが。

(というか、私は「制限なし」の代わりに「制限あり99時間」を使えばいいのですね…)

> このエントリーへのindexが2**Nになるようにするため
M*(2**N)という意味で誤って2**Nと書いてました。すみません。
他、置換表についてはとても参考になりました。

LS3600LS3600 2011/12/18 21:00 > では、このような仕様になっている理由をどう考えているのですか?

合理的な理由は、私には思いつかないです。

USIプロトコルの原案を考えた人(Tord Romstad氏)は、Glaurungというコンピューターチェスを作っていまして、たぶん、氏は、goコマンドに対してはbestmoveは1回 *のみ* 返すということを暗黙の了解のものとにUSIプロトコル原案を書き、そして実装しているのではないかと私は思います。[要検証]

USIプロトコルを将棋用にリメイクした人(将棋所の作者)が、コンピューター将棋の思考エンジンを自分で書いていないため、この部分に対する理解が欠落しており、将棋所では残念な実装になっているのではないかと私は想像します。

まあ以上は私の勝手な想像で、もしかしたら、Glaurungのほうも、同様に残念な実装になっているのかも知れません。

ともかく、将棋用のUSIプロトコル対応のホストプログラムとして、このへんは将棋所 & LesserKaiを規範とするしかなく、お手軽に思考エンジンを開発したいコンピューター将棋開発者には、USIプロトコル対応が必須課題でして、まあ、コンピューター将棋の開発者は仕方なしに使っているのが実情だと思います。

こんなしょーもないプロトコル、将棋所のようなデファクトスタンダードなUIが対応しているのでなければ使いたくもないというのが私の本音です。かと言ってCSAプロトコルのほうのダサさ(時代遅れ加減)は、USIプロトコルの比ではありませんので、CSAプロトコルを使うという選択肢はいまさら無いでしょうね。

思考エンジンを作っている将棋に造詣の深い人が、もっとマシなプロトコルを策定して、open sourceで将棋所レベルのUIを作ってくれるといいのですが…。(ちらちらとmeron686さんのほうを見ながら)

merom686merom686 2011/12/18 21:38 詳しい説明ありがとうございます。納得できました。
ところで、どうでもいいことですが私の名前をtypoしていませんかw(CPUのMeromです)

> open sourceで将棋所レベルのUIを
私もコンピュータ将棋には思い入れがあるので、当然ながらやりたいと思っています。
が、色々作ってみたことや、今回のやり取りを通しても、力不足を痛感しています。
まあそりゃそうなんですけどね。とりあえずUSIのGUIを作ろうとしているところです。

LS3600LS3600 2011/12/18 22:29 > ところで、どうでもいいことですが私の名前をtypoしていませんかw(CPUのMeromです)

はい、すみません。(´・ω・`) さきほど気づいて本文のほうは修正させていただきました。

> とりあえずUSIのGUIを作ろうとしているところです。

USIにも対応させて、かつ、拡張USI by merom686 にも対応させるのがよろしいかと。

私の将棋所での大きな不満点は、分岐のある読み筋を表示できないことや、読み筋のpass(null move pruningならPVがpassになることもしばしば)を表示する手段が無いこと、1手前の読み筋をUI上で表示できないこと、「X手の詰めろ+読み筋」のようなものを表示する機能がないこと、連続対戦のときに個別に持ち時間を設定できないことなどです。

だんだん作りたくな〜る。作りたくな〜る。(催眠術)

merom686merom686 2011/12/18 22:47 作りたくはなってますよ〜。またいつかアドバイスをいただけたらと思います。

将棋所の作者将棋所の作者 2011/12/21 19:56 ちょっと遅れましたがコメントします。

>まるで、「"byoyomi"は必ず書くものであり、書かなかったときの動作はどうなっても知らんよ」と言いたげです。

Lesserkaiは将棋所で使うことを前提にしているからそう書いただけのことですが、それがそんなに問題でしょうか?

>将棋所もこれと同じぐらいひどいコードが書かれていることは想像に難くなく、思考エンジン側から少し変な文字列を送るだけで将棋所がクラッシュするのは、このようなエラーチェックの甘さに起因するものでしょう

Lesserkaiはサンプルなのでかなり適当に書いていますが、将棋所では受信した文字列のチェックはずっと厳しく行っています。それに、USIと関係ない文字列が送られてきたら無視する(デバッグウィンドウには表示しますが)だけです。
もしかして、日本語を送っていませんか?

LS3600LS3600 2011/12/21 22:05 > Lesserkaiは将棋所で使うことを前提にしているからそう書いただけのことですが、それがそんなに問題でしょうか?

私は問題だと思います。

例えば、第三者がUSIプロトコル準拠のホストプログラムを書こうとしたときに、そのホストプログラムで"byoyomi"を送らなければLesserkaiが誤動作します。

すなわち、この意味においてLesserkaiは純粋なUSIプロトコル対応のプログラムとは言えないのですが、かと言って将棋所専用の思考エンジンということもなく、一応USI対応のソースコードに見えます。

現状USIプロトコルの準拠の将棋ホストプログラムは将棋所とLaramieぐらいしか実在せず、Laramieは完成度において将棋所に劣りますから、実質的に将棋所が唯一のUSIプロトコル対応のホストプログラムは存在しないような状況です。

その将棋所に標準でバンドルされていれば、これがUSI対応の思考エンジンのサンプルだと思うのは当然ではないでしょうか。その規範にすべきソースコードで"byoyomi"が絶対に付与されていることを前提とした書きかたがされていれば、USIプロトコルでは"byoyomi"は絶対に付与されているのだと理解する人が出てきてもおかしくはありませんし、また、Lesserkaiのソースコードをコピペして自分の思考エンジンをUSI対応にしようとしたときこのままのコードでは「USIプロトコル対応」を謳うのはまずいです。

普通、他人のソースコードをコピペしてプログラムを書くことはあまりないかも知れませんが、Lesserkai自体は使用許諾条件は「れさぴょん」に準じるわけで、「れさぴょん」は、少しでもソースコードを改変する限りは自由に使って良いという使用許諾条件になっていますから、このLesserkaiのソースコードをそのまま自分の思考エンジンに組み込むことは十分に考えられるシナリオだと思うのです。

そうやって組み込んだときに、他のUSIプロトコル対応のホストプログラムでは正常に動作しないということは十分ありうるのです。

そういう意味では、Lesserkaiが純粋なUSIプロトコル対応になっていない部分については、問題だと思いますし、そういう情報を誰かがブログ記事か何かとしてまとめてあるといいのになぁとは思います。

LS3600LS3600 2011/12/21 22:05 > Lesserkaiはサンプルなのでかなり適当に書いていますが、将棋所では受信した文字列のチェックはずっと厳しく行っています。それに、USIと関係ない文字列が送られてきたら無視する(デバッグウィンドウには表示しますが)だけです。
もしかして、日本語を送っていませんか?

送っています!大威張りで言うことではないとは思いますが、送っています!

まず、思考エンジンのデバッグで一番表示したいものは何かというのを製作者サイドに立って考えてみていただきたいのですが、デバッグのときに一番表示させたいものは次の二つです。

・盤面
・指し手

これ以外にも表示させたいものはそのときそのときでいろいろあるでしょうけども、基本的にはこの2つは頻繁に表示させたいわけです。そのためにデバッグのときに標準出力に盤面や指し手を日本語(漢字)を用いて表示させている開発者は多いはずです。

sfenやCSAを出力するといったんコピペして別のソフトで確認する必要があるのでダイレクトに視覚的に確認しようと思うと、「7六歩」のような日本語になります。このような意味で日本語を直接表示したいわけです。

ともあれ、将棋所の実装として
・日本語を送るとクラッシュする
・2バイト文字列以外は思考エンジンからの文字列を厳しくチェックしている。
・思考エンジンからの関係のない文字列はデバッグウィンドウには表示している。
の3点の情報がわかっただけでも一人の開発者として、とてもありがたいです。(これはとても貴重な情報なので、将棋所の思考エンジンの開発者向けの情報として将棋所のページで公開するに値すると思います。)

本当は将棋所に日本語文字列が送れて、送られてくる日本語文字列のコードセットをsjis,euc,utf-8,utf-16あたりから選択できるととても良いのですが…。

将棋所の作者将棋所の作者 2011/12/21 23:12 >実質的に将棋所が唯一のUSIプロトコル対応のホストプログラムは存在しないような状況です。

であれば、他にGUIが存在しないのですから、何の問題もないと思います。
だいたいサンプルなんていうのは大枠を示すものであって、そんな細かいことまで問題にしても仕方ないでしょう。れさぴょんだって細かい問題点はたくさんあるでしょうが、そんなことより、あれが存在したからプログラムを作れたという人はたくさんいるわけです。Lesserkaiもそれと同様に、USIに対応するための参考として公開しているだけで、もし問題だと思うなら自分で修正して下さい、という程度のことしか言えません。

>もしかして、日本語を送っていませんか?

>送っています!大威張りで言うことではないとは思いますが、送っています!

そうでしたか。といっても、日本語を送ると必ず落ちるというわけではないので、私もこの条件を見つけ出すのにちょっと時間がかかりました。まず、単に

printf("日本語\n");

というように書くと落ちる可能性が高いです。この場合、将棋所が文字列を受信する前の段階で落ちてしまうので、こちらではどうにもなりません。回避方法としては、

printf("debug 日本語\n");

のように、半角英数字のあとに日本語を書くか、もしくは

printf("\n");
printf("日本語\n");

のように、一度改行を入れてから日本語を書くといいようです。あとで「エンジン作成時の注意点」に追加しておきます。

あと、ついでですが、

>その将棋所の作者はコンピューター将棋を自らは作っておらず、何が必要かあまりよくわかっていないのだと私は理解しています。

これは事実ですので否定しませんが、

>こんなしょーもないプロトコル、将棋所のようなデファクトスタンダードなUIが対応しているのでなければ使いたくもないというのが私の本音です。

>思考エンジンを作っている将棋に造詣の深い人が、もっとマシなプロトコルを策定して、open sourceで将棋所レベルのUIを作ってくれるといいのですが…。

であれば、どうしてやねうらおさん自身がプロトコルを設計しないのか不思議に思います。コンピュータ将棋にすごく造詣の深い方ですし、USIプロトコルの問題点も具体的に指摘しているわけですから、完璧なプロトコルを設計できるように思います。そうすれば、そのプロトコルを元に、誰かが素晴らしいGUIを作ってくれるかもしれません。期待しています。

LS3600LS3600 2011/12/22 00:16 >>実質的に将棋所が唯一のUSIプロトコル対応のホストプログラムは存在しないような状況です。
> であれば、他にGUIが存在しないのですから、何の問題もないと思います。

今日のコメント欄のmerom686さんのように、USIプロトコル対応のホストプログラムをこれから作ろうという人は何人かいるわけで、そういう人が余計なところで躓かないようにするための配慮が必要だと私は思います。

> だいたいサンプルなんていうのは大枠を示すものであって、そんな細かいことまで問題にしても仕方ないでしょう。

Lesserkaiに関してはコピペ自由なソースコードなので、このソースコードをコピペして自分の思考エンジンをUSIプロトコル対応にする開発者は少なくないはずです。(今後も含めて考えますとそういう開発者は増え続けるでしょう。) この意味において、単に思考エンジンのサンプルである「れさぴょん」とは意味合いが全く違います。

彼ら思考エンジンの開発者の時間を損失させないためにも、USIプロトコルに準拠した適切なソースコードを提供してあるほうがいいに越したことはありませんし、LesserkaiのソースでUSI対応に関して問題のある箇所があるなら、どこかのブログ記事か何かとして情報がまとまって存在してあるべきだと私は思います。

私は一人の開発者として、サンプルのソースコードがいい加減であるために、それを修正するための時間を取られるのは非常に腹立たしいですし、そして、他の開発者の時間も同様に、無駄に損失させてはならないという考えを持っています。

将棋所も、USIプロトコルも思考エンジンの開発者の手助け(?)をするために生み出されたものだと私は理解していますが(そして将棋所というソフトウェアによって思考エンジン開発の時間はずいぶん短縮化されていますが)、しかし、それならコピペされるであろうサンプルのソースコード(Lesserkai)によって、他の開発者の時間を損失させるのがいけないことだと何故わかってもらえないのでしょうか。そのへんが不思議でなりません。

LS3600LS3600 2011/12/22 00:16 > 一度改行を入れてから日本語を書くといいようです。あとで「エンジン作成時の注意点」に追加しておきます。

これについては、素晴らしい情報ですね。本当に助かります。
将棋所のデバッグウィンドウに日本語表示が出来れば、開発効率が格段に上がります。

> プロトコルを設計しないのか不思議に思います。

まず、全くの独自プロトコルを設計する意味はあまりないと思います。

少なくとも、私もUSIプロトコル対応のためにsfenの読み書きするサブルーチンはすでに書いており、USIプロトコルをベースにしたほうがそれらのサブルーチンを使いまわせるので少ないステップ数で実装できるからです。

USIプロトコル自体を改良したい点はさまざまありますし、実際、私はクラスター化のためにUSIプロトコルをいくつか拡張したりはしています。これはGPS将棋やボンクラーズでも事情は同じだと思います。実はUSIプロトコルそのままですといくつかの点において、クラスター化のときに困るのです。

GPS将棋の金子さんが以下のようなtweetをされており、この2つ目が私が言っているのと同じ意図だと思われます。

https://twitter.com/#!/tkaneko/status/148609913449488384
> tkaneko @takodori USIの登場は開発者に選択肢が増えたというメリットが大きいと考えています。後は、バージョン記号をつけて改定してゆけると良いと思うのですが、現状は私家版の改訂案ばかりというのがきっと不便の一因ですね。

https://twitter.com/#!/tkaneko/status/148612136602578944
> tkaneko usiを改定するなら優先度の高い作業の一つは、positionやbestmoveにidをつけるオプションを用意して、対応関係を明確にできるようにすることかな。(囲碁のgtpにはある)

https://twitter.com/#!/tkaneko/status/148760227913793536
> tkaneko @takodori 知る限りでは合意形成努力は特に成されていないと思います。 (ponderが実状と合わない等は多数派だと思いますが、次の具体案としては)


それで私がコンピューター将棋に新しいプロトコルを策定しても、そのプロトコルに思考エンジンを対応させるための負担がそれぞれ思考エンジンの開発者にのしかかってくるのであれば、他の思考エンジンの開発者の足を引っ張っているだけになってしまいます。そういうのはよろしくないというのが私の考えなので、結局のところ、

1) USIプロトコルを思考エンジンの開発者間で合意形成努力をした上で拡張する
2) その拡張したUSIプロトコルに対応する将棋所レベルのホストプログラムを実装する

というのがベストだと私は考えています。1)だけやっても2)がなくてはどうにもなりませんので、私は2)をやってくれる人が現れるのを待っています。(merom686さんとか、aki.さんだとか..)

将棋所がオープンソースで公開されていれば、私が何らかの方法で1)で合意を形成した上で、その拡張仕様をそのオープンソースになっている将棋所のソースコードに手を加えてコミットすることはお約束できるのですが、将棋所をオープンソースにする予定はありませんよね?

それで、まあ、結局、私が自分で将棋所みたいなソフトウェアを作るかって言う話にはなるのですが、その時間があるなら思考エンジンの開発に少しでも時間を費やしたいんですよね。それで、オープンソースでUSIプロトコル対応のホストプログラムを作ってくれる救世主が現れるのを待っているような状況なのです。


どうか救世主が現れますように。サンタさんがクリスマスプレゼントを持って現れますように。サンタ様と天の神様にお祈り申し上げます。

トラックバック - http://d.hatena.ne.jp/LS3600/20111218

2011-12-15

指し手オーダリングのための高速ソート手法の提案(下書き)

指し手オーダリングのための高速ソート手法の提案(下書き)を含むブックマーク 指し手オーダリングのための高速ソート手法の提案(下書き)のブックマークコメント


めちゃんこすんごいろんぶんのしたがき

著者 : LS3600 & ひよこ


■ 概要

指し手のオーダリングを行なうために、生成した指し手に対して何らかの手法で点数をつけて、その点数によってソートしなければならない。これをオーダリングと呼ぶ。

本提案は、このオーダリングのときのソートの高速化のための手法を提案するものである。


■ Bonanzaでのオーダリング

Bonanzaでは、gen_next_moveがco-routineになっていて、この関数で指し手を逐次生成するが、駒を捕獲する指し手のあとはhistoryの指し手を生成するフェーズとなっている。

historyとは、指し手の移動元、移動先、駒種、手番の情報を元にうんぬんかんぬん(中略)

Bonanzaではhistoryの指し手はスコアの上位2手しか生成せず、残りの指し手は何のオーダリングもされていない。これは、たいていのノードではhistory2までにbeta cutが生じるし、そこまでにbeta cutが生じなければall node(すべての指し手がalpha値を下回るノード)であることが多いので、オーダリングのためにソートするコストに見合わないとBonanzaの作者が判断したためだと思われる。

しかし著者らの実験によれば、ここでstd::sortをしても自己対戦での勝率はほぼ変わらない。すなわち、オーダリングのためのソートがもっと高速に出来るならば導入する価値はあると言える。

そして、ここで指し手が適切にオーダリングされているなら、それを利用した枝刈り(そのノードであとのほうの指し手ほどreductionの幅を大きくするなど)がしやすくなる。

つまり、ここでソートをなるべく追加コストの少ない形で実現できればうんぬんかんぬん(中略)

■ ソートの要件

ここでは完全にソートされている必要はないと言う点に着眼する。そもそもhistoryの値自体がそこまで信頼できる値ではないので、全体的にそこそこ妥当な並びになっていれば良く、厳密な大小関係を守らなくとも良い。[要検証データ]

ここでのスコアBonanzaに倣い、その指し手の試行回数tried , その指し手がbestであった回数 good (もう少し詳しく書くべき)とを使って次のように決定する。

 score = (good + 1) / (tried + 2)

このscoreの順に指し手をソートしたい。

そこで、高速にソートする手法としてbin sortをすることを考える。

■ bin sortとは

bin sortとは(中略。ググレカス)

いまscoreを0〜65535の範囲にスケーリングするとして、bin sortを適用するには、このscoreからbinのindexへマップしなければならない。

これは、たとえば、次のようにして端数を切り捨ててしまう方法が考えられる。

index = score / 1024;

しかし、この方法だと、小さなスコアばかりのときに適切にオーダリングされない。

そこでscoreの対数をとる方法が考えられる。2を対数の底にするならば、これはbsr(BitScanReverse)が使える。bsrとは(中略。ググレカス)

index = bsr(score);

(中略)

■ 割り算をやめる

割り算は一般的にlatencyの大きな命令で、scoreの計算のときにこれを無くす方法を考える。

(中略)

ゆえに、

index = bsr(good + 1) - bsr( tried + 2 ) + 16;

のように引き算をすることにより、うんぬんかんぬん(中略)

この場合、good,triedが0〜65535の範囲であれば、indexのとりうる値の範囲は、うんぬんかんぬん(中略)


■ 具体的な効果

Bonanza 6をこのbin sortを用いてhistoryの指し手1,2以外もすべてソートするように変更し、また、そのノードで生成された指し手の順番(中略)reduction深さを(中略)したところ、自己対戦では勝率X割になった。これはレーティングで言えばXXXに相当する。

ここに比較のための資料etc(略)

■ 結論

指し手オーダリングのためにソートするコストが見合うかどうかというのは、コンピューター将棋の探索部において非常に大きな課題であり、ソートすることにより可能になる枝刈りもあるが、ソートのコストが見合わないために導入をためらっていた開発者も多い。

しかし、今回、ソートするコストを限りなく小さく抑える手法を開発できたことは著者らの喜びであり、なんやかや(略)

お前らどんどん使ってたもれ(あとで別の言葉で書き換える)

issei_yissei_y 2011/12/15 14:02 なんという下書きのままw

LS3600LS3600 2011/12/15 19:50 isseiさんを含め、コンピューター将棋の開発者の方には意味は十分伝わっているはずなので、まあこの程度でいいのかなと。

トラックバック - http://d.hatena.ne.jp/LS3600/20111215

2011-12-05

置換表に経路に依存する局面のスコアを書き込まない

置換表に経路に依存する局面のスコアを書き込まないを含むブックマーク 置換表に経路に依存する局面のスコアを書き込まないのブックマークコメント

将棋では連続王手千日手王手をしている側が負けです。だからと言って連続王手のループを見つけた時点で攻め方の負けと判断したのではGHI問題を引き起します。

さて、いま、定跡を自動構築することを考えます。ここで言う定跡とはプロ棋士棋譜の何手目までという話ではなく、与えられた局面を通常探索で探索した結果をそのまま定跡として書きだすわけです。

これを定跡と呼ぶにはいささか精度の問題があるかも知れませんが、それでも事前に10分でも20分でもその局面についてじっくり考えられるわけですから、その結果をファイルに書きだしておけば、コンピューター側が実戦でその局面に遭遇したときにノータイム(1手1秒)で指せるわけで、これの効果がないと唱える人は居ないでしょう。(プロ棋士棋譜から構築する定跡DBは別途持っているものとします。)


ところが、このように局面の情報を書きだすときに経路(そこまでの手順)に依存する場合があります。例えば、その局面に至る前の手順を含めると連続王手千日手が成立してしまうので次の一手が指せないが、しかしその局面に至る前の手順さえなければ、この局面は攻め方の勝ちという場合です。

定跡ファイルに書き出すときに、経路に依存する情報は書き出すべきではなく、その局面自体の勝ち/負けを書き出したいのです。これはどうすればいいのでしょうか?

実は通常使用している置換表に関しても同じ問題があります。普通、置換表には経路に依存する局面のスコアも書きだしてしまっています。千日手で循環していたので、「この局面は互角」だとか書きだしてしまっています。実際は千日手で循環するのは特定の手順でその局面に到達したときだけです。要するに嘘の情報です。

こういう嘘の情報を書きだしてしまうと、置換表に書いてある評価値を信じていいのかという話になってきます。そこで、置換表の値はあまり信じずに、置換表に書きだしてある指し手だけを信じたほうが勝率が上がったりします。

例えばBonanzaでは、non PV node(α==β-1のnode)でしかhashcut(置換表の値を信じてそのノードの探索を打ち切ること)はしません。PV nodeではhashcutしません。

これは置換表の値が信用できないという問題以外の要因もあるのかも知れませんが、ここでは深く追求しないことにします。

ともかく、置換表にどうやればそれが千日手絡みのスコア(評価値)かどうかを書き出せるのかを考えてみます。

まず、α値を更新したときに、それが置換表絡みのスコアであれば、スコアの上位bitを使って「これは千日手絡みのスコアですよ」(本当のスコアは不明ですよ)とフラグを立てます。次にα値を更新したときにも、直前のα値のこのbitが立っていれば、さきほどの千日手絡みのスコアが、本当はもっといい値かも知れないので、この更新されたαも信用できる値ではないという理由で同じく、このbitを立てる必要があります。(細かい議論は割愛)

このようにしてあれば、千日手絡みのスコアが出てきた場合、それが上位ノードに伝播されていきます。

千日手絡みのスコアであれば、置換表に書き出すときに、スコアは書き出さずに指し手のみ書きだすだとか、まあ何かそういうことは出来ます。

ところがこうやって千日手絡みのスコアを置換表に書き出さないと千日手スコアに汚染されて、ほとんど置換表にスコアが書き出せない状況が起こりえます。こうなると探索効率は悪化します。

そこで、この方法はおそらくまずいのだと思います。(私はこの方法は実際に実装して試していないのでよくわかりませんが、おそらくあまり効率がよろしくないはずです)

よって、千日手によってスコアが汚染される範囲をもっと明確にすることを考えます。

例えば、次のように局面X,A,B,Cがあったとして、Aの局面へ循環している場合を考えてみます。

X→A→B→C→D→A

Dの局面からAへの指し手は千日手を引き起こす手です。3手前の局面と循環しているからです。ゆえに、ここでは「3手前」(-3)という値をDで得られた評価値の上位bitに格納しておくことにします。

Dの上位ノードであるCではスコアが伝播されてきたときに、ここをインクリメント(たとえば上位8bitを用いるならば0x01000000を加算)し、「2手前」(-2)になります。

Cの上位ノードであるBでは同様に「1手前」(-1)

Aでは同様に「0手前」(0)

Xでは同様にインクリメントして 0 になります。つまり、Xの局面ではスコアはこれ以前の手順に依存していないので、Xの局面のスコアとしてはこれは信頼できる値だということです。逆にBの局面でのスコアは1手前の局面と手順が循環しているのでこの値は信頼できる値ではなく、置換表および定跡DBにはスコアは書きだしてはならないと言えます。(Aの局面については正しいスコアだと私は思うのですが、自信はありません。)

循環しているかどうかの判定は、スコアのMSB(最上位ビット)を見れば一発でわかるという仕組みです。

また、この実装はGHI問題を引き起こしません。

定跡DBを自動構築するときにはこのような実装が必要で、また、実戦において通常探索した結果を定跡DBに書き出すことを考えているなら、千日手に依存する局面のスコアを書きだすわけにはいかないので、このように実装してなければなりません。(たぶん)

df-pnの詰将棋ルーチンにもこの方法が応用できるかどうかについてはいずれまた。

2011-11-07

「入玉指向の将棋プログラムの作成」の改善案

「入玉指向の将棋プログラムの作成」の改善案を含むブックマーク 「入玉指向の将棋プログラムの作成」の改善案のブックマークコメント

GPWSで発表された田中哲郎先生の「入玉指向の将棋プログラムの作成」について思うところがありまして、以下にだらだらと書いておきます。なお、本論文は以下のURLから読めるようです。

https://ipsj.ixsq.nii.ac.jp/ej/?action=pages_view_main&active_action=repository_view_main_item_detail&item_id=78250&item_no=1&page_id=13&block_id=8

元の論文のアイデアは、「王の入玉に関する機動性(?)を評価して評価関数に加点しよう」というものでして、玉は左上・真上・右上にのみ進めて、相手の利きおよび自駒がある場所には移動できず、入玉できる経路があるかどうかを調べるというものです。


入玉に関して、入玉を阻止している駒で評価するのではなく玉の入玉経路の有無を評価関数に反映させたところが元の論文の目新しいところで小さな計算量で済む、面白い特徴因子だと思います。


疑問点1) この入玉経路を調べるコストはゼロに近いのか?ゼロに近くなければ、滅多に出現しない入玉将棋のために評価関数の計算コストが増大するので勝率自体は下がるのではないか。

疑問点2) 入玉する場所がどこでもいいというのはおかしいのではないか。盤面の左上とか右上のような隅のほうがポイントが高くなっているべきではないのか。


まず疑問点1)を解消するために、以下ではこの経路探索の発見がすこぶる小さなコストで出来る擬似コードを示し、また疑問点2)を解消するために、疑問点1)で示した擬似コードに基づく改善案を提案します。


■ 入玉経路の探索の高速化

敵の利きは事前にmake_moveのときに利きを表現するBitboardに反映しているものとします。

いま、玉が7段目の中央(5筋)にいるとします。6段目のうち、玉のいける場所は次図のようになります。

□□□■■■□□□

■ = 行ける場所

□ = 行けない場所

これは、ビットパターンで言うと、

次段 = ( (前段<<1) | 前段 | (前段>>1) ) & 0x1ff ;

のようなパターンです。最後の1ffは、演算結果を9マス(9bit)に制限するためのマスクです。

この計算は、テーブルをlook upしても良いでしょう。こうなります。

次段 = next_table[前段];

// next_tableは512byte × sizeof u16 = 1MBのサイズ。

あとは簡単ですね。これをループで回して、入玉できるか見るだけです。

i = 1 << 現在の玉の筋(0..8);

foreach 現在の玉の次の段から入玉だとする段まで

{

i = next_table[i];

i = i & (move_to >> その段のビットパターンが得られるシフト回数 ) & 0x1ff;

if (i==0) break;

}

// iに入玉できる場所を示すビットパターンが得られた

これだけですね。極めて小さな計算量で入玉だとする段のどこの筋に到達できるのかまで調べることが出来ます。

また玉が最下段のときはこのような加点せず、2段目(8段目)より上にいるときにのみ評価関数に加点するならば、調べる段は3〜9段目のみですみますから、これは64bitに収まり、私が以前提案したRBBを用いると片側64bitの盤面を見るだけです。

ゆえに上の処理はBonanza6の評価関数の1〜2%程度のコストで済むのではないかと思います。


■ 入玉できる場合、どう加点するか


仮に最上段まで入玉できるとして、そのときに入玉できる場所によって点数は異なるべきです。

すなわち、さきほど求めた入玉できる場所を示すビットパターンをそのまま加点したいわけです。

score += 入玉評価テーブル[i];

これで済めば簡単この上ないのですが、問題は512通りきちんと棋譜から学習できるかということです。

しかしよく考えると

a) ■□□□□□□□□

b) ■□■□□□□□□

c) ■■■□□□□□□

d) ■□□■□□□□□

a)よりはb)のほうが優れていることは自明であり、a) < b)のような順序関係がなりたちます。同様に b) < c)ですが、b) と d)の関係についてはよくわかりません。隅のほうが評価値が高くなるべき!という原則からすれば4筋よりは3筋のほうが価値が高いはずで b) > d)でしょうか。

まあ、このような半順序関係を利用していけば、棋譜から学習させられなくはないのかも、と思います。(よくわかりません…)



続きは誰かお願いします…(←人任せ)

トラックバック - http://d.hatena.ne.jp/LS3600/20111107

2011-10-29

USIプロトコルのponderの仕様と稲庭対策のこと

USIプロトコルのponderの仕様と稲庭対策のことを含むブックマーク USIプロトコルのponderの仕様と稲庭対策のことのブックマークコメント

昨日の記事に対して将棋所の作者様から直々にコメントをいただいたのでお答えします。

相手の正確な残り時間がわからないということですが、そもそも何のために知りたいのでしょうか。相手の残り時間を知って、それをどういうふうに使うのか、少し考えてみましたが使い道がわかりませんでした。

将棋所の作者様は、非常にクレバーな、頭のいい方だと私は理解しています。将棋所は、必要ならば取り入れるし、自分が不要だと思うものは誰かがいくら要望したところで一切取り入れない。そういうポリシーだと理解しています。一流のソフトウェアというのはそのような頑な意志の存在なくして成立しません。

そこで、私のほうとしましても「いるから、いるのだ!!」ではなく、何故必要なのかについて、納得していただけるような、論理的な説明をしなくてはなりません。しかし、それは結構大変なことで、非常に長文となりますが、お許しいただきたく思います。


まず、コンピュータ将棋界には稲庭戦法という相手の時間切れを狙う戦法がウイルスのように流行しております。私は最初、単なるウケ狙いの戦法かと思ったのですが、Bonanza4にも通用しており、世界コンピュータ将棋選手権の決勝でもちらほらと見かけるようになりました。(例えば、今年の大会でのponanza vs 激指)

そこで大会で決勝で勝ち残るためには、すべてのコンピュータ将棋の開発者にはこの稲庭戦法への対策を余儀なくされているのが実情です。例えばBonanza6では、稲庭対策のコードが追加されております。普段はオフにしてあるのですが、大会出場用に(コンパイルオプションを変えて)ビルドすれば、稲庭対策を発動します。

このときの前提条件が、「20手を過ぎて相手が歩を一切進めていない」(iterate.cに記述)というものでして、そしてこの条件に当てはまったときには、稲庭用の特別な評価関数(evaluate.cのinaniwa_score)が呼び出されます。この評価関数で、中央に向かって跳ねて行った桂と、5筋の歩が進んでいくことに対して加点してあります。要するに5筋を突破しやすくするための対策ですね。

Bonanza6に対して稲庭対策のコードが入っていることについては、もしかしたら私がこのブログで書くのが初めてかも知れません。あまり話題にはなっていないと思います。しかしコンピュータ将棋開発者ならば、みんな稲庭対策には苦慮していて、たとえばBonanzaの稲庭対策の発動条件がわかっているのでわざと20手目まで自陣の歩を突かず、無駄に桂を跳ねさせて、この桂をタダ取りしてしまおうという「稲庭対策の対策」みたいなものも成立するかも知れません。


ともかく、そのように稲庭対策が決勝で勝ち残るコンピュータ将棋のための必須課題となっているのが現状です。このときに、(大会に将棋所を使って参加しているとして)相手の正確な残り時間を調べたいということは十分考えられます。

「相手が残り時間を消費しているなら稲庭戦法ではないな」だとか、「相手が自分より残り時間を残していないならば稲庭戦法で来られても大丈夫だな」だとか、そういう相手の持ち時間を考慮に入れた思考時間制御がしたいかも知れません。(このへんはそれぞれの開発者ごとに考え方が違うと思いますが、そういうことをしたい人もいるかも知れないという話です。)

そのときにサーバータイム(サーバー側の計測値)で相手が直前の指し手で何秒使ったのかというのを知りたいというのは十分に考えられます。


あと将棋における思考時間制御は非常に難しく、一言で言うと現在のノードが安定しているノード(取り合いなどが少なく、探索することによって評価値が変わりにくい)なら早めに思考を切り上げ、さもなくば思考時間を投入するというのが基本方針ではあるのですが、このときに相手の残り時間との正確な差がわかれば、「これくらい形勢が開いていれば、こちらのほうが思考時間も少ないことだし少しぐらい早く指して形勢互角ぐらいに戻ったとしても仕方ないだろう」のような判断が出来ます。そういうタイプの思考時間制御をしたい人がどれくらい開発者のなかに居るのかはわかりませんが、ともかく、そういう形の思考時間制御もアリです。(私はそういうタイプの思考時間制御をしようと思っています。)

これは、チェスクロックを用いて人間vs人間で将棋を指すのに慣れている人ならば、そういう感覚を理解してもらいやすいと思うのですが、相手のほうが時間を残している場合、終盤で逆転されかねないので、同じぐらいの棋力であれば、序盤はサクサク指したほうが得だというのはあります。(序盤でそこまで致命的には形勢を損ねにくいので)

同様に、コンピュータ将棋の思考時間制御においても、相手の持ち時間を見て、それをもとに自分の考慮時間を決めるという方式は結構理にかなっていると思います。

これらがサーバータイムでの相手の消費時間を知り合いという理由です。



以下、USIプロトコルについての要望


それはそうと、ひよこ将棋やSunfishの作者が書いている、「現局面からponderさせてもらえる機能」みたいなものは欲しいです。「bestmove XXX ponder YYY」のYYYを省略した場合、現局面を思考エンジンに送ってきてもらって、go ponderコマンドが送られてくると非常に良いのですが。(そのあと相手が指したときに、stopコマンドとその局面とgoコマンドが送られてくれば。)

将棋所の作者将棋所の作者 2011/10/29 18:58 説明ありがとうございます。稲庭対策でしたか。ただ、説明を読んでいて、やはり疑問点が出てきました。
まず、ponderhitが返ってきた場合、相手側の消費時間を知るためにはエンジンが自分で時間計測をしなければいけませんが、これは面倒でしょうか。「面倒だ」と言われたらそれまでなので、以下の文章は無視して下さい。

さて、それが面倒ではないとして、エンジン側で時間計測をするとします。この場合、指摘の通り、サーバで計測されている消費時間と食い違う可能性があります。しかし、いま問題になっている稲庭戦法を使うソフトが相手であれば、こちらの指し手に対してはノータイムで指し手を返してくるはずです。すると、「指し手の消費時間は最低1秒」というルールにより、サーバ側では1秒と計測されます。また、エンジン側の計測でも、go ponderのあとに即座にponderhitが送られてくるので、同様に1秒と計算できます。
つまり、相手が稲庭戦法を使っているのであれば、相手の消費時間はサーバ側とエンジン側のどちらで計測しても1秒になるので、食い違いというのは生じません。であれば「通信対局だとサーバー側の時間を返してもらわないとローカルで計測してもズレてきちゃうのですが」という心配は不要だと思うのですが、いかがでしょうか。

次に、相手の残り時間に応じてこちら側の消費時間を変えたいという場合について。エンジンの先読み中に相手が長考してからponderhitが返ってくると、サーバ側とエンジン側での計測に食い違いが出る可能性はありますが、それでもせいぜい1秒単位の話でしょう。であれば、実用上は何の問題もないと思います。相手の時間が残っている場合に、1秒たりとも時間計測の食い違いがあってはならない、という状況は実際にあるのでしょうか。

なお、今までの話はサーバを通して対局したという前提ですが、サーバを使わずに将棋所だけで対局をするのであれば、将棋所の時間計測とエンジンの時間計測は同じローカルの時計を使うので、やはり計測のずれというのは発生しません。

>それはそうと、ひよこ将棋やSunfishの作者が書いている、「現局面からponderさせてもらえる機能」みたいなものは欲しいです。

予想手を使わずに現局面から先読みする方法については以前、GPS将棋の金子さんと議論したことがあるので、将棋所のサポート掲示板にある過去ログを参照して下さい。
そのときの結論としては、予想手を使わないで先読みするのであれば、エンジンはponderを使わずに単に「bestmove XXX」を返し、(この場合、将棋所はgo ponderを送らないので)そこから先は勝手に先読みする、ということになったはずです。これが一番単純で問題がないと思います。

LS3600LS3600 2011/10/29 20:06 コメントありがとうございます。

予測手を使わないで先読みする方法については、私は実装するのが面倒かなと思っていました。

> 予想手を使わないで先読みするのであれば、エンジンはponderを使わずに単に「bestmove XXX」を返し、(この場合、将棋所はgo ponderを送らないので)そこから先は勝手に先読みする、ということになったはずです。これが一番単純で問題がないと思います。

確かにその方法のほうがiterationをそのまま継続できるのでその分だけ(わずかに)思考時間が得だと言えますね。試合が途中中断された場合はgameoverメッセージがやってくるのであれば、そのタイミングで思考用のスレッドを停止させればいいのですね。

そう実装するのがちょっと面倒かなと思っていたのですが、まあ…実際のところたいしたことはなくて、その方法のほうが思考時間がわずかに得なので、ギリギリまで思考時間が欲しいならそう実装すべきというのはわかりました。

だから、「go XXX ponder」(予想手を送信せずにponderさせてもらう機能)という機能は、(この機能を使うのが思考時間的に見てベストではないので)まあ別になかったらないで特に困らないというのはわかりました。

ところで、この非ponder中に"info string"コマンドを思考エンジンが送信するのはUSIプロトコル上は許された動作なのでしょうか?将棋所では非ponder中でも"info string"コマンドの内容を表示してくれるような実装になっていますが、これはたまたま表示されるだけなのか、それともUSIプロトコルの仕様上、許されているのかどちらなのでしょう?


> 相手側の消費時間を知るためにはエンジンが自分で時間計測

タイマーを一つponderhitのためだけに思考エンジン側で用意するのが美しくないというそれだけのことで、この実装が面倒かどうかということであれば面倒ではないです。ponderhitしなかったときには時間情報が付与されてgoコマンドがくるので、それと対称になっていないのが気持ち悪い&それがゆえに思考エンジン側に余計な実装の手間が増えるというだけの話です。この意味では必要不可欠かというと、そうではなく、「あったほうがいいのになぁ」というレベルの話です。

それで、その部分はまあ実装上の美学の問題なので、まあどちらでもいいとしまして、

> エンジンの先読み中に相手が長考してからponderhitが返ってくると、サーバ側とエンジン側での計測に食い違いが出る可能性はありますが、それでもせいぜい1秒単位の話でしょう。であれば、実用上は何の問題もないと思います。相手の時間が残っている場合に、1秒たりとも時間計測の食い違いがあってはならない、という状況は実際にあるのでしょうか。

おっしゃる通り、常識的には通信遅延は1秒未満でしょうし、そもそも通信遅延が5秒ぐらいある環境ならサーバー側の計測での残り思考時間ではなく、その通信遅延を考慮して、そのメッセージを送信したDateTimeまで付与しておいて、それを考慮に入れた上で残り持ち時間をUSIプロトコルで思考エンジンに返してもらわないといけないという話にはなると思います。まあ、現実的にはそこまで通信遅延のあるような環境で対戦させることはないと思うので、これは特に考慮しなくてもいい問題かも知れません。

それで「1秒たりとも時間計測の食い違いがあってはならない、という状況は実際にあるのでしょうか。」につきましては、これは私が勝手に考えているだけですが、それがサーバー側計測で1秒なのか2秒なのか知りたいということはあると思います。

1秒はすなわちノータイムで消費時間ほぼゼロに近い形で指された手で、稲庭戦法以外にも、例えば置換表に指し手が記録されていただとか、読み通りに進行しているだとか、定跡DBに載っている指し手なのかだとか、詰みを読みきっただとか、何かそういう性質の指し手であるはずで、2秒で指された指し手とはまったく性質が異なります。

その意味で、1秒であるのか、ないのかは結構重要で、これを利用してこちらの思考時間を調整したり、相手の思考時間から見て、現局面が相手の定跡DBにhitしていないとわかったらそこからわざと(こちらの定跡DBにある)一番長い定跡手順に誘導したり(一番長い定跡手順に誘導すれば、こちらだけノータイムで指せて、相手だけが思考時間を消費するのでお得です)することも可能です。

というようなことを考えているのはもしかしたらコンピューター将棋開発者のなかでは私だけかも知れないので、もしかすると、これはかなり特殊な話かも知れません。ただまあ、こういう風に、「サーバー側の計測での相手の消費時間」がわかると思考エンジン側はそれを利用して何かできるという可能性があることを示しました。

将棋所の作者将棋所の作者 2011/10/29 21:47 >ところで、この非ponder中に"info string"コマンドを思考エンジンが送信するのはUSIプロトコル上は許された動作なのでしょうか?将棋所では非ponder中でも"info string"コマンドの内容を表示してくれるような実装になっていますが、これはたまたま表示されるだけなのか、それともUSIプロトコルの仕様上、許されているのかどちらなのでしょう?

USIプロトコルでは非ponder中にinfo stringが送られた場合については何の規定もないので、どういう動作が正しいのか私にもわかりません。将棋所は単に送られてきた文字列をそのまま表示しているだけです。

>1秒はすなわちノータイムで消費時間ほぼゼロに近い形で指された手で、稲庭戦法以外にも、例えば置換表に指し手が記録されていただとか、読み通りに進行しているだとか、定跡DBに載っている指し手なのかだとか、詰みを読みきっただとか、何かそういう性質の指し手であるはずで、2秒で指された指し手とはまったく性質が異なります。

エンジン側で計測した時間が1秒であれば、サーバ側で計測した時間も1秒なので、計測のずれは発生しません。(遅延が少しあったとしても、エンジン側で計測する時間は増えるだけなので、サーバ側の計測時間がエンジン側より短いということはあり得ない。)
エンジン側で計測した時間が2秒の場合ですが、遅延がほんのわずかあったとしても、相手が実際に考えた時間は最低でも2秒程度でしょう。通常のサーバ通信対局では消費時間の端数は切り捨てられるので、相手が1.99秒で指した場合、サーバ側で計測した時間が1秒になり、エンジン側は遅延があって2秒になる、ということは考えられます。
しかしこの場合、相手側は2秒近く考えたということですから、少なくともノータイムの指し手ではありません。つまり、エンジン側で2秒と計測されたなら、相手側が多少なりとも考えたことは間違いないので、たとえサーバ側が1秒と記録していても、「ノータイムの指し手ではない」という判断自体には間違いはないわけです。であれば、相手の思考時間を見てこちらの指し手を決める上では何の問題もないように思います。

これが例えば、相手側が10分以上も長考するような場合であれば、サーバ側とローカルPCの時計のずれのために計測時間がずれるということは考えられますが、そういう場合は多少のずれがあっても問題ないはずです。

というわけで、特に問題になることがあるようには思えませんが、いかがでしょう。

将棋所の作者将棋所の作者 2011/10/29 21:53 すいません、ちょっと間違いがありました。

誤:
(遅延が少しあったとしても、エンジン側で計測する時間は増えるだけなので、サーバ側の計測時間がエンジン側より短いということはあり得ない。)



正:
(遅延が少しあったとしても、エンジン側で計測する時間は増えるだけなので、サーバ側の計測時間がエンジン側より長いということはあり得ない。)

LS3600LS3600 2011/10/29 22:06 > USIプロトコルでは非ponder中にinfo stringが送られた場合については何の規定もないので、どういう動作が正しいのか私にもわかりません。将棋所は単に送られてきた文字列をそのまま表示しているだけです。

なるほど。了解です。将棋所で表示できるのであればやっていいことだと私は理解しました。回答ありがとうございます。

> (遅延が少しあったとしても、エンジン側で計測する時間は増えるだけなので、サーバ側の計測時間がエンジン側より長いということはあり得ない。)

この理屈はわかりましたし、思考エンジン側で計測してそれを1秒単位で切り捨てるというのはそこそこ現実的なソリューションだとは思います。

ただ、通信の遅延時間として1秒程度かかることは十分ありえる範囲だと思うので、相手とこちらの通信路でそれぞれ1秒の遅延がありますと、相手が指し手をノータイムで返したがサーバー側に届くのに1秒かかり、そこからその情報がこちらの将棋所に届くのに1秒かかります。思考エンジン側の計測ではこの場合2秒をわずかに超えますとそれを切り捨てて「相手の思考時間=2秒」と判断するしかないです。これはやはり困ります。

あるいは相手の通信路に0.1秒の遅延しかなくとも、こちらの通信路が(無線LANなどで)たまたま2秒ぐらい遅延しますと、この場合も正しく判定できません。そういうことはいまの日本の家庭内LAN環境を考えると十分ありえることなので、ゆえに、思考エンジン側で計測させるというのはプロトコルの仕様上まずいと私は思うのです。

将棋所の作者将棋所の作者 2011/10/29 22:56 >こちらの通信路が(無線LANなどで)たまたま2秒ぐらい遅延しますと、この場合も正しく判定できません。

そういうことを言い出すと、相手側が無線LANを使っていて遅延が2秒あった場合、相手ソフトが定跡手をノータイムで指したとしても、サーバ側では2秒と計測されてしまいます。そうなったら、たとえサーバ側の正確な計測時間がわかったところで、「相手の思考時間を見てこちらの指し手を決める」という作戦には何の意味もないように思いますが・・・。

LS3600LS3600 2011/10/29 23:10 ↑それはその通りです。相手側に2秒の通信遅延があるケースはワーストケースだと私は思います。

こちら側に2秒の通信遅延があるケースはサーバーでの計測時間さえponderhitのときに送ってもらえれば防ぐことは出来ます。要するにこちらの通信遅延による誤判定は防ぐことが出来ます。ゆえに、現実的には大きな意味があります。

将棋所の作者将棋所の作者 2011/10/30 01:08 長々と議論してきましたが、まとめると

・サーバ通信対局で、こちら側に2秒くらい通信の遅延があって
・相手側には遅延がないものとして(どうやって調べるのか知りませんが)
・USIの先読みに従ってbestmove ... ponder ...で先読み要求を出して
・先読みが当たった(ponderhitが送られてきた)場合に
・相手の指し手の正確な消費時間を調べて、それを使って指し手を決定するソフトを使っている

という条件を全て満たした場合には、問題があるということですね。そこまで考えて設計しないと欠陥プロトコルと言われてしまうというのも、ちょっとかわいそうな気がします。私が設計したわけではありませんけど。
いずれにしろ、そういう特殊な目的のためにプロトコルや将棋所の実装を変更することはありませんが、よろしいでしょうか。

LS3600LS3600 2011/10/30 02:04 > そこまで考えて設計しないと欠陥プロトコルと言われてしまうというのも、ちょっとかわいそうな気がします。

私が挙げた例は一例です。私はそういうケースで実際に困るという現実的につきつけられている課題のことを書きました。他の思考ルーチンの製作者も相手のサーバー側で計測された思考時間を取りたいなら私と同様の理由で困ります。また、他の思考ルーチンの製作者なら他の理由を挙げることが出来るかも知れません。少なくとも私は一人の開発者として理由を一つ挙げることができました。他の開発者もそれぞれが理由を挙げられるかも知れません。

そもそもponderhitのときにサーバー側で計測した時間を取得できない合理的な理由とは何でしょう?何故そこだけ非対称にするのか、私にはそっちのほうが理解できないのです。やはりこれはプロトコル上の欠陥だと私は思います。時間を付与しておくのは簡単で、それをわざわざここだけ付与しないのか、その理由がわからないのです。通信のトラフィックを減らすために文字数を1文字でも減らしたいことはありますが、USIプロトコル自体はそのへんはあまり主眼としていないように思います。ゆえに、「付け忘れちゃったのかなぁ」ぐらいに思えます。

それはプロトコル策定段階で、私のような“特殊な目的”のことを考慮できなかった過失のことを言っているのではなく、そこを非対称にしてしまった仕様上の汚さと不便さのことを私は“欠陥”だと言いました。

思考エンジン側は"go btime XXXX wtime XXXX"コマンドをparseするためのparserは書いてあるはずなので、思考エンジン側で"ponderhit btime XXXX wtime XXXX"という文字列をparseするための追加コストはほぼゼロに等しく、同じ理由により将棋所側に実装するのも容易なはずです。

とは言え、いまさら将棋所の実装を変更するのはどうなんだという議論はありますので、「これを現実的には実装すべきではない」という将棋所の製作者としての判断でしたらそれはわかります。そのへんは、合理的かつ現実的な判断をされる方だと存じておりますので、実装されないことに対して私のほうで不満があるわけではありませんし、実際問題としては実装しないのが正しい選択かも知れません。

結局のところ簡易実装のれさぴょんのような将棋プログラムは別として、大会で優勝するようなプログラムを考えているなら「USIプロトコル上のponderは使うべきではない」という結論になると思います。

それは相手のサーバー側の計測での考慮時間を知りたいためだけではなく、2手先の局面ではなく現在の局面で思考したいためで、思考エンジン側は"bestmove XXXX ponder YYYY"という形でponderを返すのではなく単に"bestmove XXXX"だけ返してあとは思考エンジン側は勝手に現在の局面で思考するなりなんなり好きなようにして、相手が指し手を指せば次の局面が送られてくるのでその局面が思考エンジン側の想定局面ならそのまま思考を継続するなり何なりすれば良く、しかもそのときにサーバー側で計測した時間も取得できるので何の問題もないということですね。(少し実装が面倒になることを除けば)

正直言いまして、私はそのことに気づくのにずいぶん時間がかかりました。ponderを絶対使わないといけないものだとばかり思っていまして、なんか不便だなーと思いながら実装していたのですが、これ使わないでいいのかと思ったときにパッと視界がひらけました。

いやほんと、長々とすみません。

上の話はまあ、別にどうでもよくて(←長々と書いてから言うべきことではないかも知れませんが)、将棋所という素晴らしいUIのおかげで、思考エンジンの開発上、すこぶる助かっております。素晴らしいUIを提供していただけていることに本当に感謝しております。