リバーシゲームの思考ルーチンについて。
はっきり言って,現在のコンピュータに思考させるとこは不可能です。 コンピュータは忠実に人間の命令に従うのみです。 バグもウイルスも,コンピュータが悪いのではありません。 コンピュータは人間のミス,悪意を持った命令にも忠実に従ってしまいます。 しかし,よく「思考ルーチンが・・・」と言います。 この「思考」は,もちろん実際の思考ではありません。 では,いったい何なのでしょうか。
先ほど説明したように,コンピュータは人間の命令に従うことしかできません。 しかし,コンピュータどんな人間よりも忠実に,しかも高速に仕事をこなす能力を備えています。 その能力を利用して,コンピュータに「思考の手順」を教え, その手順に従って(この場合は)「どこに打つのが最善手か」を「計算」させるのです。
人間とコンピュータの大きな違いは「感」があるかないかです。 コンピュータに「感」がないので,普段人間が思考しているものをそのままコンピュータに教えることはできません。 コンピュータを強くできるかどうかは,普段の人間の思考をどれだけ理論的に再現できるかで変わってきます。 「角は打つ方がよい」など。 大部分を理論的に再現できたとしても,強くならないときもあります。なぜでしょう?
理論的に再現すると,膨大な量の命令をコンピュータに教えることになります。まず無理でしょう。 その前に,感と経験が複雑に入り交じっているものを理論的に再現するのが難しいです。 コンピュータにはコンピュータなりの最適な方法があります。 高速計算を生かした「先読み」です。 簡単に言うと,全てのパターンを先読みして(当然, 最後まで読むことはできないので途中でうち切りますが), 状況が最も有利になる手を探します。 人間の数倍もの手を先読み(1手読むごとに桁違いに読む量が増える)して,人間の「感」の代用をします。 こうなると思考とは言い難いですが,これがコンピュータの「思考」です。 しかし,どういう状況が有利であるかを判断する方法が問題になります。
人間は,盤面を一目見ただけで全体を瞬時に認識することができます。 しかしコンピュータは厳密に言うと,1マスに注目してその周りがどうなっているのかを順番に見ていくしかありません。 通常,盤面の評価は点数で判断します。 それぞれ,先読みした状態の盤面に点数をつけ,点数が一番良いものに向かうように打つ場所を決めます。 その点数の付け方ですが,盤面に対して点数を返す関数を(盤面)評価関数と言います。 以下はその例です(Java)。
int EvalFunction(int[][] field){
// fieldは8×8の配列. コンピュータの石は1,相手の石は-1,何もないマスは0
// valueは8×8の配列. 石があるとよい所ほど大きな値.
int value[][] = {
{20, 1, 10, 7, 7, 10, 1, 20},
{ 1, -12, 2, 2, 2, 2, -12, 1},
{10, 2, 6, 3, 3, 6, 2, 10},
{ 7, 2, 3, 5, 5, 3, 2, 7},
{ 7, 2, 3, 5, 5, 3, 2, 7},
{10, 2, 6, 3, 3, 6, 2, 10},
{ 1, -12, 2, 2, 2, 2, -12, 1},
{20, 1, 10, 7, 7, 10, 1, 20}
};
int point = 0;
for (int i = 0; i < 8; i++) {
for (int j = 0; j < 8; j++) {
point += value[i][j] * field[i][j];
}
}
return point;
}
上の例では,単純に盤面上の位置で点数を決めています。 角に自分の石があると良い点数(20)がつくようになっています。 角の内側は低い点数(-12)になっています。 相手の石がある場合は正負が逆転します(value[i][j] * field[i][j] のところ)。 これらの和(どちらの石も置かれていない場所は足さない)で盤面に点数をつけています。 コンピュータが有利であれば,この関数は正の値を返します。 この関数が最も大きな値を返す盤面がよい状態であると判断させます。 コンピュータを強くさせるには,この評価関数の質をよくすればいいのです。 例えば,隅から隅まで全て自分の石ならば点数を高くするとか, 相手の石の置く場所がなければ点数を高くするとか (もちろん,上の例では弱いコンピュータしかできません)。 いつも私はどのように考えているのかな?というのを理解して(これが一番難しい), できるだけ忠実に再現できるようにこの評価関数の中に書き込むのです。
先読みする場合,当然ながら相手も最善手を打ってくるとして考えなければなりません。 しかし,相手が考える「最善手」と,コンピュータが考える「最善手」は異なります。 コンピュータには相手が考える「最善手」を知ることができないので, コンピュータが考える「最善手」を打つと仮定して先読みしなければなりません。 相手の評価関数の質(人間では本物の思考)の方が上であれば,コンピュータはもちろん勝てません (だたし,人間には「ミス」というものが存在するので,評価関数の質が悪くても勝てるときがあります)。 また,先読みには大変時間がかかります。 先読みを1手増やすだけで倍以上(倍どころではない)の時間がかかります。 対策として,これ以上先を読んでも自分が不利になると思われるものに対してはそれ以上先読みしないようにしたり(打ち切り), 先読みを減らす分評価関数の質を上げたりします。 評価関数の質がそんなに良くなくても,先読みによって強さをカバーできる(1手でもずいぶん違う)ので, 先読みアルゴリズムの高速化を図るのが普通です。
なぜか。それは,同じ打ち方しかしないからです。 コンピュータに1回勝ってしまえば, もう1度同じ場所に打っていくことで何回でも同じパターンで勝つことができてしまいます (逆に,何回でも同じパターンで負けることもできます(笑))。 人間と対戦するとそうではありませんね。 そこで(ここでは経験を利用しない方法として),ランダム性を持たせることにします。 強さにはあまり影響せずに,全く同じパターンを繰り返すことがないように,評価関数を変更します。 最も簡単な方法は,点数に微小な乱数を加える方法です。 こうすることで,同じ盤面でも1回目と2回目では違う点数が返ってくるので, 全く同じでつまらないということはなくなります。 また,いろいろな評価関数をランダムに選ぶという方法もあります。 質の良い評価関数を複数個作ることはとっても大変なので,普通はこの方法は用いないと思います。
以上のことをふまえて,いろいろなコンピュータと対戦してみましょう。 強いコンピュータと対戦すると,同じような盤面になることが多いことに気づくと思います (例えば,自然と自分の打つ場所が少なくなっていくなど)。 これは,意図的にそうプログラミングしているわけではなく, 質の良い評価関数を作った結果そうなっただけのことです。 どんな人が作っても,似たような結果を出すなんて,興味深いところですよね。
ちなみに ReverseStoe for Java では, 小学生は実質先読み0,高校生は4,サラリーマンは5としています。 状況によって先読み手数は増えます。 Java はまだまだ遅いからあまり先読みできないのが難点です。 それでもまあまあ強いでしょ?(笑)
Copyright © Yuichi 1997-2010 All rights reserved.