###遺伝的アルゴリズムの勉強
platypus、deapを動かすのに必要な遺伝的アルゴリズムの知識を、日経ソフトウェアの2019年3月号の特集記事をもとに勉強しています。
####遺伝的アルゴリズムとは
遺伝アルゴリズムとは「選択(淘汰)」「交叉」「突然変異」と言った遺伝的進化を模したアルゴリズムです。
また遺伝的アルゴリズムは、乱数を多く利用するため、乱択アルゴリズムでもある。
解ける問題は次の2つを満たすこと。
・入力パラメーターに対する数値化(評価)ができる問題
・数値が最大化、最小化となる入力を探す問題
遺伝子:形質を表現するための構成要素
染色体:複数の遺伝子が集まったもの
個体:一つの染色体によって表現される形質(個体=染色体と言う場合もある。)
個体が環境にどれだけ適しているかの評価値が「適応度」
この適応度を高めるために3つの操作を行う。
選択(淘汰):適応度が高いものを残す(適応度が低いものを淘汰する)
交叉:2つの親から遺伝子を引き継いだ子を生成する
突然変異:個体の遺伝子を一定確率で変化させる。
交叉が遺伝的アルゴリズムの特徴である。
これらの操作が一回すべて終了すると世代が1つ進む。
####OneMax問題とは
OneMax問題とは、[1,0,0,0,1,0,1,1]のように1と0からなるリストの和が最大になるようなリストを探し出す問題です。
遺伝子:0または1
染色体:[1,0,0,0,1,0,1,1]
適応度:リストの和(この場合は、[1,0,0,0,1,0,1,1]=4)
プログラムは、次の要素でできている。
パラメータ設定
関数(適応度を計算、集団を適応度順にソート、選択:適応度の高い個体を残す、交叉、突然変異)
メイン処理(初期集団を生成、選択、交叉、突然変異)
####パラメーターの設定
パラメーターとして与えるのは、5つ。遺伝子の長さ、個体数、世代数、突然変異の確率、選択割合
import random
import copy
# パラメータ
LIST_SIZE = 10 # 0/1のリスト長(遺伝子長)
POPULATION_SIZE = 10 # 集団の個体数
GENERATION = 25 # 世代数
MUTATE = 0.1 # 突然変異の確率
SELECT_RATE = 0.5 # 選択割合
この例では、GENERATION = 25
なので、25世代で終了します。一般的には、適応度が変化しなくなったら終了するなどにします。LIST_SIZE
を大きくした場合は、POPULATION_SIZE``GENERATION
を大きくしないと最適解に到達しない可能性がある。突然変異の確率、選択割合などのチューニングが重要だが、今回は適当に決めている。
####関数
# 適応度を計算する
def calc_fitness(individual):#individualは染色体
return sum(individual) # 染色体リスト要素の合計
# 集団を適応度順にソートする
def sort_fitness(population):
# 適応度と個体をタプルにしてリストへ格納する
fp = []
for individual in population:
fitness = calc_fitness(individual)#適応度を計算してfitnessに入れる。
fp.append((fitness, individual)) #fp に適応度と染色体を順に入れる
fp.sort(reverse=True) # 適用度でソート(降順)
sorted_population = [] # ソートした個体を格納する集団
for fitness, individual in fp: # 個体を取り出し、集団に格納する
sorted_population.append(individual)
return sorted_population #この中に、適用度が高い順にソートされた適用度と染色体が保存されている。
# 選択
# 適応度の高い個体を残す
def selection(population):
sorted_population = sort_fitness(population)#sort_populationにソートされた適用度と染色体が入っている。
#残す個体数。パラメーター設定で0.5なので個体数10×0.5で5個残る
n = int(POPULATION_SIZE * SELECT_RATE)
return sorted_population[0:n]#最初から5個が残る。0,1,2,3,4
#交叉
#まず、ind1をコピーして新しい個体indを作る。その後、ランダムに決めたr1~r2の範囲をind2の遺伝子に置き換える
def crossover(ind1, ind2):
r1 = random.randint(0, LIST_SIZE - 1)#r1の位置をランダムに決める
r2 = random.randint(r1 + 1, LIST_SIZE)#r1以降でランダムにr2の位置を決める。
ind = copy.deepcopy(ind1)#ind1のコピーを作る
ind[r1:r2] = ind2[r1:r2]#染色体ind2からランダムにきめたr1からr2の範囲を取り出し、ind1のコピーへ置き換える、。
return ind
#突然変異:設定した突然変異確率に従って突然変異させる
def mutation(ind1):
ind2 = copy.deepcopy(ind1)#コピーでind2を作る。
for i in range(LIST_SIZE):
if random.random() < MUTATE:#ランダムに数値を作り、突然変異率以下なら突然変異がおきる
ind2[i] = random.randint(0, 1)#突然変異のおきたところに0か1を入れる
return ind2
####メイン処理(初期集団を生成、選択、交叉、突然変異)
#初期集団を生成
population = [] # 集団
for i in range(POPULATION_SIZE):
individual = [] # 染色体
for j in range(LIST_SIZE):
# 0/1を乱数で追加
individual.append(random.randint(0, 1))
population.append(individual)
for generation in range(GENERATION):
print(str(generation + 1) + u"世代")
#選択
population = selection(population)
# 交叉と突然変異を繰り返す。交叉と突然変異で増やす個体数
n = POPULATION_SIZE - len(population)
for i in range(n):
# 集団から2個体をランダムに選び、
# 交叉した個体を生成する
r1 = random.randint(0, len(population) - 1)
r2 = random.randint(0, len(population) - 1)
#交叉
individual = \
crossover(population[r1], population[r2])
#突然変異
individual = mutation(individual)
#集団に追加する
population.append(individual)
for individual in population:
print(individual)
これでOne-Max問題が解けるプログラムです。
####結果です。
1世代
[1, 1, 1, 0, 1, 1, 0, 0, 1, 1]
[1, 0, 1, 1, 0, 0, 1, 1, 1, 0]
[1, 0, 1, 0, 1, 1, 0, 1, 1, 0]
[1, 0, 1, 1, 1, 0, 0, 0, 0, 1]
[0, 0, 1, 1, 0, 1, 1, 0, 1, 0]
[1, 1, 1, 0, 1, 1, 0, 0, 1, 0]
[0, 1, 1, 0, 1, 1, 0, 0, 1, 0]
[0, 1, 1, 0, 1, 1, 1, 0, 1, 0]
[1, 0, 1, 1, 0, 1, 0, 0, 1, 0]
[1, 0, 1, 1, 1, 1, 0, 1, 1, 0]
第14世代で、最適解が見つかりました。
14世代
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 0, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 0, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 0, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 0, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 0, 1, 1, 1]
[1, 1, 1, 1, 1, 0, 0, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 0, 0, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 0, 1, 1, 1]
第20世代で、6つまで最適解になりました。
20世代
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 0, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 0]
[1, 1, 1, 0, 1, 1, 1, 1, 1, 1]
第25世代ですべて最適解になりました。
25世代
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
これが基本のOneMax問題の解き方です。
次回は、巡回セールスマン問題を解こうと思います。