程序员数学【优化】

前言

本文其实值属于:程序员的数学【AIoT阶段二】的一部分内容,本篇把这部分内容单独截取出来,方便大家的观看,本文介绍最优化,本文涵盖了一些计算的问题并使用代码进行了实现,安装代码运行环境见博客:最详细的Anaconda Installers 的安装【numpy,jupyter】(图+文),如果你只是想要简单的了解有关线代的内容,那么只需要学习一下博文:NumPy从入门到高级,如果你是跟着博主学习AIoT的小伙伴,建议先看博文:数据分析三剑客【AIoT阶段一(下)】(十万字博文 保姆级讲解),如果你没有Python基础,那么还需先修博文:Python的进阶之道【AIoT阶段一(上)】(十五万字博文 保姆级讲解)

优化的概念

1.1 最大值最小值

🚩优化问题是找到f%28x%29的最大值或最小值

我们经常求最小值,其中x为优化变量,即自变量,f%28x%29称为目标函数,也可能有约束。一些优化问题同时具有等式约束和不等式约束。
maxf%28x%29%5Crightarrow%20min%28-f%28x%29%29

g_i%28x%29%3D0%2Ci%3D1%2C2%2C3%2C...%2Cn
h_i%28x%29%E2%89%A40%2Ci%3D1%2C2%2C3%2C...%2Cn

满足该约束且仍在f%28x%29域内的那些x的集合称为可行域D

1.2 局部极小值

🚩函数f%28x%29存在于x_0的域%5Bx_0-%5CDelta%20x%2Cx_0%2B%5CDelta%20x%5D,而f%28x%29%E2%89%A5f%28x_0%29,则f%28x_0%29是局部极小点

程序员数学【优化】

1.3 全局最小值

🚩其实在初中的时候,我们就学会了寻找极值的问题。例如,二次函数抛物线的顶点是最大值或最小值。在高中,我们也会遇到寻找极值的问题。计算一个非常复杂的函数的极值。完成初等数学技能。真正的飞跃发生在大学。微积分课程提供了求解极值的统一方法,即求导数等于程序员数学【优化】0的点。如果是多元函数,我们说它的梯度等于程序员数学【优化】0,我们只要解方程组。我们在机器学习中遇到的几乎所有函数都是可导出的。平滑引导f%27%28x%29%3D0

局部极小值,通过大量实践发现,在高维优化问题中,局部极小值和全局极小值通常没有太大区别,甚至在某些情况下局部极小值具有更好的归纳能力(泛化能力)。

2. 推导和迭代求解

2.1 求导遇到的问题

🚩 前面我们说找到极值就是等于程序员数学【优化】0的导数或者梯度,找到疑似极值就是停滞点:%E1%90%81f%28x%29%3D0%2Cf%27%28x%29%3D0

用微积分这样的方法,只要能导出函数,能解吗?

因为很多时候要找到等于程序员数学【优化】0的方程或等于程序员数学【优化】0的方程组的根并不容易,例如:

f%28x%2Cy%29%3Dx%5E3-5x%5E2%2Be%5E%7Bxy%7D-3y%5E4%2B12y%5E2%2B1024sin%28xy%29

分别求关于x%2Cy的偏导数:
程序员数学【优化】

设上述导数为程序员数学【优化】0仍不能解。对于5次及以上的代数方程,没有求根的公式,所以这个想法看起来很美,但实际上行不通,我们只好另想办法了。

2.2 近似迭代求解

🚩近似解,我们先给一个初值x_0看是否为极值点。当然,它必须满足等式或不等式约束。如果不是,我们会想办法更​​进一步,只要我们有x_n就越来越接近我们的极点,这个问题不就解决了吗!

x_0%5Crightarrow%20x_1%5Crightarrow%20x_2%5Crightarrow...x_n

因此,构造一个满足这样一个极限的序列,当k接近%2B%5Cinftyf%28x_k%29的梯度值极限接近程序员数学【优化】0,我们就找到了函数的驻点

lim_%7Bk%5Crightarrow%2B%5Cinfty%7D%E1%90%81f%28x_k%29%3D0

由此,我们要解决的核心问题就变成了,如何从当前点移动到下一个点,即如何从x_kx_%7Bk%2B1%7D,迭代法是我们在计算数学中经常使用的一种方法,它它不仅可以求极值,还可以用来求方程的解,包括线性方程、非线性方程、矩阵的特征值等等。

程序员数学【优化】

在优化中,这种迭代思想也称为梯度下降!

3.梯度下降

3.1 公式推导

🚩数值优化算法是求近似解,而不是理论之上的精确解
程序员数学【优化】
我们推导出这个公式,我们需要使用多元函数泰勒展开公式(函数f%28x%29x_0的邻域),最后加上一个高阶无穷小
程序员数学【优化】
如果忽略高阶无穷小,即x属于x_0%5Cdelta邻域,则等号变为近似相等,则将f%28x_0%29置于等号左侧:
程序员数学【优化】

这时候如果想减少得更快,就必须让等式右边取最小值,即cos%5Ctheta%3D-1时,减少的幅度最大!

单位向量表示方向,长度为1的向量,即模为1的向量称为单位向量。

将向量乘以标量不会改变它的方向,只会改变它的大小。这是缩放矢量的方法。

因此,向量可以表示为标量乘以单位向量。

为了最大化下降,那么向量%28x-x_0%29[这里的向量用%5Ctheta表示]的方向与梯度的方向相反:
程序员数学【优化】
分母是一个标量,可以并入%5Ceta,简化为:
程序员数学【优化】
需要注意的是,当xx_0足够接近时,即在其%5Cdelta邻域内,泰勒展开式中不止一项可以忽略,否则不能忽略,不是高阶无穷小,条件约等于不成立,所以%5Ceta的步长不能太大,所以我们得到梯度下降法的公式。
程序员数学【优化】
实施细节:

  • 初始值的设置,随机或设置满足约束
  • 步长设置,比较小,也可以动态调整
  • 循环终止的判断,达到max_iteration(最大迭代次数);当上一次更新减去下一次更新差值基本不变时

3.2 代码演示

3.2.1 创建模拟数据

import numpy as np
import matplotlib.pyplot as plt
# 构建方程
f = lambda x : (x - 3.5) ** 2 - 4.5 * x + 10
# 导函数
g = lambda x : 2 * (x - 3.5) - 4.5
# 创建模拟数据并可视化
x = np.linspace(0, 11.5, 100)
y = f(x)
plt.plot(x, y)
_ = plt.scatter(5.75, f(5.75), color = 'red', s = 30)

程序员数学【优化】

3.2.2 迭代法求解(梯度下降)

eta = 0.3 # 学习率
# 随机(瞎蒙),初始值x0
x = np.random.randint(0, 12, size = 1)[0]

# 多次while 循环,每次梯度下降,更新,记录一下上一次的值
# 比较精确度
# 一开始故意设置差异,目的为了有区分,不能一上来就停止
last_x = x + 0.1

# 精确度,人为设定
precision = 0.0001
print('-----------------随机x是:', x)

# 每次梯度下降,求解出来的x值,一开始随机给的
x_ = [x] # Python中列表

count = 0
while True:
    if np.abs(x - last_x) < precision: # 更新时,变化甚微,满足精确度,终止
        break
    # 更新,梯度下降
    # x是当前数值,赋值给上一个值
    last_x = x
    count += 1
    x = x - eta * g(x) # 梯度下降公式
    x_.append(x)
    print('+++++++++++++++更新之后的x是:%0.5f' % (x))
print('+++++++++++++++梯度下降次数:', count)

# x1是NumPy数组
x1 = np.linspace(0, 11.5, 100)
y1 = f(x1)
plt.figure(figsize = (9, 6))
plt.plot(x1, y1)

# 散点图
x_ = np.array(x_)
_ = plt.scatter(x_, f(x_), color = 'red', s = 30)

程序员数学【优化】

3.2.3 迭代法求解(退出条件最大迭代次数)

eta = 0.2 # 学习率
# 随机(瞎蒙),初始值
x = np.random.randint(0, 12, size = 1)[0]
# 多次while 循环,每次梯度下降,更新,记录一下上一次的值
# 一开始故意设置差异,目的为了有区分,不能一上来就停止
last_x = x + 0.1
# 最大迭代次数,人为设定
count = 30
print('-----------------随机x是:', x)
# 每次梯度下降,求解出来的x值,一开始随机给的
x_ = [x] # Python中列表
for i in range(count):
    # 更新,梯度下降
    # x是当前数值,赋值给上一个值
    last_x = x
    x = x - eta * g(x) # 梯度下降公式
    x_.append(x)
    print('+++++++++++++++更新之后的x是:%0.5f' % (x))

# x1是NumPy数组
x1 = np.linspace(0, 11.5, 100)
y1 = f(x1)
plt.figure(figsize = (9, 6))
plt.plot(x1, y1)

# 散点图
x_ = np.array(x_)
plt.scatter(x_, f(x_), color = 'red', s = 30)

程序员数学【优化】

4.牛顿法

4.1 牛顿法原理

🚩牛顿法的原理是利用函数f%28x%29的泰勒级数的前几项求方程f%28x%29%3D0的根。

x_0处的函数f%28x%29展开为泰勒级数:
程序员数学【优化】
取线性部分作为f%28x%29的逼近,f%28x_0%29%2Bf%27%28x_0%29%28x-x_0%29%3D0的解可以逼近f%28x%29%3D0的解,解为:
程序员数学【优化】
由于f%28x%29的近似只是一阶展开,因此x_1并非f%28x%29%3D0的解,只能说f%28x_1%29f%28x_0%29更接近 0 。于是,考虑迭代求解:
程序员数学【优化】

迭代过程如下:

程序员数学【优化】

4.2 牛顿法代码演示

def f(x):
    return (x - 3) ** 3        # 定义函数

def fd(x):
    return 3 * ((x - 3) ** 2)    # 定义一阶导数

y = [0] # 初始值
def newtonMethod(n, assum):
    count = n
    x = assum
    next_x = 0
    A = f(x) # 函数值
    B = fd(x) # 导数值
    print('A = %0.4f' % (A) + ', B = %0.4f' % (B) + ', time = %d' % (count))
    if f(x) == 0.0: # 是不是f(x) = 0的解
        return count, x
    else: # 不是方程 = 0的解,迭代更新
        next_x = x - A / B
        y.append(next_x)
        print('next_x = %0.4f' % (next_x))
        
    if abs(A - f(next_x)) < 1e-6: # 满足精确值,退出,什么都没做,只是打印
        # 设置迭代跳出条件,同时输出满足f(x) = 0的x值
        print('方程的解为: f(x) = 0, x = %0.4f'% (next_x))  
    else: # 不满足精确度,再次调用这个方法,递归
        return newtonMethod(n + 1, next_x)
    
# 设置从0开始计数,x0 = 4
newtonMethod(0, 0)

x = np.linspace(0, 6, 100)
plt.plot(x, f(x)) # 原图
_ = plt.scatter(y, f(np.array(y)), color = 'red') # 更新过程点

程序员数学【优化】

4.3 求解最优化问题

🚩牛顿法的优化公式如下:
程序员数学【优化】
在:
程序员数学【优化】
假设函数f%28x%29迭代k次后的值为x_k,则x_k上的二阶泰勒展开式可近似如下公式:
程序员数学【优化】
我们要求f%28x%29的最小值,必要条件是f%28x%29在极值点的一阶导数是程序员数学【优化】0,因为我们把每次迭代得到的满足目标函数最小值的x作为下一次迭代的值,所以我们可以假设round k%2B1的值是最优解:

%E1%90%81f%28_%7Bk%2B1%7D%29%3D0

代入二阶泰勒展开式并推导出:
程序员数学【优化】
制作:
程序员数学【优化】

其中H代表Hessian矩阵
程序员数学【优化】

最终的优化公式可以得到:
程序员数学【优化】
将复杂问题简化,将多元函数降级为一元函数,则优化牛顿法的二阶泰勒展开式的更新公式为:
程序员数学【优化】
梯度下降法只使用一阶导数的信息,牛顿法使用一阶导数的信息以及二阶导数的信息。梯度下降法用线性函数代替目标函数,牛顿法用二次函数代替目标函数,所以牛顿法的收敛速度更快。

4.4 求解最优化代码演示

4.4.1 使用牛顿法求最优化(11步得到答案,2.0062)

import numpy as np
import matplotlib.pyplot as plt
# 构建方程
f = lambda x : (x - 2) ** 4 + 10

# 导函数
g1 = lambda x : 4 * (x - 2) ** 3
g2 = lambda x : 12 * (x - 2) ** 2

# 创建模拟数据并可视化
x = np.linspace(1, 3, 100)
y = f(x)
plt.plot(x, y)

y = [2.8]
def newtonMethod(n, assum):
    count = n
    x = assum
    next_x = 0
    A = g1(x)
    B = g2(x)
    print('A = %0.4f' % (A) + ',B = %0.4f' % (B) + ',次数 = %d' % (count))
    if f(x) == 0.0:
        return count, x
    else:
        next_x = x - A / B
        y.append(next_x)
        print('next_x = %0.4f' % (next_x),)
    if abs(f(x) - f(next_x)) < 1e-8:
        # 设置迭代跳出条件,同时输出满足f(x) = 0的x值
        print('方程的解为: f(x) = 0, x = %0.4f'% (next_x))  
    else:
        return newtonMethod(n + 1, next_x)
    
# 设置从0开始计数,x0 = 4
newtonMethod(0, 2.8)
_ = plt.scatter(y,f(np.array(y)), color = 'red')

程序员数学【优化】

4.4.2 使用梯度下降求最优解(207步得到答案,2.04349)

import numpy as np
import matplotlib.pyplot as plt
# 构建方程
f = lambda x : (x - 2) ** 4 + 10
# 导函数
g1 = lambda x : 4 * (x - 2) ** 3

eta = 0.3 # 学习率
# 随机(瞎蒙),初始值
x = 2.8
# 多次while 循环,每次梯度下降,更新,记录一下上一次的值
# 比较精确度
# 一开始故意设置差异,目的为了有区分,不能一上来就停止
last_x = x + 0.1
# 精确度,人为设定
precision = 1e-8
print('-----------------随机x是:', x)
# 每次梯度下降,求解出来的x值,一开始随机给的
x_ = [x] # Python中列表
count = 0
while True:
    if np.abs(f(x) - f(last_x)) < precision: # 更新时,变化甚微,满足精确度,终止
        break
    # 更新,梯度下降
    # x是当前数值,赋值给上一个值
    last_x = x
    count += 1
    x = x - eta * g1(x) # 梯度下降公式
    x_.append(x)
    print('+++++++++++++++更新之后的x是:%0.5f' % (x))
print('+++++++++++++++梯度下降次数:', count)

# x1是NumPy数组
x1 = np.linspace(0, 11.5, 100)
y1 = f(x1)
plt.figure(figsize = (9, 6))
plt.plot(x1, y1)

# 散点图
x_ = np.array(x_)
_ = plt.scatter(x_, f(x_), color = 'red', s = 30)

程序员数学【优化】

4.5 拟牛顿法

🚩 上节提到,牛顿法虽然收敛速度快,但是需要计算Hessian矩阵的逆矩阵H%5E%7B-1%7D,有时目标函数的Hessian矩阵不能保持正定(多元函数微分) ,这使得牛顿法无效。为了克服这两个问题,提出了拟牛顿法。该方法的基本思想是构造一个正定对称矩阵,可以在不使用二阶偏导数的情况下逼近Hessian矩阵(或Hessian矩阵的逆)。不同的构造方法产生不同的拟牛顿法。

我们先推导出拟牛顿条件,它为“逼近Hessian矩阵(或Hessian矩阵的逆)”提供了理论指导,并指出了用于逼近的矩阵应满足的条件。

%E1%90%81f%28x%29进行二阶泰勒展开,我们得到以下近似值:

程序员数学【优化】程序员数学【优化】
制作:
程序员数学【优化】
所以:
程序员数学【优化】
x%3Dx_%7Bk%2B1%7D,我们得到:
程序员数学【优化】
y_k%3Dg_%7Bk%2B1%7D-g_k%2C%5Cdelta_k%3Dx_%7Bk%2B1%7D-x_k,然后:
程序员数学【优化】
以上是准牛顿条件!

在拟牛顿法中,选择G_k作为H_k%5E%7B-1%7D的近似值或选择B_k作为H_k的近似值,使它们满足上述牛顿条件。不同的拟牛顿法,区别在于如何确定G_kB_k

常用的拟牛顿算法有:DFPBFGSL%5C_BFGS算法。这里不展开!

5.坐标下降法

🚩坐标下降法是分而治之的思想。

minf%28x%29%2Cx%3D%28x_1%2Cx_2%2C...%2Cx_n%29

它的思想是保持其他不变,只优化其中一个,比如x_1,那么多元函数求极值的问题就变成了单变量函数求极值的问题,这样难度就大了优化的小很多,然后我们把剩下的按住不放,然后优化x_2直到x_n,一轮结束后再回来优化x_1。至于如何求解一个变量,可以通过梯度下降或牛顿法等优化算法求解,具体问题可以详细分析。

SVM中的SMO算法是优化其中两个,其他固定; liblinear库也使用了大量的坐标下降法来解决。直观上看起来像:
程序员数学【优化】

我们优化所有变量x_1%2Cx_2%2C...%2Cx_n一次,就像梯度下降迭代一次一样,这种方法也有很大的优势,就是计算量要小得多。

六、优化算法瓶颈

🚩前面的梯度下降法和牛顿法都是以%E1%90%81f%28x_k%29%3D0为基础的,我们前面也说过,函数在某一点的导数或梯度等于程序员数学【优化】0是驻点,只是函数达到的必要条件求极值,不是充分条件,即不能推导出来

%E1%90%81f%28x_k%29%3D0%5Crightarrow%20minx_k

我们前面介绍的所有数值优化算法都面临两个问题。

6.1 局部极值问题

🚩这个问题是局部极值,但不是全局极值。理论上,如果我们需要一个全局最优解,我们必须找出所有的极值。您可能必须反复设置不同的初始迭代点。解决方案让它收敛到不同的局部最小值,然后比较谁最小找到全局最小值。

程序员数学【优化】

6.2 鞍点问题

🚩 前面我们说一元函数x%5E3函数,它是坐标程序员数学【优化】0处的驻点,但它甚至不是局部最小值,对应的多元函数,我们称之为鞍点(既不是极大点也不是极点) ) 小值点的临界点称为鞍点)。严格的定义是,此时它的Hessian矩阵是不确定的,既不是正定也不是负定,因此它既不是最小值也不是最大值。

程序员数学【优化】
它在物理上更广泛,指的是一个方向上的最大值和另一个方向上的最小值的点。

程序员数学【优化】

七、凸优化问题

🚩 前面我们说过数值优化面临两个问题,一个是局部极值问题和鞍点问题。我们能避免这两个问题吗?

只要我们限制优化问题,这种问题有两个限制

1、优化变量的可行域必须是凸集
2、优化函数必须是个凸函数

同时满足这两个约束的问题称为凸优化问题,即先有局部最小值,再有全局最小值。

八、凸集

🚩凸集的定义:对于点C的集合,有x%2Cy属于C中的两点,连接两点的连线上的任意一点也属于集合C。例如,立方体是一个凸集。
程序员数学【优化】
程序员数学【优化】
欧式空间
程序员数学【优化】
它的意义在于很多情况下,可行域是欧几里得空间,它一定是凸集。只有在凸集的前提下才能解决优化问题。

9.凸函数

🚩凸函数在函数上找到任意两个点。它们的连续性意味着割线上的值大于f%28x%29的对应值
程序员数学【优化】
函数的二阶导数与函数的凹凸性有关。凹凸是怎么定义的?

我们先做一个简单的介绍。请记住,凸函数是向下凸的。反正就是凹的。无论是凸函数,都可以通过二阶导数。如果二阶导数大于程序员数学【优化】0,则为凸函数。
程序员数学【优化】如果函数是一元的,那么f%27%27%28x%29%20%E2%89%A5%200是凸函数。多元函数Hessian矩阵是半正定的,它是一个凸函数;多元函数Hessian矩阵是正定的,是一个严格凸函数。

如果每个函数f%28x_i%29都是凸的,那么它们的非负线性组合f%28x%29%3D%5Csum%5Climits%5En_%7Bi%3D1%7Dw_if_i%28x%29ifw_i%E2%89%A50也是凸的。

10. 凸优化表达式形式

🚩凸优化的​​定义是目标函数是一个凸函数,可行域是一个凸集。如果存在这两个约束,则局部最优解必然是全局最优解。

凸优化一般表达式:

min%20f%28x%29%2Cx%5Cin%20C,其中C是凸集

具有等式约束的表达式:
程序员数学【优化】
带有不等式约束的表达式:
程序员数学【优化】
往往我们需要证明一些机器学习算法是凸优化问题,比如逻辑回归、SVM、线性回归等,来证明它的可行域是凸集,目标函数是凸函数,凸优化避免了局部最小值问题,也避免了鞍点问题,f%28x%29是凸函数,它的Hessian矩阵是半正定的,是半正定的,也避免了鞍点问题。

十一、拉格朗日乘子法

🚩高等数学和微积分,相信大家都学过,用来求解方程约束下的极值问题
程序员数学【优化】
拉格朗日乘子法将x上有约束的优化问题转化为无约束的优化问题
程序员数学【优化】
L%28x%2C%5Clambda%29称为Lagrange函数(拉格朗日函数),%5Clambda称为拉格朗日乘数(实际上是系数)。求L%28x%2C%5Clambda%29x的偏导,求对%5Clambda的偏导,令导数为程序员数学【优化】0,求出x%2C%5Clambda的值,则x是函数f%28x%29在附加条件h%28x%29下的极值点。

以上是拉格朗日乘数法。拉格朗日乘子法通俗的理解是将有等式条件的约束优化问题转化为无约束优化问题来构造拉格朗日函数L%28x%2C%5Clambda%29

十二、KKT条件

🚩 假设我们面临一个不等式条件约束优化问题,如下:
程序员数学【优化】
对于上式,显然是不等式约束的优化问题,不能再使用拉格朗日乘子法,因为拉格朗日乘子法是有等式约束的优化问题。

拉格朗日乘子法的扩展,用于求解具有不等式约束的理论结果:
程序员数学【优化】
约束是:

  • %E1%90%81L%28x%2C%5Clambda%29%3D0
  • %5Clambda%E2%89%A50
  • %5Clambda%20h%28x%29%3D0

上述条件称为KKT条件。

13. 拉格朗日对偶

🚩凸优化、非线性规划问题,甚至运筹学中的线性规划问题都会涉及到这个概念。其含义是将原问题转化为另一个问题来解决,但转化后的问题更容易解决,Lager Longian multiplier method 的一种扩展,用于同时解决等式约束和不等式约束的方法,通过将原问题转化为对偶问题,通常比原始问题更容易解决。

minf%28x%29

约束如下:
h_i%28x%29%E2%89%A40%EF%BC%8Ci%3D1%2C2%2C...%2Cn

构造一个广义的拉格朗日函数,所谓的泛化还包括不等式约束。在下面的等式中,你会发现x上的约束消失了,尽管%5Cmu上有一个约束:
程序员数学【优化】
原始问题
程序员数学【优化】
我们将对偶问题定义为(上式的解等价于解下式):
程序员数学【优化】
其实minmax是互换的,当然相应的变量也要换。

双重问题有什么好处?对于原题,要先求内max,再求外min。对于对偶问题,我们可以先在其中找到min。有时,先确定x上函数的最小值,再求解%5Clambda上的最大值,比原问题更容易解决问题。

但是原始问题和对偶问题并不等价。有强对偶和弱对偶的概念。弱对偶是所有对偶问题都具有的属性。

所有向下凸函数都满足强对偶。如果两个问题是强对偶的,那么这两个问题实际上是等价的:
程序员数学【优化】

下面是弱对偶的推导过程:
程序员数学【优化】
其中,x%5E%2A%2C%5Clambda%5E%2A是函数取最大值和最小值时的最优解,即原问题总是大于等于对偶问题:
程序员数学【优化】
程序员数学【优化】

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
心中带点小风骚的头像心中带点小风骚普通用户
上一篇 2022年3月30日 下午1:36
下一篇 2022年3月30日 下午2:16

相关推荐