Spaces:
Sleeping
Sleeping
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 |