How an XLEMOO method can be used as an interactive multiobjective optimization method
In this example, we can see how an XLEMOO method can be used as an interactive multiobjective optimization method accepting reference points as preferences.
Import and helper functions
Below, we define some imports and helper functions to be utilized later in printing the results of our LEMOO model.
[1]:
import sys
sys.path.append("../../../XLEMOO")
import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)
warnings.filterwarnings("ignore", category=FutureWarning)
from XLEMOO.LEMOO import EAParams, MLParams, LEMParams, LEMOO, PastGeneration
from XLEMOO.fitness_indicators import asf_wrapper
from XLEMOO.selection import SelectNBest
from XLEMOO.plotting import show_rules
from XLEMOO.ruleset_interpreter import extract_skoped_rules
from desdeo_emo.recombination import SBX_xover, BP_mutation
from desdeo_tools.scalarization.ASF import PointMethodASF
from desdeo_problem.testproblems import vehicle_crashworthiness
import matplotlib.pyplot as plt
import numpy as np
from imodels import SkopeRulesClassifier
def extract_rule_set(lemoo, problem, n_variables):
rules, accuracies = extract_skoped_rules(lemoo.current_ml_model)
problem_lower_bounds = problem.get_variable_lower_bounds()
problem_upper_bounds = problem.get_variable_upper_bounds()
rules_for_vars = {f"X_{i}": {">": [problem_lower_bounds[i], -1], "<=": [problem_upper_bounds[i], -1]} for i in range(n_variables)}
for accuracy, rule in zip(accuracies, rules):
for key in rule:
var_name = key[0]
op = key[1]
# check accuracy
if rules_for_vars[var_name][op][1] < accuracy:
# update accuracy
rules_for_vars[var_name][op][1] = accuracy
if op == "<=":
# tighten rule, if necessary
if float(rule[(var_name, op)]) <= rules_for_vars[var_name][op][0]:
rules_for_vars[var_name][op][0] = float(rule[(var_name, op)])
elif op == ">":
# tighten rule, if necessary
if float(rule[(var_name, op)]) > rules_for_vars[var_name][op][0]:
rules_for_vars[var_name][op][0] = float(rule[(var_name, op)])
return rules_for_vars
def complete_missing_rules(lemoo, rules_for_vars):
lower_bounds = np.min(lemoo._generation_history[-1].individuals, axis=0)
upper_bounds = np.max(lemoo._generation_history[-1].individuals, axis=0)
# if there are no upper or lower bound for some vars in the rules, use the min or max from the population
for var_i, var_name in enumerate(rules_for_vars):
for op in rules_for_vars[var_name]:
if op == "<=":
if rules_for_vars[var_name][op][1] == -1:
# replace missing rule with bound from population
rules_for_vars[var_name][op][0] = upper_bounds[var_i]
if op == ">":
if rules_for_vars[var_name][op][1] == -1:
rules_for_vars[var_name][op][0] = lower_bounds[var_i]
return rules_for_vars
def print_rules(lemoo, rules_for_vars, tex=False):
lower_bounds = np.min(lemoo._generation_history[-1].individuals, axis=0)
upper_bounds = np.max(lemoo._generation_history[-1].individuals, axis=0)
print("RULES:")
print("Var\tLower(R)\t\tUpper(R)\t\tLower(P)\t\tUpper(P)")
for i, rule in enumerate(rules_for_vars):
if not tex:
msg = (f"{rule}\t{np.round(rules_for_vars[rule]['>'][0], 5)}\t({np.round(rules_for_vars[rule]['>'][1], 3)})\t\t{np.round(rules_for_vars[rule]['<='][0], 5)} ({np.round(rules_for_vars[rule]['<='][1], 3)})\t\t"
f"{np.round(lower_bounds[i], 5)}\t\t\t{np.round(upper_bounds[i], 5)}")
else:
msg = (f"${rule}$ & ${np.round(rules_for_vars[rule]['>'][0], 5)}$ & $({np.round(rules_for_vars[rule]['>'][1], 3)})$ & ${np.round(rules_for_vars[rule]['<='][0], 5)}$ & $({np.round(rules_for_vars[rule]['<='][1], 3)})$ &"
f"${np.round(lower_bounds[i], 5)}$ & ${np.round(upper_bounds[i], 5)}$ \\\\")
print(msg)
return None
/home/kilo/workspace/XLEMOO/docs/notebooks/../../../XLEMOO/XLEMOO/tree_interpreter.py:1: DeprecationWarning:
The Tix Tk extension is unmaintained, and the tkinter.tix wrapper module is deprecated in favor of tkinter.ttk
Initialize the problem
Next, we initialize a simple test problem with 3 objective functions and 5 variables. We use the vehicle crash worthiness problem.
[2]:
n_objectives = 3
n_variables = 5
problem = vehicle_crashworthiness()
Initialize an XLEMOO method
Now we are ready to define our LEMOO method. We start by defining the parameters of our model. In lem_params general parameters related to the LEMOO method are defined. In ea_params, parameters specific to the evolutionary phase of our LEMOO method are defined, and in ml_params parameters specific to the learning phase of the method are defined. For additional information about these parameters, see the API documentation of the XLEMOO framework.
[3]:
ideal = np.array([1600.0, 6.0, 0.038])
nadir = np.array([1700.0, 12.0, 0.2])
ref_point = np.array([1670.0, 7.61449, 0.085]) # the reference point
# define the achievement scalarizing function as the fitness function
ref_asf = asf_wrapper(PointMethodASF(ideal=ideal, nadir=nadir), {"reference_point": ref_point})
fitness_fun = ref_asf
lem_params = LEMParams(
use_darwin=True,
use_ml=True,
fitness_indicator=fitness_fun,
ml_probe = 1,
ml_threshold = None,
darwin_probe = None,
darwin_threshold = None,
total_iterations=10,
)
ea_params = EAParams(
population_size=50,
cross_over_op=SBX_xover(),
mutation_op=BP_mutation(problem.get_variable_lower_bounds(), problem.get_variable_upper_bounds()),
selection_op=SelectNBest(None, 50), # keep population size constant
population_init_design="LHSDesign",
iterations_per_cycle=19,
)
ml = SkopeRulesClassifier(precision_min=0.1, n_estimators=30, max_features=None, max_depth=None, bootstrap=True, bootstrap_features=True, random_state=1)
ml_params = MLParams(
H_split=0.20,
L_split=0.20,
ml_model=ml,
instantiation_factor=10,
generation_lookback=0,
ancestral_recall=0,
unique_only=True,
iterations_per_cycle=1,
)
lemoo = LEMOO(problem, lem_params, ea_params, ml_params)
Run the XLEMOO method
We run our XLEMOO method for a set number of iterations in each mode. The printed output shows how many iterations were spent in each mode.
[4]:
lemoo.run_iterations()
[4]:
{'darwin_mode': 190, 'learning_mode': 10, 'total_iterations': 10}
Extract and print the rules from skope-rules and complete missing rules
Utilizing the trained XLEMOO model and the helper functions defined earlier, we can extract the rules in a human readable format.
[5]:
rules_for_vars = extract_rule_set(lemoo=lemoo, problem=problem, n_variables=n_variables)
rules_for_vars = complete_missing_rules(lemoo=lemoo, rules_for_vars=rules_for_vars)
print(f"Best solution x = {lemoo._generation_history[-1].individuals[0]}")
print(f"Best objective vector z = {lemoo._generation_history[-1].objectives_fitnesses[0]}")
print_rules(lemoo=lemoo, rules_for_vars=rules_for_vars, tex=False)
Best solution x = [1.00037555 2.99999995 1. 1.29893855 1.03798257]
Best objective vector z = [1.66883018e+03 7.53444094e+00 8.31048744e-02]
RULES:
Var Lower(R) Upper(R) Lower(P) Upper(P)
X_0 1.00037 (-1) 1.14978 (1.0) 1.00037 1.00038
X_1 2.89008 (1.0) 3.0 (-1) 3.0 3.0
X_2 1.0 (-1) 1.00001 (1.0) 1.0 1.0
X_3 1.27361 (1.0) 1.42884 (1.0) 1.29894 1.29894
X_4 1.02803 (1.0) 1.05154 (1.0) 1.03798 1.03798
Modify variables and explore a new solution
We may then modify the variables to explore new solutions.
[6]:
x_new = np.atleast_2d([1.00184, 2.7135, 1.00000, 1.21741, 1.02602])
z_new = problem.evaluate(x_new).objectives
print(z_new)
[[1.66748556e+03 7.54345501e+00 9.49552335e-02]]
Or we may specify a new reference point and run the XLEMOO method again.