Go...
Go...
哈喽,大家好呀!今天咱来唠唠机器学习里的最优化算法。在机器学习这个领域,最优化算法可太重要了!平常我们会用到好多不同类型的最优化算法,像一阶优化算法、二阶优化算法、自适应学习率优化算法,还有基于线性规划的优化算法、带约束条件的优化算法,以及具有全局收敛性的优化算法等等。
这些算法对于初学者来说可能听起来有点复杂。接下来我就一个一个给大家简单讲讲,保证让大家都能听明白!
一阶优化算法
梯度下降法(Gradient Descent)
在机器学习的优化算法里,一阶优化算法中的梯度下降法可是非常常用的一种。它主要用来解决求函数最小值或者最大值的问题,就像是在一个复杂的 “地形” 里,找到最低洼或者最高耸的地方。
那它具体是怎么工作的呢?假设我们有一个可以求导的函数\(f(x)\),这里的x可不是一个简单的数,而是一组参数构成的向量。梯度下降法的目标,就是找到让\(f(x)\)达到最小值的x。这个寻找的过程,要借助下面这个公式:
在这个公式里,代表第n次迭代时的参数值,表示函数f在这个位置的梯度,简单理解就是函数在这一点的导数,它能告诉我们函数在这个点变化最快的方向。而被称作学习率或者步长,它用来控制我们每次迭代时走多远,如果把寻找最优解的过程比作爬山,那学习率就是每一步迈出去的距离。
实际操作的时候,梯度下降法会先随便选一个起始点,然后根据当前所在位置的梯度方向(也就是函数下降最快的方向),再结合学习率,算出下一个要去的位置,就这样一步一步地迭代,一直到满足我们设定的停止条件为止。不过要注意,如果这个函数存在好几个局部最小值,梯度下降法有可能会 “陷” 在其中一个局部最优解里,没办法找到全局的最优解。
下面我们通过一段代码,来更直观地感受一下梯度下降法的工作过程。
import numpy as np
import matplotlib.pyplot as plt
def gradient_descent(f, df, x0, learning_rate, num_iterations):
x = x0
x_history = [x]
for _ in range(num_iterations):
gradient = df(x)
x -= learning_rate * gradient
x_history.append(x)
return np.array(x_history)
# 定义函数f(x)
def f(x):
return x**2 + 10*np.sin(x)
# 定义函数f(x)的导数df(x)
def df(x):
return 2*x + 10*np.cos(x)
# 设置初始参数值和学习率
x0 = -5
learning_rate = 0.1
num_iterations = 100
# 运行梯度下降算法
x_history = gradient_descent(f, df, x0, learning_rate, num_iterations)
# 绘制函数曲线和梯度下降路径
x_range = np.linspace(-10, 10, 100)
plt.plot(x_range, f(x_range), label='f(x)')
plt.scatter(x_history, f(np.array(x_history)), c='red', label='Gradient Descent')
plt.legend()
plt.xlabel('x')
plt.ylabel('f(x)')
plt.title('Gradient Descent')
plt.show()
在这段代码里,我们先定义了一个gradient_descent函数,它接受函数f、函数f的导数df、初始参数值x0、学习率learning_rate和迭代次数num_iterations这些参数。在函数内部,我们让x从初始值x0开始,按照公式:
进行迭代,每次迭代都把当前的x值记录下来,最后返回所有迭代过程中的x值。
然后,我们又定义了要优化的函数,它是,还定义了这个函数的导数。接着设置了初始参数值 x0 为 -5 ,学习率为 0.1,迭代次数为100。最后运行gradient_descent函数,得到迭代过程中的x值,并绘制出函数的曲线以及梯度下降过程中x对应的点。
所以,下图就是我们通过运行这段代码,看到的函数的曲线,以及梯度下降算法在寻找最优解过程中走过的路径,从而更直观地理解梯度下降算法是怎么工作的。
随机梯度下降法(Stochastic Gradient Descent,SGD)
在机器学习模型的训练过程中,它的核心目的是通过不断调整模型的参数,让损失函数的值变得最小,这样模型就能更好地完成各种任务,比如预测、分类等。
那随机梯度下降法和传统的梯度下降法有什么不一样呢?传统的梯度下降法在每次更新参数时,需要把所有的样本数据都用上进行计算,而随机梯度下降法就灵活多啦,它每次只挑选一个样本数据来更新模型参数。这样做的好处就是计算量大大减少,计算速度也就更快了,特别适合处理大规模的数据集。
随机梯度下降法的计算公式是这样的:
这里的代表模型的参数,是学习率,它决定了每次参数更新的幅度大小。
表示损失函数关于参数的梯度,而和指的是第i个训练样本。
随机梯度下降法具体的工作流程是这样的:
首先,给模型参数设定一个初始值。这就好比我们要去一个地方,先确定从哪里出发。接着,从所有的样本数据里随机挑选出一个样本。这种随机选择的方式,能让算法在每次更新时都有不同的 “参考”。然后,根据这个挑选出来的样本,计算损失函数关于当前模型参数的梯度。这个梯度就像是一个 “方向标”,它能告诉我们往哪个方向调整参数,才能让损失函数的值变小。最后,按照计算出来的梯度和设定的学习率,更新模型的参数。更新后的参数就会朝着让损失函数更小的方向前进一点。
之后,不断重复第 2 步到第 4 步,一直到满足我们设定的停止条件才行。这个停止条件可以是达到了我们预先指定的迭代次数,也可以是损失函数的值下降到了一定程度,不再有明显的变化。
下面,我们通过一个具体的例子来更清楚地了解随机梯度下降法的工作原理。
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 定义损失函数
def loss_function(x, y):
return (x - 1) ** 2 + (y - 2) ** 2
# 定义梯度函数
def gradient(x, y):
return np.array([2 * (x - 1), 2 * (y - 2)])
# 生成数据
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)
Z = loss_function(X, Y)
# 绘制3D图形
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, Z, cmap='viridis')
# SGD迭代更新参数
eta = 0.1
theta = np.array([0, 0])
num_iterations = 100
for i in range(num_iterations):
gradient_value = gradient(theta[0], theta[1])
theta = theta - eta * gradient_value
ax.scatter(theta[0], theta[1], loss_function(theta[0], theta[1]), color='red')
plt.show()
在这段代码里,我们先定义了一个简单的二维损失函数loss_function,它的计算方式是:
。
同时,还定义了对应的梯度函数gradient,用来计算损失函数在某点的梯度。
接着,我们用np.linspace生成了一些数据,通过np.meshgrid把这些数据组合成网格状,再代入损失函数得到对应的Z值。这样,我们就能用matplotlib库绘制出这个损失函数的三维图形啦,这个图形就像是一个 “山谷”,而我们的目标就是找到 “山谷” 的最低点。
然后,开始使用随机梯度下降法来迭代更新参数。我们设定学习率eta为0.1,初始参数theta为[0, 0],也就是从 “山谷” 的某一个位置开始出发。一共要进行100次迭代,在每次迭代中,先计算当前参数位置的梯度值,然后按照随机梯度下降法的公式更新参数theta 。每更新一次,就在三维图上用一个红色的点标记出当前参数对应的位置以及损失函数的值。
通过运行这段代码,观察下图中红色点的变化,我们就能很直观地看到随机梯度下降法在参数空间里是怎么一步步寻找让损失函数最小的参数值的,就好像看着一个小人在 “山谷” 里摸索着往下走,直到找到最低点一样。
小批量梯度下降法(Mini-batch Gradient Descent)
在机器学习的优化算法中,小批量梯度下降法(Mini-batch Gradient Descent,简称 MBGD)是一种非常实用的方法,它巧妙地在随机梯度下降法(SGD)和批量梯度下降法(BGD)之间找到了平衡。
具体来说,小批量梯度下降法会把训练样本划分成若干个小批次(mini-batches),每个批次里包含一定数量的样本。与随机梯度下降法每次只使用一个样本进行模型参数更新不同,小批量梯度下降法每次会使用一个批次的样本进行参数更新。
小批量梯度下降法具有以下几个显著的区别和优势:
计算效率方面:和批量梯度下降法相比,小批量梯度下降法采用小批次样本进行参数更新,大大减少了计算时间。在处理大规模数据集时,批量梯度下降法需要处理所有样本,计算量巨大,而小批量梯度下降法每次只处理一个小批次样本,计算量大幅降低,所以小批量梯度下降法通常比批量梯度下降法更快。同时,和随机梯度下降法相比,小批量梯度下降法每次更新使用的样本数量更多,能够充分利用矩阵运算的并行性。比如在计算过程中,多个样本的数据可以同时进行处理,加速了参数更新的过程。稳定性方面:随机梯度下降法每次只使用一个样本更新参数,参数更新的方向可能会因为单个样本的特殊性而产生较大波动,导致不稳定。而小批量梯度下降法每次使用多个样本进行参数更新,综合考虑了多个样本的信息,使得参数更新的方向更加准确,避免了随机梯度下降法中参数更新高度不稳定的问题。在训练过程中,小批量梯度下降法更容易收敛到更好的局部最优解,就像在寻找最优解的道路上,它走得更加稳健。鲁棒性方面:由于随机梯度下降法只依赖单个样本更新参数,如果这个样本包含噪声,那么参数更新会受到很大影响。而小批量梯度下降法每次使用多个样本进行参数更新,对单个样本中的噪声信息起到了平均化的作用。例如,在一个批次的样本中,个别样本的噪声信息会被其他样本的信息所中和,从而减少了单个样本对模型的影响,使得小批量梯度下降法相对于随机梯度下降法对噪声数据具有更好的鲁棒性。泛化能力方面:小批量梯度下降法在泛化能力上相对于随机梯度下降法和批量梯度下降法都有一定优势。和随机梯度下降法相比,小批量梯度下降法使用更多样本进行参数更新,能够更全面地表征数据集的特征,使得模型在处理新数据时表现更好;和批量梯度下降法相比,小批量梯度下降法采用部分样本进行参数更新,避免了过度拟合的问题。过度拟合是指模型在训练数据上表现很好,但在新数据上表现不佳,小批量梯度下降法通过合理选择批次大小,能够更好地平衡模型的拟合能力和泛化能力。
当然,在实际应用中,小批量梯度下降法中批次大小的选择非常关键。较小的批次大小可能会使模型更快地收敛,但也增加了陷入局部最优解的风险;较大的批次大小虽然可能导致计算速度变慢,但能得到更稳定的解。所以,我们需要综合考虑数据集的规模、模型的复杂度以及计算资源的限制等因素,来选择合适的批次大小。
下面是一个简单的小批量梯度下降法的示例代码,以一个简单的线性回归问题为例:
import numpy as np
import matplotlib.pyplot as plt
# 生成一些简单的样本数据(这里模拟线性关系 y = 2x + 1 + noise)
np.random.seed(0)
x = np.random.rand(100, 1)
y = 2 * x + 1 + 0.5 * np.random.randn(100, 1)
# 添加一列全1作为截距项
X = np.hstack((np.ones((len(x), 1)), x))
# 初始化模型参数(两个参数:截距和斜率)
theta = np.array([0, 0])
# 定义损失函数(均方误差)
def loss_function(X, y, theta):
m = len(y)
predictions = np.dot(X, theta)
return (1 / (2 * m)) * np.sum((predictions - y.flatten()) ** 2)
# 定义梯度计算函数
def gradient(X, y, theta):
m = len(y)
predictions = np.dot(X, theta)
errors = predictions - y.flatten() # 确保errors是一维数组
gradients = (1 / m) * np.dot(X.T, errors) # 现在gradients形状为(2,)
return gradients
# 小批量梯度下降法实现
learning_rate = 0.1
num_iterations = 100
batch_size = 10 # 小批量的大小
m = len(X)
loss_history = []
theta_history = [theta.copy()]
for _ in range(num_iterations):
indices = np.random.choice(m, batch_size)
X_batch = X[indices]
y_batch = y[indices]
grad = gradient(X_batch, y_batch, theta)
theta = theta - learning_rate * grad # 现在形状匹配
loss = loss_function(X, y, theta) # 使用添加了全1列的X
loss_history.append(loss)
theta_history.append(theta.copy())
# 绘制损失函数随迭代次数的变化图
plt.plot(loss_history)
plt.xlabel('Iteration')
plt.ylabel('Loss')
plt.title('Mini-batch Gradient Descent Loss')
plt.show()
print("最终的参数:", theta)
在上述代码中,我们首先生成了一些模拟的线性数据,然后初始化了模型参数。接着定义了损失函数和梯度计算函数。在小批量梯度下降法的实现部分,我们设置了学习率、迭代次数和小批量的大小。每次迭代时,随机选择一个小批次的样本,计算梯度并更新参数,同时记录损失值和参数值。最后,上图看出通过绘制的损失函数随迭代次数的变化图,我们可以直观地看到小批量梯度下降法在训练过程中损失值的变化情况,以及最终得到的模型参数。
二阶优化算法
牛顿法(Newton's Method)
在机器学习和数学优化领域中,牛顿法是一种专门用于求解无约束优化问题的经典算法。什么是无约束优化问题呢?简单来说,就是在没有任何条件限制的情况下,寻找一个函数的最小值(或最大值)。牛顿法之所以强大,是因为它不仅利用了函数的一阶导数(也就是梯度),还引入了二阶导数信息,通过这些信息来更精准地更新参数,从而能够更快地找到函数的最优解,就像给寻找最优解的过程配备了更高级的导航工具。
牛顿法的核心公式是这样的: θn+1=θn−H(θn)−1∇f(θn)
这里的 θ 代表我们要求解的参数; f(θ) 是目标函数,也就是我们想要优化的函数; ∇f(θ) 表示目标函数关于参数 θ 的梯度,它能告诉我们函数在当前点变化最快的方向; H(θ) 则是目标函数关于参数 θ 的黑塞矩阵,它包含了函数二阶导数的信息,能帮助我们判断函数的凹凸性等特性; H(θ)−1 是黑塞矩阵的逆矩阵。
牛顿法具体的工作流程如下:
初始化参数:先给参数 θ 设定一个初始值,这就像我们在寻找宝藏时,先确定一个出发的地点。计算梯度:计算目标函数在当前参数值处的梯度 ∇f(θ) ,明确函数在这一点的变化方向。计算黑塞矩阵:算出目标函数在当前参数值处的黑塞矩阵 H(θ) ,进一步了解函数在该点附近的形状特性。更新参数:根据前面计算得到的梯度和黑塞矩阵的逆矩阵,按照公式 θn+1=θn−H(θn)−1∇f(θn) 更新参数,让参数朝着更接近最优解的方向移动。不断重复步骤 2 - 4,直到满足我们设定的停止条件。这个停止条件可以是达到预先指定的迭代次数,也可以是梯度下降到一个非常小的值,意味着函数已经接近最优解了。
下面通过一段代码,带大家直观感受牛顿法的工作原理:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# 定义目标函数
def objective_function(x, y):
return x**2 + 2*y**2
# 定义梯度函数
def gradient(x, y):
return np.array([2*x, 4*y])
# 定义黑塞矩阵
def hessian_matrix():
return np.array([[2, 0], [0, 4]])
# 生成数据
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)
Z = objective_function(X, Y)
# 绘制3D图形
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(X, Y, Z, cmap='viridis')
# 牛顿法迭代更新参数
theta = np.array([0, 0])
num_iterations = 10
for i in range(num_iterations):
gradient_value = gradient(theta[0], theta[1])
hessian_inverse = np.linalg.inv(hessian_matrix())
theta = theta - np.dot(hessian_inverse, gradient_value)
ax.scatter(theta[0], theta[1], objective_function(theta[0], theta[1]), color='red')
plt.show()
拟牛顿法(Quasi-Newton Methods),如BFGS、L-BFGS
在无约束非线性优化问题的求解领域,拟牛顿法是一类备受青睐的算法。这类问题就好比在一片复杂多变的地形中,寻找一个看不见的 “最低点”,而目标函数就像描述这片地形高度的规则。
拟牛顿法的核心思路十分巧妙。我们知道,牛顿法虽然强大,但在计算时需要求出目标函数的 Hessian 矩阵(二阶导数矩阵),这个矩阵的计算不仅复杂,而且在数据维度较高时,计算量会急剧增加,就像让你在一个巨大的迷宫里寻找特定的路线,难度极大。而拟牛顿法则另辟蹊径,它通过构造一个正定矩阵来近似目标函数的 Hessian 矩阵,用这个 “替身” 来指导搜索方向和步长的更新,从而避免了直接计算 Hessian 矩阵的繁琐过程,大大降低了计算成本。
在拟牛顿法的家族里,BFGS(Broyden-Fletcher-Goldfarb-Shanno)和 L-BFGS(Limited-memory BFGS,有限内存 BFGS)是最经典、应用最广泛的成员。BFGS 算法通过不断迭代更新近似矩阵,在每次迭代中根据新的梯度信息优化这个近似矩阵,逐步逼近真实的 Hessian 矩阵特性,进而找到更优的搜索方向。L-BFGS 则是 BFGS 的 “轻量级” 版本,它采用了有限内存策略,尤其适用于大规模数据场景。在这类场景下,传统 BFGS 可能因内存消耗过大而 “力不从心”,L-BFGS 通过只保留最近的若干次迭代信息来近似 Hessian 矩阵,既能保证算法的收敛速度,又大幅减少了内存占用,就像用精简高效的导航,在复杂的大数据迷宫中快速找到出路。
DFP算法
DFP算法的思想是通过迭代更新一个正定矩阵来近似Hessian矩阵。其迭代公式如下:
其中,表示第k次迭代的自变量,表示第k次迭代的梯度,表示第k次迭代的近似Hessian矩阵。
BFGS算法
在优化算法的大家族中,BFGS(Broyden-Fletcher-Goldfarb-Shanno)算法是一种经典的拟牛顿法,常用于求解无约束非线性优化问题。它的核心思想是通过迭代更新一个正定矩阵来近似目标函数的 Hessian 矩阵(二阶导数矩阵),从而避免了直接计算 Hessian 矩阵的复杂过程,同时保持了牛顿法的快速收敛特性。
BFGS 算法的迭代公式可以用数学语言描述为:
其中, 表示自变量的更新量, 表示梯度的更新量,(,) 是第 k 次迭代的近似 Hessian 矩阵。
下面通过一个简单的示例,帮助大家直观理解 BFGS 算法的工作原理。我们将在二维平面上绘制目标函数的等高线图,并展示 BFGS 算法的迭代路径:
import numpy as np
import plotly.graph_objects as go
# 定义目标函数
def f(x, y):
return x**2 + y**2
# 定义梯度函数
def gradient(x, y):
return np.array([2*x, 2*y])
# 拟牛顿法迭代更新参数
def update_parameter(parameter, approx_hessian, gradient_value):
# 计算参数更新方向(负梯度乘以近似Hessian矩阵的逆)
parameter_delta = -np.dot(approx_hessian, gradient_value)
# 更新参数
new_parameter = parameter + parameter_delta
# 计算新参数处的梯度
new_gradient_value = gradient(new_parameter[0], new_parameter[1])
# 计算梯度变化量
gradient_delta = new_gradient_value - gradient_value
# 计算BFGS算法中的rho值
rho = 1 / np.dot(gradient_delta, parameter_delta)
# 计算外积(用于更新近似Hessian矩阵)
outer_product = np.outer(parameter_delta, parameter_delta)
# 使用BFGS公式更新近似Hessian矩阵
approx_hessian += (rho * outer_product - np.dot(approx_hessian, outer_product) - np.dot(outer_product, approx_hessian)) / rho**2
return new_parameter
# 初始化参数和近似Hessian矩阵(单位矩阵)
parameter = np.array([8, 8])
approx_hessian = np.eye(2)
# 存储迭代路径
path = [parameter]
# 进行拟牛顿法迭代
num_iterations = 20
for i in range(num_iterations):
# 计算当前参数处的梯度
gradient_value = gradient(parameter[0], parameter[1])
# 更新参数和近似Hessian矩阵
parameter = update_parameter(parameter, approx_hessian, gradient_value)
# 记录每次迭代后的参数
path.append(parameter)
# 生成等高线数据
x = np.linspace(-10, 10, 100)
y = np.linspace(-10, 10, 100)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
# 绘制等高线和迭代路径
fig = go.Figure(data=[
go.Contour(x=x, y=y, z=Z, contours=dict(coloring='lines')),
go.Scatter3d(x=[p[0] for p in path], y=[p[1] for p in path], z=[f(p[0], p[1]) for p in path], mode='markers', marker=dict(size=5))
])
# 显示图形
fig.show()
在这个代码示例中,我们首先定义了一个简单的二次目标函数
,
它的图像是一个开口向上的抛物面,最低点在原点 (0,0) 处。然后,我们实现了梯度函数来计算目标函数在任意点的梯度。
核心部分是update_parameter函数,它实现了 BFGS 算法的参数更新逻辑:
计算当前参数的更新方向(负梯度乘以近似 Hessian 矩阵的逆)更新参数到新的位置计算新位置的梯度,并与旧梯度比较得到梯度变化量根据 BFGS 公式更新近似 Hessian 矩阵,使其更接近真实 Hessian 矩阵
接下来,我们从初始点 (8,8) 开始,使用 BFGS 算法进行 20 次迭代更新。每次迭代后,我们都将参数的位置记录下来,以便后续可视化。
为了直观展示算法的优化过程,我们生成了目标函数的等高线图(类似于地形图中的等高线),并在三维空间中绘制了算法迭代过程中参数点的运动轨迹。等高线图中的每一条线代表目标函数值相等的点的集合,而迭代路径则显示了算法如何从初始点逐步向最优解 (0,0) 移动。
通过观察下方的可视化结果,我们可以直观地看到 BFGS 算法的收敛特性:在初始阶段,参数更新幅度较大,随着接近最优解,更新幅度逐渐变小,最终收敛到目标点。这正是 BFGS 算法利用二阶导数信息(通过近似 Hessian 矩阵)实现快速收敛的优势所在。咱们值得注意的是,BFGS 算法特别适合处理高维优化问题,因为它不需要显式计算和存储完整的 Hessian 矩阵,而是通过迭代更新一个近似矩阵来逼近真实 Hessian 矩阵的特性。这种方法在保持较快收敛速度的同时,大大降低了计算复杂度,因此在机器学习、工程优化等领域得到了广泛应用。
共轭梯度法(Conjugate Gradient)
共轭梯度法是无约束优化算法家族中十分实用的一员,尤其擅长解决大规模线性方程组的求解问题,在机器学习、数值计算等多个领域都有广泛应用。它的核心原理是巧妙利用梯度和残差之间的共轭性质,通过多次迭代逐步找到目标函数的最优解。
我们先从它适用的目标函数类型说起。共轭梯度法主要针对二次型目标函数进行优化,这类函数的表达式为:
其中,x是自变量,它是一个向量;A是对称正定矩阵,对称正定意味着A的转置等于它本身,并且对于任意非零向量x,都大于0;b也是向量;c则是一个常数。
共轭梯度法的核心流程,是在每次迭代过程中确定一个 “共轭方向” 进行搜索,并且通过找到沿这个方向使目标函数最小化的步长,来更新自变量x的值。具体的迭代公式如下:
残差计算: 这里的表示第k次迭代时的残差,它反映了当前解与真实解之间的差距。通过用向量b减去矩阵A与当前自变量的乘积,得到残差向量。
初始共轭方向: 当 k = 0 时, 在第一次迭代开始时,将共轭方向直接设为初始残差 ,作为搜索的起始方向。
步长计算: 是第k次迭代的步长,它通过残差向量的转置与自身的乘积,除以共轭方向的转置、矩阵A与的乘积得到。这个计算方式确保了在沿着共轭方向搜索时,能找到使目标函数下降最快的步长。
自变量更新: 根据计算出的步长和共轭方向,对当前自变量进行更新,得到下一次迭代使用的自变量。
共轭方向更新: 是用于更新共轭方向的系数,通过新残差的转置与自身的乘积,除以旧残差的转置与自身的乘积得到。然后根据这个系数,结合新的残差和旧的共轭方向,计算出下一次迭代使用的共轭方向。
通过不断重复上述残差计算、步长计算、自变量更新、共轭方向更新的过程,共轭梯度法就能逐步调整自变量x的值,使目标函数 f(x) 不断减小,最终逼近最优解。总的来说,如下方这样:
自适应学习率优化算法
ADAM(Adaptive Moment Estimation)
在机器学习领域,尤其是深度学习中,优化算法对于模型的训练至关重要。ADAM(Adaptive Moment Estimation,自适应矩估计)就是其中一种备受青睐的自适应学习率优化算法,它巧妙地融合了 RMSprop 和 Momentum 两种算法的优点,在各类深度学习任务中被广泛应用。ADAM 能够根据训练过程中的数据动态调整学习率,不仅可以加快模型的收敛速度,还能让训练出的模型具备良好的泛化性能,在新数据上也能有不错的表现。
ADAM 算法的核心在于,通过对梯度的一阶矩估计和二阶矩估计,为每个参数自适应地调整学习率。具体的计算过程和公式如下:
初始化变量:首先设定初始的参数值 ,并初始化一阶矩估计变量 、二阶矩估计变量 ,通常将它们都设为全零向量。同时,确定三个超参数:学习率 ,用于控制参数更新的步长;这两个超参数用于控制历史梯度对当前一阶矩估计和二阶矩估计的影响程度,它们的值通常设置在接近 1 的水平(例如 = 0.9, = 0.999 );以及一个非常小的常数 ,用于防止计算过程中分母为零,一般取值为 。
计算梯度:在第 次迭代中,针对需要优化的参数 ,计算目标函数关于该参数的梯度 。
更新一阶矩估计:结合当前梯度 和上一次的一阶矩估计 ,更新一阶矩估计 ,公式为: 这个公式的含义是, 由两部分组成:一部分是上一次的一阶矩估计 乘以 ,体现了对历史梯度信息的继承;另一部分是当前梯度 乘以 ,反映了当前梯度的影响。通过这种方式, 能够综合历史和当前的梯度信息,对梯度的 “均值” 进行估计。
更新二阶矩估计:类似地,更新二阶矩估计 ,公式为: 这里 是当前梯度的平方,该公式同样结合了历史二阶矩估计和当前梯度平方的信息,用于估计梯度的 “方差” 。
对偏差进行修正:由于在迭代初期, 和 可能会偏向于零(因为初始值为零,且前期 和 的影响较大),所以需要对它们进行偏差修正。修正后的一阶矩估计 和二阶矩估计 分别为:
更新参数:最后,根据修正后的一阶矩估计和二阶矩估计,对参数 进行更新: 这个公式中,分子决定了参数更新的方向和幅度,分母则对更新幅度进行缩放,避免步长过大。通过这样的计算,ADAM 算法能够为每个参数单独确定合适的学习率,实现更高效的参数更新。
下面通过一段 Python 代码,利用 plotly 库绘制一个简单的三维图像,展示损失函数在参数空间中的曲面形态,后续可以结合这个曲面图,更直观地理解 ADAM 算法在优化过程中参数的收敛情况。
import plotly.graph_objects as go
# 定义损失函数
def loss_function(x, y):
return x**2 + y**2
# 设置参数空间
theta_range = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(theta_range, theta_range)
Z = loss_function(X, Y)
# 绘制三维图像
fig = go.Figure(data=[go.Surface(z=Z, x=X, y=Y)])
fig.update_layout(title='Loss Function',
scene=dict(
xaxis_title='Theta1',
yaxis_title='Theta2',
zaxis_title='Loss'))
fig.show()
上述代码中,首先定义了一个简单的损失函数 ,这是一个二元二次函数,其图像在三维空间中呈现为一个抛物面形状。然后,通过np.linspace函数设定了参数X和Y的取值范围,并利用np.meshgrid函数生成网格点,计算每个网格点对应的损失函数值 。最后,使用 plotly 库绘制出损失函数的三维曲面图,其中X轴表示参数 , Y轴表示参数 ,Z轴表示损失函数的值。借助这个直观的可视化图形,在后续研究 ADAM 算法优化过程时,就可以清楚地看到参数在这个曲面空间中是如何朝着损失函数最小值的方向移动、收敛的。
RMSProp(Root Mean Square Propagation)
RMSProp 是一种自适应学习率优化算法,专门用于调整神经网络中各个参数的学习率,在深度学习训练过程中扮演着重要角色。
在优化模型时,学习率决定了模型参数更新的步长。如果学习率过大,参数更新时容易跳过最优解,导致模型难以收敛甚至训练发散;学习率过小,又会使模型收敛速度过慢,耗费大量训练时间。而 RMSProp 的核心价值,就在于能够根据每个参数的更新情况,动态地调整其对应的学习率,让模型训练更高效。
RMSProp 通过计算梯度历史信息的均方根来自适应调整学习率。具体的计算过程如下:
初始化变量:首先,对于神经网络中的每个参数,初始化一个变量 ,这个变量后续会用来累计梯度平方的加权平均值 。同时,还需要设定两个超参数,一个是学习率 ,它控制整体的学习步长;另一个是衰减系数 ,它用于控制历史梯度对当前估计的影响程度,取值通常在 0 到 1 之间。此外,还会定义一个非常小的常数 ,一般取值在 左右,作用是保证后续计算中分母不为零,避免出现计算错误。计算梯度:在第 t 次迭代时,针对每个参数计算其对应的梯度 ,这个梯度代表了函数在当前参数位置的变化方向和变化率。更新二阶矩估计:利用以下公式更新变量 : 这个公式的含义是,当前时刻的二阶矩估计 ,是由上一时刻的二阶矩估计 乘以衰减系数 ,再加上当前梯度平方 乘以 得到。通过这种方式,将历史梯度信息与当前梯度信息进行了加权融合,实现对梯度变化趋势的 “记忆” 。如果某个参数的梯度一直较大,那么 的值会逐渐增大;反之,如果梯度较小, 增长也会较慢。更新参数:基于前面计算的结果,使用以下公式更新参数 : 从公式可以看出,分母 起到了调节学习率的作用。当某个参数对应的 较大时,意味着该参数的梯度波动较大,此时分母变大,学习率相对变小,参数更新的步长也就变小,避免参数更新幅度过大;反之,当 较小时,分母变小,学习率相对变大,加快参数的更新速度。这样,RMSProp 就实现了针对每个参数的自适应学习率调整。
AdaGrad(Adaptive Gradient,自适应梯度算法)
AdaGrad是机器学习领域中一种十分重要的自适应学习率优化算法,主要用于调整神经网络中各个参数的学习率,帮助模型更快更稳地找到最优解。
在传统的优化算法中,学习率通常是固定的,所有参数都以相同的步长进行更新。但 AdaGrad 打破了这种 “一刀切” 的模式,它能够根据每个参数的梯度历史信息,为不同参数量身定制学习率,实现 “区别对待”。具体来说,AdaGrad 会对频繁出现、梯度变化较为稳定的特征(即稀疏特征),赋予较小的学习率,让参数更新更加保守;而对于那些不常出现、梯度变化较大的特征,给予较大的学习率,促使模型更快地捕捉到这些特征的变化。
AdaGrad 算法的具体实现步骤如下:
初始化变量:首先,为每个参数初始化一个历史梯度累积变量 ,并将其设置为 0。同时设定一个全局学习率 ,以及一个极小的常数 (通常取值在 左右),用于防止分母为 0 的情况发生。计算梯度:在第 t 次迭代时,针对每个参数 ,计算其对应的梯度 ,即目标函数关于参数 \(\theta_{i}\) 在当前时刻的导数。更新历史梯度累积:将当前计算得到的梯度的平方累加到历史梯度累积变量中,更新公式为: 这一步记录了参数 从开始到当前迭代时刻的梯度变化情况,累积值越大,说明该参数在过去的更新过程中梯度波动越剧烈。更新参数:根据更新后的历史梯度累积变量,计算并更新参数 ,更新公式为: 在这个公式中,分母 起到了调节学习率的关键作用。对于梯度累积值 较大的参数,分母较大,使得学习率变小,参数更新幅度减缓;反之,对于梯度累积值较小的参数,分母较小,学习率相对较大,参数更新幅度更大。
因此,通过这样的自适应调整过程,AdaGrad 算法能够在训练过程中动态地平衡不同参数的更新速度,尤其适用于处理稀疏数据的场景,在自然语言处理、推荐系统等领域都有广泛应用 。
基于线性规划的优化算法
线性规划(Linear Programming)
线性规划是运筹学中非常重要的一个分支,专门用来解决在一组线性等式和不等式约束条件下,寻找线性目标函数最小值或最大值的问题。
线性规划的一般数学表达形式如下: 目标:最小化(或最大化)
约束条件: 其中, 是待求解的决策变量向量,这些变量代表了问题中需要确定的未知量; 是目标函数的系数向量,它决定了每个决策变量在目标函数中的权重; A 是不等式约束条件的系数矩阵, b 是对应的不等式约束向量; E 是等式约束条件的系数矩阵, f 是等式约束向量 。不等式约束 限定了决策变量取值的范围边界,等式约束 则对决策变量之间的关系做了精确的限定。
在实际应用中,我们可以借助 Python 和 plotly 库,绘制线性规划问题的可行域和目标函数在可行域上的等高线图,帮助我们更直观地理解线性规划的求解过程。以一个具体例子来说明:假设我们要最小化目标函数 ,并且存在以下两个不等式约束条件:
同时,决策变量 x 和 y 还有非负限制,即 , 。
下面是使用 Python 和 plotly 库实现的代码:
import numpy as np
import plotly.graph_objects as go
# 定义目标函数和约束条件
def objective_function(x, y):
return 3 * x + 4 * y
def constraint1(x, y):
return x + y - 6
def constraint2(x, y):
return 2 * x + y - 8
# 设置参数空间
x_range = np.linspace(0, 10, 100)
y_range = np.linspace(0, 10, 100)
X, Y = np.meshgrid(x_range, y_range)
Z = objective_function(X, Y)
# 绘制等高线图
fig = go.Figure(data=[go.Contour(z=Z, x=X, y=Y)])
fig.update_layout(title='Objective Function',
xaxis_title='x',
yaxis_title='y')
fig.show()
在上述代码中,首先定义了目标函数 objective_function ,以及两个约束条件函数 constraint1 和 constraint2 。接着,使用 np.linspace 函数在 0 到 10 的区间内生成 100 个等间距的点,分别构成 x 和 y 的取值范围;再通过 np.meshgrid 函数生成网格数据,进而计算出每个网格点对应的目标函数值 Z 。最后,使用 plotly 库绘制出目标函数的等高线图,图中的每一条等高线代表目标函数取相同值的点集。
而不等式约束条件 和 ,会在 平面上划分出一个区域,在这个区域内的点都满足所有约束条件,这个区域就叫做可行域。结合等高线图,我们能够观察目标函数在可行域内的变化趋势。通过线性规划算法,就可以找到在这个可行域中,能让目标函数取得最小(或最大)值的决策变量 x 和 y 的取值,从而解决实际问题。
整数规划(Integer Programming)
整数规划是在线性规划基础上延伸而来的一种数学优化方法,最大的特点在于它对决策变量有着特殊要求 —— 必须取整数值。在线性规划里,决策变量可以是任意实数,但在整数规划中,变量只能是像 0、1、2 这样的整数,这种限制让问题变得更加复杂,求解难度也大幅提升。
整数规划的一般数学表达形式如下:
目标函数:最小化(或最大化)
约束条件:
其中, 是我们要求解的决策变量向量,每一个 都必须是整数; 是目标函数的系数向量,它决定了每个决策变量在目标函数中的权重;A 和 b 构成不等式约束条件,A 是不等式约束的系数矩阵,b 是对应的向量,这些约束规定了决策变量取值的范围边界;E 和 d 构成等式约束条件,E 是等式约束的系数矩阵,d 是对应的向量,它们限定了决策变量需要满足的特定等式关系。
相比线性规划,整数规划的求解难度更高。因为线性规划的解空间是连续的,算法可以在实数范围内灵活搜索最优解;而整数规划由于变量的离散特性,解空间是由一个个离散的整数点构成,需要从这些离散点中找到满足所有约束条件且使目标函数最优的解,这无疑大大增加了搜索范围和计算复杂度。
为了求解整数规划问题,人们研究出了多种方法。比如 分支定界法,它通过不断将问题分解(分支)成更小的子问题,并估算每个子问题解的边界(定界),逐步缩小搜索范围,排除不可能包含最优解的区域; 割平面法 则是通过在解空间中添加额外的约束条件(割平面),不断切割掉不包含整数解的区域,逐步逼近整数最优解; 混合整数线性规划(MILP) 处理的是部分决策变量为整数,部分为实数的情况,它结合了线性规划和整数规划的求解策略,在实际应用中使用广泛 。
具有约束条件的优化算法
条件梯度法(Conditional Gradient Method),也称为Frank-Wolfe算法
条件梯度法,也被称作 Frank - Wolfe 算法,是专门用于解决带有约束条件优化问题的数值计算方法。在实际的数学建模与机器学习任务中,许多优化问题并非毫无限制,而是需要在满足特定约束的情况下,寻找目标函数的最小值或最大值,条件梯度法就是处理这类问题的有效工具。
假设我们面临一个带有等式约束条件的优化问题: 目标:最小化(或最大化)目标函数 约束条件:,其中 ,这里 x 是由多个变量组成的向量, 是关于 x 的函数, 代表一个个约束条件函数。
条件梯度法的核心思路是,在每一次迭代过程中,借助拉格朗日乘子法构建一个与原目标函数相近的新目标函数,随后沿着这个新目标函数的负梯度方向对变量进行更新。通过这样一次次迭代,逐步靠近原问题的最优解,并且保证每一步的解都满足给定的约束条件。
条件梯度法具体的迭代步骤如下:
初始化:首先要设定决策变量 x 和拉格朗日乘子 的初始值。这一步是整个迭代过程的起点,初始值的选择在一定程度上会影响算法的收敛速度和最终结果,但在缺乏先验知识时,通常会采用一些经验性或随机的初始设置。计算梯度:分别计算目标函数 的梯度向量 以及约束函数 的梯度向量 。梯度向量能够指示函数在当前点变化最快的方向,这对于后续确定变量的更新方向至关重要。更新决策变量:按照公式 来更新决策变量 x 的值。其中 表示第 k 次迭代时的决策变量值, 是第 k 次迭代的步长,它控制着每次变量更新的幅度; 是根据目标函数和约束条件确定的搜索方向,通常与负梯度方向相关 。更新拉格朗日乘子:使用公式 来更新拉格朗日乘子 ,这里 是更新拉格朗日乘子的步长, 是拉格朗日函数 关于拉格朗日乘子 的梯度。判断终止条件:检查是否满足终止条件。常见的终止条件包括达到预先设定的最大迭代次数,或者目标函数在相邻几次迭代中的变化量小于某个极小的阈值(即认为目标函数已收敛)。如果满足终止条件,则停止迭代;否则,返回第 2 步,继续进行下一轮的计算和更新。
以下是使用 Python 和相关库来展示条件梯度法应用过程的代码:
import numpy as np
import matplotlib.pyplot as plt
import plotly.graph_objects as go
# 定义目标函数和约束条件
def objective_function(x, y):
return x**2 + y**2
def constraint(x, y):
return x - y
# 设置参数空间
x = np.linspace(-10, 10, 100)
y = np.linspace(-10, 10, 100)
X, Y = np.meshgrid(x, y)
Z = objective_function(X, Y)
# 绘制等高线图
plt.contourf(X, Y, Z, levels=30, cmap='viridis')
plt.colorbar(label='Objective Function')
# 绘制约束条件
plt.contour(X, Y, constraint(X, Y), levels=[0], colors='r')
# 执行条件梯度法迭代过程
x_init = -5.0
y_init = 5.0
alpha = 0.1 # 步长
for _ in range(100):
x_grad = 2 * x_init
y_grad = 2 * y_init
x_init -= alpha * x_grad
y_init -= alpha * y_grad
plt.plot(x_init, y_init, 'bo', markersize=3)
plt.xlabel('x')
plt.ylabel('y')
plt.title('Constrained Optimization with Method of Constrained Gradients')
plt.show()
在上述代码中,首先定义了目标函数 objective_function(x, y)=x**2 + y**2 ,以及约束条件函数 constraint(x, y)=x - y 。接着,利用 numpy 库生成参数空间中的数据点,并通过 matplotlib 库绘制出目标函数的等高线图,其中不同颜色和高度的等高线代表目标函数在不同取值下的分布情况;同时,绘制出约束条件函数的曲线(在代码中通过 plt.contour(X, Y, constraint(X, Y), levels=[0], colors='r') 绘制出 \(x - y=0\) 这条约束曲线 )。
然后,设定决策变量 x 和 y 的初始值 x_init = -5.0 ,y_init = 5.0 ,并指定步长 alpha = 0.1 。在后续的循环迭代中,计算目标函数在当前点关于 x 和 y 的梯度 x_grad 和 y_grad ,并根据梯度和步长更新 x 和 y 的值,每次更新后在图上标记出当前点的位置。通过这样的过程,可以直观地看到在参数空间中,条件梯度法的优化过程轨迹,以及它如何在满足约束条件(红色曲线)的情况下,逐步寻找目标函数的最优解。
基于投影的优化算法,如投影梯度下降法(Projected Gradient Descent)
在优化问题的求解过程中,常常会遇到带有各种约束条件的情况,比如在资源有限、边界条件明确的场景下,如何找到最优解。基于投影的优化算法,尤其是投影梯度下降法(Projected Gradient Descent),就是专门用来处理这类带约束条件优化问题的有效方法。
假设我们面临这样一个优化问题:需要最小化(或者最大化)目标函数 ,同时x要满足一系列约束条件。这些约束条件包含等式约束 ()和不等式约束 ()。这里的是我们希望优化的目标,比如最小化生产成本、最大化利润;代表必须严格满足的条件,例如生产中物料配比必须等于某个固定值; 则是一些限制范围的条件,像生产投入不能超过预算。
基于投影的优化算法核心思路是:在每一次迭代过程中,先算出目标函数的梯度,依据梯度方向对变量x进行更新,得到 ,这里是步长,它控制着每次变量更新的幅度。由于直接更新后的 x' 可能超出约束条件限定的范围(即可行域),所以需要将 x' 通过投影操作,映射回可行域内,得到新的变量,其中表示投影操作,它能让变量重新满足所有约束条件。不断重复这个过程,变量就会逐步靠近满足约束条件下的最优解。
具体迭代步骤如下:
初始化:设定决策变量x的初始值,作为迭代的起始点。计算梯度:针对当前的变量x,计算目标函数的梯度向量,明确函数值变化最快的方向。变量更新:根据步长和梯度,更新变量x的值,公式为 。投影操作:将更新后的变量通过投影操作,得到满足约束条件的新变量。判断终止:检查是否满足终止条件。终止条件一般是达到预先设定的最大迭代次数,或者目标函数的变化已经非常小,满足收敛要求。如果满足终止条件,迭代结束;否则,将\(x_{new}\)作为新的当前变量,返回第 2 步继续迭代。
具有全局收敛性的优化算法
全局优化算法,如随机搜索、模拟退火、遗传算法等
在优化问题的求解中,具有全局收敛性的优化算法致力于寻找目标函数的全局最优解,而非局限于局部最优解。随机搜索就是这类算法中一种简单且直观的方法,它不依赖目标函数的导数(梯度)信息,通过在整个搜索空间内随机采样候选解来探索潜在的最优解。
随机搜索的基本流程如下:
在预先设定的搜索空间范围内,随机生成一个候选解。比如对于二维空间中的变量 x 和 y,若 x∈[a,b],y∈[c,d],则随机生成满足该范围的一组 (x,y) 作为候选解。将候选解代入目标函数,计算出对应的目标函数值。假设目标函数为 f(x,y),那么将 (x,y) 代入后得到 f(x,y) 的具体数值。把计算得到的目标函数值与当前记录的最优目标函数值进行比较。如果新的目标函数值更优(对于求最小值问题,即新值小于当前最优值;对于求最大值问题,即新值大于当前最优值),则将该候选解更新为当前的最优解。不断重复上述三个步骤,直到满足预先设定的终止条件,例如达到最大迭代次数,或者目标函数值的变化小于某个阈值等。
随机搜索的公式可以表示为:
对于目标函数 f(x),其中 x∈Ω(Ω 表示搜索空间)。在第 k 次迭代中,随机生成一个候选解 x(k)∈Ω,计算目标函数值 f(x(k))。设当前最优解为 x∗,对应的最优目标函数值为 f(x∗),若 f(x(k))
随机搜索算法具有明显的优点,它实现起来非常简单,算法逻辑容易理解,特别适用于那些没有明确数学表达式,或者难以计算梯度的目标函数。但该算法也存在不足,由于其完全依赖随机采样,采样过程具有随机性,为了找到较好的解,往往需要进行大量的采样,耗费较多的计算资源和时间。
下面通过具体代码示例来展示随机搜索的过程:
import numpy as np
import plotly.graph_objects as go
# 定义目标函数
def objective_function(x, y):
return np.sin(x) + np.cos(y)
# 生成网格点
x = np.linspace(-5, 5, 100)
y = np.linspace(-5, 5, 100)
X, Y = np.meshgrid(x, y)
Z = objective_function(X, Y)
# 随机搜索
num_iterations = 100
best_solution = None
best_fitness = float('inf')
for _ in range(num_iterations):
# 随机采样候选解
candidate_solution = [np.random.uniform(-5, 5), np.random.uniform(-5, 5)]
# 计算目标函数值
fitness = objective_function(candidate_solution[0], candidate_solution[1])
# 更新最优解
if fitness < best_fitness:
best_solution = candidate_solution
best_fitness = fitness
# 绘制三维图像
fig = go.Figure(data=[go.Surface(x=X, y=Y, z=Z)])
fig.add_trace(go.Scatter3d(x=[best_solution[0]], y=[best_solution[1]], z=[best_fitness],
mode='markers', marker=dict(size=5, color='red')))
fig.update_layout(title='Objective Function with Best Solution from Random Search', autosize=False,
width=700, height=500, scene=dict(
xaxis_title='X',
yaxis_title='Y',
zaxis_title='Z'
))
fig.show()
在这段代码中,首先定义了目标函数 objective_function,它以 x 和 y 为输入,计算 的值。接着通过 np.linspace 函数生成一系列的 x 和 y 值,并使用 np.meshgrid 函数将它们组合成网格点,计算每个网格点对应的目标函数值,用于后续绘制目标函数的三维曲面图。
在随机搜索部分,设定了最大迭代次数 num_iterations 为 100,初始化 best_solution 为 None,best_fitness 为正无穷大。在每次迭代中,通过 np.random.uniform 函数在 [-5,5] 范围内随机生成候选解,计算该候选解对应的目标函数值 fitness。如果 fitness 小于当前的 best_fitness,则更新 best_solution 和 best_fitness。
最后,使用 plotly.graph_objects 库绘制目标函数的三维曲面图,并将随机搜索得到的最优解以红色点的形式标注在图中,直观展示随机搜索算法找到的最优解在目标函数曲面上的位置。
最后
今天了解了多种常见的最优化算法,包括一阶优化算法、二阶优化算法、自适应学习率优化算法、基于线性规划的优化算法、具有约束条件的优化算法等。每种算法都有其独特的原理、适用场景和优缺点,在实际应用中,需要根据具体问题的特点和需求来选择合适的优化算法。