基於Jupyter 實現python對拉格朗日和KKT條件求極值

一、拉格朗日乘子法和KKT條件是兩種最常用的方法概念

求解最優化問題中,拉格朗日乘子法和KKT條件是兩種最常用的方法。在有等式約束時使用拉格朗日乘子法,在有不等式約束時使用KKT條件。這個最優化問題指某一函數在作用域上的全局最小值(最小值與最大值可以相互轉換)。

最優化問題通常有三種情況(這裏說兩種):

  1. 無約束條件
    求解辦法是求導等於0得到極值點。將結果帶回原函數驗證。

  2. 等式約束條件
    設目標函數f(x),約束條件hk(x),
    min f(x)  s.t. hk(x)=0   (k = 1,2…l)
    l表示有l個約束條件。
    該類問題解決辦法是消元法或拉格朗日法。消元法簡單,這裏講拉格朗日法,後面提到的KKT條件是對拉格朗日乘子法的泛化。

例子:
橢球內接長方體的最大體積,即求 f(x,y,z) = 8xyz 的最大值。
方法1:消元法
根據條件消去z,然後帶入函數轉化爲無條件極值問題。(有時這種方法麻煩,甚至解不出來)
方法2:拉格朗日乘法
思想:通過引入拉格朗日乘子將含有n個變量和k個約束條件的約束優化問題轉化爲含有(n+k)個變量的無約束優化問題。
首先定義拉格朗日函數F(x),f(x)爲目標函數,h(x)爲約束條件:
(λk是各個約束條件的待定係數)
對各個變量求偏導:

方程組的解就可能是最優化值,將結果帶回原方程驗證。
上面的函數,通過拉格朗日乘數法將問題轉化爲:

對 F(x,y,z,λ) 求偏導得到

聯立前面3個方程得到 bx = ay 和 az = cx,帶入第4個方程:

帶入原函數得到最大體積:

爲什麼這麼做是最優解?
舉個例子:min f(x,y)  s.t.  g(x,y) = c
畫出 z = f(x,y)的等高線

綠線是約束g(x,y) = c的軌跡。藍線是f(x,y)的等高線。箭頭表示斜率,和等高線的發現平行。從梯度的方向看,d1>d2(梯度下降法越接近目標,步長越小,前進越慢)。
在沒有約束條件,f(x,y)的最小值是落在最裏面等高線內部的某一點。加上約束條件的 f(x,y) 最小值是 f(x,y)的等高線和約束線相切的位置,因爲如果只是相交意味着還存在其它的等高線在該等高線的內部或外部,使新的等高線與目標函數的交點值更大或更小,只有等高線與目標函數的曲線相切時,取到最優值。

從圖看出,想讓目標函數 f(x,y) 的等高線和約束 g(x,y) 相切,則他們切點的梯度在一條直線上( f 和 g 斜率平行)。
即在最優解的時候:∇f(x,y)=λ(∇g(x,y)-C) (∇爲梯度算子,即:f(x)的梯度 = λ*g(x)的梯度,λ是非0常數)
即:▽[f(x,y)+λ(g(x,y)−c)]=0  (λ≠0)
那麼拉格朗日函數: F(x,y)=f(x,y)+λ(g(x,y)−c) 在達到極值時與 f(x,y) 相等,因爲 F(x,y) 達到極值時 g(x,y)-c 總等於0。
min(F(x,λ))取得極小值時其導數爲0,即f(x)和h(x)的梯度共線。
簡單的說,在F(x,λ)取最優解的時候,即F(x,λ)取極值的時候(導數爲0,▽[f(x,y)+λ(g(x,y)−c)]=0)。f(x)與g(x)梯度共線,此時就是在條件約束g(x)下,f(x)的最優解。

  1. 不等式約束
    設目標函數f(x),不等式約束爲g(x),等式約束條件h(x)。此時的約束優化問題描述如下:

我們定義不等式約束下的拉格朗日函數L:

其中f(x)是目標函數,hj(x)是第j個等式約束條件,λj是對應的約束係數,gk(x)是第k個不等式約束,uk是對應的約束係數。
不等式約束常用的方法是KKT條件,同樣的,把所有的不等式約束、等式約束和目標函數全部寫爲一個式子L(a, b, x)= f(x) + ag(x)+bh(x)
KKT條件是說最優值必須滿足以下條件:
L(a, b, x)對x求導爲零;
h(x) =0;
a*g(x) = 0;
求取這些等式之後就能得到候選最優值。其中第三個式子,因爲g(x)<=0,如果要滿足這個等式,必須a=0或者g(x)=0。

在這裏插入圖片描述

二、手工推導方法

在這裏插入圖片描述

三、利用python代碼實現

from sympy import *
x1,x2,k = symbols('x1,x2,k')
f = 60-10*x1-4*x2+(x1)**2+(x2)**2-x1*x2
g = x1+x2-8

#構造拉格朗日等式
L=f-k*g


#求導,構造KKT條件
dx1 = diff(L, x1)   # 對x1求偏導
print("dx1=",dx1)

dx2 = diff(L,x2)   #對x2求偏導
print("dx2=",dx2)

dk = diff(L,k)   #對k求偏導
print("dk=",dk)
dx1= -k + 2*x1 - x2 - 10
dx2= -k - x1 + 2*x2 - 4
dk= -x1 - x2 + 8
#求出個變量解
m= solve([dx1,dx2,dk],[x1,x2,k])   
print(m)
{x1: 5, x2: 3, k: -3}
#給變量重新賦值
x1=m[x1]
x2=m[x2]
k=m[k]
#計算方程的值
f = 60-10*x1-4*x2+(x1)**2+(x2)**2-x1*x2
print("方程的極小值爲:",f)
方程的極小值爲: 17

四、分析

同過手工推導以及python代碼實現的結果一致,極小值都是17,可以驗證python代碼的正確性。此次實驗還是很繁瑣的,複雜在用python的推導的過程。

參考鏈接:
https://www.cnblogs.com/keye/p/10916118.html
https://www.cnblogs.com/ty123/p/10529541.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章