| import math |
| import torch |
| import numpy as np |
|
|
| class Node: |
|
|
| def __init__(self, game, args, state, parent = None, action = None, prob = 0, visits = 0): |
| self.game = game |
| self.args = args |
| self.state = state |
| self.parent = parent |
| self.action = action |
| self.prob = prob |
|
|
| self.children = [] |
| self.visits = visits |
| self.value = 0 |
|
|
| def leaf_or_not(self): |
| return len(self.children) > 0 |
|
|
| def search(self): |
| best_child = None |
| best_ucb = -np.inf |
| for child in self.children: |
| ucb = self.get_ucb(child) |
| if best_ucb < ucb: |
| best_ucb = ucb |
| best_child = child |
| return best_child |
|
|
| def get_ucb(self, child): |
| if child.visits == 0: |
| q_value = 0 |
| else: |
| q_value = 1 - (((child.value / child.visits) + 1) / 2) |
|
|
| return q_value + self.args['EXPLORATION_CONSTANT'] * (math.sqrt(self.visits) / (child.visits + 1)) * child.prob |
|
|
| def expand(self, policy): |
|
|
| for move, prob in enumerate(policy): |
| if prob > 0: |
| child = self.state.copy() |
| child = self.game.make_move(child, move, 1) |
| if self.args["ADVERSARIAL"]: |
| child = self.game.change_perspective(child, player = -1) |
|
|
| child = Node(self.game, self.args, child, self, move, prob) |
| self.children.append(child) |
|
|
| def backpropagate(self,state_value): |
| self.value += state_value |
| self.visits += 1 |
| if self.args["ADVERSARIAL"]: |
| state_value = self.game.get_opponent_value(state_value) |
| if self.parent is not None: |
| self.parent.backpropagate(state_value) |
|
|
|
|
| class Alpha_MCTS: |
|
|
| def __init__(self, game, args, model): |
| self.game = game |
| self.args = args |
| self.model = model |
|
|
| @torch.no_grad() |
| def search(self, state): |
| root = Node(self.game, self.args, state, visits = 1) |
|
|
| if self.args["ROOT_RANDOMNESS"]: |
| policy, _ = self.model( |
| torch.tensor(self.game.get_encoded_state(state), device = self.model.device |
| ).unsqueeze(0)) |
|
|
| policy = torch.softmax(policy, axis = 1).squeeze(0).cpu().numpy() |
|
|
| policy = (1 - self.args["DIRICHLET_EPSILON"]) * policy + self.args["DIRICHLET_EPSILON"] * np.random.dirichlet([self.args["DIRICHLET_ALPHA"]] * self.game.possible_state) |
|
|
| valid_state = self.game.get_valid_moves(state) |
| policy *= valid_state |
| policy /= np.sum(policy) |
| root.expand(policy) |
|
|
| for _ in range(self.args["NO_OF_SEARCHES"]): |
| node = root |
| no_moves = 0 |
| while node.leaf_or_not(): |
| node = node.search() |
| no_moves += 1 |
|
|
| is_terminal, value = self.game.know_terminal_value(node.state, node.action) |
|
|
| if self.args["ADVERSARIAL"]: |
| value = self.game.get_opponent_value(value) |
|
|
| if not is_terminal: |
|
|
| policy, value = self.model( |
| torch.tensor(self.game.get_encoded_state(node.state), device = self.model.device).unsqueeze(0) |
| ) |
|
|
| valid_state = self.game.get_valid_moves(node.state) |
| policy = torch.softmax(policy, axis = 1).squeeze(0).cpu().numpy().astype(np.float64) |
|
|
| policy *= valid_state |
| policy /= np.sum(policy) |
|
|
| value = value.item() |
|
|
| node.expand(policy) |
|
|
| node.backpropagate(value) |
|
|
| move_probability = np.zeros(self.game.possible_state) |
| for children in root.children: |
| move_probability[children.action] = children.visits |
| move_probability /= np.sum(move_probability) |
| return move_probability |
|
|