class MainWindow(QtWidgets.QMainWindow): def __init__(self, settings, show=False, fps=2000): super().__init__() self.printIndividualSnakeFitness = True self.setAutoFillBackground(True) palette = self.palette() palette.setColor(self.backgroundRole(), QtGui.QColor(240, 240, 240)) self.setPalette(palette) self.settings = settings self._SBX_eta = self.settings['SBX_eta'] self._mutation_bins = np.cumsum([self.settings['probability_gaussian'], self.settings['probability_random_uniform'] ]) self._crossover_bins = np.cumsum([self.settings['probability_SBX'], self.settings['probability_SPBX'] ]) self._SPBX_type = self.settings['SPBX_type'].lower() self._mutation_rate = self.settings['mutation_rate'] # Determine size of next gen based off selection type self._next_gen_size = None if self.settings['selection_type'].lower() == 'plus': self._next_gen_size = self.settings['num_parents'] + self.settings['num_offspring'] elif self.settings['selection_type'].lower() == 'comma': self._next_gen_size = self.settings['num_offspring'] else: raise Exception('Selection type "{}" is invalid'.format(self.settings['selection_type'])) self.board_size = settings['board_size'] #self.border = (0, 10, 0, 10) # Left, Top, Right, Bottom self.border = (0, self.board_size[0], 0, self.board_size[1]) # Left, Top, Right, Bottom self.snake_widget_width = SQUARE_SIZE[0] * self.board_size[0] self.snake_widget_height = SQUARE_SIZE[1] * self.board_size[1] # Allows padding of the other elements even if we need to restrict the size of the play area self._snake_widget_width = max(self.snake_widget_width, 620) self._snake_widget_height = max(self.snake_widget_height, 600) self.top = 150 self.left = 150 self.width = self._snake_widget_width + 700 + self.border[0] + self.border[2] self.height = self._snake_widget_height + self.border[1] + self.border[3] + 200 individuals: List[Individual] = [] #load the best snakes, if there are any self.pathToBestSnakes = 'best_snakes/' for f in os.scandir(self.pathToBestSnakes): if f.is_dir(): individuals.append(load_snake(self.pathToBestSnakes,f.name)) #load up new snakes, minus the best snakes for _ in range(self.settings['num_parents'] - len(individuals)): individual = Snake(self.board_size, hidden_layer_architecture=self.settings['hidden_network_architecture'], hidden_activation=self.settings['hidden_layer_activation'], output_activation=self.settings['output_layer_activation'], lifespan=self.settings['lifespan'], apple_and_self_vision=self.settings['apple_and_self_vision']) individuals.append(individual) #clear out any saved snakes try: shutil.rmtree('./snakes/') except OSError as e: print("Error: %s : %s" % ('./snakes/', e.strerror)) self.best_fitness = 0 self.best_score = 0 self._current_individual = 0 self.population = Population(individuals) self.snake = self.population.individuals[self._current_individual] self.current_generation = 0 self.init_window() self.timer = QtCore.QTimer(self) self.timer.timeout.connect(self.update) self.timer.start(1000//fps) if show: self.show() def init_window(self): self.centralWidget = QtWidgets.QWidget(self) self.setCentralWidget(self.centralWidget) self.setWindowTitle('Snake AI') self.setGeometry(self.top, self.left, self.width, self.height) # Create the Neural Network window self.nn_viz_window = NeuralNetworkViz(self.centralWidget, self.snake) self.nn_viz_window.setGeometry(QtCore.QRect(0, 0, 600, self._snake_widget_height + self.border[1] + self.border[3] + 200)) self.nn_viz_window.setObjectName('nn_viz_window') # Create SnakeWidget window self.snake_widget_window = SnakeWidget(self.centralWidget, self.board_size, self.snake) self.snake_widget_window.setGeometry(QtCore.QRect(600 + self.border[0], self.border[1], self.snake_widget_width, self.snake_widget_height)) self.snake_widget_window.setObjectName('snake_widget_window') # Genetic Algorithm Stats window self.ga_window = GeneticAlgoWidget(self.centralWidget, self.settings) self.ga_window.setGeometry(QtCore.QRect(600, self.border[1] + self.border[3] + self.snake_widget_height, self._snake_widget_width + self.border[0] + self.border[2] + 100, 200-10)) self.ga_window.setObjectName('ga_window') def update(self) -> None: self.snake_widget_window.update() self.nn_viz_window.update() # Current individual is alive if self.snake.is_alive: self.snake.move() if self.snake.score > self.best_score: self.best_score = self.snake.score self.ga_window.best_score_label.setText(str(self.snake.score)) # Current individual is dead else: # Calculate fitness of current individual self.snake.calculate_fitness() fitness = self.snake.fitness if self.printIndividualSnakeFitness and fitness > 1000000: print(self._current_individual, fitness) #outputs the individual snakes fitness # fieldnames = ['frames', 'score', 'fitness'] # f = os.path.join(os.getcwd(), 'test_del3_1_0_stats.csv') # write_header = True # if os.path.exists(f): # write_header = False # #@TODO: Remove this stats write # with open(f, 'a') as csvfile: # writer = csv.DictWriter(csvfile, fieldnames=fieldnames, delimiter=',') # if write_header: # writer.writeheader() # d = {} # d['frames'] = self.snake._frames # d['score'] = self.snake.score # d['fitness'] = fitness # writer.writerow(d) if fitness > self.best_fitness: self.best_fitness = fitness self.ga_window.best_fitness_label.setText('{:.2E}'.format(Decimal(fitness))) self._current_individual += 1 # Next generation if (self.current_generation > 0 and self._current_individual == self._next_gen_size) or\ (self.current_generation == 0 and self._current_individual == settings['num_parents']): print(self.settings) print(f'======================= Generation {self.current_generation} =======================') print(f'----Max fitness:{self.population.fittest_individual.fitness}') print(f'----Best Score:{self.population.fittest_individual.score}') print(f'----Average fitness:{self.population.average_fitness}') self.next_generation() save_snake('./snakes/',f'{self.current_generation}', self.snake, self.settings) else: current_pop = self.settings['num_parents'] if self.current_generation == 0 else self._next_gen_size self.ga_window.current_individual_label.setText('{}/{}'.format(self._current_individual + 1, current_pop)) self.snake = self.population.individuals[self._current_individual] self.snake_widget_window.snake = self.snake self.nn_viz_window.snake = self.snake def next_generation(self): self._increment_generation() self._current_individual = 0 # Calculate fitness of individuals for individual in self.population.individuals: individual.calculate_fitness() self.population.individuals = elitism_selection(self.population, self.settings['num_parents']) random.shuffle(self.population.individuals) next_pop: List[Snake] = [] # parents + offspring selection type ('plus') if self.settings['selection_type'].lower() == 'plus': # Decrement lifespan for individual in self.population.individuals: individual.lifespan -= 1 for individual in self.population.individuals: params = individual.network.params board_size = individual.board_size hidden_layer_architecture = individual.hidden_layer_architecture hidden_activation = individual.hidden_activation output_activation = individual.output_activation lifespan = individual.lifespan apple_and_self_vision = individual.apple_and_self_vision start_pos = individual.start_pos apple_seed = individual.apple_seed starting_direction = individual.starting_direction # If the individual is still alive, they survive if lifespan > 0: s = Snake(board_size, chromosome=params, hidden_layer_architecture=hidden_layer_architecture, hidden_activation=hidden_activation, output_activation=output_activation, lifespan=lifespan, apple_and_self_vision=apple_and_self_vision)#, next_pop.append(s) while len(next_pop) < self._next_gen_size: p1, p2 = roulette_wheel_selection(self.population, 2) L = len(p1.network.layer_nodes) c1_params = {} c2_params = {} # Each W_l and b_l are treated as their own chromosome. # Because of this I need to perform crossover/mutation on each chromosome between parents for l in range(1, L): p1_W_l = p1.network.params['W' + str(l)] p2_W_l = p2.network.params['W' + str(l)] p1_b_l = p1.network.params['b' + str(l)] p2_b_l = p2.network.params['b' + str(l)] # Crossover # @NOTE: I am choosing to perform the same type of crossover on the weights and the bias. c1_W_l, c2_W_l, c1_b_l, c2_b_l = self._crossover(p1_W_l, p2_W_l, p1_b_l, p2_b_l) # Mutation # @NOTE: I am choosing to perform the same type of mutation on the weights and the bias. self._mutation(c1_W_l, c2_W_l, c1_b_l, c2_b_l) # Assign children from crossover/mutation c1_params['W' + str(l)] = c1_W_l c2_params['W' + str(l)] = c2_W_l c1_params['b' + str(l)] = c1_b_l c2_params['b' + str(l)] = c2_b_l # Clip to [-1, 1] np.clip(c1_params['W' + str(l)], -1, 1, out=c1_params['W' + str(l)]) np.clip(c2_params['W' + str(l)], -1, 1, out=c2_params['W' + str(l)]) np.clip(c1_params['b' + str(l)], -1, 1, out=c1_params['b' + str(l)]) np.clip(c2_params['b' + str(l)], -1, 1, out=c2_params['b' + str(l)]) # Create children from chromosomes generated above c1 = Snake(p1.board_size, chromosome=c1_params, hidden_layer_architecture=p1.hidden_layer_architecture, hidden_activation=p1.hidden_activation, output_activation=p1.output_activation, lifespan=self.settings['lifespan']) c2 = Snake(p2.board_size, chromosome=c2_params, hidden_layer_architecture=p2.hidden_layer_architecture, hidden_activation=p2.hidden_activation, output_activation=p2.output_activation, lifespan=self.settings['lifespan']) # Add children to the next generation next_pop.extend([c1, c2]) # Set the next generation random.shuffle(next_pop) self.population.individuals = next_pop def _increment_generation(self): self.current_generation += 1 self.ga_window.current_generation_label.setText(str(self.current_generation + 1)) # self.ga_window.current_generation_label.setText("<font color='red'>" + str(self.loaded[self.current_generation]) + "</font>") def _crossover(self, parent1_weights: np.ndarray, parent2_weights: np.ndarray, parent1_bias: np.ndarray, parent2_bias: np.ndarray) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: rand_crossover = random.random() crossover_bucket = np.digitize(rand_crossover, self._crossover_bins) child1_weights, child2_weights = None, None child1_bias, child2_bias = None, None # SBX if crossover_bucket == 0: child1_weights, child2_weights = SBX(parent1_weights, parent2_weights, self._SBX_eta) child1_bias, child2_bias = SBX(parent1_bias, parent2_bias, self._SBX_eta) # Single point binary crossover (SPBX) elif crossover_bucket == 1: child1_weights, child2_weights = single_point_binary_crossover(parent1_weights, parent2_weights, major=self._SPBX_type) child1_bias, child2_bias = single_point_binary_crossover(parent1_bias, parent2_bias, major=self._SPBX_type) else: raise Exception('Unable to determine valid crossover based off probabilities') return child1_weights, child2_weights, child1_bias, child2_bias def _mutation(self, child1_weights: np.ndarray, child2_weights: np.ndarray, child1_bias: np.ndarray, child2_bias: np.ndarray) -> None: scale = .2 rand_mutation = random.random() mutation_bucket = np.digitize(rand_mutation, self._mutation_bins) mutation_rate = self._mutation_rate if self.settings['mutation_rate_type'].lower() == 'decaying': mutation_rate = mutation_rate / sqrt(self.current_generation + 1) # Gaussian if mutation_bucket == 0: # Mutate weights gaussian_mutation(child1_weights, mutation_rate, scale=scale) gaussian_mutation(child2_weights, mutation_rate, scale=scale) # Mutate bias gaussian_mutation(child1_bias, mutation_rate, scale=scale) gaussian_mutation(child2_bias, mutation_rate, scale=scale) # Uniform random elif mutation_bucket == 1: # Mutate weights random_uniform_mutation(child1_weights, mutation_rate, -1, 1) random_uniform_mutation(child2_weights, mutation_rate, -1, 1) # Mutate bias random_uniform_mutation(child1_bias, mutation_rate, -1, 1) random_uniform_mutation(child2_bias, mutation_rate, -1, 1) else: raise Exception('Unable to determine valid mutation based off probabilities.')
class MainWindow(QtWidgets.QMainWindow): def __init__(self, settings, show=True, fps=1000): # tốc độ rắn và đồ hoạ super().__init__() self.setAutoFillBackground(True) palette = self.palette() palette.setColor(self.backgroundRole(), QtGui.QColor(240, 240, 240)) self.setPalette(palette) self.settings = settings self._SBX_eta = self.settings['SBX_eta'] self._mutation_bins = np.cumsum([ self.settings['probability_gaussian'], # tỉ lệ lai ghép self.settings['probability_random_uniform'] # đột biến ]) self._crossover_bins = np.cumsum([ self.settings['probability_SBX'], self.settings['probability_SPBX'] ]) self._SPBX_type = self.settings['SPBX_type'].lower() self._mutation_rate = self.settings['mutation_rate'] # Determine size of next gen based off selection type self._next_gen_size = None if self.settings['selection_type'].lower() == 'plus': #chế độ plus self._next_gen_size = self.settings['num_parents'] + self.settings[ 'num_offspring'] elif self.settings['selection_type'].lower() == 'comma': #chế độ comma self._next_gen_size = self.settings['num_offspring'] else: raise Exception('Selection type "{}" is invalid'.format( self.settings['selection_type'])) self.board_size = settings['board_size'] self.border = (0, 10, 0, 10) # Left, Top, Right, Bottom self.snake_widget_width = SQUARE_SIZE[0] * self.board_size[0] self.snake_widget_height = SQUARE_SIZE[1] * self.board_size[1] # Cho phép đệm các yếu tố khác ngay cả khi chúng ta hạn chế kích thước của khu vực chơi self._snake_widget_width = max(self.snake_widget_width, 620) self._snake_widget_height = max(self.snake_widget_height, 600) self.top = 150 self.left = 150 self.width = self._snake_widget_width + 700 + self.border[ 0] + self.border[2] self.height = self._snake_widget_height + self.border[1] + self.border[ 3] + 200 individuals: List[Individual] = [] for _ in range(self.settings['num_parents']): individual = Snake( self.board_size, hidden_layer_architecture=self. settings['hidden_network_architecture'], hidden_activation=self.settings['hidden_layer_activation'], output_activation=self.settings['output_layer_activation'], lifespan=self.settings['lifespan'], apple_and_self_vision=self.settings['apple_and_self_vision']) individuals.append(individual) self.best_fitness = 0 self.best_score = 0 self._current_individual = 0 self.population = Population(individuals) self.snake = self.population.individuals[self._current_individual] self.current_generation = 0 self.init_window() self.timer = QtCore.QTimer(self) self.timer.timeout.connect(self.update) self.timer.start(1000. / fps) if show: self.show() def init_window(self): self.centralWidget = QtWidgets.QWidget(self) self.setCentralWidget(self.centralWidget) self.setWindowTitle('Snake AI-Project I ( version 2.0)') self.setGeometry(self.top, self.left, self.width, self.height) # Tạo khung cho neural_network self.nn_viz_window = NeuralNetworkViz(self.centralWidget, self.snake) self.nn_viz_window.setGeometry( QtCore.QRect( 0, 0, 600, self._snake_widget_height + self.border[1] + self.border[3] + 200)) self.nn_viz_window.setObjectName('nn_viz_window') # Tạo khung SnakeWidget self.snake_widget_window = SnakeWidget(self.centralWidget, self.board_size, self.snake) self.snake_widget_window.setGeometry( QtCore.QRect(600 + self.border[0], self.border[1], self.snake_widget_width, self.snake_widget_height)) self.snake_widget_window.setObjectName('snake_widget_window') # Cửa sổ thống kê thuật toán di truyền self.ga_window = GeneticAlgoWidget(self.centralWidget, self.settings) self.ga_window.setGeometry( QtCore.QRect( 600, self.border[1] + self.border[3] + self.snake_widget_height, self._snake_widget_width + self.border[0] + self.border[2] + 100, 200 - 10)) self.ga_window.setObjectName('ga_window') def update(self) -> None: self.snake_widget_window.update() self.nn_viz_window.update() # Cá nhân hiện tại còn sống if self.snake.is_alive: self.snake.move() if self.snake.score > self.best_score: self.best_score = self.snake.score self.ga_window.best_score_label.setText(str(self.snake.score)) # Cá nhân hiện tại đã chết else: # Calculate fitness of current individual self.snake.calculate_fitness() fitness = self.snake.fitness print(self._current_individual, fitness) # fieldnames = ['frames', 'score', 'fitness'] # f = os.path.join(os.getcwd(), 'test_del3_1_0_stats.csv') # write_header = True # if os.path.exists(f): # write_header = False # #@TODO: Remove this stats write # with open(f, 'a') as csvfile: # writer = csv.DictWriter(csvfile, fieldnames=fieldnames, delimiter=',') # if write_header: # writer.writeheader() # d = {} # d['frames'] = self.snake._frames # d['score'] = self.snake.score # d['fitness'] = fitness # writer.writerow(d) if fitness > self.best_fitness: self.best_fitness = fitness self.ga_window.best_fitness_label.setText('{:.2E}'.format( Decimal(fitness))) self._current_individual += 1 # Next generation, giúp in ra tài liệu cứu rắn if (self.current_generation > 0 and self._current_individual == self._next_gen_size) or\ (self.current_generation == 0 and self._current_individual == settings['num_parents']): print(self.settings) print( '======================= Thế hệ thứ {} =======================' .format(self.current_generation)) print('----Năng lực tối đa:', self.population.fittest_individual.fitness) print('----Điểm tốt nhất:', self.population.fittest_individual.score) print('----Năng lực trung bình:', self.population.average_fitness) self.next_generation() else: current_pop = self.settings[ 'num_parents'] if self.current_generation == 0 else self._next_gen_size self.ga_window.current_individual_label.setText('{}/{}'.format( self._current_individual + 1, current_pop)) self.snake = self.population.individuals[self._current_individual] self.snake_widget_window.snake = self.snake self.nn_viz_window.snake = self.snake def next_generation(self): self._increment_generation() self._current_individual = 0 # Calculate fitness of individuals for individual in self.population.individuals: individual.calculate_fitness() self.population.individuals = elitism_selection( self.population, self.settings['num_parents']) random.shuffle(self.population.individuals) next_pop: List[Snake] = [] # parents + offspring selection type ('plus') if self.settings['selection_type'].lower() == 'plus': # Giảm tuổi thọ for individual in self.population.individuals: individual.lifespan -= 1 for individual in self.population.individuals: params = individual.network.params board_size = individual.board_size hidden_layer_architecture = individual.hidden_layer_architecture hidden_activation = individual.hidden_activation output_activation = individual.output_activation lifespan = individual.lifespan apple_and_self_vision = individual.apple_and_self_vision start_pos = individual.start_pos apple_seed = individual.apple_seed starting_direction = individual.starting_direction # Nếu cá thể vẫn còn sống, nó vẫn tồn tại if lifespan > 0: s = Snake( board_size, chromosome=params, hidden_layer_architecture=hidden_layer_architecture, hidden_activation=hidden_activation, output_activation=output_activation, lifespan=lifespan, apple_and_self_vision=apple_and_self_vision) #, next_pop.append(s) while len(next_pop) < self._next_gen_size: p1, p2 = roulette_wheel_selection(self.population, 2) L = len(p1.network.layer_nodes) c1_params = {} c2_params = {} # Mỗi W_l và b_l được coi là nhiễm sắc thể của riêng họ. # Vì điều này, ta cần thực hiện trao đổi chéo / đột biến trên mỗi nhiễm sắc thể giữa cha mẹ for l in range(1, L): p1_W_l = p1.network.params['W' + str(l)] p2_W_l = p2.network.params['W' + str(l)] p1_b_l = p1.network.params['b' + str(l)] p2_b_l = p2.network.params['b' + str(l)] # Crossover # @NOTE: I am choosing to perform the same type of crossover on the weights and the bias. c1_W_l, c2_W_l, c1_b_l, c2_b_l = self._crossover( p1_W_l, p2_W_l, p1_b_l, p2_b_l) # Mutation # @NOTE: Ta đang chọn để thực hiện cùng một loại đột biến về trọng lượng và sai lệch. self._mutation(c1_W_l, c2_W_l, c1_b_l, c2_b_l) # Chỉ định con cái từ chéo / đột biến c1_params['W' + str(l)] = c1_W_l c2_params['W' + str(l)] = c2_W_l c1_params['b' + str(l)] = c1_b_l c2_params['b' + str(l)] = c2_b_l # Clip to [-1, 1] np.clip(c1_params['W' + str(l)], -1, 1, out=c1_params['W' + str(l)]) np.clip(c2_params['W' + str(l)], -1, 1, out=c2_params['W' + str(l)]) np.clip(c1_params['b' + str(l)], -1, 1, out=c1_params['b' + str(l)]) np.clip(c2_params['b' + str(l)], -1, 1, out=c2_params['b' + str(l)]) # Tạo con từ nhiễm sắc thể được tạo ở trên c1 = Snake(p1.board_size, chromosome=c1_params, hidden_layer_architecture=p1.hidden_layer_architecture, hidden_activation=p1.hidden_activation, output_activation=p1.output_activation, lifespan=self.settings['lifespan']) c2 = Snake(p2.board_size, chromosome=c2_params, hidden_layer_architecture=p2.hidden_layer_architecture, hidden_activation=p2.hidden_activation, output_activation=p2.output_activation, lifespan=self.settings['lifespan']) # Thêm con vào thế hệ tiếp theo next_pop.extend([c1, c2]) # Đặt thế hệ tiếp theo random.shuffle(next_pop) self.population.individuals = next_pop def _increment_generation(self): self.current_generation += 1 self.ga_window.current_generation_label.setText( str(self.current_generation + 1)) # self.ga_window.current_generation_label.setText("<font color='red'>" + str(self.loaded[self.current_generation]) + "</font>") def _crossover( self, parent1_weights: np.ndarray, parent2_weights: np.ndarray, parent1_bias: np.ndarray, parent2_bias: np.ndarray ) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]: rand_crossover = random.random() crossover_bucket = np.digitize(rand_crossover, self._crossover_bins) child1_weights, child2_weights = None, None child1_bias, child2_bias = None, None # SBX if crossover_bucket == 0: child1_weights, child2_weights = SBX(parent1_weights, parent2_weights, self._SBX_eta) child1_bias, child2_bias = SBX(parent1_bias, parent2_bias, self._SBX_eta) # Crossover nhị phân một điểm (SPBX) elif crossover_bucket == 1: child1_weights, child2_weights = single_point_binary_crossover( parent1_weights, parent2_weights, major=self._SPBX_type) child1_bias, child2_bias = single_point_binary_crossover( parent1_bias, parent2_bias, major=self._SPBX_type) else: raise Exception( 'Không thể xác định lai chéo hợp lệ dựa trên xác suất') return child1_weights, child2_weights, child1_bias, child2_bias def _mutation(self, child1_weights: np.ndarray, child2_weights: np.ndarray, child1_bias: np.ndarray, child2_bias: np.ndarray) -> None: scale = .2 rand_mutation = random.random() mutation_bucket = np.digitize(rand_mutation, self._mutation_bins) mutation_rate = self._mutation_rate if self.settings['mutation_rate_type'].lower() == 'decaying': mutation_rate = mutation_rate / sqrt(self.current_generation + 1) # Gaussian if mutation_bucket == 0: # Mutate weights gaussian_mutation(child1_weights, mutation_rate, scale=scale) gaussian_mutation(child2_weights, mutation_rate, scale=scale) # Mutate bias gaussian_mutation(child1_bias, mutation_rate, scale=scale) gaussian_mutation(child2_bias, mutation_rate, scale=scale) # Uniform random elif mutation_bucket == 1: # Mutate weights random_uniform_mutation(child1_weights, mutation_rate, -1, 1) random_uniform_mutation(child2_weights, mutation_rate, -1, 1) # Mutate bias random_uniform_mutation(child1_bias, mutation_rate, -1, 1) random_uniform_mutation(child2_bias, mutation_rate, -1, 1) else: raise Exception( 'Unable to determine valid mutation based off probabilities.')