import torch import torch.nn.functional as F from torch import optim from tugo import encoders from tugo.agents.base import Agent from tugo.agents.helpers import is_point_an_eye # from tugo.game_logic import goboard from tugo.game_logic import goboard_fast as goboard def policy_gradient_loss(y_true, y_pred): clip_pred = torch.clamp(y_pred, torch.finfo(torch.float32).eps, 1 - torch.finfo(torch.float32).eps) loss = -1 * y_true * torch.log(clip_pred) return torch.mean(torch.sum(loss, dim=1)) def normalize(x): total = torch.sum(x) return x / total class PolicyAgent(Agent): def __init__(self, model, encoder): Agent.__init__(self) self._model = model self._encoder = encoder self._collector = None self._temperature = 0.0 def predict(self, game_state): encoded_state = self._encoder.encode(game_state) input_tensor = torch.tensor([encoded_state]) return self._model(input_tensor)[0] def set_temperature(self, temperature): self._temperature = temperature def set_collector(self, collector): self._collector = collector def select_move(self, game_state): num_moves = self._encoder.board_width * self._encoder.board_height board_tensor = self._encoder.encode(game_state) x = torch.tensor([board_tensor]) if torch.rand(1).item() < self._temperature: move_probs = torch.ones(num_moves) / num_moves else: move_probs = self._model(x)[0] eps = 1e-5 move_probs = torch.clamp(move_probs, eps, 1 - eps) move_probs = move_probs / torch.sum(move_probs) candidates = torch.arange(num_moves) ranked_moves = torch.multinomial(move_probs, num_moves, replacement=False) for point_idx in ranked_moves: point = self._encoder.decode_point_index(point_idx.item()) if game_state.is_valid_move(goboard.Move.play(point)) and \ not is_point_an_eye(game_state.board, point, game_state.next_player): if self._collector is not None: self._collector.record_decision( state=board_tensor, action=point_idx.item() ) return goboard.Move.play(point) return goboard.Move.pass_turn() def train(self, experience, lr=0.0000001, clipnorm=1.0, batch_size=512): opt = optim.SGD(self._model.parameters(), lr=lr, clipnorm=clipnorm) n = experience.states.shape[0] num_moves = self._encoder.board_width * self._encoder.board_height y = torch.zeros((n, num_moves)) for i in range(n): action = experience.actions[i] reward = experience.rewards[i] y[i][action] = reward self._model.train() opt.zero_grad() outputs = self._model(experience.states) loss = F.cross_entropy(outputs, y) loss.backward() opt.step() def serialize(self, h5file): h5file.create_group('encoder') h5file['encoder'].attrs['name'] = self._encoder.name() h5file['encoder'].attrs['board_width'] = self._encoder.board_width h5file['encoder'].attrs['board_height'] = self._encoder.board_height h5file.create_group('model') torchutil.save_model_to_hdf5_group(self._model, h5file['model']) def save(self, file_path): torch.save({ 'model_state_dict': self._model.state_dict(), 'encoder_name': self._encoder.name(), 'encoder_board_width': self._encoder.board_width, 'encoder_board_height': self._encoder.board_height, 'temperature': self._temperature, }, file_path) @classmethod def load(cls, file_path): device = 'cuda' if torch.cuda.is_available() else 'cpu' # model = AlphaGoModel(encoder.get_input_shape(), is_policy_net=True).to(device) # model.load_state_dict(torch.load(file_path)) # return cls(model, encoder) checkpoint = torch.load(file_path) model = TheModelClass() # TheModelClass should be replaced with your model class model.load_state_dict(checkpoint['model_state_dict']) encoder_name = checkpoint['encoder_name'] board_width = checkpoint['encoder_board_width'] board_height = checkpoint['encoder_board_height'] encoder = encoders.get_encoder_by_name( encoder_name, (board_width, board_height)) return cls(model, encoder)