Multi-objective optimization algorithm package pymoo reference guide

Multi-objective optimization algorithm package pymoo reference guide

1 Multi-objective function definition

Without losing any generality, the optimization problem can be defined as:

In the formula:

  • x i x_i xiPart IIi variablesto be optimized;
  • x i L x^L_i xiLxi U x^U_ixiUas its lower and upper bounds ;
  • f m f_m fmis mmm objectivefunctions;
  • g j g_j gjFor the jjthj inequalityconstraints;
  • h k h_k hkFor the kthk equalityconstraints.

objective function fff should satisfy all equality and inequality constraints. If a specific objective function isto maximize(max ⁡ fi \max f_imaxfi), the problem can be redefined to minimize its negative value ( min ⁡ − fi \min - f_iminfi)。

The following aspects need to be considered in optimization problems:

1) Variable type

The variables cover the search space Ω of the optimization problem . Different variable types, such as continuous, discrete/integer, binary or permutation, define the characteristics of the search space. In some cases, variable types may even be mixed, adding further complexity.

2) Number of variables

Not only the type, but also the number of variables (N) is essential. You can imagine that solving a problem with only ten variables is completely different from solving a problem with thousands of variables. For large-scale optimization problems, where even the computation of second-order derivatives is very expensive, efficient handling of memory plays a more important role.

3) Target quantity

Some optimization problems have multiple conflicting objectives (M>1) to be optimized .

Single-objective optimization is just a special case of M=1. In multi-objective optimization, the solution governs the comparison of scalars in single-objective optimization. There are multiple dimensions in the target space, and the optimal (most of the time) consists of a set of non-dominated solutions. Population-based algorithms are mainly used as solvers because a set of solutions is to be obtained.

4) Constraints

There are two kinds of constraints in optimization problems : inequality (g) constraints and equality (h) constraints .

No matter how well-aimed a solution is, it is considered infeasible if it ends up violating just one constraint .

Constraints have a strong impact on problem complexity. For example, if only a few solutions in the search space are feasible, or a large number of constraints (|J|+|K|) need to be satisfied. For genetic algorithms , satisfying equality constraints is quite challenging. Therefore, this problem needs to be solved in different ways, for example, by mapping the search space to a utility space that always satisfies the equality constraints , or by customizing the injection of knowledge of the equality constraints .

5) Multi-modal

In multi-modal fitness scenarios, optimization becomes more difficult due to the existence of a small or even large number of local optima . For a solution found, one must always ask whether the method explores a sufficient area of ​​the search space to maximize the probability of obtaining the global optimum . Multimodal search spaces quickly reveal the limitations of local search and can easily get stuck .

6) Differentiability

A differentiable function means that first and even second derivatives can be calculated . Differentiable functions allow the use of gradient-based optimization methods , which have significant advantages over gradient-free methods. Most gradient-based algorithms are point-wise and are very efficient for single-modal fitness scenarios. In practice, however , functions are often not differentiable , or more complex functions require global search rather than local search . The field of research that solves problems without knowing mathematical optimization is also known as black-box optimization .

7) Uncertainty.

The objective and constraint functions are usually assumed to be deterministic . However, if one or more objective functions are uncertain, this introduces noise or also known as uncertainty . One technique to account for potential randomness is to repeat the evaluation with different random seeds and average the resulting values . Additionally, the standard deviation derived from multiple evaluations can be used to determine the performance and reliability of a specific solution . Generally speaking, optimization problems with potential uncertainty are studied by the stochastic optimization research field.

2 Bi-objective optimization problem

Bi-objective optimization problem:

Among the above questions:

  • There are two objective functions ( M = 2 M=2M=2 ), whichminimizes f 1 ( x ) f_1(x)f1( x ) ,maximize f 2 (x) f_2(x)f2(x)
  • There are two inequality constraints in the optimization objective ( J = 2 J=2J=2 ), whereg 1 ( x ) g_1(x)g1( x ) isless than or equal to,g 2 (x) g_2(x)g2( x ) isgreater than or equal to.
  • The problem is defined by two variables ( N = 2 N=2N=2), x 1 x_1 x1and x 2 x_2x2, both range in [ − 2 , 2 ] [-2,2][2,2]
  • This optimization problem does not contain equality constraints ( K = 0 K=0K=0)。

First analyze the location of the Pareto optimal solution.

f 1 ( x ) f_1(x) f1The minimum valueof ( x ) is at(0, 0) (0,0)(0,0 ) ,f 2 ( x ) f_2(x)f2The maximum valueof ( x ) is at(1, 0) (1,0)(1,0 ) place. Since both functionsare quadratic, the optimal solutionis given by a straight line between the two optimal solutions. This means that all pareto optimal solutions (ignoring the constraints for now) havex 2 = 0 x_2=0x2=0x 1 ∈ ( 0 , 1 ) x1\in(0,1)x 1(0,1)

The first constraint depends only on x 1 x_1x1, and satisfies x 1 ∈ ( 0.1 , 0.9 ) x_1\in(0.1,0.9)x1(0.1,0.9) x 1 ∈ ( 0.4 , 0.6 ) x_1 \in(0.4,0.6) x1(0.4,0 . 6 ) satisfies the second constraintg 2 g_2g2. This means that from an analytical perspective, the Pareto optimal set is:

P S = { ( x 1 , x 2 ) ∣ ( 0.1 ≤ x 1 ≤ 0.4 ) ∨ ( 0.6 ≤ x 1 ≤ 0.9 ) ∧ x 2 = 0 } PS=\left\{\left(x_{1}, x_{2}\right) \mid\left(0.1 \leq x_{1} \leq 0.4\right) \vee\left(0.6 \leq x_{1} \leq 0.9\right) \wedge x_{2}=0\right\} PS={ (x1,x2)(0.1x10.4)(0.6x10.9)x2=0}

View the Pareto optimal set (orange) by plotting the function:

import numpy as np

X1, X2 = np.meshgrid(np.linspace(-2, 2, 500), np.linspace(-2, 2, 500))

F1 = X1**2 + X2**2
F2 = (X1-1)**2 + X2**2
G = X1**2 - X1 + 3/16

G1 = 2 * (X1[0] - 0.1) * (X1[0] - 0.9)
G2 = 20 * (X1[0] - 0.4) * (X1[0] - 0.6)


import matplotlib.pyplot as plt
plt.rc('font', family='serif')

levels = [0.02, 0.1, 0.25, 0.5, 0.8]
plt.figure(figsize=(7, 5))
CS = plt.contour(X1, X2, F1, levels, colors='black', alpha=0.5)
CS.collections[0].set_label("$f_1(x)$")

CS = plt.contour(X1, X2, F2, levels, linestyles="dashed", colors='black', alpha=0.5)
CS.collections[0].set_label("$f_2(x)$")

plt.plot(X1[0], G1, linewidth=2.0, color="green", linestyle='dotted')
plt.plot(X1[0][G1<0], G1[G1<0], label="$g_1(x)$", linewidth=2.0, color="green")

plt.plot(X1[0], G2, linewidth=2.0, color="blue", linestyle='dotted')
plt.plot(X1[0][X1[0]>0.6], G2[X1[0]>0.6], label="$g_2(x)$",linewidth=2.0, color="blue")
plt.plot(X1[0][X1[0]<0.4], G2[X1[0]<0.4], linewidth=2.0, color="blue")

plt.plot(np.linspace(0.1,0.4,100), np.zeros(100),linewidth=3.0, color="orange")
plt.plot(np.linspace(0.6,0.9,100), np.zeros(100),linewidth=3.0, color="orange")

plt.xlim(-0.5, 1.5)
plt.ylim(-0.5, 1)
plt.xlabel("$x_1$")
plt.ylabel("$x_2$")

plt.legend(loc='upper center', bbox_to_anchor=(0.5, 1.12),
          ncol=4, fancybox=True, shadow=False)

plt.tight_layout()
plt.show()

Next, the Pareto-front is derived by mapping the pareto set to the target space. Pareto-front equation based on f 2 f_2f2and depends on f 1 f_1f1Variables.

We know that the optimal value is x 2 = 0 x_2=0x2=0 This means that the objective function can be simplified tof 1 ( x ) = 100 x 1 2 , f 2 ( x ) = − ( x 1 − 1 ) 2 f_1(x)=100 x^2_1, f_2(x)= -(x_1-1)^2f1(x)=100x12,f2(x)=(x11)2 . The first targetf 1 f_1f1It can be converted into x 1 = f 1 100 x_1=\sqrt{\frac{f_1}{100}}x1=100f1 , and then put in the second target, the result is:

f 2 = − ( f 1 100 − 1 ) 2 f_{2}=-\left(\sqrt{\frac{f_{1}}{100}}-1\right)^{2} f2=(100f1 1)2

The equation defines the shape. Next we need to define f 1 f_1f1all possible values ​​of . If x 1 x_1x1Substitute the value of f 1 f_1f1, you will get the points of interest [1, 16] [1,16][1,1 6 ] and[ 36 , 81 ] [36,81][36,8 1 ] . Therefore, Pareto-front is:

import numpy as np
import matplotlib.pyplot as plt

plt.figure(figsize=(7, 5))

f2 = lambda f1: - ((f1/100) ** 0.5 - 1)**2
F1_a, F1_b = np.linspace(1, 16, 300), np.linspace(36, 81, 300)
F2_a, F2_b = f2(F1_a), f2(F1_b)

plt.rc('font', family='serif')
plt.plot(F1_a,F2_a, linewidth=2.0, color="green", label="Pareto-front")
plt.plot(F1_b,F2_b, linewidth=2.0, color="green")

plt.xlabel("$f_1$")
plt.ylabel("$f_2$")

plt.legend(loc='upper center', bbox_to_anchor=(0.5, 1.10),
          ncol=4, fancybox=True, shadow=False)

plt.tight_layout()
plt.show()

It can be seen from the figure that this is a non-dominated set. For this optimization problem, the first goal is to minimize, the second goal is to maximize, and the better solution is in the upper left corner.

3 Use pymoo to optimize the target

Most optimization frameworks strive to minimize or maximize all objectives and have only ≤ or ≥ constraints . In pymoo, every objective function should be minimal and every constraint needs to be provided in the form ≤0 .

Therefore, one needs to −1multiply an objective that should be maximized and then minimize it. This leads to minimizing − f 2 ( x ) −f_2(x)f2( x ) instead of maximizingf 2 ( x ) f_2(x)f2(x)

Furthermore, inequality constraints need to be expressed as 小于零(≤0)constraints. Therefore, g 2 ( x ) g_2(x)g2( x )−1 toflipthe inequality relationship.

Furthermore, it is recommended to normalize constraints so that they operate on the same scale and give them equal importance. For g 1 ( x ) g_1(x)g1( x ) , the coefficient is2 ⋅ ( − 0.1 ) ⋅ ( − 0.9 ) = 0.18 2⋅(−0.1)⋅(−0.9)=0.182(0.1)(0.9)=0.18 g 2 ( x ) g_2(x) g2( x ) , the coefficient is20 ⋅ ( − 0.4 ) ⋅ ( − 0.6 ) = 4.8 20⋅(−0.4)⋅(−0.6)=4.820(0.4)(0.6)=4.8

By using g 1 ( x ) g_1(x)g1( x ) andg 2 (x) g_2(x)g2( x ) is divided by its corresponding coefficientto normalize the constraints.

The final objective function is:

pymoo installation:

pip install -U pymoo

1) Element-based problem definition

ElementwiseProblemA new Python target inherited from is defined and the correct properties are set, such as the number of targets (n_obj) and the number of constraints (n_constr) as well as the lower bound (xl) and upper bound (xu) .

The function responsible for evaluation is _evaluate. The interface of the function is the parameter xxx o u t out o u t . For this element-based implementation,xxx is an_varone-dimensional NumPy array of length representing the single solution to be computed. out outo u t is a dictionary.

Target values ​​should be n_objwritten as a NumPy array list of length out["F"]and constraints should be written as a list of n_constrlength out["G"].

import numpy as np
from pymoo.core.problem import ElementwiseProblem

class MyProblem(ElementwiseProblem):

    def __init__(self):
        super().__init__(n_var=2,
                         n_obj=2,
                         n_constr=2,
                         xl=np.array([-2,-2]),
                         xu=np.array([2,2]))

    def _evaluate(self, x, out, *args, **kwargs):
        f1 = 100 * (x[0]**2 + x[1]**2)
        f2 = (x[0]-1)**2 + x[1]**2

        g1 = 2*(x[0]-0.1) * (x[0]-0.9) / 0.18
        g2 = - 20*(x[0]-0.4) * (x[0]-0.6) / 4.8

        out["F"] = [f1, f2]
        out["G"] = [g1, g2]


problem = MyProblem()

A problem can be defined in several different ways. The above demonstrates an element-based implementation, meaning that for each solution xxx calls _evaluate.

Another approach is vectorized, where xxx representsa complete set of solutionsor a functional, possibly more pythonic approach that provides each objective and constraint in the form of a function.

For details, see:

https://pymoo.org/problems/definition.html

2) Initialization algorithm

List of pymoo algorithms:

algorithm
1. Genetic algorithm GA , single objective
2. Differential evolution DE, single objective
3. Biased random key genetic algorithm BRKGA, single objective
4. Simplex NelderMead, single target
5. Pattern search PatternSearch, single target
6. CMAES, single target
7. Evolution strategy ES, single objective
8. Random sorting evolution strategy SRES, single objective
9. Improved random sorting evolution strategy ISRES, single objective
10. NSGA-II, NSGA2, multiple targets
11. R-NSGA-II, RNSGA2, multi-target
12. NSGA-III, NSGA3, multiple targets
13. U-NSGA-III, UNSGA3, single target and multiple targets
14. R-NSGA-III, RNSGA3, single target and multi-target
15. Multi-target decomposition MOEAD, single target and multi-target
16. AGE-MOEA, single target and multiple targets
17. C-TAEA, single target and multiple targets

For details, see:

https://pymoo.org/algorithms/list.html#nb-algorithms-list

The multi-objective algorithm NSGA-II is selected here for explanation. For most algorithms, you can choose the default hyperparameters, or create your own version of the algorithm by modifying them.

For example, for this relatively simple problem, choose a population size of 40 ( pop_size=40) and only 10 offspring per generation ( ) n_offsprings=10). Enable duplicate checking ( eliminate_duplicate =True) to ensure that matings produce offspring that differ from both their own and the existing population's design space values.

from pymoo.algorithms.moo.nsga2 import NSGA2
from pymoo.factory import get_sampling, get_crossover, get_mutation

algorithm = NSGA2(
    pop_size=40,
    n_offsprings=10,
    sampling=get_sampling("real_random"),
    crossover=get_crossover("real_sbx", prob=0.9, eta=15),
    mutation=get_mutation("real_pm", eta=20),
    eliminate_duplicates=True
)

3) Define termination criteria

Furthermore, termination criteria need to be defined to initiate the optimization process. The most common way to define termination is to limit the total number of function evaluations or simply limit the number of iterations of the algorithm .

Additionally, some algorithms have implemented their own algorithms, such as Nelder-Mead when simplex degenerates, or CMA-ES when using vendor libraries. Due to the simplicity of the problem, a rather small algorithm of 40 iterations was used

from pymoo.factory import get_termination
termination = get_termination("n_gen", 40)

For a list and explanation of termination criteria, see:

https://pymoo.org/interface/termination.html

4) Optimization

Finally, the problem is solved with a defined algorithm and termination. Functional interfaces use the minimization approach.

By default, the function execution algorithm is minimized and deep copies of objects are terminated to ensure that they are not modified during function calls . This is important to ensure that repeated function calls with the same random seed end up with the same result. When the algorithm terminates, the minimization function returns a Result object.

from pymoo.optimize import minimize

res = minimize(problem,
               algorithm,
               termination,
               seed=1,
               save_history=True,
               verbose=True)

X = res.X
F = res.F

Each row in the above figure represents an iteration. The first two columns are the current build counter and the number of calculations so far. For constrained problems, the third and fourth columns show the minimum constraint violation ( cv (min)) and the average constraint violation ( cv (avg)) in the current population. Next is the number of non-dominated solutions ( n_nds) and two other indicators representing movement in the target space.

5) Visualization

import matplotlib.pyplot as plt
xl, xu = problem.bounds()
plt.figure(figsize=(7, 5))
plt.scatter(X[:, 0], X[:, 1], s=30, facecolors='none', edgecolors='r')
plt.xlim(xl[0], xu[0])
plt.ylim(xl[1], xu[1])
plt.title("Design Space")
plt.show()
F = res.F
xl, xu = problem.bounds()
plt.figure(figsize=(7, 5))
plt.scatter(F[:, 0], F[:, 1], s=30, facecolors='none', edgecolors='blue')
plt.title("Objective Space")
plt.show()

4 Multi-criteria decision-making

After obtaining a set of non-dominated solutions, how does the decision maker determine this set of solutions into a few or even a single solution. This decision-making process for multi-objective problems is also called multi-criteria decision-making (MCDM).

1) Normalization of objective function value

In multi-objective optimization, the scale of the objective function needs to be considered :

fl = F.min(axis=0)
fu = F.max(axis=0)
print(f"Scale f1: [{
      
      fl[0]}, {
      
      fu[0]}]")
print(f"Scale f2: [{
      
      fl[1]}, {
      
      fu[1]}]")

# Scale f1: [1.3377795039158837, 74.97223429467643]
# Scale f2: [0.01809179532919018, 0.7831767823138299]

It can be seen that the target f 1 f_1f1and f 2 f_2f2The upper and lower bounds of are very different and need to be normalized.

Without standardization, we are comparing oranges and apples. The first target will dominate any distance calculation in target space due to its larger size . Dealing with targets of different sizes is an inherent part of any multi-objective algorithm, so the same needs to be done for post-processing.

A common approach is to use so-called ideal and nadir points for specification . It is assumed here that the ideal point and the lowest point (also called the boundary point) and the Pareto-front are unknown. So these points can be approximated as:

approx_ideal = F.min(axis=0)
approx_nadir = F.max(axis=0)
plt.figure(figsize=(7, 5))
plt.scatter(F[:, 0], F[:, 1], s=30, facecolors='none', edgecolors='blue')
plt.scatter(approx_ideal[0], approx_ideal[1], facecolors='none', edgecolors='red', marker="*", s=100, label="Ideal Point (Approx)")
plt.scatter(approx_nadir[0], approx_nadir[1], facecolors='none', edgecolors='black', marker="p", s=100, label="Nadir Point (Approx)")
plt.title("Objective Space")
plt.legend()
plt.show()

The target values ​​are normalized by:

nF = (F - approx_ideal) / (approx_nadir - approx_ideal)

fl = nF.min(axis=0)
fu = nF.max(axis=0)
print(f"Scale f1: [{
      
      fl[0]}, {
      
      fu[0]}]")
print(f"Scale f2: [{
      
      fl[1]}, {
      
      fu[1]}]")

plt.figure(figsize=(7, 5))
plt.scatter(nF[:, 0], nF[:, 1], s=30, facecolors='none', edgecolors='blue')
plt.title("Objective Space")
plt.show()

# Scale f1: [0.0, 1.0]
# Scale f2: [0.0, 1.0]

2) Compromise Programming

One way to make decisions is to use decomposition functions . It is necessary to define weights that reflect the user's wishes . The weights given by the vector are only positive floating point numbers summing to 1 , with a length equal to the target number . For a dual-objective problem, assuming that the first objective is not as important as the second objective, set the weight:

weights = np.array([0.2, 0.8])

Next, choose the Augmented Scalarization Function (ASF) decomposition method.

from pymoo.decomposition.asf import ASF
decomp = ASF()

Since ASF should be minimized , the smallest ASF value is selected from all solutions .

Why are the weights not passed directly, but 1/weights? For ASF, there are different formulas, one where values ​​are divided and another where values ​​are multiplied. In "pymoo", "divide" does not reflect user standards. Therefore, the inverse function needs to be applied.

i = decomp.do(nF, 1/weights).argmin()

After finding the solution ( iii ), the result can be expressed in its original proportion:

print("Best regarding ASF: Point \ni = %s\nF = %s" % (i, F[i]))

plt.figure(figsize=(7, 5))
plt.scatter(F[:, 0], F[:, 1], s=30, facecolors='none', edgecolors='blue')
plt.scatter(F[i, 0], F[i, 1], marker="x", color="red", s=200)
plt.title("Objective Space")
plt.show()

# Best regarding ASF: Point
# i = 21
# F = [43.28059434  0.12244878]

One benefit of this approach is that any type of decomposition function can be used.

3) Pseudo-Weights

In a multi-objective optimization context, a simple method for selecting solutions from a solution set is the pseudo-weight vector method. Calculate the iith respectivelyPseudo weightswi w_i of i objective functionswi

This equation calculates each objective ii The normalized distance from i to the worst solution . Note that fornon-convex Pareto sets, the pseudo-weights do not correspond to the optimization results using weighted sums. However, fora convex Pareto set, the pseudo-weights represent positions in the target space.

from pymoo.mcdm.pseudo_weights import PseudoWeights
i = PseudoWeights(weights).do(nF)
print("Best regarding Pseudo Weights: Point \ni = %s\nF = %s" % (i, F[i]))

plt.figure(figsize=(7, 5))
plt.scatter(F[:, 0], F[:, 1], s=30, facecolors='none', edgecolors='blue')
plt.scatter(F[i, 0], F[i, 1], marker="x", color="red", s=200)
plt.title("Objective Space")
plt.show()

# Best regarding Pseudo Weights: Point
# i = 39
# F = [58.52211061  0.06005482]

5 Convergence analysis

The convergence analysis should consider two cases, i) the Pareto set is unknown, or ii) the Pareto set has been derived analytically, or a reasonable approximation exists.

1) Convergence visualization

To further check how well the results match the analytically derived optimality, the target space values ​​must be converted to a second target f 2 f_2f2The original definition of maximization. Then draw a Pareto-front diagram to show the extent to which the algorithm can converge.

from pymoo.util.misc import stack

class MyTestProblem(MyProblem):

    def _calc_pareto_front(self, flatten=True, *args, **kwargs):
        f2 = lambda f1: ((f1/100) ** 0.5 - 1)**2
        F1_a, F1_b = np.linspace(1, 16, 300), np.linspace(36, 81, 300)
        F2_a, F2_b = f2(F1_a), f2(F1_b)

        pf_a = np.column_stack([F1_a, F2_a])
        pf_b = np.column_stack([F1_b, F2_b])

        return stack(pf_a, pf_b, flatten=flatten)

    def _calc_pareto_set(self, *args, **kwargs):
        x1_a = np.linspace(0.1, 0.4, 50)
        x1_b = np.linspace(0.6, 0.9, 50)
        x2 = np.zeros(50)

        a, b = np.column_stack([x1_a, x2]), np.column_stack([x1_b, x2])
        return stack(a,b, flatten=flatten)

problem = MyTestProblem()

The Pareto front of test problems can be implemented in the following ways:

pf_a, pf_b = problem.pareto_front(use_cache=False, flatten=False)
pf = problem.pareto_front(use_cache=False, flatten=True)
plt.figure(figsize=(7, 5))
plt.scatter(F[:, 0], F[:, 1], s=30, facecolors='none', edgecolors='b', label="Solutions")
plt.plot(pf_a[:, 0], pf_a[:, 1], alpha=0.5, linewidth=2.0, color="red", label="Pareto-front")
plt.plot(pf_b[:, 0], pf_b[:, 1], alpha=0.5, linewidth=2.0, color="red")
plt.title("Objective Space")
plt.legend()
plt.show()

For drawing of high-dimensional space, please refer to:

https://pymoo.org/visualization/index.html

Alternatively, you can use save_history=Trueto check convergence:

from pymoo.optimize import minimize

res = minimize(problem,
               algorithm,
               ("n_gen", 40),
               seed=1,
               save_history=True,
               verbose=False)

X, F = res.opt.get("X", "F")

hist = res.history
print(len(hist)) # 40

n_evals = []             # corresponding number of function evaluations\
hist_F = []              # the objective space values in each generation
hist_cv = []             # constraint violation in each generation
hist_cv_avg = []         # average constraint violation in the whole population

for algo in hist:

    # store the number of function evaluations
    n_evals.append(algo.evaluator.n_eval)

    # retrieve the optimum from the algorithm
    opt = algo.opt

    # store the least contraint violation and the average in each population
    hist_cv.append(opt.get("CV").min())
    hist_cv_avg.append(algo.pop.get("CV").mean())

    # filter out only the feasible and append and objective space values
    feas = np.where(opt.get("feasible"))[0]
    hist_F.append(opt.get("F")[feas])

2) Constraints are satisfied

First, see when the first working solution was found :

k = np.where(np.array(hist_cv) <= 0.0)[0].min()
print(f"At least one feasible solution in Generation {
      
      k} after {
      
      n_evals[k]} evaluations.")
# At least one feasible solution in Generation 0 after 40 evaluations.

# replace this line by `hist_cv` if you like to analyze the least feasible optimal solution and not the population
vals = hist_cv_avg

k = np.where(np.array(vals) <= 0.0)[0].min()
print(f"Whole population feasible in Generation {
      
      k} after {
      
      n_evals[k]} evaluations.")

plt.figure(figsize=(7, 5))
plt.plot(n_evals, vals,  color='black', lw=0.7, label="Avg. CV of Pop")
plt.scatter(n_evals, vals,  facecolor="none", edgecolor='black', marker="p")
plt.axvline(n_evals[k], color="red", label="All Feasible", linestyle="--")
plt.title("Convergence")
plt.xlabel("Function Evaluations")
plt.ylabel("Constraint Violation")
plt.legend()
plt.show()

# Whole population feasible in Generation 8 after 120 evaluations.

3) Pareto collection unknown

When the Pareto-front is unknown, it is impossible to know whether the algorithm has converged to the true optimum. At least no further information. However, it is possible to see when the algorithm has made most of its progress during the optimization process , and whether the number of iterations should be fewer or more. HypervolumeCan be used to compare two algorithms.

Hypervolume is a set (concept) based on boundary normalization, used for performance indicators of multi-objective problems. It complies with the Pareto criterion and is based on the capacity between predefined reference points and provided solutions. Therefore, hypervolume needs to define a reference point ref_point, which must be greater than the maximum value of Pareto front.

# 在多目标优化中,归一化是非常重要的
approx_ideal = F.min(axis=0)
approx_nadir = F.max(axis=0)

from pymoo.indicators.hv import Hypervolume

metric = Hypervolume(ref_point= np.array([1.1, 1.1]),
                     norm_ref_point=False,
                     zero_to_one=True,
                     ideal=approx_ideal,
                     nadir=approx_nadir)

hv = [metric.do(_F) for _F in hist_F]

plt.figure(figsize=(7, 5))
plt.plot(n_evals, hv,  color='black', lw=0.7, label="Avg. CV of Pop")
plt.scatter(n_evals, hv,  facecolor="none", edgecolor='black', marker="p")
plt.title("Convergence")
plt.xlabel("Function Evaluations")
plt.ylabel("Hypervolume")
plt.show()

As the number of dimensions increases, the computational cost of Hypervolume increases. It can efficiently calculate accurate Hypervolume for 2 and 3 targets. For higher dimensions, some researchers use hypervolume approximation, which is not yet available in pymoo.

An alternative method of analyzing runs when the true Pareto front is unknown has been recently proposed running metric. Running metrics show the differences in the target space between generations and visualize improvements using the algorithm's viability. In pymoo, this metric is used to determine the termination of a multi-objective optimization algorithm if no default termination criterion is defined.

For example, through analysis, it can be found that the algorithm has been significantly improved from the 4th generation to the 5th generation.

from pymoo.util.running_metric import RunningMetric

running = RunningMetric(delta_gen=5,
                        n_plots=3,
                        only_if_n_plots=True,
                        key_press=False,
                        do_show=True)

for algorithm in res.history[:15]:
    running.notify(algorithm)

Plot until the population shows convergence, with only slight improvements here:

from pymoo.util.running_metric import RunningMetric

running = RunningMetric(delta_gen=10,
                        n_plots=4,
                        only_if_n_plots=True,
                        key_press=False,
                        do_show=True)

for algorithm in res.history:
    running.notify(algorithm)

4) The Pareto set is known or approximated

For real problems, approximations must be used. By running the algorithm multiple times and extracting non-dominated solutions from all solution sets , an approximate solution can be obtained. If there is only one run , another option is to use the resulting set of nondominated solutions as an approximation . However, the results only reflect the progress of the algorithm in converging to the final set.

from pymoo.indicators.igd import IGD

metric = IGD(pf, zero_to_one=True)

igd = [metric.do(_F) for _F in hist_F]

plt.plot(n_evals, igd,  color='black', lw=0.7, label="Avg. CV of Pop")
plt.scatter(n_evals, igd,  facecolor="none", edgecolor='black', marker="p")
plt.axhline(10**-2, color="red", label="10^-2", linestyle="--")
plt.title("Convergence")
plt.xlabel("Function Evaluations")
plt.ylabel("IGD")
plt.yscale("log")
plt.legend()
plt.show()
from pymoo.indicators.igd_plus import IGDPlus

metric = IGDPlus(pf, zero_to_one=True)

igd = [metric.do(_F) for _F in hist_F]

plt.plot(n_evals, igd,  color='black', lw=0.7, label="Avg. CV of Pop")
plt.scatter(n_evals, igd,  facecolor="none", edgecolor='black', marker="p")
plt.axhline(10**-2, color="red", label="10^-2", linestyle="--")
plt.title("Convergence")
plt.xlabel("Function Evaluations")
plt.ylabel("IGD+")
plt.yscale("log")
plt.legend()
plt.show()

For detailed indicators, see:

https://pymoo.org/misc/indicators.html

reference:

https://pymoo.org/index.html

Guess you like

Origin blog.csdn.net/mengjizhiyou/article/details/124647597