Crossover#

Layer 3: Extend Components

GA(saealib.GA)は、選択した親個体から子個体を生成する処理を、Crossoverという差し替え可能な演算子に委ねています。 交叉の方式を変えたいときは、GA本体ではなくこのCrossoverだけを差し替えればよいです。

Crossoverの役割#

Crossoverが実装する必要があるメソッドはcrossover_batch(parents_batch, bounds=None, rng=...)の1つだけです。形状(n_pair, n_parents, dim)のn_pair個の親グループを受け取り、形状(n_pair, n_children, dim)の子を返します。既定では各グループがn_parents = 2個の親を含み、n_children = 2個の子を生成します。2親2子以外の交叉方式を実装するには、サブクラスでn_parents/n_childrenクラス属性を上書きします。boundsには設計変数の下限と上限が(lb, ub)タプルとして渡され、Noneの場合は上限・下限なしです。

基底クラスは、crossover_batch()を1つの親グループで呼び出すことで、簡便メソッドcrossover(parent, bounds=None, rng=...)を提供します。GAはvariation_execution="sequential"でこのメソッドを使います。crossover()だけを上書きしても既定のバッチモードには影響しません。variation_executionについてはAlgorithmとGAのAPIリファレンスを参照してください。

交叉を実行するかどうかを決める個体レベルの確率は、どちらの交叉メソッド内でもなくGAが判定します。既定のバッチモードではGA.ask()がゲートを抽選し、通過した親グループだけをcrossover_batch()へ渡します。逐次モードでは、通過したグループごとにcrossover()を呼びます。ゲートを通過しなかったグループは親をそのまま子として複製するため、どちらのメソッドも交叉が実行される前提で実装できます。

組み込みCrossover#

クラス

パラメータ

特徴

CrossoverBLXAlpha

prob, alpha

BLX-α交叉(Eshelman & Schaffer, 1993が導入)。alphaが大きいほど、子が親の値の範囲外まで広がりうる

CrossoverSBX

prob, eta, *, prob_var=0.5

シミュレーテッドバイナリ交叉[11]。boundsが有限値なら境界付き版に自動で切り替わる。etaが大きいほど子個体は親に近づく

CrossoverUniform

prob, swap_rate=0.5

各次元を独立にswap_rateの確率で親同士を入れ替える(Syswerda, 1989が導入)

CrossoverOnePoint

prob

1点交叉

CrossoverTwoPoint

prob

2点交叉

CrossoverIntegerSBX

prob, eta, *, prob_var=0.5

CrossoverSBX[11]と同じ計算をしたのち整数に丸める。整数変数向け

CrossoverCategorical

prob

各次元を50/50でどちらかの親の値をそのままコピーする。カテゴリ変数向け

連続変数だけの問題であれば、この中から1つを選んでGA(crossover=..., ...)に渡せばよいです。 CrossoverBLXAlpha/CrossoverUniformは無制約な問題で素直に使え、境界を活かした交叉が必要ならCrossoverSBXを選ぶ、という判断が基本になります。

設計変数に整数変数やカテゴリ変数が混在する問題では、GAは変数の型ごとに異なるCrossoverインスタンスを使い分けます。GAコンストラクタのinteger_crossover/categorical_crossover引数を省略すると、CrossoverIntegerSBX/CrossoverCategoricalが自動的に補われます(eta/probは連続変数用のcrossoverから引き継がれます)。GA.ask()は親個体を変数型ごとの列に分割し、各Crossoverを該当する列だけに適用してから結果を組み立て直します。既定のバッチモードでは型ごとのcrossover_batch()を各列に適用し、逐次モードでは型ごとのcrossover()を呼び出して従来のペア単位のルーティングと乱数列を保ちます。この仕組み上、integer_crossover/categorical_crossoverへ独自クラスを渡す場合、そのn_children/n_parentsを連続変数用のcrossoverと一致させる必要があります。一致しない場合はConfigurationErrorになります。

変数の型とCrossoverの対応づけはProblemのvariables引数で決まります。

注釈

CrossoverBLXAlphaのみ@register()済みで、他の6クラスは現状Registry未登録です。Registry経由でクラスを文字列から解決する場合はこの違いに注意してください。

外部ライブラリアダプタ#

PymooCrossover(operator, *, prob=None, n_parents=None, n_children=None)は、構築済みのpymoo交叉演算子(例:SBX())をラップし、既存のpymooベースの研究コードをGA内でそのまま再利用できるようにします。 prob/n_parents/n_childrenは、ラップした演算子自身の値が既定になります。

GAの既定のバッチモードでは、PymooCrossover.crossover_batch()がゲートを通過したバッチ全体に対して、ラップした演算子の_do()を1回だけ呼び、pymooの集団単位のベクトル化を維持します。variation_execution="sequential"では、継承したcrossover()が1つの親グループをバッチ実装へ渡すため、直接crossover()を呼ぶ場合と同じくグループごとに_do()が1回呼ばれます。大きな集団でこのグループ単位のオーバーヘッドを避けられることが、既定のバッチモードでアダプタを使う主な利点です。rngはpymooのrandom_state引数を介してラップした演算子へ渡され、saealibのシード管理下で再現性が保たれます。

pymoo extraのインストールについてはインストールを参照してください。

拡張フック#

境界外に出た値を丸めるといった後処理だけを足したい場合、サブクラスを新設せずにwith_post(fn)で既存のCrossoverインスタンスへ処理を追加できます。 with_postは元のインスタンスを変更せず、fnを追加したコピーを返します。

import numpy as np
from saealib import CrossoverBLXAlpha

base = CrossoverBLXAlpha(prob=1.0, alpha=0.5)


def clip_to_bounds(offspring, parents, rng, ctx=None):
    return np.clip(offspring, -1.0, 1.0)


repaired = base.with_post(clip_to_bounds)

fnのシグネチャはfn(offspring, parents, rng, ctx) -> np.ndarrayで、既存のフック(既定では何もしない恒等関数)の結果を受け取って追加の変換を返します。 複数回with_postを呼べば、フックは呼び出した順に連結されます。

バッチモードでは、GAがpost_crossover_batch(offspring_batch, parents_batch, rng, ctx)を呼びます。既定の実装は親グループごとにpost_crossover()を呼ぶため、with_post()で追加したフックも引き続き動作します。後処理自体をベクトル化する場合はpost_crossover_batch()を上書きします。この上書きでは、維持したいwith_post()フックも自ら合成する必要があります。

独自Crossoverの実装方法#

独自の交叉方式が必要な場合は、Crossoverを継承してcrossover_batch()を実装します。次の例は、各親ペアの平均を2個の子として返します。

import numpy as np
from saealib import Crossover


class AverageCrossover(Crossover):
    def __init__(self, prob: float = 1.0):
        super().__init__()
        self.prob = prob

    def crossover_batch(self, parents_batch, bounds=None, rng=np.random.default_rng()):
        mean = parents_batch.mean(axis=1)
        return np.repeat(mean[:, np.newaxis, :], self.n_children, axis=1)

n_parents/n_childrenを2以外にする場合や、boundsを使う独自の丸め処理が必要な場合は、クラス属性を上書きしてcrossover_batch()内でboundsを使います。継承したcrossover()が1つの親グループを自動的に処理します。crossover()だけの上書きが有効なのは、実装済みの具体的な交叉をvariation_execution="sequential"専用に拡張する場合です。

関連コンポーネント#

  • Algorithm:GAがCrossoverをどう組み合わせるか

  • Mutation:交叉の次に呼ばれる、対になる演算子

  • ParentSelection:Crossoverに渡す親個体を選ぶ演算子

  • Problem:整数変数とカテゴリ変数の定義と、混合変数向けCrossoverの対応関係

  • 拡張のガイドライン:with_post系フックの一般的な設計思想

参照#