前言
本篇将开始介绍优化算法。首先是梯度下降法,在最小二乘与深度学习中,都是最常用的最优化求解方法和思想。
微积分基础
一元函数的极值,导数与泰勒展开
对于一元函数
f
(
x
)
f(x)
f(x)而言,当
x
0
x_0
x0满足以下条件时,
f
(
x
0
)
f(x_0)
f(x0)取得极值:
f
′
(
x
0
)
=
0
,
f
′
′
(
x
0
)
≠
0
f'(x_0)=0,f''(x_0)\ne 0
f′(x0)=0,f′′(x0)=0
将
f
(
x
)
f(x)
f(x)在
x
0
x_0
x0处泰勒展开:
f
(
x
)
=
f
(
x
0
)
+
f
′
(
x
0
)
(
x
−
x
0
)
+
1
2
!
f
′
′
(
x
0
)
(
x
−
x
0
)
2
+
o
(
(
x
−
x
0
)
2
)
f(x)=f(x_0)+f'(x_0)(x-x_0)+\frac{1}{2!}f''(x_0)(x-x_0)^2+o((x-x_0)^2)
f(x)=f(x0)+f′(x0)(x−x0)+2!1f′′(x0)(x−x0)2+o((x−x0)2)
可以从泰勒展开中看出,当
f
′
(
x
)
=
0
,
f
′
′
(
x
0
)
>
0
f'(x)=0,f''(x_0)> 0
f′(x)=0,f′′(x0)>0,在
x
0
x_0
x0附近必然有
f
(
x
)
>
f
(
x
0
)
f(x)>f(x_0)
f(x)>f(x0)成立;当
f
′
(
x
)
=
0
,
f
′
′
(
x
0
)
<
0
f'(x)=0,f''(x_0)< 0
f′(x)=0,f′′(x0)<0,在
x
0
x_0
x0附近必然有
f
(
x
)
<
f
(
x
0
)
f(x)<f(x_0)
f(x)<f(x0)成立。这就把一元函数的局部最值与泰勒公式联系起来了。
多元函数的泰勒展开
对于实值向量函数
f
(
x
)
f(\bf x)
f(x),其在
x
0
\bf x_0
x0处的泰勒展开可表示为:
f
(
x
)
=
f
(
x
0
)
+
∇
f
(
x
0
)
⋅
(
x
−
x
0
)
+
(
x
−
x
0
)
T
H
(
x
0
)
(
x
−
x
0
)
+
o
n
f({\bf x})=f({\bf x_0})+ \nabla f({\bf x_0}) \cdot ({\bf x}-{\bf x_0})+ ({\bf x}-{\bf x_0})^TH({\bf x_0}) ({\bf x}-{\bf x_0}) +o^n
f(x)=f(x0)+∇f(x0)⋅(x−x0)+(x−x0)TH(x0)(x−x0)+on
类似的,当梯度
∇
f
(
x
0
)
=
0
\nabla f({\bf x_0})={\bf 0}
∇f(x0)=0, 海森矩阵
H
H
H正定时,
f
(
x
0
)
f(\bf x_0)
f(x0)是一个局部极值点。
从多元函数的泰勒展开也能看出,如果 x 0 \bf x_0 x0处的梯度不为 0 \bf 0 0,则在周围存在某一点 x \bf x x使得 f ( x ) > f ( x 0 ) f({\bf x})>f({\bf x_0}) f(x)>f(x0),同时也存在某一点 x \bf x x使得 f ( x ) < f ( x 0 ) f({\bf x})<f({\bf x_0}) f(x)<f(x0)。
自然而然的想到,如果我们要寻找函数的极大值,可以从 x 0 {\bf x_0} x0开始,选择周围的某一点 x = Δ x + x 0 {\bf x}=\Delta \bf x+ x_0 x=Δx+x0,使得 f ( x ) > f ( x 0 ) f({\bf x})>f({\bf x_0}) f(x)>f(x0),到达点 x 1 \bf x_1 x1,然后再选择周围某一个点 x = Δ x + x 1 {\bf x}=\Delta \bf x+ x_1 x=Δx+x1,继续迭代到满足局部极值条件为止。寻找极小值同理。
这就是梯度下降(上升)法的思想。
梯度下降法
以上迭代具有两个核心问题:①如何选择周围点,也就是如何选择 Δ x \Delta \bf x Δx;②如何判断满足局部极值条件,也是就是什么时候结束迭代。下面以最常见的寻找局部最小为例。
梯度方向
首先讨论问题①,回到多元泰勒展开式:
f
(
x
)
=
f
(
x
0
)
+
∇
f
(
x
0
)
⋅
(
x
−
x
0
)
+
1
2
(
x
−
x
0
)
T
H
(
x
0
)
(
x
−
x
0
)
+
o
n
f({\bf x})=f({\bf x_0})+ \nabla f({\bf x_0}) \cdot ({\bf x}-{\bf x_0})+ \frac{1}{2} ({\bf x}-{\bf x_0})^TH({\bf x_0}) ({\bf x}-{\bf x_0}) +o^n
f(x)=f(x0)+∇f(x0)⋅(x−x0)+21(x−x0)TH(x0)(x−x0)+on
梯度
∇
f
(
x
0
)
≠
0
\nabla f({\bf x_0}) \ne {\bf 0}
∇f(x0)=0。假设我们要寻找的
Δ
x
\Delta \bf x
Δx的长度固定,则每次迭代的函数增量为:
Δ
f
=
f
(
x
)
−
f
(
x
0
)
=
∇
f
(
x
0
)
⋅
(
Δ
x
)
+
o
n
\Delta f=f({\bf x})-f({\bf x_0})=\nabla f({\bf x_0}) \cdot (\Delta \bf x)+o^n
Δf=f(x)−f(x0)=∇f(x0)⋅(Δx)+on
等式右边是一个内积,可以简化表示为:
g
x
0
=
∇
f
(
x
0
)
e
Δ
x
=
Δ
x
Δ
f
=
f
(
x
)
−
f
(
x
0
)
=
g
x
0
⋅
e
Δ
x
=
∣
g
x
0
∣
∣
e
Δ
x
∣
cos
θ
g_{\bf x_0}=\nabla f({\bf x_0}) \\ e_{\Delta \bf x} = {\Delta \bf x} \\ \Delta f=f({\bf x})-f({\bf x_0}) = g_{\bf x_0} \cdot e_{\Delta \bf x}=|g_{\bf x_0}||e_{\Delta \bf x}|\cos \theta
gx0=∇f(x0)eΔx=ΔxΔf=f(x)−f(x0)=gx0⋅eΔx=∣gx0∣∣eΔx∣cosθ
其中,
cos
θ
\cos \theta
cosθ是梯度向量
∇
f
(
x
0
)
\nabla f(\bf x_0)
∇f(x0)与
Δ
x
\Delta \bf x
Δx的夹角。
θ
=
0
\theta=0
θ=0,
Δ
f
>
0
\Delta f>0
Δf>0并且取最大值;
θ
=
π
\theta=\pi
θ=π,
Δ
f
<
0
\Delta f<0
Δf<0且取最小值。
由此得出一个结论:梯度方向是函数上升最快的方向,梯度反方向是函数下降最快的方向。
因此,在迭代过程中,我们选择的
Δ
x
\Delta \bf x
Δx的方向与梯度方向相反,即:
Δ
x
=
−
∣
∣
Δ
x
∣
∣
2
∣
∣
∇
f
(
x
0
)
∣
∣
2
∇
f
(
x
0
)
\Delta {\bf x}=-\frac{||\Delta {\bf x}||_2}{||\nabla f({\bf x_0})||_2}\nabla f({\bf x_0})
Δx=−∣∣∇f(x0)∣∣2∣∣Δx∣∣2∇f(x0)
另外,可以通过学习率
λ
\lambda
λ控制
Δ
x
\Delta \bf x
Δx的长度。一方面是由于泰勒展开只在
x
0
\bf x_0
x0的局部范围才成立,另一个方面是较大的
Δ
x
\Delta \bf x
Δx可能会导致函数找不到局部极值区域,而较小的
Δ
x
\Delta \bf x
Δx需要更多的迭代次数。梯度下降法的迭代方程可表示为:
h
(
x
)
=
x
−
λ
∇
f
(
x
)
h({\bf x})={\bf x} - \lambda \nabla f({\bf x})
h(x)=x−λ∇f(x)
终止条件
对于问题②,有比较多的方式来终止迭代,比如设置最大迭代次数,设置增量阈值,梯度限制等。
可以设置函数值增量
Δ
f
\Delta f
Δf阈值来控制迭代过程:
i
f
Δ
f
l
a
s
t
<
t
h
r
e
s
h
o
l
d
t
h
e
n
x
=
h
l
a
s
t
(
x
)
if \quad \Delta f_{last}<threshold \\ then \quad {\bf x}= {h_{last}(\bf x)}
ifΔflast<thresholdthenx=hlast(x)
也可以设置
Δ
x
\Delta \bf x
Δx的长度阈值:
i
f
Δ
x
<
t
h
r
e
s
h
o
l
d
t
h
e
n
x
=
x
l
a
s
t
if \quad \Delta {\bf x}<threshold \\ then \quad {\bf x}= {\bf x}_{last}
ifΔx<thresholdthenx=xlast
代码举例
这里给出一个我自己写的求二元函数 f ( x , y ) = x 2 + y 2 f(x,y)=x^2+y^2 f(x,y)=x2+y2与 f ( x , y ) = x 2 + 2 y 2 + 2 x y + 3 x − y − 2 f(x,y)=x^2+2y^2+2xy+3x-y-2 f(x,y)=x2+2y2+2xy+3x−y−2的极小值的代码:
def partial_derivate_xy(x, y, F):
dx = (F(x + 0.001, y) - F(x, y))/0.001
dy = (F(x, y + 0.001) - F(x, y))/0.001
return dx, dy
def non_linear_func(x, y):
fxy = 0.5 * (x ** 2 + y ** 2)
return fxy
def non_linear_func_2(x, y):
fxy = x*x + 2*y*y + 2*x*y + 3*x - y - 2
return fxy
def non_linear_func_3(x, y):
fxy = 0.5 * (x ** 2 - y ** 2)
return fxy
def grad_decent(x, y, F, lr):
grad_x, grad_y = partial_derivate_xy(x, y, F)
x_opt = x - lr * grad_x
y_opt = y - lr * grad_y
return x_opt, y_opt
def optimizer(x0, y0, F, lr=0.01, th=0.0001):
x = x0
y = y0
counter = 0
while True:
x_opt, y_opt = grad_decent(x, y, F, lr)
if (x_opt - x)**2 + (y_opt - y)**2 < th**2:
break
x = x_opt
y = y_opt
counter = counter + 1
print('iter: {}'.format(counter), 'optimized (x, y) = ({}, {})'.format(x, y))
return x, y
if __name__ == '__main__':
x0 = 2.
y0 = 2.
result_x, result_y = optimizer(x0, y0, non_linear_func)
print(result_x, result_y)
后记
本篇记录了梯度下降法的原理,后续会再总结SGD,Adam等优化器。