import random from typing import Tuple, Dict, List import matplotlib.pyplot as plt import numpy as np class Individual: '''各個体のクラス args: 個体の持つ遺伝子情報(np.array)''' def __init__(self, genom_size): self.body_hair = np.random.randint(0, 2, genom_size).tolist() self.body_size = np.random.randint(0, 2, genom_size).tolist() self.herd_num = np.random.randint(0, 2, genom_size).tolist() self.eating = np.random.randint(0, 2, genom_size).tolist() self.body_color = np.random.randint(0, 2, genom_size).tolist() self.ferocity = np.random.randint(0, 2, genom_size).tolist() self.all_genoms = { "body_hair": self.body_hair, "body_size": self.body_size, "herd_num": self.herd_num, "eating": self.eating, "body_color": self.body_color, "ferocity": self.ferocity, } self.fitness = { "body_hair": 0, "body_size": 0, "herd_num": 0, "eating": 0, "body_color": 0, "ferocity": 0, } # 個体の適応度(set_fitness関数で設定) self.set_fitness() self.set_all_genoms() def set_fitness(self): '''個体に対する目的関数(OneMax)の値をself.fitnessに代入''' self.fitness = {key: sum(value) for key, value in self.all_genoms.items()} def get_fitness(self): '''self.fitnessを出力''' return self.fitness def set_all_genoms(self): '''self.all_parameterの中身をself.body_hair以下に代入する''' self.body_hair = self.all_genoms["body_hair"] self.body_size = self.all_genoms["body_size"] self.herd_num = self.all_genoms["herd_num"] self.eating = self.all_genoms["eating"] self.body_color = self.all_genoms["body_color"] self.ferocity = self.all_genoms["ferocity"] def mutate(self): '''遺伝子の突然変異''' for i, (parameter, genom) in enumerate(self.all_genoms.items()): tmp = genom.copy() i = np.random.randint(0, len(genom) - 1) tmp[i] = float(not genom[i]) self.all_genoms[parameter] = tmp self.set_all_genoms() self.set_fitness() def random_temperature() -> float: """ 火星の気温20℃〜-140℃の範囲でrandomにfloat値を返す args times (int): 試行回数 return float: ランダムに作成した 火星の気温 """ temperature = random.uniform(-140, 30) return temperature def random_food_volume(food_volume): """ 餌の量 args times (int): 試行回数 return float: ランダムに作成した 火星の気温 """ food_volume += random.randint(-100, 100) if food_volume < 0: food_volume = 0 return food_volume def create_generation(POPURATIONS, GENOMS_SIZE): '''初期世代の作成 return: 個体クラスのリスト''' generation = {} for i in range(POPURATIONS): individual = Individual(GENOMS_SIZE) generation[individual] = 0 return generation def select_tournament( generation_: List[Tuple[Individual, int]], TOUNAMENT_NUM ) -> List[Tuple[Individual, int]]: """ 選択の関数(トーナメント方式)。すべてのgenerationから3つ選び、強い(scoreが最も高い)genomを1つ選ぶ。これをgenerationのサイズだけ繰り返す args generation List[Tuple[Individual, int]]: Individual で作成したゲノム情報 [["body_hair"], ["body_size"], ["herd_num"]] , 評価score return List[Tuple[Individual, int]] : トーナメント戦で生き残ったゲノム1つ """ selected_genoms = [] for i in range(len(generation_)): # 最もスコアのよいgeneration を採用 tournament = random.sample(generation_, TOUNAMENT_NUM) max_genom = max(tournament, key=lambda x: x[1]) selected_genoms.append(max_genom) return selected_genoms def cross_two_point_copy(child1, child2): '''二点交叉''' new_child1 = {} new_child2 = {} for parameter_genom_1, parameter_genom_2 in zip(child1[0].all_genoms.items(), child2[0].all_genoms.items()): size = len(parameter_genom_1[1]) tmp_child_parameter1 = parameter_genom_1[0] tmp_child_parameter2 = parameter_genom_2[0] tmp_child_genom1 = parameter_genom_1[1].copy() tmp_child_genom2 = parameter_genom_2[1].copy() cxpoint1 = np.random.randint(1, size) cxpoint2 = np.random.randint(1, size - 1) if cxpoint2 >= cxpoint1: cxpoint2 += 1 else: cxpoint1, cxpoint2 = cxpoint2, cxpoint1 tmp_child_genom1[cxpoint1:cxpoint2], tmp_child_genom2[cxpoint1:cxpoint2] = tmp_child_genom2[cxpoint1:cxpoint2].copy(), tmp_child_genom1[cxpoint1:cxpoint2].copy() child1[0].all_genoms[tmp_child_parameter1] = tmp_child_genom1 child2[0].all_genoms[tmp_child_parameter2] = tmp_child_genom2 child1[0].set_all_genoms() child1[0].set_fitness() child2[0].set_all_genoms() child2[0].set_fitness() return child1, child2 def crossover(selected, POPURATIONS, CROSSOVER_PB): '''交叉の関数''' children = [] if POPURATIONS % 2: selected.append(selected[0]) for child1, child2 in zip(selected[::2], selected[1::2]): if np.random.rand() < CROSSOVER_PB: child1, child2 = cross_two_point_copy(child1, child2) children.append(child1) children.append(child2) children = children[:POPURATIONS] return children def mutate(children, MUTATION_PB): tmp_children = [] for child in children: individual, score = child[0], child[1] if np.random.rand() < MUTATION_PB: individual.mutate() tmp_children.append((individual, score)) return tmp_children def reset_generation_score(generation_): for i, (individual, score) in enumerate(generation_): generation_[i] = (individual, 0) return generation_ def scoring(generation_, temperature, food_volume): MAX_NUM = 4 # fitness の最大値 THREASHOLD_TEMPRETURE = 10 # score の判定に用いる気温のしきい値 THREASHOLD_FOOD_VOLUME = 3000 # score の判定に用いる食料のしきい値 generation_ = reset_generation_score(generation_) # scoring を実施 for i, (individual, score) in enumerate(generation_): # 各パラメーターの特性値を探索 for parameter, fitness in individual.get_fitness().items(): # 気温が高い if temperature > THREASHOLD_TEMPRETURE: if parameter == "body_hair": # body_hair が小さいほうが有利、大きいほうが不利 score += MAX_NUM - fitness elif parameter == "body_size": # body_size が小さいほうが有利、大きいほうが不利 score += MAX_NUM - fitness elif parameter == "body_color": # body_color が暗い方が有利、明るいほうが不利 score += fitness # 気温が低い else: if parameter == "body_hair": # body_hair が大きいほうが有利、小さいほうが不利 score += fitness elif parameter == "body_size": # body_size が大きいほうが有利、小さいほうが不利 score += fitness elif parameter == "body_color": # body_color が明るい方が有利、暗いほうが不利 score += MAX_NUM - fitness # エサが多い if food_volume > THREASHOLD_FOOD_VOLUME: if parameter == "body_size": # body_size が大きいほうが有利、小さいほうが不利 score += fitness elif parameter == "herd_num": # herd_num が大きいほうが有利、小さいほうが不利 score += fitness elif parameter == "eating": # eating が大きい(肉食)ほうが有利、小さい(草食)ほうが不利 score += fitness # エサが少ない else: if parameter == "body_size": # body_size が小さいほうが有利、大きいほうが不利 score += MAX_NUM - fitness elif parameter == "herd_num": # herd_num が小さいほうが有利、大きいほうが不利 score += MAX_NUM - fitness elif parameter == "eating": # eating が小さい(草食)ほうが有利、大さい(肉食)ほうが不利 score += MAX_NUM - fitness # 強さ if parameter == "body_size": # body_size が大きいほうが有利、小さい方が不利 score += fitness elif parameter == "herd_num": score += fitness elif parameter == "ferocity": # ferocity が大きい(凶暴)ほうが有利、小さい(おとなしい)ほうが不利 score += fitness # score を更新 generation_[i] = (individual, int(score)) return generation_ def ga_solve(generation, NUM_GENERATION, POPURATIONS, TOUNAMENT_NUM, CROSSOVER_PB, MUTATION_PB): '''遺伝的アルゴリズムのソルバー return: 最終世代の最高適応値の個体、最低適応値の個体''' best = [] worst = [] temperature_transition = [] food_volume = 500 food_volume_transition = [] parameter_transiton = { "body_size" : [], "body_hair" : [], "herd_num" : [], "eating" : [], "body_color" : [], "ferocity" : [], } # --- Generation loop print('Generation loop start.') # Dict[Individual, int] から List[Tuple(individual, int)]へ変換 # Dict だと Key の重複ができないため generation_ = [(individual, score) for individual, score in generation.items()] for i in range(NUM_GENERATION): temperature = random_temperature() temperature_transition.append(temperature) food_volume = random_food_volume(food_volume) food_volume_transition.append(food_volume) # スコアリング generation_ = scoring(generation_, temperature, food_volume) # --- Step1. Print fitness in the generation best_individual_score = max(generation_, key=lambda x: x[1]) best.append(best_individual_score[0].fitness) worst_individual_score = min(generation_, key=lambda x: x[1]) worst.append(worst_individual_score[0].fitness) # print("Generation: " + str(i) \ # + ": Best fitness: " + str(best_individual_score[0].fitness) + "Best fitness score: " + str(best_individual_score[1]) \ # + ". Worst fitness: " + str(worst_individual_score[0].fitness) + "Worst fitness score: " + str(worst_individual_score[1]) # ) # --- Step2. Selection (Roulette) selected_genoms = select_tournament(generation_, TOUNAMENT_NUM) # --- Step3. Crossover (two_point_copy) children = crossover(selected_genoms, POPURATIONS, CROSSOVER_PB) # --- Step4. Mutation generation_ = mutate(children, MUTATION_PB) for parameter, genom in best_individual_score[0].all_genoms.items(): parameter_transiton[parameter].append(sum(genom)) best_individual_score[0].set_all_genoms() best_individual_score[0].set_fitness() print("Generation loop ended. The best individual: ") print(best_individual_score[0].all_genoms) plt.figure(figsize=(20, 5)) plt.title("temperature") plt.plot(temperature_transition) plt.ylabel("temperature Celsius") plt.xlabel("generation") plt.savefig("simlation_tempreture.png") plt.figure(figsize=(20, 5)) plt.title("food volume") plt.plot(food_volume_transition) plt.ylim(0); plt.ylabel("food volume") plt.xlabel("generation") plt.savefig("simlation_food_volume.png") plt.figure(figsize=(20, 16)) for i, (parameter, transition) in enumerate(parameter_transiton.items()): plt.subplot(6, 1, i+1) plt.title(parameter) plt.plot(transition, label=parameter) plt.legend() plt.ylim(0, 4) plt.tight_layout() plt.savefig("each_parameter_transition.png") return best, worst def get_word_for_image_generate(word_dict, best, index): # アルゴリズムの結果に対応するwordを抽出 word_list = [word_dict[parameter][int(fitness)] for parameter, fitness in best[index].items()] # 最終的な I/F 補足: スペース区切りの文字列 を渡す, 英語もありうる word = " ".join(word_list) return word