2011/07/02

Scout

αβ探索はmove orderingが最適のとき探索に必要なノード数が最も少なくなるという意味で最適な探索である。そこで、Killer heuristicおよびHistory heuristicはmove orderingを改善することで探索ノード数を最小にしようとした。

それに対して、Judea Pearlによって考案されたScout探索[1]は、最悪の場合、αβ探索よりも遅くなることもあるが、多くの場合はαβ探索よりも速くなる手法である。

Scout探索はevalとtestの二種類の関数からなる。eval(node)はnodeのminmax値を求める関数で、まず一番左の子ノードのminmax値sを再帰的にevalを呼び出すことで求める。次に、maxノードの場合、左から2つ目以降の子ノードが先のminmax値sを超えているかどうかをtest関数を用いて求める。もし、超えていれば再帰的にeval関数を用いることでminmax値を求めsを更新する。minノードの場合も同様である。

αβ探索との違いはtest関数である。この関数はeval関数とは異なり値を超えているか、あるいは超えていないかだけを調べるため、子ノードを順に見ていく際に一つでも値が超えていた場合、そこでtrueを返すことができる。そうすることによって、通常のαβ探索以上に高速な探索が可能になる。

プログラムは次の通り。
abstract class ScoutPlayer[N <: Node[N]](val maxDepth: Int) extends Player[N] {

  override def play(ply: Int, node: N, last: Move): Move = {
    val (m, s) = evalMax(node, maxDepth)
    m
  }

  def evalMax(node: N, depth: Int): (Move, Int) = {
    if (depth == 0 || node.isTerminal) {
      return (Move.empty, score(node))
    }
    var bestMove = Move.empty
    var s = Int.MinValue
    val moves = node.possibleMoves()
    bestMove = moves.head
    val n1 = node.play(bestMove).get
    s = evalMin(n1, depth - 1)._2
    for (m <- moves.tail) {
      val n = node.play(m).get
      if (testGTMin(n, s, depth - 1)) {
        bestMove = m
        s = evalMin(n, depth - 1)._2
      }
    }
    (bestMove, s)
  }

  def testGTMin(node: N, s: Int, depth: Int): Boolean = {
    if (depth == 0 || node.isTerminal) {
      return (score(node) > s)
    }
    val moves = node.possibleMoves()
    for (m <- moves) {
      val n = node.play(m).get
      if (!testGTMax(n, s, depth - 1)) {
        return false
      }
    }
    true
  }

  def testGTMax(node: N, s: Int, depth: Int): Boolean = {
    if (depth == 0 || node.isTerminal) {
      return (score(node) > s)
    }
    val moves = node.possibleMoves()
    for (m <- moves) {
      val n = node.play(m).get
      if (testGTMin(n, s, depth - 1)) {
        return true
      }
    }
    false
  }

  def evalMin(node: N, depth: Int): (Move, Int) = {
    if (depth == 0 || node.isTerminal) {
      return (Move.empty, score(node))
    }
    var bestMove = Move.empty
    var s = Int.MaxValue
    val moves = node.possibleMoves()
    bestMove = moves.head
    val n1 = node.play(bestMove).get
    s = evalMax(n1, depth - 1)._2
    for (m <- moves.tail) {
      val n = node.play(m).get
      if (testLTMax(n, s, depth - 1)) {
        bestMove = m
        s = evalMax(n, depth - 1)._2
      }
    }
    (bestMove, s)
  }

  def testLTMax(node: N, s: Int, depth: Int): Boolean = {
    if (depth == 0 || node.isTerminal) {
      return (score(node) < s)
    }
    val moves = node.possibleMoves()
    for (m <- moves) {
      val n = node.play(m).get
      if (!testLTMin(n, s, depth - 1)) {
        return false
      }
    }
    true
  }

  def testLTMin(node: N, s: Int, depth: Int): Boolean = {
    if (depth == 0 || node.isTerminal) {
      return (score(node) < s)
    }
    val moves = node.possibleMoves()
    for (m <- moves) {
      val n = node.play(m).get
      if (testLTMax(n, s, depth - 1)) {
        return true
      }
    }
    false
  }

  def score(node: N): Int
}
実際にベンチマークをとってみると次のようになる。
depth
name               2        3        4        5        6        7        8        9       10
  negaalpha       13      117      139      866     1278     7055    11826    56216   102936
  scout           17      122      184     1355     1746    10638    14448    90827   159611

, , test = end41.pos

           depth
name               2        3        4        5        6        7        8        9       10
  negaalpha       30      289      525     4332     6865    55580    78076   795144   637477
  scout           31      147      523     1685     7813    16346    62773   259737   720301

, , test = end42.pos

           depth
name               2        3        4        5        6        7        8        9       10
  negaalpha       30      223      338     3747     4063    47325    48221   538729   579236
  scout           46      208      507     1796     3929    17116    54538   242091   637888

, , test = end43.pos

           depth
name               2        3        4        5        6        7        8        9       10
  negaalpha       49       77      673     1388    10677    23115   115886   319520  1253254
  scout           85      180      501     1792     5469    20568    75687   406273  1449598

, , test = end44.pos

           depth
name               2        3        4        5        6        7        8        9       10
  negaalpha       37      208      575     4023     6969    59839    53319   834028   541004
  scout           51      181      831     1754     6878    14971    45735   135692   426776

, , test = end45.pos

           depth
name               2        3        4        5        6        7        8        9       10
  negaalpha       34      370      329     5777     2723    61868    26343   817688   226751
  scout           36      262      313     2588     3061    24128    27437   184520   306221

, , test = end46.pos

           depth
name               2        3        4        5        6        7        8        9       10
  negaalpha       25      335      594    10135    11688   124574   152214  1237488  1574325
  scout           25      269      600     4603    12058    52798   159862   639592  1366585

, , test = end47.pos

           depth
name               2        3        4        5        6        7        8        9       10
  negaalpha       47       77      788      843    14354     7810   191791    43789  1106182
  scout           71      171      865     4122    15544    51052   170941   227355   710531

, , test = end48.pos

           depth
name               2        3        4        5        6        7        8        9       10
  negaalpha       29      313      470     6681     9337    89444   145149  1129818  1887604
  scout           38      235      530     3214     9771    56977   123631   620781  1160128

, , test = end49.pos

           depth
name               2        3        4        5        6        7        8        9       10
  negaalpha       34      273      494     2408     6707    20749   125380   310137  1682987
  scout           48      125      569     1718     7189    43022   168662   703102  1383906

, , test = end50.pos

           depth
name               2        3        4        5        6        7        8        9       10
  negaalpha       33      452      626    15963    10816   534240   202532 25385511  3547451
  scout           42      325      711     5129    10956   102361   213611  1484040  3335089

, , test = end51.pos

           depth
name               2        3        4        5        6        7        8        9       10
  negaalpha       59      183     1160     2032    31843    24937   479486   347731 11632682
  scout           97      391     1425     2848    20183    58913   516050  1038602 12026764

, , test = end52.pos

           depth
name               2        3        4        5        6        7        8        9       10
  negaalpha       63      204      992     1946     6498    22494    94032   247170  1771168
  scout           78      404      966     2461     5686    30081   106730   390460  1331091

, , test = end53.pos

           depth
name               2        3        4        5        6        7        8        9       10
  negaalpha       43      350     1214    11389    29471   576664   332677 41483269  4314728
  scout           58      239     1355     3738    26987    71788   377530  1003209  3523635

, , test = end54.pos

           depth
name               2        3        4        5        6        7        8        9       10
  negaalpha       23      239      586     2323    14222    22906   211911   292473  2569049
  scout           23      125      385     2688     5905    21854   100181   502976  1062680

, , test = end55.pos

           depth
name               2        3        4        5        6        7        8        9       10
  negaalpha       49      322      841     5657    20688    82789   223240  1981849  3913589
  scout           70      524     1076     7771    23559   105182   225428  1312618  4845125

, , test = end56.pos

           depth
name               2        3        4        5        6        7        8        9       10
  negaalpha       56      148     1532     2013    26438    37070   373227   380459  7594219
  scout           92      466     1758     3716    15043    58080   226731  1140123  7096800

, , test = end57.pos

           depth
name               2        3        4        5        6        7        8        9       10
  negaalpha       38      289      458     5651     4485   107143    44017  1887772   575659
  scout           60      145      528     1040     4698    13081    39672   105975   569878

, , test = end58.pos

           depth
name               2        3        4        5        6        7        8        9       10
  negaalpha       55      828      904    15508    15619   238957   206252  3275672  2451690
  scout           84      298     1401     4369    22385    72483   292685   927379  3730359

, , test = end59.pos

           depth
name               2        3        4        5        6        7        8        9       10
  negaalpha       18      240      210     3747     2592    63628    31206   690918   271286
  scout           20      150      221     1922     3015    24676    33356   283456   228431

実験結果から、速くなることもあれば、遅くなることもあることがわかる。

[1] Pearl. SCOUT: A Simple Game-Searching Algorithm with Proven Optimal Properties. AAAI (1980) pp. 143-145

0 件のコメント:

コメントを投稿