多目的最適化#

Layer 1: Use

saealib を使って、目的間のトレードオフがある多目的最適化問題を解きます。

minimize()は目的関数の数に応じたデフォルトのコンポーネントを使います。 アルゴリズム、代理モデル、評価戦略を個別に設定する場合は Optimizer.set_*() を使います。

このページでは、2つ以上の目的を持つ問題に固有の内容として、Comparatorの選択とParetoフロントの取得を扱います。

このページでできるようになること

このページを終えると、多目的問題でComparatorを選び、得られたParetoフロントを取り出せます。

単目的問題の基本手順は 単目的最適化 を使い、独自の Comparator が必要な場合は 独自コンポーネント を参照してください。 このページでは、多目的 Problem を実行し、Comparatorを選択してParetoフロントを取得します。

問題を設定する#

複数の目的関数がトレードオフの関係にあると、一方を改善すると他方が悪化する解が存在します。

この関係において、すべての目的で他の解に支配されない解の集合を Paretoフロント と呼びます。

ここでは例として、saealib に組み込まれたZDT1関数を最小化します。

from saealib.benchmarks import zdt1

problem = zdt1(n_var=10)

zdt1 は、凸型のParetoフロントを持つ2目的のベンチマーク問題を返す Problem インスタンスです。

minimizeを実行する#

Problem インスタンスを直接渡すと、そこから目的数が引き継がれます。

from saealib import minimize

result = minimize(problem, max_fe=2000, seed=0)

print(result.x.shape)  # (n_pareto, dim)
print(result.f.shape)  # (n_pareto, n_obj)

単目的の場合に1点だった result.x/result.f は、多目的の場合にはParetoフロントを形成する複数の解になります。

Optimizerを構成する#

Optimizer(problem)では、各コンポーネントを独立して設定できます。 set_*()は連鎖呼び出しのために同じ Optimizer を返し、run()または iterate()で最適化を実行します。

from saealib import Optimizer, Termination, max_fe

optimizer = Optimizer(problem, seed=0).set_termination(Termination(max_fe(2000)))
ctx = optimizer.run()
pareto_f = ctx.pareto_archive.get_array("f")

未設定のコンポーネントはデフォルトに解決されます。 多目的の順位付けは、次のComparatorの選択で設定します。

Comparatorを選択する#

多目的最適化では、Comparator が候補解同士の相対的な優劣を決めます。

Problem の comparator 引数を省略すると、目的数に基づいて自動選択されます(n_obj == 1 では SingleObjectiveComparator、n_obj > 1 では NSGA2Comparator)。

クラス

動作

NSGA2Comparator

非劣ソートと混雑度距離による多様性維持(既定)

SPEA2Comparator

支配の強さと近傍密度に基づく適応度

HypervolumeComparator

ハイパーボリューム寄与で優劣を判定

EpsilonDominanceComparator

ε支配で優劣を判定

NSGA3Comparator

参照点による多様性維持。reference_points が必要です

RNSGA2Comparator

指定した参照点の近くに解を集中させます。reference_points が必要です

Problem インスタンスの属性として comparator を交換できます。

from saealib.comparators import HypervolumeComparator

problem.comparator = HypervolumeComparator()
result = minimize(problem, max_fe=2000, seed=0)

パレートフロントを抽出する#

実行後、最終的なParetoフロントは result.ctx.pareto_archive に保持されます。

pareto_x = result.ctx.pareto_archive.get_array("x")
pareto_f = result.ctx.pareto_archive.get_array("f")

任意の目的値配列からParetoフロントを計算するには、non_dominated_sort を直接使えます。

from saealib.comparators import non_dominated_sort

archive_f = result.ctx.archive.get_array("f")
ranks, fronts = non_dominated_sort(archive_f, direction=problem.direction)
front0_f = archive_f[fronts[0]]  # first non-dominated front

関連コンセプトと参考情報#