Minimaxアルゴリズムの紹介
1. 概要
この記事では、ミニマックスアルゴリズムとそのAIへの応用について説明します。 これはゲーム理論のアルゴリズムなので、それを使用して簡単なゲームを実装します。
また、アルゴリズムを使用する利点について説明し、アルゴリズムを改善する方法を確認します。
2. 前書き
ミニマックスは意思決定アルゴリズム、typically used in a turn-based, two player gamesです。 アルゴリズムの目標は、最適な次の動きを見つけることです。
このアルゴリズムでは、1人のプレーヤーを最大化装置と呼び、他のプレーヤーを最小化装置と呼びます。 評価スコアをゲームボードに割り当てると、1人のプレーヤーが最大スコアのゲーム状態を選択しようとし、もう1人が最小スコアの状態を選択しようとします。
つまり、themaximizer works to get the highest score, while the minimizer tries get the lowest score by trying to counter movesです。
これは、zero-sum gameの概念に基づいています。 In a zero-sum game, the total utility score is divided among the players. An increase in one player’s score results into the decrease in another player’s score.したがって、合計スコアは常にゼロです。 1人のプレイヤーが勝つには、もう1人のプレイヤーが負けなければなりません。 そのようなゲームの例は、チェス、ポーカー、チェッカー、三目並べです。
興味深い事実 - 1997年、IBMのチェスをするコンピューターDeep Blue(ミニaxで構築)は、Garry Kasparov(チェスの世界チャンピオン)を破りました。
3. ミニマックスアルゴリズム
私たちの目標は、プレーヤーに最適な動きを見つけることです。 そのためには、最高の評価スコアを持つノードを選択するだけです。 プロセスをよりスマートにするために、潜在的な対戦相手の動きを先読みして評価することもできます。
各動きについて、計算能力が許す限り多くの動きを先読みできます。 アルゴリズムは、対戦相手が最適にプレーしていると仮定します。
技術的には、ルートノードから始めて、最適なノードを選択します。 評価スコアに基づいてノードを評価します。 この場合、評価関数はスコアを結果ノード(リーフ)にのみ割り当てることができます。 したがって、スコアを持つ葉に再帰的に到達し、スコアを逆伝播します。
以下のゲームツリーを検討してください。
Maximizerstarts with the root nodeで、最大スコアの移動を選択します。 残念ながら、リーフにのみ評価スコアがあるため、アルゴリズムは再帰的にリーフノードに到達する必要があります。 特定のゲームツリーでは、現在、最小化がchoose a move from the leaf nodesになっているため、スコアが最小のノード(ここではノード3と4)が選択されます。 ルートノードに到達するまで、同様に最適なノードを選択し続けます。
それでは、アルゴリズムのステップを正式に定義しましょう。
-
完全なゲームツリーを構築する
-
評価関数を使用して葉のスコアを評価する
-
選手の種類を考慮して、葉から根までのバックアップスコア:
-
最大プレーヤーの場合、最大スコアの子を選択します
-
最小プレーヤーの場合、最小スコアの子を選択します
-
-
ルートノードで、最大値を持つノードを選択し、対応する移動を実行します
4. 実装
それでは、ゲームを実装しましょう。
ゲームには、heap with n number of bonesがあります。 両方のプレイヤーは順番にpick up 1,2 or 3 bonesする必要があります。 骨を取ることができないプレイヤーはゲームに負けます。 各プレイヤーは最適にプレイします。 nの値を前提として、AIを作成しましょう。
ゲームのルールを定義するために、GameOfBonesクラスを実装します。
class GameOfBones {
static List getPossibleStates(int noOfBonesInHeap) {
return IntStream.rangeClosed(1, 3).boxed()
.map(i -> noOfBonesInHeap - i)
.filter(newHeapCount -> newHeapCount >= 0)
.collect(Collectors.toList());
}
}
さらに、NodeクラスとTreeクラスの実装も必要です。
public class Node {
int noOfBones;
boolean isMaxPlayer;
int score;
List children;
// setters and getters
}
public class Tree {
Node root;
// setters and getters
}
次に、アルゴリズムを実装します。 先を見越して最高の動きを見つけるにはゲームツリーが必要です。 それを実装しましょう:
public class MiniMax {
Tree tree;
public void constructTree(int noOfBones) {
tree = new Tree();
Node root = new Node(noOfBones, true);
tree.setRoot(root);
constructTree(root);
}
private void constructTree(Node parentNode) {
List listofPossibleHeaps
= GameOfBones.getPossibleStates(parentNode.getNoOfBones());
boolean isChildMaxPlayer = !parentNode.isMaxPlayer();
listofPossibleHeaps.forEach(n -> {
Node newNode = new Node(n, isChildMaxPlayer);
parentNode.addChild(newNode);
if (newNode.getNoOfBones() > 0) {
constructTree(newNode);
}
});
}
}
次に、両方のプレーヤーに最適な動きを選択することにより、プレイアウトをシミュレートするcheckWinメソッドを実装します。 スコアを次のように設定します。
-
+1、マキシマイザーが勝った場合
-
-1、ミニマイザーが勝った場合
checkWinは、最初のプレーヤー(この場合はマキシマイザー)が勝った場合にtrueを返します。
public boolean checkWin() {
Node root = tree.getRoot();
checkWin(root);
return root.getScore() == 1;
}
private void checkWin(Node node) {
List children = node.getChildren();
boolean isMaxPlayer = node.isMaxPlayer();
children.forEach(child -> {
if (child.getNoOfBones() == 0) {
child.setScore(isMaxPlayer ? 1 : -1);
} else {
checkWin(child);
}
});
Node bestChild = findBestChild(isMaxPlayer, children);
node.setScore(bestChild.getScore());
}
ここで、findBestChildメソッドは、プレーヤーがマキシマイザーである場合に最大スコアを持つノードを見つけます。 それ以外の場合、最小スコアの子を返します。
private Node findBestChild(boolean isMaxPlayer, List children) {
Comparator byScoreComparator = Comparator.comparing(Node::getScore);
return children.stream()
.max(isMaxPlayer ? byScoreComparator : byScoreComparator.reversed())
.orElseThrow(NoSuchElementException::new);
}
最後に、n(ヒープ内のボーンの数)の値を使用してテストケースを実装しましょう。
@Test
public void givenMiniMax_whenCheckWin_thenComputeOptimal() {
miniMax.constructTree(6);
boolean result = miniMax.checkWin();
assertTrue(result);
miniMax.constructTree(8);
result = miniMax.checkWin();
assertFalse(result);
}
5. 改善
ほとんどの問題では、ゲームツリー全体を構築することはできません。 In practice, we can develop a partial tree (construct the tree till a predefined number of levels only)。
次に、evaluation function,を実装する必要があります。これにより、プレーヤーにとって現在の状態がどの程度良好かを判断できるはずです。
完全なゲームツリーを構築しなくても、分岐係数の高いゲームの動きを計算するには時間がかかる場合があります。
ゲームツリーのFortunately, there is an option to find the optimal move, without exploring every node。 いくつかのルールに従うことでいくつかのブランチをスキップできますが、最終結果には影響しません。 This process is calledpruning。 Alpha–beta pruningは、ミニマックスアルゴリズムの一般的な変形です。
6. 結論
Minimaxアルゴリズムは、コンピューターボードゲームで最も人気のあるアルゴリズムの1つです。 ターンベースのゲームに広く適用されています。 プレイヤーがゲームに関する完全な情報を持っている場合、これは良い選択です。
分岐係数が非常に高いゲーム(たとえば、 GOのゲーム)。 それにもかかわらず、適切な実装を考えると、かなりスマートなAIになります。
いつものように、アルゴリズムの完全なコードはover on GitHubにあります。