You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Pymoo二进制变量固定和约束双目标优化的技术咨询

解决Pymoo双目标0-1优化的两个关键问题

嘿,作为Pymoo新手碰到这种带特定约束的问题确实容易卡壳,我来一步步帮你搞定这两个疑问:

一、如何定义“sum(X)=9”的等式约束

Pymoo里处理等式约束有两种常用方式,最直观规范的是直接指定等式约束数量并计算约束值:

  1. 修改Problem初始化:在__init__里添加n_eq_constr=1(代表1个等式约束),同时明确变量类型为整数(避免浮点型干扰):
class MyProblem(Problem):
    def __init__(self):
        super().__init__(n_var=13, n_obj=2, n_eq_constr=1, xl=0, xu=1, var_type=int)

这里去掉原来的n_constr=1,因为它是给不等式约束用的。

  1. _evaluate中计算约束值:等式约束的结果要存在out["H"]中,计算每个个体的元素和与9的差值即可:
def _evaluate(self, X, out, *args, **kwargs):
    f1 = self.first_objective_function(X)
    f2 = self.second_objective_function(X)
    # X是二维数组,axis=1按行计算每个个体的元素和
    h = np.sum(X, axis=1) - 9
    out["F"] = np.column_stack([f1, f2])
    out["H"] = h  # Pymoo会自动将H中的值视为等式约束,要求其接近0(默认容差1e-6)

如果你偏好用不等式约束替代,也可以拆成两个约束:sum(X)-9 ≤09-sum(X)≤0,此时设置n_constr=2,并在out["G"]中传入这两个约束值,不过用等式约束更简洁。

二、采样、交叉、突变算法的适用性优化

你当前选的算子不太适配固定1的数量的场景:随机采样会生成大量不满足sum=9的无效个体,普通交叉和位翻转突变会破坏sum约束,导致大部分个体被过滤,优化效率极低。推荐换成以下专门适配的算子:

1. 采样:直接生成符合约束的初始种群

自定义采样函数,确保每个初始个体都恰好有9个1:

def fixed_sum_sampling(**kwargs):
    def _do(self, problem, n_samples, **kwargs):
        X = np.zeros((n_samples, problem.n_var), dtype=int)
        for i in range(n_samples):
            # 随机选9个位置设为1
            idx = np.random.choice(problem.n_var, size=9, replace=False)
            X[i, idx] = 1
        return X
    return Sampling(func=_do)

替换算法中的采样参数:sampling=fixed_sum_sampling(),

2. 交叉:保持1的数量不变的交换式交叉

普通HUX交叉可能改变1的数量,自定义交叉逻辑:在两个父代之间交换相同数量的0和1,确保子代的1的数量仍为9:

def fixed_sum_crossover(prob=0.9):
    def _do(self, problem, X, **kwargs):
        n_matings, n_var = X.shape
        off = np.copy(X)
        for i in range(0, n_matings, 2):
            if np.random.random() < prob:
                parent1 = X[i]
                parent2 = X[i+1]
                # 找出父代1是1、父代2是0的位置,以及父代1是0、父代2是1的位置
                ones_zero = np.where((parent1 == 1) & (parent2 == 0))[0]
                zero_ones = np.where((parent1 == 0) & (parent2 == 1))[0]
                # 交换相同数量的位(取两者的最小长度)
                swap_num = min(len(ones_zero), len(zero_ones))
                if swap_num > 0:
                    swap_idx1 = np.random.choice(ones_zero, swap_num, replace=False)
                    swap_idx2 = np.random.choice(zero_ones, swap_num, replace=False)
                    off[i, swap_idx1] = 0
                    off[i, swap_idx2] = 1
                    off[i+1, swap_idx1] = 1
                    off[i+1, swap_idx2] = 0
        return off
    return Crossover(func=_do, n_parents=2, n_offsprings=2)

替换算法中的交叉参数:crossover=fixed_sum_crossover(prob=0.9),

3. 突变:保持1的数量不变的双位翻转

普通位翻转会改变sum,换成双位翻转:每次随机选一个0和一个1翻转,确保sum始终为9:

def fixed_sum_mutation(prob=0.1):
    def _do(self, problem, X, **kwargs):
        for i in range(X.shape[0]):
            if np.random.random() < prob:
                # 找到当前个体的0和1的位置
                zeros = np.where(X[i] == 0)[0]
                ones = np.where(X[i] == 1)[0]
                if len(zeros) > 0 and len(ones) > 0:
                    z_idx = np.random.choice(zeros)
                    o_idx = np.random.choice(ones)
                    X[i, z_idx] = 1
                    X[i, o_idx] = 0
        return X
    return Mutation(func=_do)

替换算法中的突变参数:mutation=fixed_sum_mutation(prob=0.1),

整合后的完整代码示例

把以上修改整合到你的代码中,最终版本如下:

import numpy as np
from pymoo.core.problem import Problem
from pymoo.algorithms.moo.nsga2 import NSGA2
from pymoo.core.sampling import Sampling
from pymoo.core.crossover import Crossover
from pymoo.core.mutation import Mutation
from pymoo.optimize import minimize

# 自定义固定sum的采样
def fixed_sum_sampling(**kwargs):
    def _do(self, problem, n_samples, **kwargs):
        X = np.zeros((n_samples, problem.n_var), dtype=int)
        for i in range(n_samples):
            idx = np.random.choice(problem.n_var, size=9, replace=False)
            X[i, idx] = 1
        return X
    return Sampling(func=_do)

# 自定义固定sum的交叉
def fixed_sum_crossover(prob=0.9):
    def _do(self, problem, X, **kwargs):
        n_matings, n_var = X.shape
        off = np.copy(X)
        for i in range(0, n_matings, 2):
            if np.random.random() < prob:
                parent1 = X[i]
                parent2 = X[i+1]
                ones_zero = np.where((parent1 == 1) & (parent2 == 0))[0]
                zero_ones = np.where((parent1 == 0) & (parent2 == 1))[0]
                swap_num = min(len(ones_zero), len(zero_ones))
                if swap_num > 0:
                    swap_idx1 = np.random.choice(ones_zero, swap_num, replace=False)
                    swap_idx2 = np.random.choice(zero_ones, swap_num, replace=False)
                    off[i, swap_idx1] = 0
                    off[i, swap_idx2] = 1
                    off[i+1, swap_idx1] = 1
                    off[i+1, swap_idx2] = 0
        return off
    return Crossover(func=_do, n_parents=2, n_offsprings=2)

# 自定义固定sum的突变
def fixed_sum_mutation(prob=0.1):
    def _do(self, problem, X, **kwargs):
        for i in range(X.shape[0]):
            if np.random.random() < prob:
                zeros = np.where(X[i] == 0)[0]
                ones = np.where(X[i] == 1)[0]
                if len(zeros) > 0 and len(ones) > 0:
                    z_idx = np.random.choice(zeros)
                    o_idx = np.random.choice(ones)
                    X[i, z_idx] = 1
                    X[i, o_idx] = 0
        return X
    return Mutation(func=_do)

class MyProblem(Problem):
    def __init__(self):
        super().__init__(n_var=13, n_obj=2, n_eq_constr=1, xl=0, xu=1, var_type=int)
        
    def first_objective_function(self, X):
        # 替换成你的实际目标函数,X是二维数组,每行对应一个个体
        y = np.sum(X, axis=1)  # 示例函数,需替换为你的业务逻辑
        return y
    
    def second_objective_function(self, X):
        # 替换成你的实际目标函数
        y = np.sum(X[:, :6], axis=1)  # 示例函数,需替换为你的业务逻辑
        return y
    
    def _evaluate(self, X, out, *args, **kwargs):
        f1 = self.first_objective_function(X)
        f2 = self.second_objective_function(X)
        h = np.sum(X, axis=1) - 9  # 等式约束:sum(X) = 9
        out["F"] = np.column_stack([f1, f2])
        out["H"] = h

my_problem = MyProblem()
algorithm = NSGA2(
    pop_size=40,
    n_offsprings=10,
    sampling=fixed_sum_sampling(),
    crossover=fixed_sum_crossover(prob=0.9),
    mutation=fixed_sum_mutation(prob=0.1),
    eliminate_duplicates=True)

res = minimize(my_problem, algorithm, ('n_gen', 100), verbose=True)

这样修改后,所有生成的个体都会自动满足sum(X)=9的约束,优化效率会大幅提升,同时约束定义也完全符合Pymoo的规范。

内容的提问来源于stack exchange,提问作者Xudong

火山引擎 最新活动