遺伝的アルゴリズムとは?仕組みとDEAPでのPython実装をわかりやすく解説
遺伝的アルゴリズム(GA:Genetic Algorithm)は、生物の進化になぞらえて「良い解を残し、掛け合わせ、まれに変異させる」を繰り返し、答えの候補を少しずつ改良していく最適化手法です。数式の微分に頼らないため、勾配が定義できない問題や、組み合わせが膨大で総当たりできない問題に使えます。この記事では選択・交叉・突然変異という3つの操作の役割をわかりやすく整理したうえで、PythonライブラリDEAP 1.4.4で実際に動くコードを示し、パラメータが収束速度にどう効くかを30試行の実測値で示します。
まとめ
- 遺伝的アルゴリズムは、選択(良い個体を残す)→交叉(掛け合わせる)→突然変異(一部を変える)を世代ごとに繰り返す探索手法。最適解を保証しない代わりに、微分不能・組み合わせ爆発する問題でも実用的な解に到達できます。
- Pythonでの実装に最も広く使われるライブラリの一つがDEAP(Distributed Evolutionary Algorithms in Python)。最新版は1.4.4(2026年4月18日公開・日本時間)で、Python 3.13でも動作します。
- パラメータの効き方は実測すると明確です。100ビットのOneMax問題では、突然変異率indpbは0.02が最速(中央値56世代)、0.1まで上げると30試行中3回しか最適解に到達できません。交叉率は0.8で頭打ちになります(0.95に上げても43.0世代で変わりません)。
- 個体数を増やすと世代数は減るが、計算コスト(適応度の評価回数)はむしろ増えます。個体数30なら中央値3,162回で解けた問題が、個体数400では7,992回かかりました。「世代数が少ない=速い」ではありません。
- 制約が線形で表せて厳密な最適解が要るなら、GAではなく数理最適化のソルバを使うべきです。GAが向くのは、目的関数がブラックボックスで、良い解が早く欲しい場面です。
遺伝的アルゴリズムの仕組み
選択・交叉・突然変異を繰り返す探索ループ
遺伝的アルゴリズムは、ジョン・ホランドが1975年の著書『Adaptation in Natural and Artificial Systems』で体系化した手法です。解の候補を「個体」、その集まりを「集団」と呼び、次の流れを1世代として繰り返します。
- 初期集団の生成:解の候補をランダムに複数作る
- 適応度の評価:それぞれの候補がどれくらい良いかを数値化する
- 選択:適応度の高い個体を優先的に次世代の親として選ぶ
- 交叉:2つの親の遺伝子を組み替えて子を作る
- 突然変異:子の遺伝子の一部を確率的に書き換える
- 2に戻る(世代数の上限に達するか、解が十分良くなるまで)
3つの操作は役割が違います。選択は「良い方向に集団を寄せる」力、交叉は「既にある良い部品を組み合わせる」力、突然変異は「集団が持っていない値を新しく持ち込む」力です。突然変異がないと、初期集団に含まれない遺伝子は永遠に現れません。逆に突然変異が強すぎると、せっかく見つけた良い解が毎世代壊されてランダム探索に退化します。この均衡が後述するパラメータ調整の本質です。
進化計算・進化的アルゴリズムとの関係
生物進化を模した最適化手法の総称が進化計算(Evolutionary Computation)、あるいは進化的アルゴリズム(Evolutionary Algorithm)です。遺伝的アルゴリズムはその中の一手法で、ほかに遺伝的プログラミング(解を木構造のプログラムとして進化させる)、進化戦略、差分進化法(Differential Evolution:個体間の差分ベクトルで変異を作る連続値向けの手法)などがあります。
より広い分類では、GAはメタヒューリスティックに属します。最適性の保証と引き換えに、現実的な時間で「十分良い解」を得ることを狙う手法群です。焼きなまし法や粒子群最適化(PSO)も同じ枠に入ります。
遺伝的操作の中身と適応度関数の設計
解の表現(エンコーディング)が演算子を決める
実装で最初に決めるのは、解を何の並びとして表すかです。ここが決まると、使える交叉・突然変異の演算子が自動的に絞られます。選択肢は実質3つです。ビット列(0か1の並び。特徴選択やナップサック問題の採用・不採用)、実数列(連続値のパラメータ調整)、順列(要素を重複なく1回ずつ並べる。巡回路や作業順序)。順列だけは「重複してはいけない」という制約が表現自体に埋め込まれているため、専用の演算子が要ります。
交叉:一点・二点・一様・順序交叉の使い分け
交叉は親2個体から子を作る操作で、解の表現方法によって使える方式が決まります。DEAPでの関数名・必須引数とあわせて整理します。
| 方式 | DEAPの関数 | 必須引数 | 動作 | 適する表現 |
|---|---|---|---|---|
| 一点交叉 | cxOnePoint | なし | 1箇所で切って入れ替え | ビット列・実数列 |
| 二点交叉 | cxTwoPoint | なし | 2箇所で切り中間を入れ替え | ビット列・実数列 |
| 一様交叉 | cxUniform | indpb | 遺伝子ごとに確率で親を選ぶ | ビット列・実数列 |
| ブレンド交叉 | cxBlend | alpha | 親の値の内分・外分点を取る | 実数値 |
| 順序交叉 | cxOrdered | なし | 要素の重複を作らず順序を継承 | 順列(巡回路・スケジュール) |
必須引数の欄は、toolbox.registerで登録するときに値を渡さないとTypeErrorになる引数です。突然変異側も同様で、mutFlipBitとmutShuffleIndexesはindpb、mutGaussianはmu・sigma・indpbの3つを渡す必要があります。
選択を誤ると解が壊れます。巡回セールスマン問題のように「30都市を重複なく1回ずつ通る順列」を扱う場合、二点交叉を使うと同じ都市が2回現れる不正な個体ができてしまうためです。順列表現には順序交叉(cxOrdered)や部分写像交叉(cxPartialyMatched)を使います。
突然変異:ビット反転・ガウス・シャッフル
突然変異も表現に対応させます。ビット列にはmutFlipBit(各ビットを確率indpbで反転)、実数値にはmutGaussian(正規分布のノイズを加える)、順列にはmutShuffleIndexes(要素の位置を入れ替える)を使います。
ここで混同しやすいのが、DEAPのmutpbとindpbという2つの確率です。mutpbは「その個体に突然変異操作を適用するかどうか」の確率、indpbは「操作が適用された個体の中で、各遺伝子を書き換えるかどうか」の確率です。100ビットの個体でmutpb=0.2、indpb=0.01なら、個体の2割が変異対象となり、その個体では平均1ビットが反転します。片方だけを見て「突然変異率0.2」=1個体あたり20ビットが変わると考えると、変異対象になった個体で比べても20倍、集団全体の平均(0.2ビット)で比べれば100倍の見誤りになります。
適応度関数の設計と適応度地形
適応度関数は「候補がどれくらい良いか」を1つ以上の数値で返す関数で、GAの性能はここでほぼ決まります。数理最適化でいう目的関数と制約条件を、1つの評価値にまとめたものと考えると位置づけがつかめます。設計時の判断基準は3つです。
連続的な勾配を持たせる。制約を1つでも破ったら適応度0、という関数にすると、集団のほとんどが0になって選択が働きません。違反量に比例したペナルティを引く形にすれば、「違反が少ない個体ほどマシ」という情報が選択に伝わります。
評価コストを抑える。GAは適応度関数を数千回から数万回呼びます。後述の実測では、100ビットのOneMax問題を個体数100の基準設定で解くのに中央値4,005回の評価が必要でした。1回の評価にシミュレーションで10秒かかるなら、それだけで11時間を超えます。評価が重い場合は、代理モデルで近似するか、DEAPのtoolbox.register("map", ...)で並列化を検討します。
目的が複数あるなら重み付き和にまとめず多目的化する。コストと納期のように単位の違う目的を無理に足し合わせると、結果を支配するのは目的関数ではなく重みの設定です。DEAPはweights=(-1.0, -1.0)のように複数の重みを指定でき、環境選択にtools.selNSGA2を使えば多目的遺伝的アルゴリズム(NSGA-II)としてパレート最適解の集合が得られます。ただしeaSimpleにそのまま差し込む形ではなく、公式のNSGA-II例では交配相手の選択にtools.selTournamentDCDを併用し、世代ループを自前で書きます。
なお適応度地形(fitness landscape)とは、解空間の各点に適応度を高さとして与えた地形のイメージです。山が1つだけの単峰性なら勾配法でも解けますが、山が多数ある多峰性の地形では、局所的な山の頂上(局所解)に留まらずに探索を続けられるかが分かれ目になります。GAが集団で探索する理由はここにあります。
DEAPによるPython実装
DEAP 1.4.4のインストールと4つの構成要素
DEAPはDistributed Evolutionary Algorithms in Pythonの略で、進化計算で最も広く使われるPythonライブラリの一つです。最新版は1.4.4(2026年4月18日公開・日本時間)。インストールはpipのみで完結します。
pip install deap
DEAPを読むうえで押さえるモジュールは4つです。creatorで適応度クラスと個体クラスを動的に作り、base.Toolboxに交叉・突然変異・選択・評価の関数を名前で登録し、toolsの既製の遺伝的操作を組み合わせ、algorithmsの進化ループを回す、という分担になっています。自前で世代ループを書くこともできるため、独自の打ち切り条件を入れたい場合も対応できます。よりAPIが簡素なPyGAD(最新版3.7.0)という選択肢もありますが、順列表現の交叉や多目的最適化まで扱うならDEAPが標準です。
細かい点ですが、インストールしたバージョンはpip show deapで1.4.4と表示される一方、deap.__version__は"1.4"を返します。バージョン判定を自動化する場合は、importlib.metadata.version("deap")を使ってください。
OneMax問題の実装コードと実行結果
OneMaxは「100個のビットをすべて1にする」という、GAの動作確認に使われる定番問題です。最適解の適応度は100と分かっているため、探索がどこまで進んだかを一目で判断できます。
import random
from deap import base, creator, tools, algorithms
creator.create("FitnessMax", base.Fitness, weights=(1.0,))
creator.create("Individual", list, fitness=creator.FitnessMax)
def eval_onemax(individual):
return (sum(individual),)
toolbox = base.Toolbox()
toolbox.register("attr_bool", random.randint, 0, 1)
toolbox.register("individual", tools.initRepeat, creator.Individual,
toolbox.attr_bool, n=100)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
toolbox.register("evaluate", eval_onemax)
toolbox.register("mate", tools.cxTwoPoint)
toolbox.register("mutate", tools.mutFlipBit, indpb=0.01)
toolbox.register("select", tools.selTournament, tournsize=3)
random.seed(42)
pop = toolbox.population(n=100)
hof = tools.HallOfFame(1)
algorithms.eaSimple(pop, toolbox, cxpb=0.5, mutpb=0.2, ngen=40,
halloffame=hof, verbose=False)
print("40世代後の最良適応度:", hof[0].fitness.values[0])
Python 3.13.14・DEAP 1.4.4・numpy 2.5.1で実行した結果は次のとおりです。
40世代後の最良適応度: 97.0
入門記事でよく使われる40世代という設定では、最適解の100に届かず97で止まりました。適応度関数の返り値がタプル((sum(individual),))である点も見落としやすい部分です。DEAPは多目的最適化を前提に設計されているため、目的が1つでもタプルで返す必要があります。
巡回セールスマン問題への応用(順序交叉)
順列を扱う例として、30都市の巡回セールスマン問題を解きます。weights=(-1.0,)で最小化に切り替え、交叉にcxOrdered、突然変異にmutShuffleIndexesを使う点が先ほどとの違いです。
import random, math
from deap import base, creator, tools, algorithms
random.seed(1)
N_CITY = 30
cities = [(random.uniform(0, 100), random.uniform(0, 100)) for _ in range(N_CITY)]
def total_distance(order):
d = 0.0
for i in range(len(order)):
x1, y1 = cities[order[i - 1]]
x2, y2 = cities[order[i]]
d += math.hypot(x2 - x1, y2 - y1)
return (d,)
creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
creator.create("Route", list, fitness=creator.FitnessMin)
toolbox = base.Toolbox()
toolbox.register("indices", random.sample, range(N_CITY), N_CITY)
toolbox.register("individual", tools.initIterate, creator.Route, toolbox.indices)
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
toolbox.register("evaluate", total_distance)
toolbox.register("mate", tools.cxOrdered)
toolbox.register("mutate", tools.mutShuffleIndexes, indpb=0.05)
toolbox.register("select", tools.selTournament, tournsize=3)
random.seed(42)
pop = toolbox.population(n=300)
hof = tools.HallOfFame(1)
algorithms.eaSimple(pop, toolbox, cxpb=0.8, mutpb=0.2, ngen=300,
halloffame=hof, verbose=False)
print("初期ルート(0番から順に巡回):", round(total_distance(list(range(N_CITY)))[0], 1))
print("GA最良ルート:", round(hof[0].fitness.values[0], 1))
初期ルート(0番から順に巡回): 1855.2
GA最良ルート: 523.6
都市を番号順に回る初期ルートの総距離1855.2に対し、300世代後の最良ルートは523.6まで短縮されました。出発点を固定しても巡回路の候補は29!通り(約8.8×10の30乗)あり総当たりは不可能ですが、GAは76,041回の適応度評価でこの水準に到達しています。この回数は個体数×世代数の90,000回より少なくなります。eaSimpleは交叉や突然変異で中身が変わった個体だけを再評価し、変化しなかった個体は前世代の適応度を使い回すためです。配送計画への適用は経路最適化の解説もあわせて参照してください。
収束を早めるパラメータチューニング(実測)
ここからは、100ビットのOneMax問題で最適解(適応度100)に到達するまでの世代数を、パラメータを変えて実測した結果です。乱数シード0〜29の30試行、上限400世代、基準設定は個体数100・cxpb=0.5・mutpb=0.2・indpb=0.01・tournsize=3としました。計測に使ったコードは次のとおりです。
import random, statistics
from deap import base, creator, tools, algorithms
creator.create("FitnessMax", base.Fitness, weights=(1.0,))
creator.create("Individual", list, fitness=creator.FitnessMax)
def eval_onemax(individual):
return (sum(individual),)
def build(indpb, tournsize):
tb = base.Toolbox()
tb.register("attr_bool", random.randint, 0, 1)
tb.register("individual", tools.initRepeat, creator.Individual,
tb.attr_bool, n=100)
tb.register("population", tools.initRepeat, list, tb.individual)
tb.register("evaluate", eval_onemax)
tb.register("mate", tools.cxTwoPoint)
tb.register("mutate", tools.mutFlipBit, indpb=indpb)
tb.register("select", tools.selTournament, tournsize=tournsize)
return tb
def gens_to_solve(pop_size, indpb, tournsize, cxpb, mutpb, seed, max_gen=400):
random.seed(seed)
tb = build(indpb, tournsize)
pop = tb.population(n=pop_size)
nevals = 0
for ind in pop:
ind.fitness.values = tb.evaluate(ind)
nevals += 1
for gen in range(1, max_gen + 1):
offspring = algorithms.varAnd(tb.select(pop, len(pop)), tb, cxpb, mutpb)
for ind in offspring:
if not ind.fitness.valid:
ind.fitness.values = tb.evaluate(ind)
nevals += 1
pop = offspring
if max(ind.fitness.values[0] for ind in pop) >= 100:
return gen, nevals
return None, nevals
results = [gens_to_solve(100, 0.01, 3, 0.5, 0.2, seed) for seed in range(30)]
solved = [r for r in results if r[0] is not None]
print("到達:", len(solved), "/ 30")
print("世代数の中央値:", statistics.median(g for g, _ in solved))
print("評価回数の中央値:", statistics.median(n for _, n in solved))
到達: 30 / 30
世代数の中央値: 64.5
評価回数の中央値: 4005.0
突然変異率:0.02が最速で0.1では探索が破綻
| indpb | 400世代以内に到達 | 世代数の中央値 |
|---|---|---|
| 0.005 | 30/30 | 72.0 |
| 0.01 | 30/30 | 64.5 |
| 0.02 | 30/30 | 56.0 |
| 0.05 | 30/30 | 118.0 |
| 0.1 | 3/30 | 305(到達した3試行のみ) |
indpb=0.1では到達できなかった27試行の平均最良適応度が98.3で、あと2ビットのところで足踏みしています。
この問題では遺伝子長が100なので、indpb=0.01は「1個体あたり平均1ビットが反転」に相当します。それを2ビット相当(0.02)に上げると56世代まで短縮できましたが、5ビット相当(0.05)では逆に倍以上かかり、10ビット相当(0.1)では400世代を回しても30試行中27回が最適解に届きませんでした。変異による破壊が改善を上回っている状態です。収束が遅いときに突然変異率を上げるのは、遺伝子長の1〜2%程度までが有効な範囲で、それ以上はランダム探索に近づくだけです。
選択圧:tournsize5以上で頭打ち
| tournsize | 世代数の中央値 |
|---|---|
| 2 | 76.5 |
| 3 | 64.5 |
| 5 | 54.5 |
| 10 | 53.0 |
トーナメント選択は、集団からtournsize個をランダムに取り出して最良の1個体を親にする方式です。この値を上げるほど選択圧が強まり、収束は速くなります。ただし2から3への変更で12世代縮んだのに対し、5から10では1.5世代しか縮んでおらず、頭打ちになります。
選択圧を上げると多様性が失われて局所解に陥る、という説明がよくされますが、多峰性の関数で確かめると単純な話ではありません。局所解が多数あるRastrigin関数(10次元、最小値0)で選択圧だけを変えて測ります。
import random, math, statistics
from deap import base, creator, tools, algorithms
DIM = 10
creator.create("FitnessMin", base.Fitness, weights=(-1.0,))
creator.create("Individual", list, fitness=creator.FitnessMin)
def rastrigin(individual):
return (10 * DIM + sum(x * x - 10 * math.cos(2 * math.pi * x)
for x in individual),)
def run(tournsize, seed, ngen=200):
random.seed(seed)
tb = base.Toolbox()
tb.register("attr_float", random.uniform, -5.12, 5.12)
tb.register("individual", tools.initRepeat, creator.Individual,
tb.attr_float, n=DIM)
tb.register("population", tools.initRepeat, list, tb.individual)
tb.register("evaluate", rastrigin)
tb.register("mate", tools.cxBlend, alpha=0.5)
tb.register("mutate", tools.mutGaussian, mu=0, sigma=0.5, indpb=0.2)
tb.register("select", tools.selTournament, tournsize=tournsize)
pop = tb.population(n=100)
hof = tools.HallOfFame(1)
algorithms.eaSimple(pop, tb, cxpb=0.7, mutpb=0.3, ngen=ngen,
halloffame=hof, verbose=False)
unique = len({tuple(round(v, 3) for v in ind) for ind in pop})
return hof[0].fitness.values[0], unique
for tournsize in (2, 3, 5, 10, 20):
results = [run(tournsize, seed) for seed in range(60)]
best = statistics.median(r[0] for r in results)
uniq = statistics.median(r[1] for r in results)
print(f"tournsize={tournsize:2d} 最良値の中央値={best:6.3f} ユニーク個体数の中央値={uniq:5.1f}")
乱数シード0〜59の60試行で得られた結果が次の表です。
| tournsize | 最良値の中央値(0が最適) | 最終世代のユニーク個体数 |
|---|---|---|
| 2 | 4.634 | 96/100 |
| 3 | 4.002 | 35/100 |
| 5 | 1.749 | 28.5/100 |
| 10 | 0.696 | 29.5/100 |
| 20 | 0.107 | 29/100 |
tournsize=2は集団の多様性を最もよく保っています(100個体中96個がユニーク)が、精度は最下位でした。逆にtournsize=20は多様性が3割まで落ちているのに最良値は0.107と最も最適解に近づいています。突然変異が十分に効いている構成(mutpb=0.3、mutGaussianのsigma=0.5・indpb=0.2)では、選択圧を強めても新しい解の供給が続くため、多様性の低下がそのまま探索の失敗にはつながりませんでした。この設定では「多様性の指標が下がった」ことだけを根拠に選択圧を下げると、精度を40倍以上悪化させます。判断は多様性ではなく到達した適応度で行ってください。上のコードのcxpb・sigma・indpbを変えれば傾向も変わるため、自分の問題では同じ形で測り直すことをおすすめします。
個体数:世代数の減少と評価回数の増加
| 個体数 | 世代数の中央値 | 評価回数の中央値 |
|---|---|---|
| 30 | 174.0 | 3,162 |
| 50 | 99.0 | 3,049 |
| 100 | 64.5 | 4,005 |
| 200 | 41.0 | 5,168 |
| 400 | 31.5 | 7,992 |
個体数を30から400へ増やすと、必要な世代数は174から31.5へ5.5分の1になります。ここだけ見れば「個体数を増やせば速い」と読めますが、実際の計算コストである適応度の評価回数は3,162回から7,992回へ2.5倍に増えています。世代数は計算量の指標にならないということです。1回の評価にシミュレーションや実機測定を伴う場合、コストを決めるのは評価回数なので、個体数はむしろ小さめから試すべきです。この問題では個体数50前後(3,049回)が最も評価回数が少なくなりました。
交叉率と世代数の目安
| cxpb | 世代数の中央値 |
|---|---|
| 0.0 | 150.0 |
| 0.3 | 77.0 |
| 0.5 | 64.5 |
| 0.8 | 44.0 |
| 0.95 | 43.0 |
交叉率0(突然変異と選択だけ)でも150世代で解けますが、0.8にすると44世代と3分の1以下になります。交叉は「別々の個体が持つ良い部分をまとめる」操作なので、部分解を組み合わせれば全体解になる問題ほど効きます。このOneMax問題では0.8で頭打ちになりました。部分解の組み合わせが全体解になる型の問題であれば、0.6〜0.9が出発点になります。
世代数の目安に固定値を決める意味はありません。上の表のとおり、同じ問題でもパラメータ次第で31世代から、400世代を回しても到達しない設定まで幅がありました。実務的には、世代数を打ち切り条件にするのではなく、「最良適応度がN世代連続で改善しなければ停止」という条件を自前のループに入れるほうが、無駄な計算を避けられます。DEAPのeaSimpleは世代数固定なので、この場合は自前でループを書きます。
他の最適化手法との使い分け
勾配法・数理最適化ソルバとの違い
| 手法 | 必要な前提 | 最適性 | 向く場面 |
|---|---|---|---|
| 勾配法 | 目的関数が微分可能 | 局所最適 | 単峰性・連続な問題 |
| 数理最適化ソルバ | 線形・整数計画で定式化できる | 厳密な最適解 | 制約が明示できる計画問題 |
| 遺伝的アルゴリズム | 適応度が計算できればよい | 保証なし | ブラックボックス・組み合わせ問題 |
| 差分進化法 | 連続値の解空間 | 保証なし | 連続値パラメータの調整 |
判断の分岐点は「問題を数式の制約として書けるかどうか」です。書けるならソルバにかけるほうが速く、しかも最適性の証明まで得られます。GAの出番は、目的関数がシミュレータや実測値でしか得られない、あるいは制約が複雑すぎて線形式に落とせない場合です。
遺伝的アルゴリズムを選ぶべきでない場面
次の3つに当てはまるなら、GAは選ばないほうがよいと考えます。
厳密な最適解が要求される場面。GAは最適解に到達したかどうかを自分で判定できません。上のOneMaxの実測でも、正解が100と分かっているから「到達した」と言えただけです。正解が未知の実問題では、得られた解が最適なのか、単に改善が止まっただけなのかを区別できません。監査や契約で最適性の根拠を示す必要があるなら、Gurobiのような数理最適化ソルバの証明付きの解を使うべきです。
適応度の評価が重い場面。実測のとおり数千回の評価が必要です。1回10分かかる構造解析を回すなら、GAをそのまま適用する前に代理モデル(サロゲートモデル)やベイズ最適化を検討してください。ベイズ最適化は数十〜数百回の評価で済むよう設計された手法です。
結果の再現性・説明責任が重い場面。GAは乱数に依存するため、シードを固定しない限り実行ごとに違う解が出ます。上のRastrigin関数の実験でも、同じ設定の60試行で結果に幅が出たため中央値で比較しました。「なぜこの解になったのか」を説明する必要がある業務では、この性質が障害になります。
実務での応用例:シフト作成・配送計画・ハイパーパラメータ探索
GAが実際に使われるのは、組み合わせが膨大で、かつ「そこそこ良い解」で十分な問題です。
シフト・勤務表の自動作成
従業員の希望、資格要件、連続勤務の制限といった条件を適応度のペナルティとして表現します。全条件を満たす解が存在しないことも多く、違反の少ない解を返せるGAの性質が実務に合います。
配送ルートの最適化
上で実装した巡回セールスマン問題の拡張です。車両台数や積載量、時間指定の制約が加わると厳密解法では時間内に解けなくなるため、GAを含むメタヒューリスティックが使われます。
機械学習のハイパーパラメータ探索
学習率や層の数のように、勾配が定義できないパラメータの調整に使えます。ただし1回の評価がモデルの学習1回分なので、評価回数の少ないベイズ最適化のほうが選ばれる場面も多くあります。ニューラルネットワークの構造自体を進化させるニューロエボリューションもこの系統です。
設計パラメータの最適化
材料配合や部品形状のように、シミュレーションでしか性能を評価できない設計問題では、複数の目的(強度と重量など)を同時に扱えるNSGA-IIが使われます。
よくある質問
DEAPとは何の略ですか?
Distributed Evolutionary Algorithms in Python(Pythonによる分散進化的アルゴリズム)の略です。遺伝的アルゴリズムをはじめとする進化計算のためのPythonライブラリで、最新版は1.4.4(2026年4月18日公開・日本時間)です。pipでインストールでき、Python 3.13でも動作します。
遺伝的アルゴリズムの収束を早めるには何を変えればよいですか?
効果が大きい順に、交叉率を0.8前後まで上げる、トーナメントサイズを3から5へ上げる、突然変異率indpbを遺伝子長の1〜2%に合わせる、の3つです。100ビットのOneMax問題での実測では、交叉率0.5から0.8で中央値64.5世代が44.0世代に、tournsize3から5で54.5世代に短縮しました。ただし突然変異率を遺伝子長の5%以上にすると逆に遅くなり、10%(indpb=0.1)では30試行中27回が400世代以内に最適解へ到達しませんでした。
世代数と個体数はどのくらいに設定すればよいですか?
固定の目安はなく、問題とパラメータで大きく変わります。同じOneMax問題でも設定次第で31世代から、400世代を回しても到達しない場合まで幅がありました。個体数については、増やすと世代数は減りますが適応度の評価回数は増えます。実測では個体数30で3,162回、400で7,992回でした。評価が重い問題では個体数50前後から試し、世代数は固定せず「N世代改善がなければ停止」で打ち切るのが実用的です。
遺伝的アルゴリズムの弱点は何ですか?
3つあります。第一に最適解を保証せず、得られた解が最適なのか改善が止まっただけなのかを自力で判定できません。第二に適応度の評価回数が多く、100ビットのOneMax問題でも中央値4,005回、30都市の巡回セールスマン問題では76,041回を要しました。第三に乱数に依存するため、シードを固定しない限り実行のたびに違う解が出ます。最適性の証明や再現性が要る業務では数理最適化ソルバを選んでください。
交叉と突然変異はそれぞれ何のためにあるのですか?
交叉は集団の中に既にある良い部分同士を組み合わせる操作、突然変異は集団が持っていない値を新しく持ち込む操作です。突然変異がないと初期集団に含まれない遺伝子は現れず、交叉がないと部分解を統合できません。実測では交叉率0(突然変異と選択のみ)でも150世代で解けましたが、交叉率0.8では44世代で済みました。
遺伝的アルゴリズムと機械学習はどう違いますか?
目的が異なります。機械学習はデータからパターンを学習して予測や分類を行う技術、遺伝的アルゴリズムは評価関数を最大化・最小化する解を探す最適化手法です。両者は競合せず、機械学習モデルのハイパーパラメータ探索や、ニューラルネットワークの重み・構造の探索にGAを使うといった組み合わせ方をします。