遺伝的アルゴリズムは、自然選択と遺伝的メカニズムを模倣した最適化アルゴリズムであり、主に最適化問題を解決するために使用されます。生物進化の過程における遺伝、交叉、突然変異の過程をシミュレーションし、優秀な個体を進化させ続けることで徐々に全体最適解を探索します。
始める前に、遺伝的アルゴリズムの概念をいくつか理解しましょう。
コンセプト 1: 遺伝子と染色体
遺伝的アルゴリズムでは、最初に解決すべき問題を数学的問題にマッピングする必要があります。これは「数学モデリング」と呼ばれ、次にこの問題の実行可能な解決策は「染色体」と呼ばれます。実現可能な解決策は通常複数の要素で構成され、各要素は染色体上の「遺伝子」と呼ばれます。
たとえば、次の関数の場合、[1,2,3]、[1,3,2]、[3,2,1] はすべてこの関数の実行可能な解です (それに代入すると実行可能な解になります)。実行可能な解決策は、遺伝的アルゴリズムでは染色体と呼ばれます。
3x+4y+5z<100
これらの実現可能な解決策は 3 つの要素で構成されているため、遺伝的アルゴリズムでは、各要素は染色体を構成する遺伝子と呼ばれます。
コンセプト2:フィットネス機能
自然界には、世代ごとにより優れた個体を選択し、環境適合性の低い個体を排除できる神が存在するようです。では、遺伝的アルゴリズムでは、染色体の長所と短所をどのように測定するのでしょうか? これはフィットネス機能によって行われます。適応度関数は、遺伝的アルゴリズムにおけるこの「神の」役割を果たします。
遺伝的アルゴリズムは実行プロセス中に N 回反復を実行し、各反復でいくつかの染色体が生成されます。適応度関数は、この反復で生成されたすべての染色体にスコアを付けて、これらの染色体の適応度を判断し、その後、適応度の低い染色体を削除し、適応度の高い染色体のみを保持するため、数回の反復後に後の染色体の品質が向上します。どんどん良くなっていきます。
コンセプト3:クロスオーバー
遺伝的アルゴリズムの反復ごとに N 個の染色体が生成されます。遺伝的アルゴリズムでは、各反復を「進化」と呼びます。では、それぞれの進化によって新しく生成された染色体はどのようにして生まれるのでしょうか? ——答えは交配ともわかる「クロスオーバー」です。
交叉のプロセスでは、前の世代の染色体から 2 本の染色体 (1 つは父親、もう 1 つは母親) を見つける必要があります。次に、これら 2 本の染色体の特定の位置が切断され、接合されて新しい染色体が作成されます。この新しい染色体には、一定数の父親の遺伝子と一定数の母親の遺伝子の両方が含まれています。
では、前世代の染色体から父親と母親の遺伝子を選択するにはどうすればよいでしょうか? これはランダムに選択されるのではなく、通常はルーレット アルゴリズムを使用して行われます。
各進化が完了した後、各染色体の適応度が計算され、次の式を使用して各染色体の適応確率が計算されます。次に、交叉プロセス中に、この確率に従って親染色体を選択する必要があります。適応度が高い染色体は、選択される確率が高くなります。これが、遺伝的アルゴリズムが優れた遺伝子を保持できる理由です。
染色体 i が選択される確率 = 染色体 i の適合度 / すべての染色体の適合度の合計
コンセプト4:バリエーション
クロスオーバーでは、各進化で良好な遺伝子が確実に残されるようにできますが、選択されるのは元の結果セットのみであり、まだ少数の遺伝子が存在しますが、それらの組み合わせの順序は入れ替わります。これは、N 回の進化後の計算結果が局所最適解に近づくことを保証することしかできませんが、大域最適解に到達する方法はなく、この問題を解決するには、突然変異を導入する必要があります。
バリエーションがよく分かります。交叉によって新しい染色体を生成する場合、新しい染色体上のいくつかの遺伝子をランダムに選択し、その遺伝子の値をランダムに変更する必要があります。これにより、既存の染色体に新しい遺伝子が導入され、現在の検索限界を突破することができます。アルゴリズムが全体的な最適解を見つけるのに有益です。
コンセプト 5: レプリケーション
それぞれの進化において、前世代の優れた染色体を保持するためには、前世代で最も適合度の高い染色体をそのままの状態で次世代に直接コピーする必要があります。
各進化で N 個の染色体を生成する必要があると仮定すると、各進化で NM 染色体は交叉によって生成される必要があり、残りの M 染色体は、前の世代で最も高い適合度を備えた M 染色体をコピーすることによって取得されます。
遺伝的アルゴリズムの基本的なプロセスは次のとおりです。
- 初期集団: 個人のグループを集団としてランダムに生成します。
- 適応度の評価: 各個人の適応度を評価し、通常は目的関数を使用して個人の適応度を計算します。
- 選択操作:各個体の適応度に応じて、次世代を生成する親となる個体をいくつか選択する。
- 交叉操作:親個体に対して交叉操作を行い、新たな個体を生成します。
- 突然変異操作: 新しい個体に対して突然変異操作を実行し、より多様性を生み出します。
- 新しい個人を評価する: 新しい個人の適合性を評価します。
- 終了条件の判定:終了条件を満たしている場合は最適解を出力し、満たしていない場合は手順3に戻ります。
何回進化する必要がありますか?
進化するたびに改良が加えられるため、理論的には進化の回数が多いほど良いことになりますが、実際のアプリケーションでは、結果の精度と実行効率のバランス ポイントが見つかることが多く、一般に 2 つの方法があります。
1. 進化の数を制限する
一部の実際のアプリケーションでは、進化の数を事前にカウントできます。たとえば、多くの実験を通じて、入力データがどのように変化しても、N 回の進化後にアルゴリズムが最適な解を得ることができることがわかった場合は、進化の回数を N 回に設定できます。
しかし、実際の状況はそれほど理想的ではないことが多く、入力が異なると最適解を求める際の反復回数に大きな差が生じることがよくあります。
2. 許容範囲を制限する
アルゴリズムが全体的な最適解を達成したい場合、何度も進化を繰り返す必要があり、これはシステムのパフォーマンスに大きな影響を与えます。そうすれば、アルゴリズムの精度とシステムの効率の間のバランスを見つけることができます。許容可能な結果の範囲を事前に設定することができ、アルゴリズムが X 回進化した後、現在の結果が誤差範囲内にあることが判明すると、アルゴリズムは終了します。
ただし、この方法には欠点もあり、数回の進化で誤差許容範囲に入る場合もありますが、誤差許容範囲に入るまでに何度も進化する必要がある場合もあります。この不確実性により、アルゴリズムの実行効率が制御不能になります。
したがって、アルゴリズムの反復回数を制御するためにどの方法を選択するかは、特定のビジネス シナリオに従って合理的に選択する必要があります。ここで与える普遍的な方法はありません。実際の実践で答えを見つける必要があります。
遺伝的アルゴリズムを使用して負荷分散スケジュールの問題を解決する
アルゴリズムは実際的な問題を解決するために使用されます。ここまでで、遺伝的アルゴリズムについて包括的に理解できたと思います。次に、遺伝的アルゴリズムを使用して、実際的な問題負荷分散スケジューリング問題を解決します。
N 個のタスクがあると仮定すると、ロード バランサは処理のために M 個のサーバー ノードに割り当てる必要があります。各タスクのタスク長と各サーバノード(以下、ノード)の処理速度はわかっていますが、全タスクの合計処理時間が最短になるようなタスクの割り当て方法を提供してください。
数学的モデリング
この問題を取得したら、まずこの実際の問題を遺伝的アルゴリズムの数学モデルにマッピングする必要があります。
タスク長行列(タスク行列と呼ぶ)
すべてのタスクのタスクの長さを次のようなマトリックス タスクで表します。
タスク={2,4,6,8}
そして、tasks[i]のiはタスクの番号を表し、tasks[i]はタスクiのタスク長を表す。
ノード処理速度マトリクス(略称:ノードマトリクス)
すべてのサーバー ノードの処理速度は、次のようなマトリックス ノードで表します。
ノード={2,1}
そして、nodes[j]のjはノードの番号を表し、nodes[j]はノードjの処理速度を表す。
タスク処理時間マトリックス
タスク行列 Tasksとノード行列 Nodesが決まると 、すべてのノードに割り当てられたすべてのタスクのタスク処理時間が決まります。これを表すために、2 次元配列である行列 timeMatrix を使用します。
1 2
2 4
3 6
4 8
timeMatrix[i][j] は、タスク i をノード j に割り当てて処理するのに必要な時間を表し、次の式で計算されます。
timeMatrix[i][j] = タスク[i]/ノード[j]
染色体
上記のことから、各進化によって N 個の染色体が生成されることがわかり、各染色体は現在の問題に対する実行可能な解決策であり、実行可能な解決策は複数の要素で構成されており、各要素は染色体の遺伝子と呼ばれます。次に、染色体行列を使用して、アルゴリズムの各進化プロセスにおける実現可能な解決策を記録します。
染色体は次のもので構成されます。
染色体={1,2,3,4}
染色体は 1 ビットの配列であり、1 ビットの配列の添字はタスクの番号を示し、配列の値はノードの番号を示します。したがって、chromosome[i]=j の意味は次のようになります。タスク i をノード j に割り当てます。
上記の例では、タスクセットが Tasks={2,4,6,8}、ノードセットが Nodes={2,1} である場合、chromosome={3,2,1,0} の意味は次のようになります。 :
- タスク0をノード3に割り当てます
- タスク 1 をノード 2 に割り当てます
- タスク 2 をノード 1 に割り当てます
- タスク 3 をノード 0 に割り当てます
フィットネスマトリックス
以上のことから、適応度関数は各染色体の適応度を判断し、適応度の高い染色体を残し、適応度の低い染色体を排除する、遺伝的アルゴリズムにおける「神の」役割を果たしていることがわかります。次に、アルゴリズムを実装するときに、次のように、現在の N 染色体の適合性を記録するための適合性行列が必要になります。
適応性={0.6, 2, 3.2, 1.8}
adaptability 配列の添字は染色体の番号を示し、adaptability[i]は番号 i の染色体の適合度を示します。
負荷分散スケジューリングの例では、N 個のタスクの合計実行時間を適合性評価の基準として使用します。すべてのタスクを割り当てた場合、合計時間が長いほど適応度は低くなり、合計時間が短いほど適応度は高くなります。
選択確率行列
上記から、各進化プロセスでは、次の進化で各染色体が選択される確率を適応度行列に従って計算する必要があることがわかります。この行列は次のとおりです。
選択確率={0.1, 0.4, 0.2, 0.3}
行列の添字は染色体の番号を示し、行列内の値は染色体に対応する選択確率を示します。その計算式は次のとおりです。
選択確率[i] = 適応性[i] / 適応度の合計
遺伝的アルゴリズムの実装
上記の知識ポイントをすべて配置したら、コードをアップロードします。Talk は安いと思います。コードを見せてください。
/**
* 遗传算法
* @param iteratorNum 迭代次数
* @param chromosomeNum 染色体数量
*/
function gaSearch(iteratorNum, chromosomeNum) {
// 初始化第一代染色体
var chromosomeMatrix = createGeneration();
// 迭代繁衍
for (var itIndex=1; itIndex<iteratorNum; itIndex++) {
// 计算上一代各条染色体的适应度
calAdaptability(chromosomeMatrix);
// 计算自然选择概率
calSelectionProbability(adaptability);
// 生成新一代染色体
chromosomeMatrix = createGeneration(chromosomeMatrix);
}
}
コードが出てくるとすぐにすべてが明らかであり、あまり説明する必要はないようです。上記は遺伝的アルゴリズムの主要なフレームワークであり、詳細の一部は各サブ関数にカプセル化されています。遺伝的アルゴリズムの原理を理解した後は、コードについてあまり説明する必要はないと思います。完全なコードは私の Github にあります。Star へようこそ。
以下は、Python を使用して遺伝的アルゴリズムを実装し、1 変数の関数の最小値の問題を解決するサンプル コードです。
import random
# 目标函数:f(x) = x^2
def objective_function(x):
return x ** 2
# 生成随机个体
def generate_individual():
return random.uniform(-10, 10)
# 计算个体适应度
def calculate_fitness(individual):
return 1 / (1 + objective_function(individual))
# 选择操作
def selection(population):
fitnesses = [calculate_fitness(individual) for individual in population]
total_fitness = sum(fitnesses)
probabilities = [fitness / total_fitness for fitness in fitnesses]
selected = random.choices(population, weights=probabilities, k=len(population))
return selected
# 交叉操作
def crossover(individual1, individual2):
alpha = random.uniform(0, 1)
new_individual1 = alpha * individual1 + (1 - alpha) * individual2
new_individual2 = alpha * individual2 + (1 - alpha) * individual1
return new_individual1, new_individual2
# 变异操作
def mutation(individual):
new_individual = individual + random.uniform(-1, 1)
return new_individual
# 遗传算法求解最小值问题
population_size = 100
population = [generate_individual() for i in range(population_size)]
num_generations = 1000
for generation in range(num_generations):
# 选择操作
selected_population = selection(population)
# 交叉操作
offspring_population = []
for i in range(population_size):
offspring1, offspring2 = crossover(selected_population[i], selected_population[(i+1) % population_size])
offspring_population.append(offspring1)
offspring_population.append(offspring2)
# 变异操作
for i in range(population_size):
if random.uniform(0, 1) < 0.1:
offspring_population[i] = mutation(offspring_population[i])
#