Spaces:
Sleeping
Sleeping
# -*- coding: utf-8 -*- | |
"""simulation.ipynb | |
Automatically generated by Colaboratory. | |
Original file is located at | |
https://colab.research.google.com/drive/1ZVm7p9Kb39Br5GUk1qVby8sG7YmlXLJX | |
- [遺伝アルゴリズム 参考記事](https://mori-memo.hateblo.jp/entry/2022/06/16/232644#%E5%88%9D%E6%9C%9F%E4%B8%96%E4%BB%A3%E3%81%AE%E4%BD%9C%E6%88%90) | |
- [バックアップ](https://colab.research.google.com/drive/1CfA0nBjBrrTS3lj0H5-TbOTTD0JdSCqW#scrollTo=rLmsKrtg1NPE) | |
- [【NASAより】火星の気温](https://mars.nasa.gov/files/mep/facts/Temperature-Mars-Facts.jpg) | |
""" | |
import numpy as np | |
import matplotlib.pyplot as plt | |
import random | |
from typing import Tuple, Dict, List | |
"""### HACK | |
1. (済) 画像生成実行colab で下記のシミュレーションを実行できるよう修正 | |
2. (済) 画像生成ワードを3つから6つに戻す | |
3. (済) 各パラメーターのスコアの推移を表示 | |
4. スコアがintデータから途中でfloat型になってしまう -> スコアは修正済み、遺伝子の中の数が変 | |
5. パラメーター調整 -> 気温や餌の量のしきい値、共同体の規模や世代数など | |
""" | |
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]]) -> 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_gemems = [] | |
for i in range(len(generation)): | |
# 最もスコアのよいgeneration を採用 | |
tournament = random.sample(generation_, TOUNAMENT_NUM) | |
max_genom = max(tournament, key=lambda x: x[1]) | |
selected_gemems.append(max_genom) | |
return selected_gemems | |
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): | |
'''交叉の関数''' | |
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): | |
for child in children: | |
if np.random.rand() < MUTATION_PB: | |
child.mutate() | |
return children | |
def mutate(children): | |
tmp_children = [] | |
for child in children: | |
individual, score = child[0], child[1] | |
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): | |
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): | |
'''遺伝的アルゴリズムのソルバー | |
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(GENERATIONS): | |
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_) | |
# --- Step3. Crossover (two_point_copy) | |
children = crossover(selected_genoms) | |
# --- Step4. Mutation | |
generation_ = mutate(children) | |
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 | |
"""### 実行""" | |
# シード値の固定 | |
SEED = 42 | |
np.random.seed(seed=SEED) | |
random.seed(SEED) | |
# パラメーター | |
POPURATIONS = 200 | |
GENOMS_SIZE = 4 # 遺伝配列 0, 1 のどちらかを要素とした配列のサイズ | |
GENERATIONS = 1000 # 世代数 | |
CROSSOVER_PB = 0.5 # cross over(交差) する確率 | |
MUTATION_PB = 0.7 # mutation(突然変異)する確率 | |
TOUNAMENT_NUM = 10 # トーナメント方式で競わせる数 | |
MAX_NUM = 4 # fitness の最大値 | |
THREASHOLD_TEMPRETURE = 5 | |
THREASHOLD_FOOD_VOLUME = 1500 | |
# 渡すデータの加工イメージ | |
# dict 型 {項目(key): 画像生成プログラムに与えるキーワード{value}} | |
word_dict = { | |
"body_size": ["Very small", "Small", "Ordinary size", "Big", "Giant"], | |
"body_hair": ["Very Skinny", "Skinny", "Ordinary Skin", "Furry", "Very Furry"], | |
"herd_num": ["Lone", "Pair", "Eight", "Many", "Too Many"], | |
"eating": ["Very Herbivorous", "Herbivorous", "Omnivorous", "Carnivorous","Very Carnivorous"], | |
"body_color": ["Lightest", "Lighter", "Ordinary Tone", "Darker", "Darkest"], | |
"ferocity": ["Very Gentle", "Gentle", "Ordinary", "Ferocious", "Very Ferocious"], | |
} | |
# create first genetarion | |
generation = create_generation(POPURATIONS, GENOMS_SIZE) | |
# アルゴリズムの実行 | |
best, worst = ga_solve(generation) | |
# 抽出する世代 | |
first_generation = 0 | |
midle_generation = len(best) // 2 | |
last_generation = -1 | |
first_generation_word = get_word_for_image_generate(word_dict, best, first_generation) + " alien from Mars" | |
midle_generation_word = get_word_for_image_generate(word_dict, best, midle_generation) + " alien from Mars" | |
last_generation_word = get_word_for_image_generate(word_dict, best, last_generation) + " alien from Mars" | |
# 抽出する世代のワードを出力 | |
print() | |
print( | |
"最初の世代:", first_generation_word, "\n", | |
f"中間の{midle_generation}世代目:", midle_generation_word, "\n", | |
f"最後の{GENERATIONS}世代目:", last_generation_word, "\n", | |
) | |