梯度下降算法是一种用于最小化目标函数的优化算法,用于寻找最小化损失函数(或成本函数)的参数值,广泛应用于机器学习和人工智能中的参数优化问题。
梯度下降的工作原理: 通过计算损失函数关于模型参数的梯度,然后沿着梯度的反方向(即最陡峭的下降方向)更新参数。这样,每次迭代都会使损失函数值减小(至少在局部上是这样的),从而逐渐接近损失函数的最小值。
目标函数
假设有一个目标函数 f ( X ) f(X) f(X),其中 X X X 是需要优化的参数向量。
梯度
梯度是一个向量,其方向指向函数增长最快的方向。梯度的计算公式为:
∇ f ( X ) = [ ∂ f ∂ x 1 , ∂ f ∂ x 2 , … , ∂ f ∂ x n ] \nabla f(X)=\left[\frac{\partial f}{\partial x_1},\frac{\partial f}{\partial x_2},\dots,\frac{\partial f}{\partial x_n} \right] ∇f(X)=[∂x1∂f,∂x2∂f,…,∂xn∂f]
其中, ∂ f ∂ x i \frac{\partial f}{\partial x_i} ∂xi∂f表示函数 f f f对 x \mathbf{x} x的第 i i i个元素 x i x_i xi的偏导数。
算法步骤
学习率
学习率 α \alpha α 是一个超参数,决定了每次迭代参数更新的步长。
学习率的选择对算法的收敛速度和稳定性至关重要。
对于一元函数,梯度下降算法可以表示为:
x i + 1 = x i − α ∗ f ′ ( x i ) x_{i+1}=x_i - \alpha \ast f^\prime (x_i) xi+1=xi−α∗f′(xi)
其中 f ′ ( x i ) f^\prime (x_i) f′(xi) 是函数 f ( x ) f(x) f(x)在 x i x_i xi处的导数,也称为函数 f ( x ) f(x) f(x)在 x i x_i xi处的梯度,它是一个有正
有负(表示梯度下降的方向)且有大小(表示梯度下降的大小)的值
对于二元函数,梯度下降算法可以表示为:
{ x i + 1 = x i − α ∗ ∂ z ∂ x ∣ x = x i , y = y i y i + 1 = y i − α ∗ ∂ z ∂ y ∣ x = x i , y = y i \begin{cases} x_{i+1}=x_i - \alpha \ast \frac{\partial z}{\partial x}|_{x=x_i,y=y_i} \\ y_{i+1}=y_i - \alpha \ast \frac{\partial z}{\partial y}|_{x=x_i,y=y_i} \end{cases} {xi+1=xi−α∗∂x∂z∣x=xi,y=yiyi+1=yi−α∗∂y∂z∣x=xi,y=yi
其中 ( ∂ z ∂ x , ∂ z ∂ y ) (\frac{\partial z}{\partial x},\frac{\partial z}{\partial y}) (∂x∂z,∂y∂z) 是函数 z ( x , y ) z(x,y) z(x,y)在 x = x i , y = y i x=x_i,y=y_i x=xi,y=yi处分别对于 x x x和 y y y所求的偏导数组成的一个向量,称为函数 z ( x , y ) z(x,y) z(x,y)在 x = x i , y = y i x=x_i,y=y_i x=xi,y=yi处的梯度,它是一个向量,有方向(表示梯度下降的方向)且有大小(表示梯度下降的大小)。
梯度表示在 x = x i , y = y i x=x_i,y=y_i x=xi,y=yi 处函数z的值变化最大(最陡峭)的方向。如果取 z = z j z=z_j z=zj 的曲线,则梯度是该曲线在 x = x i , y = y i x=x_i,y=y_i x=xi,y=yi处的法向量的反方向。
{ x 1 i + 1 = x 1 i − α ∗ ∂ z ∂ x 1 ∣ x = x 1 i , x 2 = x 2 i , … , x n = x n i x 2 i + 1 = x 2 i − α ∗ ∂ z ∂ x 2 ∣ x = x 1 i , x 2 = x 2 i , … , x n = x n i … x n i + 1 = x n i − α ∗ ∂ z ∂ x n ∣ x = x 1 i , x 2 = x 2 i , … , x n = x n i \begin{cases} x_{1_{i+1}}=x_{1_{i}} - \alpha \ast \frac{\partial z}{\partial x_1}|_{x=x_{1_i},x_2=x_{2_i},\dots ,x_n = x_{n_i}} \\ x_{2_{i+1}}=x_{2_{i}} - \alpha \ast \frac{\partial z}{\partial x_2}|_{x=x_{1_i},x_2=x_{2_i},\dots ,x_n = x_{n_i}} \\ \dots \\ x_{n_{i+1}}=x_{n_{i}} - \alpha \ast \frac{\partial z}{\partial x_n}|_{x=x_{1_i},x_2=x_{2_i},\dots ,x_n = x_{n_i}} \\ \end{cases} ⎩ ⎨ ⎧x1i+1=x1i−α∗∂x1∂z∣x=x1i,x2=x2i,…,xn=xnix2i+1=x2i−α∗∂x2∂z∣x=x1i,x2=x2i,…,xn=xni…xni+1=xni−α∗∂xn∂z∣x=x1i,x2=x2i,…,xn=xni
其中 ( ∂ z ∂ x 1 , ∂ z ∂ x 2 , … , ∂ z ∂ x n ) (\frac{\partial z}{\partial x_1},\frac{\partial z}{\partial x_2},\dots,\frac{\partial z}{\partial x_n}) (∂x1∂z,∂x2∂z,…,∂xn∂z) 是函数 z ( x 1 , x 2 , … , x n ) z(x_1,x_2,\dots,x_n) z(x1,x2,…,xn)在 x 1 = x 1 i , x 2 = x 2 i , … , x n = x n i x_1=x_{1_i},x_2=x_{2_i},\dots,x_n = x_{n_i} x1=x1i,x2=x2i,…,xn=xni处分别对于 x 1 , x 2 , … , x n x_1,x_2,\dots,x_n x1,x2,…,xn的偏导数组成的一个向量,称为函数 z ( x 1 , x 2 , … , x n ) z(x_1,x_2,\dots,x_n) z(x1,x2,…,xn)在 x 1 = x 1 i , x 2 = x 2 i , … , x n = x n i x_1=x_{1_i},x_2=x_{2_i},\dots,x_n = x_{n_i} x1=x1i,x2=x2i,…,xn=xni处的梯度,它是一个向量,有方向(表示梯度下降的方向)且有大小(表示梯度下降的大小)。
梯度下降的核心思想是通过迭代地调整模型参数的值,使得损失函数逐渐减小。具体来说,它的步骤如下:
实现过程如下:
批量梯度下降每次学习都使用整个训练集,因此这些计算是冗余的,因为每次都使用完全相同的样本集。但其优点在于每次更新都会朝着正确的方向进行,最后能够保证收敛于极值点(凸函数收敛于全局极值点,非凸函数可能会收敛于局部极值点),但是其缺点在于每次学习时间过长,如果训练集很大以至于需要消耗大量的内存,并且全量梯度下降不能进行在线模型参数更新。
它的具体思路是在更新每一参数时都使用所有的样本来进行更新。它得到的是一个全局最优解,但是每迭代一步,都要用到训练集所有的数据,所以在下图的梯度下降过程中可以看到,它是一个近乎直线的下降过程,直接前往最低点。
为了克服批量梯度下降的缺点,有人提出了随机梯度下降(Stochastic Gradient Descent)算法,即每次更新系数只随机抽取一个样本参与计算,因此既可以减少迭代次数,节省计算时间,又可以防止内存溢出,降低了计算开销。但是随机梯度下降也有一个缺点,每次更新可能并不会按照正确的方向进行,因此可以带来优化波动(扰动),即参数更新频率太快,有可能出现目标函数值在最优值附近的震荡现象,并且高频率的参数更新导致了高方差。不过从另一个方面来看,随机梯度下降所带来的波动有个好处就是,对于类似盆地区域(即很多局部极小值点)那么这个波动的特点可能会使得优化的方向从当前的局部极小值点跳到另一个更好的局部极小值点,这样便可能对于非凸函数,最终收敛于一个较好的局部极值点,甚至全局极值点。
随机梯度下降虽然提高了计算效率,但是由于每次迭代只随机选择一个样本,因此随机性比较大,所以下降过程中非常曲折。
小批量梯度下降(Mini-batch Gradient Descent)是介于上述两种方法之间的优化方法,即在更新参数时,只使用一部分样本(一般256以下)来更新参数,这样既可以保证训练过程更稳定,又可以利用批量训练方法中的矩阵计算的优势。MBGD在每次更新参数时使用b个样本(b一般为10)。
从前面的式子中可以看出梯度下降对学习率,很敏感,如果学习率太大,会出现反复横跳阻碍收敛(在极值点附近震荡)。学习率太小,则会导致收敛的速度非常慢,浪费计算量。
另一方面对于非凸目标函数,除了BGD算法外,无法保证找到全局最低点,很容易陷入局部最低点。
在使用梯度下降时,调优方向:
(1)算法的步长选择。在前面的算法描述中,我提到取步长为1,但是实际上取值取决于数据样本,可以多取一些值,从大到小,分别运行算法,看看迭代效果,如果损失函数在变小,说明取值有效,否则要增大步长。前面说了。步长太大,会导致迭代过快,甚至有可能错过最优解。步长太小,迭代速度太慢,很长时间算法都不能结束。所以算法的步长需要多次运行后才能得到一个较为优的值。
(2)算法参数的初始值选择。 初始值不同,获得的最小值也有可能不同,因此梯度下降求得的只是局部最小值;当然如果损失函数是凸函数则一定是最优解。由于有局部最优解的风险,需要多次用不同初始值运行算法,关键损失函数的最小值,选择损失函数最小化的初值。
(3)归一化。由于样本不同特征的取值范围不一样,可能导致迭代很慢,为了减少特征取值的影响,可以对特征数据归一化,也就是对于每个特征x,求出它的期望x和标准差std(x),然后转化为同一范围内的值。
它通过引入动量的概念来加速梯度下降法的收敛速度,同时减少在最小值附近的摆动。动量算法的核心思想是计算梯度的指数加权平均数,并利用该平均值更新权重。这种方法可以看作是梯度下降法的一种改进,它通过减少局部最小值附近的震荡,使得算法能够更快地收敛到全局最小值。
在动量梯度下降法中,参数更新不仅依赖于当前的梯度,还考虑了之前梯度的加权平均,即动量项。这个动量项可以看作是之前梯度的一个累积效应,它能够帮助梯度下降更平滑地进行,避免在复杂的函数曲面上陷入局部最小值。
Nesterov Accelerated Gradient (NAG) 算法是基于冲量梯度下降算法进行改进的一种算法,也是梯度下降算法的变种,以其快速的收敛速度而受到青睐。NAG是Momentum算法的扩展,通过引入“向前看”的策略来改善梯度下降过程中的加速问题,从而减少在最小值点附近的过冲现象 。
NAG的核心思想是在每一步更新中,不仅仅考虑当前位置的梯度,而是先根据动量更新一个预测的下一步位置,然后计算这个预测位置的梯度,并用这个梯度来更新参数。这种方法可以看作是在梯度下降中加入了一种“惯性”,让参数更新更加平滑,同时提前考虑下一步的位置信息,以实现更有效的更新。
AdaGrad(Adaptive Gradient Algorithm)是一种自适应学习率的梯度下降算法,由Duchi等人于2011年提出,它根据参数的历史梯度信息自适应地调整每个参数的学习率 。AdaGrad的核心思想是将学习率调整为与参数梯度平方的累积和的逆相关,从而实现对每个参数独立调整学习率的效果 。这使得对于出现频率高的特征,其学习率会较低;而对于出现频率低的特征,其学习率会较高,这种方式特别适用于稀疏数据 。
AdaGrad算法的优点包括:
自适应学习率:根据每个参数的梯度历史信息自动调整学习率,减少了手动调节学习率的需要。
适用于稀疏数据:对于稀疏特征,AdaGrad能够自动提高其学习率,使得模型更快地学习到这些特征的重要性。
然而,AdaGrad也有其缺点:
为了解决AdaGrad学习率持续衰减的问题,研究者提出了一些改进算法,如AdaDelta和RMSprop。AdaDelta通过使用指数加权平均而非简单的累积和来更新学习率,而RMSprop则是AdaDelta的无运行平均更新版本 。这些改进算法在各种机器学习任务中得到了广泛的应用。
AdaDelta算法是一种自适应学习率的优化方法,由Matthew D. Zeiler在2012年提出,主要解决了AdaGrad算法在迭代后期可能较难找到有用解的问题 。AdaDelta算法没有学习率这个超参数,而是通过使用有关自变量更新量平方的指数加权移动平均的项来替代RMSProp算法中的学习率 。它使用了小批量随机梯度的指数加权移动平均变量,并引入了超参数ρ(通常设为0.9)来计算状态变量 。
AdaDelta算法的优点包括:
然而,AdaDelta算法也存在一些局限性:
在实际应用中,AdaDelta算法已被证明在多种深度学习任务中有效,包括图像分类、语音识别和自然语言处理等 。尽管存在一些挑战,AdaDelta算法因其自适应学习率和鲁棒性而在深度学习优化中占有一席之地。
import numpy as np
# 假设的线性回归模型
def linear_regression_model(x, weights, bias):
"""
:param x: 特征向量
:param weights: 权重向量
:param bias: 偏置
:return: 预测值
"""
return np.dot(x, weights) + bias
# 计算均方误差损失函数
def mean_squared_error(y_true, y_pred):
"""
:param y_true: 实际值
:param y_pred: 预测值
:return: 均方误差
"""
return ((y_true - y_pred) ** 2).mean()
# 批量梯度下降算法
def batch_gradient_descent(X, y, weights, bias, learning_rate, num_iterations):
"""
:param X: 特征矩阵,每一行代表一个训练样本的特征,每一列代表一个特征
:param y: 目标向量,每个元素对应一个训练样本的目标
:param weights: 初始权重向量
:param bias: 初始偏置
:param learning_rate: 学习率
:param num_iterations: 迭代次数
:return: 最终的权重和偏置
"""
m = X.shape[0] # 样本数量
# 迭代指定次数
for i in range(num_iterations):
# 计算预测值
y_pred = linear_regression_model(X, weights, bias)
# 计算梯度并优化
# 权重梯度计算及优化
grad_weights = (-2 / m) * X.T.dot(y - y_pred)
# 偏置梯度计算及优化
grad_bias = (-2 / m) * np.sum(y - y_pred)
# 更新权重和偏置
weights -= learning_rate * grad_weights
bias -= learning_rate * grad_bias
# 可选:打印每迭代的损失值
if i % 100 == 0:
loss = mean_squared_error(y, y_pred)
print(f"Iteration {i}: Loss: {loss}")
return weights, bias
# 示例数据(特征矩阵X和目标向量y)
X = np.array([[1, 1], [2, 2], [3, 3], [4, 4]])
y = np.array([2, 4, 6, 8])
# 初始化参数
weights = np.zeros(X.shape[1])
bias = 0
# 批量梯度下降设置
learning_rate = 0.01
num_iterations = 1000
# 执行批量梯度下降
weights, bias = batch_gradient_descent(X, y, weights, bias, learning_rate, num_iterations)
print(f"Trained weights: {weights}")
print(f"Trained bias: {bias}")
import numpy as np
# 假设我们有线性回归模型 y = wx + b
# 其中 w 是权重,b 是偏置,x 是特征,y 是目标值
# 特征和目标值
X = np.array([[1, 2], [2, 3], [3, 4], [4, 5]]) # (m, n) 形状,m 为样本数,n 为特征数
y = np.array([3, 5, 7, 9]) # (m,) 形状
# 初始化参数
weights = np.zeros(X.shape[1])
bias = 0
# 学习率和迭代次数
learning_rate = 0.01
num_iterations = 1000
# 随机梯度下降算法
for iteration in range(num_iterations):
for i in range(X.shape[0]):
# 预测当前样本的输出
y_pred = X[i].dot(weights) + bias
# 计算梯度
grad_weight = 2 * X[i] * (y_pred - y[i])
grad_bias = 2 * (y_pred - y[i])
# 更新参数
weights -= learning_rate * grad_weight
bias -= learning_rate * grad_bias
# 打印最终的参数
print(f"最终的权重: {weights}")
print(f"最终的偏置: {bias}")
import numpy as np
# 假设我们有一个线性回归模型 y = w * x + b
# 其中 w 是权重向量,b 是偏置,x 是特征向量,y 是目标值
def generate_data(num_samples, num_features):
"""生成随机数据集"""
np.random.seed(42) # 设置随机种子以获得可复现的结果
X = 2 * np.random.rand(num_samples, num_features) - 1 # 生成特征矩阵
w_true = np.array([np.pi, 2 * np.pi]) # 真实权重
b_true = 1.5 # 真实偏置
y = X.dot(w_true) + b_true + 0.5 * np.random.randn(num_samples) # 生成目标值,加入噪声
return X, y
def mean_squared_error(y_true, y_pred):
"""计算均方误差损失"""
return np.round(((y_true - y_pred) ** 2).mean(),4)
def sgd_minibatch(X, y, learning_rate, batch_size, num_iterations):
"""
小批量梯度下降算法实现
参数:
- X: 特征矩阵,形状 (m, n)
- y: 目标值向量,形状 (m,)
- learning_rate: 学习率
- batch_size: 小批量大小
- num_iterations: 迭代次数
"""
num_samples, num_features = X.shape
weights = np.zeros(num_features) # 初始化权重
bias = 0 # 初始化偏置
for iteration in range(num_iterations):
# 随机打乱样本索引
indices = np.random.permutation(num_samples)
X_shuffled = X[indices]
y_shuffled = y[indices]
# 按小批量处理打乱后的数据
for i in range(0, num_samples, batch_size):
batch_X = X_shuffled[i:i + batch_size]
batch_y = y_shuffled[i:i + batch_size]
# 计算预测值
y_pred = batch_X.dot(weights) + bias
# 计算梯度
grad_weights = (-2 / batch_size) * batch_X.T.dot(y_pred - batch_y)
grad_bias = (-2 / batch_size) * np.sum(y_pred - batch_y)
# 更新权重和偏置
weights -= learning_rate * grad_weights
bias -= learning_rate * grad_bias
# 每100次迭代打印一次损失值
if iteration % 100 == 0:
y_full_pred = X.dot(weights) + bias
loss = mean_squared_error(y, y_full_pred)
print(f"Iteration {iteration}: Loss {loss}")
return weights, bias
# 参数设置
learning_rate = 0.01
batch_size = 5
num_iterations = 800
# 生成数据
X, y = generate_data(num_samples=100, num_features=2)
# 执行小批量梯度下降算法
weights, bias = sgd_minibatch(X, y, learning_rate, batch_size, num_iterations)
print(f"找到的权重: {weights}")
print(f"找到的偏置: {bias}")
这个实现是非常基础的,没有包含正则化、更高级的优化技术或模型验证等概念。在实际应用中,你可能需要根据具体问题调整和优化这个算法。此外,对于更复杂的模型,可能需要使用更高级的库,如TensorFlow或PyTorch。