Pymoo二进制变量固定和约束双目标优化的技术咨询
嘿,作为Pymoo新手碰到这种带特定约束的问题确实容易卡壳,我来一步步帮你搞定这两个疑问:
一、如何定义“sum(X)=9”的等式约束
Pymoo里处理等式约束有两种常用方式,最直观规范的是直接指定等式约束数量并计算约束值:
- 修改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,因为它是给不等式约束用的。
- 在
_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 ≤0 和 9-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




