1 拉格朗日乘子法的数学背景
当使用前面介绍的罚函数法求解约束问题时,为获得足够好的近似解,罚参数需取足够大的值,这将导致增广目标函数的黑森矩阵出现病态,从而导致数值计算上的困难。因此提出拉格朗日乘子法。
学高数的时候我们就学过等式约束条件下的拉格朗日乘子法。延续前一节中对约束最优化问题的定义,则拉格朗日函数为。局部最优解的条件是对x求梯度及对所有求导均为0。
下面来讨论不等式和等式同时约束条件下的拉格朗日乘子法。拉格朗日函数为。对的情况需要讨论:
- 最优解在内部时,,约束条件无效,有
- 最优解在边界上时,,约束条件有效。此时对x求梯度设为0,有,指向内部(因为边界处是最优解),也指向内部(因为边界处为0,内大于0),所以
可见,无论最优解在何处,都有,这个条件被称为互补松弛条件。
因此针对不等式和等式同时约束条件下的拉格朗日乘子法,局部最优解的条件可以用如下的KKT条件表示:
满足KKT条件的点被称为KKT点。
2 拉格朗日乘子法的构成
2.1 等式约束问题的乘子法
将外点罚函数法的思想引入拉格朗日函数的最优化问题。设,构造辅助函数
求驻点,令
根据KKT条件有
若
则
- 为了使得收敛向,有
- 为了使得,每一步增加罚因子(为放大系数)
由此得到等式约束问题乘子法的步骤:
- 选定初始点,初始乘子估计,初始罚因子,常数,,精度,置
- 构造增广目标函数
- 以为初始点求解无约束问题,其解为
- 若,则得解,停止迭代
- 若成立,则令,否则
- 令,令,转步2
2.2 一般约束问题的乘子法
引入松弛变量z将不等式问题化为等价的等式约束问题:
构造增广拉格朗日函数:
配方后可得:
将该式对求偏导,令偏导为0,有
因此,代入消去z得
则
根据KKT条件有
为了使得收敛向,根据KKT条件,有
可以得到一般约束条件下的增广目标函数和的递推公式:
由此得到一般约束问题乘子法的步骤:
- 选定初始点,初始乘子估计,初始罚因子,常数,,精度,置
- 构造增广目标函数
- 以为初始点求解无约束问题,其解为
- 若,则得解,停止迭代
- 若成立,则令,否则
- 计算,令,转步2
3 实战测试
对于上节深入浅出最优化(7) 罚函数法中提出的约束最优化问题,的初值均在的范围内随机生成,总共生成100组起点。统计迭代成功(在1000步内得到最优解且单次步长搜索迭代次数不超过1000次)的样本的平均迭代步数、平均迭代时间和得到的最优解及开销函数最小值。
迭代步数 | 迭代时间 | 最优解 | 函数最小值 |
---|---|---|---|
17 | 24.3729s |
代码实现
本博客所有代码在https://github.com/HarmoniaLeo/optimization-in-a-nutshell开源,如果帮助到你,请点个star,谢谢这对我真的很重要!
你可以在上面的GitHub链接或本文集的第一篇文章深入浅出最优化(1) 最优化问题概念与基本知识中找到Function.py和lagb.py
使用共轭梯度PRP+法的拉格朗日乘子法
import numpy as np
from Function import Function #定义法求导工具
from lagb import * #线性代数工具库
from scipy import linalg
n=3 #x的长度
mu=2
lj=np.ones(1) #λj初值,长度等于等式限制条件个数
li=np.ones(2) #λi初值,长度等于不等式限制条件个数
def func(x): #目标函数,x是一个包含所有参数的列表
return (x[0]-1)**2+(x[0]-x[1])**2+(x[1]-x[2])**2
def hj(x): #构造数组h,第j位是第j+1个等式限制条件计算的值,x是一个包含所有参数的列表
return np.array([x[0]*(1+x[1]**2)+x[2]**4-4-3*np.sqrt(2)])
def gi(x): #构造数组g,第i位是第i+1个不等式限制条件计算的值,x是一个包含所有参数的列表
return np.array([x[0]+10,-x[0]+10])
def myFunc(x):
return func(x)+\
1/(2*mu)*np.sum(np.power(np.where(li-mu*gi(x)>0,li-mu*gi(x),0),2)-np.power(li,2))-\
np.sum(lj*hj(x))+0.5*mu*np.sum(np.power(hj(x),2))
def cdt(x):
return np.sqrt(np.sum(np.power(hj(x),2)))+np.sqrt(np.sum(np.power(np.where(gi(x)>0,0,gi(x)),2)))
sigma2=1.5 #放大因子
e2=0.001
beta=0.5
x=np.array([2.0,2.0,2.0]) #初值点
k1=0
while cdt(x)>=e2:
e=0.001
beta1=1
sigma=0.4
rho=0.55
tar=Function(myFunc)
k=0
d=-tar.grad(x)
x1=x
while tar.norm(x)>e:
a=1
if not (tar.value(x+a*d)<=tar.value(x)+rho*a*dot(turn(tar.grad(x)),d) and \
np.abs(dot(turn(tar.grad(x+a*d)),d))>=sigma*dot(turn(tar.grad(x)),d)):
a=beta1
while tar.value(x+a*d)>tar.value(x)+rho*a*dot(turn(tar.grad(x)),d):
a*=rho
while np.abs(dot(turn(tar.grad(x+a*d)),d))<sigma*dot(turn(tar.grad(x)),d):
a1=a/rho
da=a1-a
while tar.value(x+(a+da)*d)>tar.value(x)+rho*(a+da)*dot(turn(tar.grad(x)),d):
da*=rho
a+=da
lx=x
x=x+a*d
beta=np.max((dot(turn(tar.grad(x)),tar.grad(x)-tar.grad(lx))/(tar.norm(lx)**2),0)) #PRP+
d=-tar.grad(x)+beta*d
k+=1
print(k1,k)
if cdt(x)/cdt(x1)>beta:
mu*=sigma2
k1+=1
print(x)