梯度下降求解线性模型
1. 问题设定
线性模型假设函数:
hθ(x)=θ0+θ1x1+θ2x2+...+θnxn=θTxh_\theta(x) = \theta_0 + \theta_1 x_1 + \theta_2 x_2 + ... + \theta_n x_n = \theta^T xhθ(x)=θ0+θ1x1+θ2x2+...+θnxn=θTx
损失函数(均方误差):
J(θ)=12m∑i=1m(hθ(x(i))−y(i))2J(\theta) = \frac{1}{2m} \sum_{i=1}^{m} (h_\theta(x^{(i)}) - y^{(i)})^2J(θ)=2m1i=1∑m(hθ(x(i))−y(i))2
其中:
- mmm 是样本数量
- θ\thetaθ 是参数向量
- x(i),y(i)x^{(i)}, y^{(i)}x(i),y(i) 是第 iii 个样本
为什么用 12m\frac{1}{2m}2m1 而不是 1m\frac{1}{m}m1?
这是为了简化求导结果 。系数 12\frac{1}{2}21 可以抵消求导时产生的系数 2,让梯度公式更简洁。
如果用 1m\frac{1}{m}m1:
∂J∂θj=1m∑i=1m2(hθ−y)⋅xj=2m∑i=1m(hθ−y)⋅xj\frac{\partial J}{\partial \theta_j} = \frac{1}{m} \sum_{i=1}^{m} 2(h_\theta - y) \cdot x_j = \frac{2}{m} \sum_{i=1}^{m} (h_\theta - y) \cdot x_j∂θj∂J=m1i=1∑m2(hθ−y)⋅xj=m2i=1∑m(hθ−y)⋅xj
梯度公式里多了一个 2。
如果用 12m\frac{1}{2m}2m1:
∂J∂θj=12m∑i=1m2(hθ−y)⋅xj=1m∑i=1m(hθ−y)⋅xj\frac{\partial J}{\partial \theta_j} = \frac{1}{2m} \sum_{i=1}^{m} 2(h_\theta - y) \cdot x_j = \frac{1}{m} \sum_{i=1}^{m} (h_\theta - y) \cdot x_j∂θj∂J=2m1i=1∑m2(hθ−y)⋅xj=m1i=1∑m(hθ−y)⋅xj
系数正好抵消,公式更清爽。
注意:两种写法最终收敛到同一个最优解,因为:
- 梯度的方向是一样的(只是缩放了 2 倍)
- 这个差异可以被学习率吸收:αnew=αold2\alpha_{new} = \frac{\alpha_{old}}{2}αnew=2αold
所以 12m\frac{1}{2m}2m1 纯粹是数学书写习惯。
2. 梯度计算
对每个参数 θj\theta_jθj 计算偏导数:
∂J(θ)∂θj=1m∑i=1m(hθ(x(i))−y(i))⋅xj(i)\frac{\partial J(\theta)}{\partial \theta_j} = \frac{1}{m} \sum_{i=1}^{m} (h_\theta(x^{(i)}) - y^{(i)}) \cdot x_j^{(i)}∂θj∂J(θ)=m1i=1∑m(hθ(x(i))−y(i))⋅xj(i)
从标量形式到向量形式的推导
定义矩阵和向量:
X=[x0(1)x1(1)⋯xn(1)x0(2)x1(2)⋯xn(2)⋮⋮⋱⋮x0(m)x1(m)⋯xn(m)]m×(n+1),θ=[θ0θ1⋮θn](n+1)×1,y=[y(1)y(2)⋮y(m)]m×1X = \begin{bmatrix} x_0^{(1)} & x_1^{(1)} & \cdots & x_n^{(1)} \\ x_0^{(2)} & x_1^{(2)} & \cdots & x_n^{(2)} \\ \vdots & \vdots & \ddots & \vdots \\ x_0^{(m)} & x_1^{(m)} & \cdots & x_n^{(m)} \end{bmatrix}{m \times (n+1)}, \quad \theta = \begin{bmatrix} \theta_0 \\ \theta_1 \\ \vdots \\ \theta_n \end{bmatrix}{(n+1) \times 1}, \quad y = \begin{bmatrix} y^{(1)} \\ y^{(2)} \\ \vdots \\ y^{(m)} \end{bmatrix}_{m \times 1}X= x0(1)x0(2)⋮x0(m)x1(1)x1(2)⋮x1(m)⋯⋯⋱⋯xn(1)xn(2)⋮xn(m) m×(n+1),θ= θ0θ1⋮θn (n+1)×1,y= y(1)y(2)⋮y(m) m×1
注意:x0(i)=1x_0^{(i)} = 1x0(i)=1(偏置项)
预测值向量:
h=Xθ=[hθ(x(1))hθ(x(2))⋮hθ(x(m))]h = X\theta = \begin{bmatrix} h_\theta(x^{(1)}) \\ h_\theta(x^{(2)}) \\ \vdots \\ h_\theta(x^{(m)}) \end{bmatrix}h=Xθ= hθ(x(1))hθ(x(2))⋮hθ(x(m))
误差向量:
e=h−y=Xθ−y=[hθ(x(1))−y(1)hθ(x(2))−y(2)⋮hθ(x(m))−y(m)]e = h - y = X\theta - y = \begin{bmatrix} h_\theta(x^{(1)}) - y^{(1)} \\ h_\theta(x^{(2)}) - y^{(2)} \\ \vdots \\ h_\theta(x^{(m)}) - y^{(m)} \end{bmatrix}e=h−y=Xθ−y= hθ(x(1))−y(1)hθ(x(2))−y(2)⋮hθ(x(m))−y(m)
将标量偏导数写成向量形式:
标量偏导数:
∂J∂θj=1m∑i=1me(i)⋅xj(i)\frac{\partial J}{\partial \theta_j} = \frac{1}{m} \sum_{i=1}^{m} e^{(i)} \cdot x_j^{(i)}∂θj∂J=m1i=1∑me(i)⋅xj(i)
观察这个公式,它实际上是误差向量 eee 与 XXX 的第 jjj 列的内积(点积):
∂J∂θj=1m[xj(1)xj(2)⋯xj(m)][e(1)e(2)⋮e(m)]=1m(X[:,j])T⋅e\frac{\partial J}{\partial \theta_j} = \frac{1}{m} \begin{bmatrix} x_j^{(1)} & x_j^{(2)} & \cdots & x_j^{(m)} \end{bmatrix} \begin{bmatrix} e^{(1)} \\ e^{(2)} \\ \vdots \\ e^{(m)} \end{bmatrix} = \frac{1}{m} (X[:,j])^T \cdot e∂θj∂J=m1[xj(1)xj(2)⋯xj(m)] e(1)e(2)⋮e(m) =m1(X[:,j])T⋅e
其中 X[:,j]X[:,j]X[:,j] 表示 XXX 的第 jjj 列。
梯度向量(所有偏导数的组合):
∇J(θ)=[∂J∂θ0∂J∂θ1⋮∂J∂θn]=1m[(X[:,0])Te(X[:,1])Te⋮(X[:,n])Te]\nabla J(\theta) = \begin{bmatrix} \frac{\partial J}{\partial \theta_0} \\ \frac{\partial J}{\partial \theta_1} \\ \vdots \\ \frac{\partial J}{\partial \theta_n} \end{bmatrix} = \frac{1}{m} \begin{bmatrix} (X[:,0])^T e \\ (X[:,1])^T e \\ \vdots \\ (X[:,n])^T e \end{bmatrix}∇J(θ)= ∂θ0∂J∂θ1∂J⋮∂θn∂J =m1 (X[:,0])Te(X[:,1])Te⋮(X[:,n])Te
这个结构正好等于 XXX 的转置与误差向量 eee 的乘积:
∇J(θ)=1mXTe=1mXT(Xθ−y)\nabla J(\theta) = \frac{1}{m} X^T e = \frac{1}{m} X^T (X\theta - y)∇J(θ)=m1XTe=m1XT(Xθ−y)
验证维度:
XT 的维度:(n+1)×m,e 的维度:m×1X^T \text{ 的维度}: (n+1) \times m, \quad e \text{ 的维度}: m \times 1XT 的维度:(n+1)×m,e 的维度:m×1
XTe 的维度:(n+1)×1(与 θ 同维度,正确)X^T e \text{ 的维度}: (n+1) \times 1 \quad \text{(与 } \theta \text{ 同维度,正确)}XTe 的维度:(n+1)×1(与 θ 同维度,正确)
向量形式:
∇J(θ)=1mXT(Xθ−y)\nabla J(\theta) = \frac{1}{m} X^T (X\theta - y)∇J(θ)=m1XT(Xθ−y)
3. 参数更新规则
批量梯度下降(Batch Gradient Descent):
θj:=θj−α∂J(θ)∂θj\theta_j := \theta_j - \alpha \frac{\partial J(\theta)}{\partial \theta_j}θj:=θj−α∂θj∂J(θ)
向量形式更新:
θ:=θ−α⋅1mXT(Xθ−y)\theta := \theta - \alpha \cdot \frac{1}{m} X^T (X\theta - y)θ:=θ−α⋅m1XT(Xθ−y)
其中 α\alphaα 是学习率(learning rate)。
4. 算法流程
输入:训练数据 X, y,学习率 α,迭代次数或收敛阈值 ε
1. 初始化参数 θ(通常为0或随机小值)
2. 设定学习率 α 和迭代次数或收敛条件
3. 循环迭代:
a. 计算预测值 h = Xθ
b. 计算误差 error = h - y
c. 计算梯度 gradient = (1/m) X^T error
d. 更新参数 θ = θ - α * gradient
e. 计算当前损失 J(θ)
f. 检查收敛(如 |J_new - J_old| < ε)
4. 返回最终参数 θ
5. Python 实现
基础实现
python
import numpy as np
def gradient_descent(X, y, alpha=0.01, iterations=1000):
"""
批量梯度下降求解线性回归
参数:
- X: 特征矩阵 (m, n),包含偏置项列
- y: 目标值向量 (m,)
- alpha: 学习率
- iterations: 迭代次数
返回:
- theta: 参数向量 (n,)
- loss_history: 损失历史记录
"""
m, n = X.shape
theta = np.zeros(n)
loss_history = []
for i in range(iterations):
# 计算预测值
h = X.dot(theta)
# 计算误差
error = h - y
# 计算损失
loss = (1/(2*m)) * np.sum(error**2)
loss_history.append(loss)
# 计算梯度
gradient = (1/m) * X.T.dot(error)
# 更新参数
theta = theta - alpha * gradient
return theta, loss_history
带收敛检测的实现
python
def gradient_descent_with_convergence(X, y, alpha=0.01, max_iterations=10000, epsilon=1e-6):
"""
带收敛检测的梯度下降
参数:
- epsilon: 收敛阈值,当损失变化小于此值时停止
"""
m, n = X.shape
theta = np.zeros(n)
prev_loss = float('inf')
for i in range(max_iterations):
h = X.dot(theta)
error = h - y
loss = (1/(2*m)) * np.sum(error**2)
# 检查收敛
if abs(prev_loss - loss) < epsilon:
print(f"在第 {i} 次迭代收敛")
break
prev_loss = loss
gradient = (1/m) * X.T.dot(error)
theta = theta - alpha * gradient
return theta, loss, i
使用示例
python
# 生成示例数据
np.random.seed(42)
m = 100 # 样本数
n = 2 # 特征数(包含偏置)
X = np.random.randn(m, n-1)
X = np.column_stack([np.ones(m), X]) # 添加偏置列
y = 2 + 3 * X[:, 1] + np.random.randn(m) * 0.5 # 真实参数: θ0=2, θ1=3
# 运行梯度下降
theta, loss_history = gradient_descent(X, y, alpha=0.1, iterations=1000)
print(f"估计参数: θ0={theta[0]:.4f}, θ1={theta[1]:.4f}")
print(f"真实参数: θ0=2, θ1=3")
6. 梯度下降变体
| 变体 | 特点 | 适用场景 |
|---|---|---|
| 批量梯度下降 (BGD) | 每次用全部数据计算梯度 | 小数据集,精确收敛 |
| 随机梯度下降 (SGD) | 每次用单个样本更新 | 大数据集,速度快但噪声大 |
| 小批量梯度下降 (Mini-batch) | 每次用一小批数据 | 最常用,平衡效率和稳定性 |
小批量梯度下降实现
python
def mini_batch_gradient_descent(X, y, alpha=0.01, batch_size=32, iterations=1000):
m, n = X.shape
theta = np.zeros(n)
for i in range(iterations):
# 随机选择小批量
indices = np.random.choice(m, batch_size, replace=False)
X_batch = X[indices]
y_batch = y[indices]
# 计算梯度并更新
h = X_batch.dot(theta)
error = h - y_batch
gradient = (1/batch_size) * X_batch.T.dot(error)
theta = theta - alpha * gradient
return theta
7. 关键注意事项
学习率选择
- 太小:收敛速度慢,需要大量迭代
- 太大:可能跳过最优解,甚至发散
- 建议:从较小值开始(如0.01),观察损失变化,逐步调整
特征缩放
建议对特征进行标准化,使各特征在相近范围内:
xj(i)=xj(i)−μjσjx_j^{(i)} = \frac{x_j^{(i)} - \mu_j}{\sigma_j}xj(i)=σjxj(i)−μj
这能让损失函数的等高线更圆,梯度下降收敛更快。
收敛判断
常用方法:
- 损失变化小于阈值:∣Jnew−Jold∣<ϵ|J_{new} - J_{old}| < \epsilon∣Jnew−Jold∣<ϵ
- 梯度接近零:∣∣∇J(θ)∣∣<ϵ||\nabla J(\theta)|| < \epsilon∣∣∇J(θ)∣∣<ϵ
- 参数变化小:∣∣θnew−θold∣∣<ϵ||\theta_{new} - \theta_{old}|| < \epsilon∣∣θnew−θold∣∣<ϵ
8. 数学推导细节
损失函数偏导数推导
对于单个参数 θj\theta_jθj:
∂J∂θj=∂∂θj12m∑i=1m(hθ(x(i))−y(i))2\frac{\partial J}{\partial \theta_j} = \frac{\partial}{\partial \theta_j} \frac{1}{2m} \sum_{i=1}^{m} (h_\theta(x^{(i)}) - y^{(i)})^2∂θj∂J=∂θj∂2m1i=1∑m(hθ(x(i))−y(i))2
=12m∑i=1m2(hθ(x(i))−y(i))⋅∂hθ(x(i))∂θj= \frac{1}{2m} \sum_{i=1}^{m} 2(h_\theta(x^{(i)}) - y^{(i)}) \cdot \frac{\partial h_\theta(x^{(i)})}{\partial \theta_j}=2m1i=1∑m2(hθ(x(i))−y(i))⋅∂θj∂hθ(x(i))
由于 hθ(x(i))=∑k=0nθkxk(i)h_\theta(x^{(i)}) = \sum_{k=0}^{n} \theta_k x_k^{(i)}hθ(x(i))=∑k=0nθkxk(i),则:
∂hθ(x(i))∂θj=xj(i)\frac{\partial h_\theta(x^{(i)})}{\partial \theta_j} = x_j^{(i)}∂θj∂hθ(x(i))=xj(i)
因此:
∂J∂θj=1m∑i=1m(hθ(x(i))−y(i))⋅xj(i)\frac{\partial J}{\partial \theta_j} = \frac{1}{m} \sum_{i=1}^{m} (h_\theta(x^{(i)}) - y^{(i)}) \cdot x_j^{(i)}∂θj∂J=m1i=1∑m(hθ(x(i))−y(i))⋅xj(i)
9. 与正规方程对比
| 方法 | 梯度下降 | 正规方程 |
|---|---|---|
| 公式 | θ:=θ−α∇J\theta := \theta - \alpha \nabla Jθ:=θ−α∇J | θ=(XTX)−1XTy\theta = (X^TX)^{-1}X^Tyθ=(XTX)−1XTy |
| 复杂度 | O(kmn)O(kmn)O(kmn) 每次迭代 | O(n3)O(n^3)O(n3) |
| 适用性 | 大特征数 (n>104)(n > 10^4)(n>104) | 小特征数 (n<104)(n < 10^4)(n<104) |
| 迭代 | 要多次迭代 | 直接求解 |
| 特征缩放 | 需要 | 不需要 |
正规方程时间复杂度详解
正规方程的时间复杂度是 O(n³),不是 O(n)。这是因为:
θ=(XTX)−1XTy\theta = (X^T X)^{-1} X^T yθ=(XTX)−1XTy
计算步骤分解:
| 步骤 | 操作 | 复杂度 | 说明 |
|---|---|---|---|
| 1 | 计算 XTXX^T XXTX | O(mn2)O(mn^2)O(mn2) | 矩阵乘法:n×mn \times mn×m 与 m×nm \times nm×n |
| 2 | 求逆 (XTX)−1(X^T X)^{-1}(XTX)−1 | O(n3)O(n^3)O(n3) | ← 最耗时步骤 |
| 3 | 计算 XTyX^T yXTy | O(mn)O(mn)O(mn) | 矩阵向量乘法 |
| 4 | 矩阵相乘 | O(n2)O(n^2)O(n2) | 得到最终结果 |
瓶颈是矩阵求逆 :对 n×nn \times nn×n 矩阵求逆需要 O(n3)O(n^3)O(n3),这是数学上的上限。
为什么不是 O(n)?
O(n) 意味着线性增长,但矩阵求逆无法做到这个速度。实际上:
- 求逆 ≈ 解 n 个线性方程组
- 每个 n×nn \times nn×n 系统求解是 O(n2)O(n^2)O(n2)(如 LU 分解)
- 做 n 次就是 O(n3)O(n^3)O(n3)
实际中的替代方法
实践中不直接求逆,用更稳定的方法:
| 方法 | 复杂度 | 说明 |
|---|---|---|
| Cholesky 分解 | O(n3)O(n^3)O(n3) | 正定矩阵专用,更稳定 |
| QR 分解 | O(mn2)O(mn^2)O(mn2) | 不需要显式求逆 |
| SVD 分解 | O(mn2+n3)O(mn^2 + n^3)O(mn2+n3) | 处理奇异矩阵,最稳健 |
虽然计算更稳定,但复杂度级别仍是 n 的立方。
梯度下降时间复杂度详解
每次迭代的计算:
| 步骤 | 操作 | 复杂度 |
|---|---|---|
| 1 | 计算 XθX\thetaXθ | O(mn)O(mn)O(mn) |
| 2 | 计算 XT(Xθ−y)X^T(X\theta - y)XT(Xθ−y) | O(mn)O(mn)O(mn) |
| 合计 | 每次迭代 | O(mn)O(mn)O(mn) |
若迭代 k 次,总复杂度为 O(kmn)O(kmn)O(kmn)。
当特征数 n 很大时(如 n>104n > 10^4n>104),正规方程的 O(n3)O(n^3)O(n3) 会非常慢,梯度下降更适合。