背景:最近我在做一些求最优化的问题。
有上下界约束,有等式约束和不等式约束,其中在不等式约束中需要变量x0,x1,x2,x3(出库)分别大于x4,x5,x6,x7(发电),这个约束对我的实际问题来说很重要,但是呢,在巡游的过程中,并不能保证完全不出现这种情况,尤其当我因为运算速度比较慢,将种子数量和迭代次数都减少了以后,这种情况变得更多了。
我用的算法库:scikit-opt,算法我用的GA
https://github.com/scikit-opt/scikit-opt
代码示例,不一定正确,就说一下几个关键点的使用方法:
def obj_func(p):
x1, x2, x3 = p
return x1 ** 2 + x2 ** 2 + x3 ** 2
constraint_eq = [
lambda x: 1 - x[1] - x[2]
]
constraint_ueq = [
lambda x: 1 - x[0] * x[1],
lambda x: x[0] * x[1] - 5
]
ga = GA(func=obj_func, n_dim=3, size_pop=50, max_iter=100,
prob_mut=0.1, lb=[-5, -5, -5], ub=[5, 5, 5], precision=[1e-3, 1e-3, 1e-3])
经过网上搜索和自己的思考总结了以下几种解决方案:
1.约束上下界
这种办法简单直接有效,但是对我的实际情况来说没办法用。
2.用precision控制精度
这个不保证一定能起作用,且精度太小会导致迭代很难收敛。
3.在obj-fun里面加一个很大的惩罚因子
首先我们迭代的目的就是为了让obj-fun最小(如果要最大,你就在前面加个负号),你用下面的方法如果不满足你的约束,你就给他强制返回一个很大很大的数,这样就让迭代过程不选择这组解。但是还是有问题,比如他也不能完全保证不出现这样的解,或者不靠近这样的解,但是我们却不想要这样的解,所以种方案会使得计算过程变慢。
def objective(x):
penalty = 1e6 # 惩罚因子
if x[0] < -5 or x[0] > 5 or x[1] < -5 or x[1] > 5:
return penalty # 约束不满足,给一个大惩罚值
return x[0]**2 + x[1]**2 # 目标函数
ga = GA(func=objective, n_dim=2, size_pop=50, max_iter=100, lb=[-5, -5], ub=[5, 5])
4.在不等式约束中添加
这个我当然当的添加了,但是没起作用。
5.自定义修正算子
在 GA 运行过程中,每次 变异 和 交叉 之后,手动调整解 让其符合约束。
ga.mutation 重新定义了 GA 的变异过程,确保每次更新都满足约束。
这个方法最直接有效,不影响 GA 本身的搜索能力!
具体如下:
import numpy as np
from sko.GA import GA
def objective(x):
return sum(x) # 目标函数示例
def constraint_correction(X):
""" 修正解,使其满足 x0, x1, x2, x3 >= x4, x5, x6, x7 """
for i in range(len(X)): # 遍历种群中的每个个体
X[i, :4] = np.maximum(X[i, :4], X[i, 4:]) # 修正前4个变量 >= 后4个变量
return X
ga = GA(func=objective, n_dim=8, size_pop=50, max_iter=100, prob_mut=0.1,
lb=[-10] * 8, ub=[10] * 8)
# 重写变异函数,保证变异后的个体满足约束
ga.mutation = lambda X: constraint_correction(X + ga.mutation(X))
best_x, best_y = ga.run()
print("最优解:", best_x, "最优值:", best_y)
6.直接调整 GA 交叉策略
交叉(Crossover)时 确保新个体满足约束,可以手动调整 crossover 逻辑。
这个方法不会破坏 GA 的搜索能力,只是确保每次交叉生成的新解都符合约束。
适用于约束不复杂的情况,效果很好!
def custom_crossover(X):
""" 交叉时修正解,使其满足 x0, x1, x2, x3 >= x4, x5, x6, x7 """
X[:, :4] = np.maximum(X[:, :4], X[:, 4:]) # 强制前 4 个变量大于后 4 个
return X
ga = GA(func=objective, n_dim=8, size_pop=50, max_iter=100, prob_mut=0.1,
lb=[-10] * 8, ub=[10] * 8)
ga.crossover = custom_crossover # 替换默认交叉策略
best_x, best_y = ga.run()
print("最优解:", best_x, "最优值:", best_y)
总结
3.最简单直接,但是可能会收敛得慢,因为 GA 仍然会探索被惩罚的区域,而且也不保证完全有效。
5.可以在任何情况下使用,强制修正解,直接有效,缺点是可能略影响搜索。
6.适用于轻量约束问题,只影响交叉阶段,不影响变异,缺点是可能仍有超界情况。
算了毁灭吧,我都试了,没有保证有效的,可能跟初始值有关系,等我学会设定初始值了再来。