deap 使い方メモ (OneMax)

# はじめに
deapの使い方メモです。
ここでは、deap\examples\onemax_numpy.py を例にメモを記載します。

※ setupは記載しません。

deap
_https://deap.readthedocs.io/en/master/_

deap github
_https://github.com/DEAP/deap_

# OneMax？
[1, 1, 1, 1, 0, 0, 0, 1, 1, 0]のように0,1からなる数列の和を最大化する問題です。
⇒　求めるべき解は[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]となります。

下記コードでは、長さ100の数列を問題としています。


# コードと実行結果
メモ付きコードを貼ります。

<details><summary> メモ付きコード </summary><div>

```python:my_onemax_numpy.py
import random
import numpy
from deap import algorithms
from deap import base
from deap import creator
from deap import tools

# parameter
n_gene = 100            # 1個体あたりの遺伝子数
n_individuals = 300     # 1世代あたりの個体数
n_generations = 1000    # 世代数

p_cxpb = 0.5            # 交叉率（交叉する個体数）
p_mutpb = 0.2           # 変異率（変異する個体数）
p_mutate = 0.05         # 変異確率（遺伝子の変異率）

n_tournsize = 3         # トーナメントサイズ

def evalOneMax(individual):
    """評価関数 onemax"""
    return sum(individual),

def init_creator():
    """目的関数の方向性を設定"""
    # 評価する目的関数は1つ、個体の適応度を最大化
    creator.create("FitnessMax", base.Fitness, weights=(1.0,))
    # numpy.ndarrayクラスを継承して、
    # fitness=creator.FitnessMaxというメンバ変数を追加したIndividualクラスを作成する
    creator.create("Individual", numpy.ndarray, fitness=creator.FitnessMax)
    return creator

def my_gene_generator(min, max):
    """遺伝子生成関数"""
    return random.randint(min, max)

def init_generator(creator):
    """遺伝子、個体、世代の生成手法の設定"""
    toolbox = base.Toolbox()
    # 遺伝子を生成する関数の定義
    toolbox.register("attr_bool", my_gene_generator, 0, 1)
    # 個体を生成する関数の定義
    toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.attr_bool, n_gene)
    # 世代を生成する関数の定義
    toolbox.register("population", tools.initRepeat, list, toolbox.individual)
    return toolbox

def operator_registration(toolbox):
    """評価関数・戦略の設定"""
    toolbox.register("evaluate", evalOneMax)                                # evaluate = 評価関数
    toolbox.register("mate", tools.cxTwoPoint)                              # mate = 2点交叉
    toolbox.register("mutate", tools.mutFlipBit, indpb=p_mutate)            # mutate = bit反転
    toolbox.register("select", tools.selTournament, tournsize=n_tournsize)  # select = tournament(3)

def stats_register():
    """ステート定義設定"""
    stats = tools.Statistics(lambda ind: ind.fitness.values)
    stats.register("avg", numpy.mean)
    stats.register("std", numpy.std)
    stats.register("min", numpy.min)
    stats.register("max", numpy.max)
    return stats

def get_cxpoint(size):
    """2点交叉用 2点の設定"""
    cxpoint1 = random.randint(1, size)
    cxpoint2 = random.randint(1, size - 1)
    if cxpoint2 >= cxpoint1:
        cxpoint2 += 1
    else: # Swap the two cx points
        cxpoint1, cxpoint2 = cxpoint2, cxpoint1
    return cxpoint1, cxpoint2

def cxTwoPointCopy(ind1, ind2):
    """numpy用 2点交叉"""
    size = min(len(ind1), len(ind2))
    cxpoint1, cxpoint2 = get_cxpoint(size)
    
    ind1[cxpoint1:cxpoint2], ind2[cxpoint1:cxpoint2] \
        = ind2[cxpoint1:cxpoint2].copy(), ind1[cxpoint1:cxpoint2].copy()
        
    return ind1, ind2

def set_seed(seed=42):
    random.seed(seed)

def main(toolbox):
    set_seed()

    # 初期世代の生成
    pop = toolbox.population(n=n_individuals)

    # numpy用 elite=1 戦略
    hof = tools.HallOfFame(1, similar=numpy.array_equal)

    # stats定義
    stats = stats_register()

    # main loop
    algorithms.eaSimple(pop, toolbox, cxpb=p_cxpb, mutpb=p_mutpb, ngen=n_generations, stats=stats,
                        halloffame=hof)

    # best個体の表示
    best_ind = tools.selBest(pop, 1)[0]
    print("Best individual is \n Eval:\n  %s, \n Gene:\n  %s" % (best_ind.fitness.values, best_ind))

    return pop, stats, hof


if __name__ == "__main__":
    # 目的関数の方向性を設定
    creator = init_creator()

    # 遺伝子、個体、世代の生成手法の設定
    toolbox = init_generator(creator)

    # 進化手法の設定
    operator_registration(toolbox)

    # メインルーチン
    main(toolbox)
```
</details></div>

実行結果

<details><summary> 実行結果 </summary><div>

```
gen     nevals  avg     std     min     max
0       300     49.88   4.82344 36      64
1       172     54.27   3.60792 45      68
2       181     57.24   3.32    47      68 
・・・
987     183     98.9767 2.3642  89      100
988     171     99.1467 2.02941 89      100
989     192     99.0567 2.26424 90      100
990     176     99.1167 2.12047 88      100
991     183     99.2733 1.94901 90      100
992     164     98.97   2.30704 90      100
993     178     99.03   2.0581  90      100
994     188     98.9767 2.24264 89      100
995     174     98.95   2.3211  86      100
996     177     98.83   2.33833 90      100
997     186     99.0367 2.15453 89      100
998     177     98.9833 2.16095 91      100
999     175     98.9933 2.25683 90      100
1000    181     98.8967 2.23443 89      100
Best individual is
 Eval:
  (100.0,),
 Gene:
  [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]
```
</details></div>

# はまりどころ
①　評価関数の方向性（最大化/最小化）を定める方法がよくわからない
・適応度を最小化する際は負の重みを、最大化する際には正の重みをつける（値は何でも良い）
・weightはタプルである必要がある

```python:creator.py
# 評価する目的関数は1つ、個体の適応度を最大化
creator.create("FitnessMax", base.Fitness, weights=(1.0,))
# 評価する目的関数は2つ、1つ目は適応度最小化、2つ目は最大化
creator.create("FitnessMulti", base.Fitness, weights=(-1.0, 1.0))
```

② 遺伝子の生成・評価はどうするの？
・toolboxに生成関数を登録する
　ここでは、my_gene_generatorという0 or 1 をランダムに生成する関数を
　toolbox へ "attr_bool" という名前で登録する

```python:gene_generator.py
def my_gene_generator(min, max):
    """遺伝子生成関数"""
    return random.randint(min, max)

def init_generator(creator):
    """遺伝子、個体、世代の生成手法の設定"""
    toolbox = base.Toolbox()
    # 遺伝子を生成する関数の定義
    toolbox.register("attr_bool", my_gene_generator, 0, 1)

```

・toolboxに評価関数を登録する
　ここでは、evalOneMaxという関数を
　toolbox へ "evaluate" という名前で登録する

```python:gene_eval.py
def evalOneMax(individual):
    """評価関数 onemax"""
    return sum(individual),

def operator_registration(toolbox):
    """評価関数・戦略の設定"""
    toolbox.register("evaluate", evalOneMax) # evaluate = 評価関数

```

③ メインループどこ？
・ここです

```python
algorithms.eaSimple(pop, toolbox, cxpb=p_cxpb, mutpb=p_mutpb, ngen=n_generations, stats=stats, halloffame=hof)
```