在上一篇博客中,我们学习了线性回归的基本概念、损失函数(如MSE)以及最小二乘法。最小二乘法通过求解解析解(直接计算出最优参数)的方式得到线性回归模型,但它有一个明显的局限:当特征数量很多时,计算过程会非常复杂(涉及矩阵求逆等操作)。今天我们来学习另一种更通用、更适合大规模数据的参数优化方法——梯度下降。
一、什么是梯度下降?
梯度下降(Gradient Descent)是一种迭代优化算法,核心思想是:通过不断地沿着损失函数"下降最快"的方向调整参数,最终找到损失函数的最小值(或近似最小值)。
我们可以用一个生活中的例子理解:假设你站在一座山上,周围被大雾笼罩,你看不见山脚在哪里,但你想以最快的速度走到山脚下。此时,你能做的最合理的选择就是:先感受一下脚下的地面哪个方向坡度最陡且向下,然后沿着那个方向走一步;走到新的位置后,再重复这个过程——感受坡度最陡的向下方向,再走一步;直到你感觉自己已经走到了最低点(脚下各个方向都不再向下倾斜)。
这个过程就是梯度下降的直观体现:
- 这座山就是我们的损失函数
- 你的位置代表当前参数值
- 你感受到的"坡度最陡的向下方向"就是负梯度方向
- 你每次走的"一步"的长度就是学习率
- 最终到达的"山脚下"就是损失函数的最小值点
在线性回归中,我们的目标是找到最优参数(如权重w),使得损失函数L(w)达到最小值。梯度下降的作用就是帮助我们一步步调整这些参数,最终找到让损失函数最小的参数值。
二、梯度下降的基本步骤
梯度下降的过程可以总结为4个核心步骤,我们以单特征且不含偏置项的线性回归模型y = wx为例(即b=0,损失函数使用MSE),逐步说明:
步骤1:初始化参数
首先需要给参数w设定初始值。初始值可以是任意的(比如随机值、0或1),因为梯度下降会通过迭代不断优化它。
为什么初始值可以任意选择?因为梯度下降是一个迭代优化的过程,无论从哪个点开始,只要迭代次数足够多且学习率合适,最终都会收敛到损失函数的最小值附近。
例如:我们可以简单地将初始值设为w = 0,然后开始优化过程。
步骤2:计算损失函数的梯度
“梯度"在单参数情况下就是损失函数对该参数的导数,它表示损失函数在当前参数位置的"变化率"和"变化方向”。
对单特征且b=0的模型y = wx,我们只需要计算一个导数:
- 损失函数L对w的导数:∂L/∂w(表示当w变化时,损失函数L的变化率)
这个导数就是"梯度",它指向损失函数增长最快的方向。这很重要:梯度指向的是损失函数值上升最快的方向,所以要让损失函数减小,我们需要向相反的方向移动。
步骤3:更新参数
为了让损失函数减小,我们需要沿着梯度的反方向(即负梯度方向)调整参数。更新公式为:
w = w - α · (∂L/∂w)
其中α是"学习率"(后面会详细解释),它控制参数更新的"步长"。
为什么是减去梯度而不是加上?因为梯度指向损失函数增大的方向,所以减去梯度就意味着向损失函数减小的方向移动,这正是我们想要的。
步骤4:重复迭代,直到收敛
重复步骤2和步骤3:每次计算当前参数的梯度,然后沿负梯度方向更新参数。当满足以下条件之一时,停止迭代(即"收敛"):
- 梯度的绝对值接近0(此时损失函数变化很小,接近最小值);
- 损失函数L(w)的变化量小于某个阈值(比如连续两次迭代的损失差小于10⁻⁶);
- 达到预设的最大迭代次数(防止无限循环)。
"收敛"这个词可以理解为:参数值已经稳定下来,继续迭代也不会有明显变化,此时我们可以认为找到了最优参数。
三、梯度下降的公式推导(单特征且b=0)
要实现梯度下降,核心是求出损失函数对参数w的导数。我们以MSE损失函数为例(且b=0),详细推导∂L/∂w的计算过程,每一步都会给出详细说明。
已知条件
-
模型:y_pred = wx(预测值,因b=0,无偏置项)
-
真实值:y
-
损失函数(MSE):
L(w) = (1/2n)Σ(yᵢ - y_pred,ᵢ)² = (1/2n)Σ(yᵢ - wxᵢ)²
(注:公式中加入1/2是为了后续求导时抵消平方项的系数2,使计算更简洁,不影响最终结果)
推导∂L/∂w(损失函数对w的导数)
-
先对单个样本的损失求导:
单个样本的损失为lᵢ = (1/2)(yᵢ - wxᵢ)²,对w求导:∂lᵢ/∂w = 2 · (1/2)(yᵢ - wxᵢ) · (-xᵢ) = -(yᵢ - wxᵢ)xᵢ
这里用到了复合函数求导法则(链式法则):首先对平方项求导得到2·(1/2)(…),然后对括号内的内容求导,由于我们是对w求导,所以(wxᵢ)对w的导数是xᵢ,前面有个负号,所以整体是-(yᵢ - wxᵢ)xᵢ。
-
对所有样本的损失求和后求导:
总损失L是所有单个样本损失的平均值:L = (1/n)Σlᵢ,因此:∂L/∂w = (1/n)Σ(∂lᵢ/∂w) = (1/n)Σ[-(yᵢ - wxᵢ)xᵢ] = -(1/n)Σ(yᵢ - y_pred,ᵢ)xᵢ
这一步的含义是:总损失对w的导数等于所有单个样本损失对w的导数的平均值。
最终更新公式
将上面得到的导数代入参数更新公式(参数 = 参数 - 学习率 × 导数),得到:
w = w + α · (1/n)Σ(yᵢ - y_pred,ᵢ)xᵢ
(注:负负得正,公式中的减号变为加号)
这个公式的含义是:
- 如果预测值y_pred,ᵢ小于真实值yᵢ(即yᵢ - y_pred,ᵢ为正),则w会增大;反之则减小。
- 增大或减小的幅度取决于三个因素:误差大小(yᵢ - y_pred,ᵢ)、特征值xᵢ的大小和学习率α。
- 特征值xᵢ越大,相同误差下w的更新幅度也越大,这体现了特征对参数调整的影响。
四、学习率(α)的作用
学习率(Learning Rate)是梯度下降中最重要的超参数(需要人工设定的参数),它控制参数更新的"步长"。我们继续用"下山"的例子来理解:
- 如果学习率α太小:就像每次只迈一小步下山,虽然安全,但需要走很多步才能到达山脚(迭代次数多,效率低)。
- 如果学习率α太大:就像每次迈一大步下山,可能会直接跨过山脚,甚至走到对面的山坡上(跳过最小值,甚至导致损失函数越来越大,无法收敛)。
- 合适的学习率:步长适中,能快速逼近最小值,既不会太慢也不会跳过。
实际应用中,学习率通常需要通过尝试确定,常见的初始值有0.1、0.01、0.001等。一种常用的策略是"学习率衰减":随着迭代次数增加,逐渐减小学习率,这样在开始时可以快速接近最小值,后期可以精细调整。
举个形象的例子:假设你在下山,开始时你离山脚很远,可以大踏步前进(较大的学习率);当快到山脚时,你会放慢脚步,小步移动(较小的学习率),以免走过头。
完整示例(手动实现梯度下降,单特征,b=0)
import numpy as np
import matplotlib.pyplot as plt # 可视化# 创建数据 植物的温度、和生长高度 [[20,10],[22,10],[27,12],[25,16]]
data =np.array([[20,10],[22,10],[27,12],[25,16]])
# 划分
x=data[:,0]
y=data[:,1]
print(x)
print(y)# 创建一个模型
def model(x,w):return x*w# 定义损失函数
# def loss(y_pred,y):
# return np.sum((y_pred-y)**2)/len(y)# 手动将损失函数展开 便于下面写梯度函数
def loss(w):return 2238*(w**2) - 1144*w + 600# 梯度函数 即,将损失函数求导
def gradient(w):return 2*2238*w - 1144# 梯度下降 给定初始系数w 迭代100次 优化w
w=0
learning_rate = 1e-5 # 降低学习率避免溢出
for i in range(100):w=w-learning_rate*gradient(w)print('e:',loss(w),'w:',w)# 绘制损失函数
plt.plot(np.linspace(0,1,100),loss(np.linspace(0,1,100)))# 绘制模型
def draw_line(w):point_x = np.linspace(0, 30, 100)point_y = model(point_x, w)plt.plot(point_x, point_y, label=f'Fitted line (w={w:.4f})')plt.scatter(x, y, color='red', label='Data points')plt.legend()plt.xlabel("Temperature")plt.ylabel("Height")plt.title("Linear Regression via Gradient Descent")plt.grid(True)plt.show()# draw_line(w)
五、多特征的梯度下降(以2个特征为例)
现实中,我们遇到的问题往往有多个特征(比如用"面积"和"房间数"预测房价)。下面我们推导2个特征的线性回归模型的梯度下降公式,方法与单特征类似,但需要考虑更多参数。
模型与损失函数
-
2个特征的模型:y_pred = w₁x₁ + w₂x₂(x₁、x₂是两个特征,w₁、w₂是对应的权重,因b=0,无偏置项)
-
损失函数(MSE):
L(w₁,w₂) = (1/2n)Σ(yᵢ - (w₁x₁,ᵢ + w₂x₂,ᵢ))²
推导各参数的偏导数
与单特征思路一致,我们分别对w₁、w₂求偏导:
-
对w₁的偏导:
∂L/∂w₁ = -(1/n)Σ(yᵢ - y_pred,ᵢ)x₁,ᵢ
推导过程与单特征中w的导数完全相同,只是这里特征是x₁,所以最后乘以x₁,ᵢ。
-
对w₂的偏导:
∂L/∂w₂ = -(1/n)Σ(yᵢ - y_pred,ᵢ)x₂,ᵢ
同理,这里特征是x₂,所以最后乘以x₂,ᵢ。
参数更新公式
将上述偏导数代入更新公式,得到:
w₁ = w₁ + α · (1/n)Σ(yᵢ - y_pred,ᵢ)x₁,ᵢ
w₂ = w₂ + α · (1/n)Σ(yᵢ - y_pred,ᵢ)x₂,ᵢ
多特征的扩展规律
从2个特征的推导可以看出,梯度下降的公式可以很容易扩展到k个特征的情况:
-
模型:y_pred = w₁x₁ + w₂x₂ + … + wₖxₖ
-
对第j个权重wⱼ的更新公式:
wⱼ = wⱼ + α · (1/n)Σ(yᵢ - y_pred,ᵢ)xⱼ,ᵢ
(xⱼ,ᵢ表示第i个样本的第j个特征值)
这个规律非常重要,它告诉我们:无论有多少个特征,梯度下降的更新规则都是相似的——每个权重wⱼ的更新量都与对应特征xⱼ和误差(yᵢ - y_pred,ᵢ)的乘积有关。
完整示例(手动实现梯度下降,两个特征,b=0)
import numpy as np
import matplotlib.pyplot as plt
# 如果使用中文显示,建议添加以下配置
plt.rcParams['font.sans-serif'] = ['SimHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False # 用来正常显示负号# 创建数据 [[1,1,3],[2,1,4],[1,2,5],[2,2,6]]
data = np.array([[1,1,3],[2,1,4],[1,2,5],[2,2,6]])
# 划分
x=data[:,:-1]
y=data[:,-1]
print(x)
print(y)# 创建模型
def model(x,w):return np.sum(x*w)# 创建损失函数
# def loss(w,x):# return np.sum((np.sum(x*w,axis=1)-y)**2)# return np.sum((model(x,w)-y)**2)
def loss(w1,w2):return 5*w1**2 + 5*w2**2 +9*w1*w2 -28*w1-29*w2 +43# 创建梯度函数
def gradient_w1(w1,w2):return 10*w1+9*w2-28def gradient_w2(w1,w2):return 9*w1+10*w2-29# 初始化w1,w2
w1=0
w2=0# 迭代100次 优化w1,w2
for i in range(100):w1,w2=w1-0.01*gradient_w1(w1,w2),w2-0.01*gradient_w2(w1,w2)print('e:',loss(w1,w2),'w1:',w1,'w2:',w2)# # 绘制模型 没写出来(所以注释了)
# def draw_line(w1,w2):
# point_x=np.linspace(0,5,100)
# point_y=model(point_x,w1,w2)
# plt.plot(point_x,point_y)# draw_line(w1,w2)
六、梯度下降与最小二乘法的对比
特点 | 梯度下降 | 最小二乘法 |
---|---|---|
本质 | 迭代优化(数值解) | 直接求解方程(解析解) |
计算复杂度 | 低(适合大规模数据/多特征) | 高(涉及矩阵求逆) |
适用性 | 几乎所有损失函数 | 仅适用于凸函数且有解析解 |
超参数依赖 | 需要调整学习率等 | 无需超参数 |
内存需求 | 低(可分批处理数据) | 高(需要一次性加载所有数据) |
简单来说,当特征数量较少时,最小二乘法可能更简单直接;但当特征数量很多(比如超过1000个)时,梯度下降通常是更好的选择。
总结
梯度下降是机器学习中最基础也最常用的优化算法,它通过"沿损失函数负梯度方向迭代更新参数"的方式,找到使损失最小的参数值。与最小二乘法相比,梯度下降更适合处理大规模数据和复杂模型。
本文我们从概念、步骤、公式推导(单特征且b=0和双特征)、学习率作用等方面详细讲解了梯度下降,希望能帮助你理解其核心逻辑。掌握梯度下降不仅对理解线性回归至关重要,也是学习更复杂机器学习算法(如神经网络)的基础。
下一篇博客中,我们将通过实际案例演示如何用梯度下降实现线性回归,进一步加深理解。