逻辑回归学习笔记-梯度下降求解回归方程

梯度下降求解线性模型

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

系数正好抵消,公式更清爽。

注意:两种写法最终收敛到同一个最优解,因为:

  1. 梯度的方向是一样的(只是缩放了 2 倍)
  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

这能让损失函数的等高线更圆,梯度下降收敛更快。

收敛判断

常用方法:

  1. 损失变化小于阈值:∣Jnew−Jold∣<ϵ|J_{new} - J_{old}| < \epsilon∣Jnew−Jold∣<ϵ
  2. 梯度接近零:∣∣∇J(θ)∣∣<ϵ||\nabla J(\theta)|| < \epsilon∣∣∇J(θ)∣∣<ϵ
  3. 参数变化小:∣∣θ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) 会非常慢,梯度下降更适合。


相关推荐
人道领域1 小时前
【LeetCode刷题日记】1047:双栈法与双指针法巧妙消除相邻重复字符
java·算法·leetcode·职场和发展
切糕师学AI1 小时前
布隆过滤器(Bloom Filter)技术详解
数学·算法
礼拜天没时间.2 小时前
力扣热题100实战 | 第33期:搜索旋转排序数组——二分查找的变体艺术
算法·leetcode·职场和发展·旋转数组·搜索旋转排序数组
Jenlybein2 小时前
用 uv 替代 conda,速度飙升(从 0 到 1 开始使用 uv)
后端·python·算法
400分2 小时前
LangChain 与大模型技术全链路详解
算法·架构
电科一班林耿超2 小时前
第 14 课:动态规划(DP)—— 算法思想的巅峰,面试的终极分水岭
数据结构·算法·动态规划
lihao lihao2 小时前
Linux文件与fd
java·linux·算法
Navigator_Z2 小时前
LeetCode //C - 1026. Maximum Difference Between Node and Ancestor
c语言·算法·leetcode
We་ct2 小时前
LeetCode 63. 不同路径 II:动态规划解题详解
前端·算法·leetcode·typescript·动态规划