# SPDX-License-Identifier: MPL-2.0
# Copyright (C) 2020- The University of Tokyo
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
from __future__ import annotations
from typing import Optional
import numpy as np
from ...misc import min_max_scaling
[ドキュメント]
class ParEGO:
num_objectives: int
"""Number of objectives"""
weight_sum: float
"""Weight of the sum of objectives"""
weight_max: float
"""Weight of the max of objectives"""
weights: Optional[np.ndarray]
"""Weights for the objectives.
Weights are automatically normalized to sum to 1.
If None, random weights are used for each call.
"""
weights_discrete: int
"""Number of discrete points to generate random weights"""
def __init__(
self,
num_objectives: int,
weight_sum: float = 0.05,
weight_max: float = 1.0,
weights: Optional[np.ndarray] = None,
weights_discrete: int = 0,
):
r""" ParEGO unified objective function
The unified objective function of original objectives t is defined as:
t_unified = weight_max * max(weights * t) + weight_sum * sum(weights * t)
Before calculating the unified objective function, the original objectives are normalized to 0 and 1 using min-max scaling.
Parameters
----------
num_objectives: int
Number of objectives
weight_sum: float
Weight of the sum of objectives, default is 0.05
weight_max: float
Weight of the max of objectives, default is 1.0
weights: np.ndarray
Weights for the objectives.
Weights are automatically normalized to sum to 1.
If None (default), random weights are used for each call.
weights_discrete: int
Number of discrete points :math:`s` to generate random weights.
The weights are generated as :math:`w_i = \frac{a_i}{s}`, where :math:`a_i` is random integer in :math:`[0, s)` such that :math:`\sum_{i=1}^{num_objectives} a_i = s`.
(See Equation (1) in [1] for details.)
If 0 (default), each weight is generated randomly from :math:`[0, 1)` and normalized to sum to 1.
References
----------
[1] J. Knowles, ParEGO: a hybrid algorithm with on-line landscape approximation for expensive multiobjective optimization problems, IEEE Trans. Evol. Comput. 10, 50 (2006) (doi:10.1109/tevc.2005.851274).
"""
self.num_objectives = num_objectives
self.weight_sum = weight_sum
self.weight_max = weight_max
if weights is not None:
weights = np.array(weights)
self.weights = weights / np.sum(weights)
else:
self.weights = None
self.weights_discrete = weights_discrete
def __call__(self, t: np.ndarray) -> np.ndarray:
"""
Calculate unified objective function by ParEGO method
Parameters
----------
t: np.ndarray
Values of the original objective functions
Shape: (N, num_objectives)
Returns
-------
t_unified: np.ndarray
Values of the unified objective function
Shape: (N, 1)
"""
if self.weights is None:
if self.weights_discrete > 0:
weights = sample_weights(self.num_objectives, self.weights_discrete)
else:
weights = np.random.rand(self.num_objectives)
weights /= np.sum(weights)
else:
weights = self.weights
if t.ndim == 1:
t = t.reshape(1, -1)
assert t.shape[1] == self.num_objectives, f"The number of objectives in t ({t.shape[1]}) must be the same as the number of objectives in the ParEGO ({self.num_objectives})"
t_weighted = min_max_scaling(t, low=-1.0, high=0.0) * weights.reshape(1, -1)
t_sum = np.sum(t_weighted, axis=1)
t_max = np.max(t_weighted, axis=1)
t_unified = self.weight_max * t_max + self.weight_sum * t_sum
return t_unified.reshape(-1, 1)
[ドキュメント]
def sample_weights(num_objectives: int, s: int) -> np.ndarray:
"""
Sample weights for the objectives
Parameters
----------
num_objectives: int
Number of objectives
s: int
Number of discrete points to sample weights
"""
ls = np.zeros(num_objectives, dtype=int)
for d in range(num_objectives - 1):
ls[d] = np.random.randint(s + 1)
s -= ls[d]
ls[num_objectives - 1] = s
return ls / np.sum(ls)