470 likes | 693 Views
vsOtha version 13.06. 2004/08/13 xhl. vsOtha version 13.06. I 概要 II 探索アルゴリズム III 評価関数 IV その他 V パフォーマンス. I 概要. 目次 プログラム構成 ステージ 文献等. 目次. 探索アルゴリズム α-β 法 Move Ordering 置換表 (Transposition Table) 反復深化法 (Iterative Deepening) ProbCut による前向き枝刈り 評価関数 狐 Original
E N D
vsOtha version 13.06 2004/08/13 xhl
vsOtha version 13.06 • I 概要 • II 探索アルゴリズム • III 評価関数 • IV その他 • V パフォーマンス
I 概要 • 目次 • プログラム構成 • ステージ • 文献等
目次 • 探索アルゴリズム • α-β法 • Move Ordering • 置換表(Transposition Table) • 反復深化法(Iterative Deepening) • ProbCutによる前向き枝刈り • 評価関数 • 狐Original • 狐General
プログラム構成 • 主に探索部と評価関数部で構成。 • 両者は分離されている。 • 盤の実装などは“一応”切り離されているのでオセロ以外にも対応できる。 • 実質C言語。評価函数のみC++でクラス化されている。 • 某氏「(拡張子を見て)あ、xhl氏がC++書いてる」私「拡張子が.cppなだけのCだったりして……」 • 機械生成、不使用コードも入れて15000行程度?
ステージ • オセロの60手を4手×15のステージに分割。stage 0 ~ stage 14。 • 評価函数他、便利なのでいろいろ使っている。
文献等 • ProbCutと狐系評価関数についてはBuro氏の論文を、置換表についてはオセ7のソースコードを、またネット上の諸情報を反復深化法その他について参考にした。 • 終盤探索速度を上げるのは楽しいですね。(時間食うと空しくもなるが……)
II 探索アルゴリズム • ゲームを通じて共通の探索ルーチン。パラメータを変化させることで序中盤~終盤に対応。 • α-β法 • Move Ordering • 置換表(Transposition Table) • 反復深化法(Iterative Deepening) • ProbCutによる前向き枝刈り • 終盤解析
Move Ordering - 概要 • 「良さそうな手」から先読みすることによってα-βを効率化する手法。
Move Ordering - 実装 • 古典的評価関数による評価値によりソート。また、置換表にある盤面を優先して探索。 • a = (隅にある自石の数) - (隅にある相手石の数)b = (Xにある自石の数) - (Xにある相手石の数)c = 相手の着手可能数 • 序中盤: 評価値 = 50a - 10b - c • 終盤 : 評価値 = 2a - b - 3c
Move Ordering - 実装 • 終盤を分けることで終盤完全読みが40%くらい? 高速化。 • 終盤(空きが17ヶ所以下)は速さ優先探索(Fastest-First Heuristic)の変種。 • 純粋な速さ優先探索にすると探索が遅くなる盤面が存在したため、平均的に速くなるよう a, b の項を入れて補正を図った。
Move Ordering - 実装 • Move Orderingにはある程度のオーバーヘッドがかかる。→ゲーム木全体で実行すると遅くなる。 • ゲーム木の下の方ではオーバーヘッドに見合うだけの高速化が見込めないので、Move Orderingをある深さ(depth_MO)より上に限定した。
Transposition Table - 概要 • 既に探索したことのある局面をハッシュなどに保持し、同じ盤面が再度出現した場合に再度探索せずに済ませる手法。
Transposition Table - 実装 • 盤面と共に、評価値の最大値・最小値、及びMove Orderingで使用するための評価値を格納。 • 盤面は16バイトに圧縮して格納。 • 最大値・最小値から判断して、確実にαorβカットされる場合には即カットできる。 • 実装には普通のChained Hashを使用した。
Transposition Table - 実装 • Move Ordering同様、ゲーム木の全てで置換表を有効にすると遅くなり、また膨大なメモリが必要になる。→置換表に登録する盤面を、ある深さ(depth_TT)より上に限定。
Transposition Table - 実装 • おまけ: 2つめの置換表Chained Hashよりも高速で動作する置換表を、depth_TTより下でdepth_TT2(<depth_TT)より上の領域で動作させる。 • 具体的には開番地法のハッシュにし、衝突が起きた場合には古い要素に新しい要素を上書きする。(別に律儀に全ての盤面を保持しなくてもよいので。Full-Associative Cache?)
Iterative Deepening - 概要 • まず4手読みして、次に5手読み、6手読み……と順々に探索を深くしていく手法。 • 置換表と組み合せて使うことで効果的に。n手読む→置換表のエントリにn手読みの時の評価値を格納→n+1手読む時のMove Orderingにその評価値を利用。Move Ordering同様、よさげな手から順に読むことを助ける。 • 探索時間制限を設けることが容易。
パラメータ • パラメータを換えてn局面平均で速度を出して実験的に決定した。 • 序中盤: depth_MO = depth_TT = 3 • 終盤: • depth depth_MO depth_TT • 1~7 MO無し 置換表無し • 8~13 7~9 置換表無し • 14~25 9~11 10~11
パラメータ • 時間制限:stageごとに分割。順に2[sec], 5, 10, 20, 20, 20, 10, 10, 5, 5, 完全読み
効果 • 完全18手読み 完全20手読み 中盤10手読み(stage 5) • 64.55sec/132.42M - 511.02sec/30.63M • MO 2.46sec/ 4.65M 23.53sec/43.84M 34.02sec/ 1.76M • TT 2.06sec/ 3.73M 17.84sec/31.09M 30.45sec/ 1.49M • ID1.20sec/ 1.75M 8.18sec/12.11M 11.78sec/ 0.62M • 置換表(TT)は導入時はかなり効果大だった(3倍くらいとか)はずなのだが……?
ProbCut - 概要 • M. Buro氏による前向き枝刈り手法。 • d > d’ (d = 8, d’ = 4 とか) • d手先読みした場合の評価値vとd’手先読みした場合の評価値v’とには、単純な線形相関がある(Buro氏の論文及びvsOthaの場合はそうだった)。 • →v’からvを予測できる。 • →d手先読みする場合にまずd’手先読みし、αβの範囲外になりそうだったらカットする。
ProbCut - 概要 • 利点及び欠点(前向き枝狩り共通) • 利点: 探索が高速になる。 • 欠点: 切ってはいけない枝を刈ることもあるので結果はやや不正確。 • 欠点をそれを上回る利点でカバーできるか。
ProbCut - 実装 • 必要なパラメータ: a, b, σvの予測値 = v’ * a + bvの予測誤差(標準偏差) = σ • ゲームステージ依存、思考ルーチン依存。 • 棋譜から2,000局面取り出して、d手先読みの評価値とd’手先読みの評価値を算出し、回帰分析によりa, b, σを算出。
ProbCut - 効果 • Stage SpeedUp SameMove SameValue • 3 1.91 100% 98% • 5 2.08 98% 92% • 7 2.52 94% 86% • 9 1.84 98% 98% • 11 1.33 100% 98% • Buro論文みたいに「6.6倍」などにはならなかった。
終盤解析 - 概要 • 例えば20手完全読みの場合、狐ルーチンとかで10手ぐらい反復深化法で読んでから20手完全読みを行う。
終盤解析 - 高速化 • 1. Leaf数を少なくするか。 • 2. 速度(Leaf/sec)を上げるか。→単純最適化
終盤解析 - Leaf削減 • アルゴリズムでSignificant/Importantなのは • Move Ordering ← 並び替え方法の改良 • 置換表(メモリを大量に使って25手とか読むとかなり効く) • 反復深化法(最初の反復深化の良し悪しでその後の完全読みのLeaf数に影響する)←良い評価函数。 精度が上がるとLeaf数は下がる
終盤解析 - Leaf削減 • パラメータ評価による改善 • 小技いろいろ • 最終的にはいくつかの局面or棋譜の10個盤面平均とかで速度/Leaf数を出して評価することに。
終盤解析 - 単純最適化 • 後述。
III 評価関数 • 狐Original • 狐General
狐Original • Buro氏の論文“Experiments with Multi-ProbCut and a NewHigh-Quality Evaluation Function for Othello”を参考にして作ったルーチン。 • 英語が分からなかったことと、線形ではどうしても良い値が出なかったことから、パターンを流用してニューラルネットワーク(3層パーセプトロン)にした。
狐Original • 中間ユニット8個の3層パーセプトロン。 • ロジスティック函数使用。 • 入力: • 着手可能数 • 空きの数の偶奇 • 石差 • 隅、Xなどにある石の数 • パターン(LOGISTELLOと同じ) : 出現回数を値とする • hori/vert 2-4, diag4-8, edge+2X, corner 2*5, corner 3*3
狐Original • 学習 • 誤差逆伝播アルゴリズム。 • 浮動小数点で学習してその後整数に変換。 • NECのFTPサイトにあった棋譜ftp://ftp.nj.nec.com/pub/igord/IOS/archive/??E4.gam.bzのうち、最後まで指された約31万を用いた。
狐General • 狐Originalの後継ルーチン。 • 基本は狐Originalと同じ。いろいろ整理したので狐Originalよりプログラマにとっては扱いやすい。←強さとは無関係。 • 学習法を工夫することで、棋譜が無くても一応動作できる。(棋譜ありに比べると劣るらしいが)←今回は無関係。 • 入力に微変更あり。
狐General • 棋譜データの修正←今回のメイン • 実は棋譜データの終盤解析をすると結構間違った手を打っている。(2個空きで間違えて負けた棋譜なんてのもあった) • 棋譜データの目的は、完全読みできないような中盤の局面に対して「この局面は××石勝ちです」というできるだけ正確なデータを提供することにあると考えた。 • 各棋譜の終盤20手ぐらいを終盤解析して正確な石差を算出し、その石差を用いて学習。 • 時間不足(CPUもプログラマも)のため、やや手を抜く。
狐General - 勝率の改善 • 5分制限、()内はProbCutの有無。 • 狐(無) vs 狐Org(無) 12勝0分4敗 +146 • 狐(有) vs 狐Org(無) 13勝1分2敗 +118
IV その他の実装 • 盤の実装 • 地道な最適化 • 終盤解析のとてもこまごましたこと
盤の実装 • 8×8 = 64バイトの配列。0 = 黒, 1 = 白, 2 = 空き。 • 石を打つ場合には盤面をコピー。 • 石を打つルーチン、着手可能数を数えるルーチンは展開してある。ex) switch( pos ){ case a1: if( a2が相手 and (a3が自分 or (a3が相手 and (a4が...
地道な最適化 • プログラマの時間を食うのであまり意味が無い気もするがついやってしまった最適化。 • 要らない条件分岐類を剥ぎ取る。+10%。 • 空きマスのlistを保持し、先読み時にはそのリスト内のマスについてのみループを回して処理。+30%。 • 最終1手~3手読みのループを展開。+25%。 • その他いろいろ単純最適化。+60%。
V パフォーマンス • 探索速度(Leaf per sec) • 5分制限時の先読み手数 • 終盤完全読み能力
探索速度(Leaf per sec) • Celeron 2.6GHzで測定。学科ノートでも同程度。 • 序中盤(狐ルーチンの速度) : 50KLeaf/sec • 終盤完全 : 1500KLeaf/sec
終盤完全読み能力 • 棋譜から10局面選んで平均を取った。 • depth sec Mleaf • 18 1.13 1.75 • 20 7.89 12.14 • 24 168.70 283.56