遺伝図書館の紹介

Jenetics Libraryの紹介

1. 前書き

The aim of this seriesは、遺伝的アルゴリズムの概念を説明し、最もよく知られている実装を示します。

このチュートリアルでは、describe a very powerful Jenetics Java library that can be used for solving various optimization problems.

遺伝的アルゴリズムについてさらに学ぶ必要があると思われる場合は、this articleから始めることをお勧めします。

2. それはどのように機能しますか?

そのofficial documentsによると、JeneticsはJavaで記述された進化的アルゴリズムに基づくライブラリです。 進化的アルゴリズムは、生殖、突然変異、組換え、選択などの生物学的進化に触発されたメカニズムを使用するため、生物学にそのルーツがあります。

JeneticsはJavaStreamインターフェースを使用して実装されているため、残りのJavaStreamAPIとスムーズに連携します。

主な機能は次のとおりです。

  • frictionless minimization –適応度関数を変更または微調整する必要はありません。 Engineクラスの構成を変更するだけで、最初のアプリケーションを開始する準備が整います。

  • dependency free –Jeneticsを使用するために必要なランタイムサードパーティライブラリはありません

  • Java 8 readyStreamとラムダ式の完全サポート

  • multithreaded –進化的ステップを並行して実行できます

Jeneticsを使用するには、pom.xmlに次の依存関係を追加する必要があります。


    io.jenetics
    jenetics
    3.7.0

最新バージョンはin Maven Centralで見つけることができます。

3. ユースケース

Jeneticsのすべての機能をテストするために、単純なバイナリアルゴリズムから始まり、ナップサック問題で終わる、さまざまなよく知られた最適化問題の解決を試みます。

3.1. 単純な遺伝的アルゴリズム

最も単純なバイナリ問題を解決する必要があると仮定しましょう。ここでは、0と1で構成される染色体の1ビットの位置を最適化する必要があります。 まず、問題に適したファクトリを定義する必要があります。

Factory> gtf = Genotype.of(BitChromosome.of(10, 0.5));

長さが10で、染色体に1が含まれる確率が0.5のBitChromosomeを作成しました。

それでは、実行環境を作成しましょう。

Engine engine
  = Engine.builder(SimpleGeneticAlgorithm::eval, gtf).build();

eval()メソッドはビットカウントを返します。

private Integer eval(Genotype gt) {
    return gt.getChromosome().as(BitChromosome.class).bitCount();
}

最後のステップでは、進化を開始し、結果を収集します。

Genotype result = engine.stream()
  .limit(500)
  .collect(EvolutionResult.toBestGenotype());

最終結果は次のようになります。

Before the evolution:
[00000010|11111100]
After the evolution:
[00000000|11111111]

遺伝子内の1の位置を最適化することができました。

3.2. サブセット和問題

Jeneticsのもう1つの使用例は、subset sum problemを解くことです。 手短に言えば、最適化の課題は、整数のセットが与えられた場合、合計がゼロである空でないサブセットを見つける必要があるということです。

Jeneticsには、このような問題を解決するための事前定義されたインターフェースがあります。

public class SubsetSum implements Problem, EnumGene, Integer> {
    // implementation
}

ご覧のとおり、3つのパラメーターを持つProblem<T, G, C>を実装します。

  • <T> –問題の適応度関数の引数タイプ。この場合、不変で順序付けられた固定サイズのIntegerシーケンスISeq<Integer>

  • <G> –進化エンジンが使用している遺伝子タイプ。この場合、可算Integer遺伝子EnumGene<Integer>

  • <C> –適応度関数の結果タイプ。ここではIntegerです

Problem<T, G, C>インターフェースを使用するには、次の2つのメソッドをオーバーライドする必要があります。

@Override
public Function, Integer> fitness() {
    return subset -> Math.abs(subset.stream()
      .mapToInt(Integer::intValue).sum());
}

@Override
public Codec, EnumGene> codec() {
    return codecs.ofSubSet(basicSet, size);
}

最初の例ではフィットネス関数を定義しますが、2番目の例は、一般的な問題のエンコーディングを作成するファクトリメソッドを含むクラスです。たとえば、この場合のように、特定の基本セットから最適な固定サイズサブセットを検索します。

これで、主要部分に進むことができます。 最初に、問題で使用するサブセットを作成する必要があります。

SubsetSum problem = of(500, 15, new LCG64ShiftRandom(101010));

Jeneticsが提供するLCG64ShiftRandomジェネレーターを使用していることに注意してください。 次のステップでは、ソリューションのエンジンを構築しています。

次のステップでは、ソリューションのエンジンを構築しています。

Engine, Integer> engine = Engine.builder(problem)
  .minimizing()
  .maximalPhenotypeAge(5)
  .alterers(new PartiallyMatchedCrossover<>(0.4), new Mutator<>(0.3))
  .build();

子孫を変更するために使用される表現型の年齢と変更者を設定することにより、結果を最小化しようとします(最適には結果は0になります)。 次のステップで結果を取得できます。

Phenotype, Integer> result = engine.stream()
  .limit(limit.bySteadyFitness(55))
  .collect(EvolutionResult.toBestPhenotype());

述語を返すbySteadyFitness()を使用していることに注意してください。これにより、指定された世代数の後に適切な表現型が見つからない場合、進化ストリームが切り捨てられ、最良の結果が収集されます。 運が良ければ、ランダムに作成されたセットの解決策があると、次のようなものが表示されます。

運が良ければ、ランダムに作成されたセットの解決策があると、次のようなものが表示されます。

[85|-76|178|-197|91|-106|-70|-243|-41|-98|94|-213|139|238|219] --> 0

そうでない場合、サブセットの合計は0とは異なります。

3.3. ナップザックファーストフィット問題

Jeneticsライブラリを使用すると、Knapsack problemなどのさらに高度な問題を解決できます。 簡単に言えば、この問題では、ナップザックのスペースが限られているため、どのアイテムを入れるかを決める必要があります。

バッグのサイズとアイテムの数を定義することから始めましょう。

int nItems = 15;
double ksSize = nItems * 100.0 / 3.0;

次のステップでは、KnapsackItemオブジェクト(sizeおよびvalueフィールドで定義)を含むランダム配列を生成し、Firstを使用して、これらのアイテムをナップザック内にランダムに配置します。フィット方法:

KnapsackFF ff = new KnapsackFF(Stream.generate(KnapsackItem::random)
  .limit(nItems)
  .toArray(KnapsackItem[]::new), ksSize);

次に、Engineを作成する必要があります。

Engine engine
  = Engine.builder(ff, BitChromosome.of(nItems, 0.5))
  .populationSize(500)
  .survivorsSelector(new TournamentSelector<>(5))
  .offspringSelector(new RouletteWheelSelector<>())
  .alterers(new Mutator<>(0.115), new SinglePointCrossover<>(0.16))
  .build();

ここで注意すべき点がいくつかあります。

  • 人口規模は500

  • 子孫はトーナメントとルーレットのホイールの選択を通じて選択されます

  • 前のサブセクションで行ったように、新しく作成された子孫の変更者を定義する必要もあります

There is also one very important feature of Jenetics. We can easily collect all statistics and insights from the whole simulation duration.EvolutionStatisticsクラスを使用してこれを行います。

EvolutionStatistics statistics = EvolutionStatistics.ofNumber();

最後に、シミュレーションを実行しましょう。

Phenotype best = engine.stream()
  .limit(bySteadyFitness(7))
  .limit(100)
  .peek(statistics)
  .collect(toBestPhenotype());

各世代の後に評価統計を更新していることに注意してください。これは、安定した7世代および最大100世代に制限されています。 より詳細には、2つの可能なシナリオがあります。

  • 7つの安定した世代を達成すると、シミュレーションが停止します

  • 100世代未満で7つの安定した世代を取得できないため、2番目のlimit()が原因でシミュレーションが停止します。

最大世代数の制限を設けることが重要です。そうしないと、シミュレーションが妥当な時間内に停止しない可能性があります。

最終結果には多くの情報が含まれています。

+---------------------------------------------------------------------------+
|  Time statistics                                                          |
+---------------------------------------------------------------------------+
|             Selection: sum=0,039207931000 s; mean=0,003267327583 s        |
|              Altering: sum=0,065145069000 s; mean=0,005428755750 s        |
|   Fitness calculation: sum=0,029678433000 s; mean=0,002473202750 s        |
|     Overall execution: sum=0,111383965000 s; mean=0,009281997083 s        |
+---------------------------------------------------------------------------+
|  Evolution statistics                                                     |
+---------------------------------------------------------------------------+
|           Generations: 12                                                 |
|               Altered: sum=7 664; mean=638,666666667                      |
|                Killed: sum=0; mean=0,000000000                            |
|              Invalids: sum=0; mean=0,000000000                            |
+---------------------------------------------------------------------------+
|  Population statistics                                                    |
+---------------------------------------------------------------------------+
|                   Age: max=10; mean=1,792167; var=4,657748                |
|               Fitness:                                                    |
|                      min  = 0,000000000000                                |
|                      max  = 716,684883338605                              |
|                      mean = 587,012666759785                              |
|                      var  = 17309,892287851708                            |
|                      std  = 131,567063841418                              |
+---------------------------------------------------------------------------+

今回は、最高のシナリオで合計値が716,68のアイテムを配置できました。 また、進化と時間の詳細な統計を見ることができます。

テスト方法

これは非常に簡単なプロセスです。問題に関連するメインファイルを開き、最初にアルゴリズムを実行するだけです。 一般的なアイデアが得られたら、パラメーターを試してみましょう。

4. 結論

この記事では、実際の最適化問題に基づいたJeneticsライブラリの機能について説明しました。

このコードは、GitHubでMavenプロジェクトとして利用できます。 Springsteen Record(はい、存在します!)や巡回セールスマン問題などの最適化の課題のコード例を提供したことに注意してください。

遺伝的アルゴリズムの他の例を含むこのシリーズのすべての記事については、次のリンクをご覧ください。