线性回归梯度下降 Python 3.11 实现:从零推导 3 个核心函数到完整训练循环

在机器学习的入门阶段,线性回归往往是我们接触的第一个算法。它不仅简单直观,而且包含了机器学习中许多核心概念。本文将带你从零开始,用 Python 3.11 实现线性回归的完整训练流程,重点讲解代价函数、梯度计算和参数更新这三个核心函数的数学原理与代码实现,最终构建一个包含数据生成、训练和可视化的完整可运行脚本。

1. 线性回归与梯度下降基础

线性回归是一种用于建立输入变量(特征)与输出变量(目标)之间线性关系的统计方法。给定一组训练数据 {(x⁽ⁱ⁾, y⁽ⁱ⁾)}, i=1,...,m,我们希望找到一个线性函数:

ŷ = wx + b

使得预测值 ŷ 尽可能接近真实值 y。这里的 w 是权重(斜率),b 是偏置(截距)。

梯度下降是一种优化算法,用于寻找使代价函数最小化的参数值。其核心思想是:

  1. 初始化参数 w 和 b
  2. 计算代价函数对参数的梯度
  3. 沿梯度反方向更新参数
  4. 重复步骤2-3直到收敛

提示:学习率(alpha)是梯度下降中的重要超参数,控制每次更新的步长。过大会导致震荡甚至发散,过小则收敛缓慢。

2. 核心函数实现

2.1 代价函数计算

代价函数(损失函数)用于衡量模型预测值与真实值之间的差异。对于线性回归,我们通常使用均方误差(MSE):

def compute_cost(x, y, w, b):
    """
    计算线性回归的代价函数(均方误差)
    参数:
        x: 输入特征 (m,)
        y: 真实标签 (m,)
        w: 权重
        b: 偏置
    返回:
        total_cost: 计算得到的代价
    """
    m = x.shape[0]  # 样本数量
    cost_sum = 0
    
    for i in range(m):
        f_wb = w * x[i] + b  # 预测值
        cost = (f_wb - y[i]) ** 2  # 单个样本的平方误差
        cost_sum += cost
    
    total_cost = (1 / (2 * m)) * cost_sum  # 平均代价
    return total_cost

数学解释:

  • 计算每个样本的预测值 f_wb = wx⁽ⁱ⁾ + b
  • 计算预测值与真实值的平方误差 (f_wb - y⁽ⁱ⁾)²
  • 对所有样本的误差求平均 (1/2m)Σ(f_wb - y⁽ⁱ⁾)²

2.2 梯度计算

梯度计算是梯度下降的核心,我们需要计算代价函数对 w 和 b 的偏导数:

def compute_gradient(x, y, w, b):
    """
    计算线性回归的梯度
    参数:
        x: 输入特征 (m,)
        y: 真实标签 (m,)
        w: 当前权重
        b: 当前偏置
    返回:
        dj_dw: 代价函数对w的梯度
        dj_db: 代价函数对b的梯度
    """
    m = x.shape[0]
    dj_dw = 0
    dj_db = 0
    
    for i in range(m):
        f_wb = w * x[i] + b
        dj_dw_i = (f_wb - y[i]) * x[i]  # 对w的偏导
        dj_db_i = f_wb - y[i]  # 对b的偏导
        dj_db += dj_db_i
        dj_dw += dj_dw_i
    
    dj_dw = dj_dw / m  # 平均梯度
    dj_db = dj_db / m
    return dj_dw, dj_db

数学推导:

  • ∂J/∂w = (1/m)Σ(f_wb - y⁽ⁱ⁾)x⁽ⁱ⁾
  • ∂J/∂b = (1/m)Σ(f_wb - y⁽ⁱ⁾)

2.3 参数更新

有了梯度后,我们可以按照以下规则更新参数:

def gradient_descent(x, y, w_init, b_init, alpha, iters, compute_cost, compute_gradient):
    """
    执行梯度下降算法
    参数:
        x: 输入特征 (m,)
        y: 真实标签 (m,)
        w_init: 初始权重
        b_init: 初始偏置
        alpha: 学习率
        iters: 迭代次数
        compute_cost: 代价函数
        compute_gradient: 梯度计算函数
    返回:
        w: 学习到的权重
        b: 学习到的偏置
        cost_history: 每次迭代的代价记录
    """
    w = w_init
    b = b_init
    cost_history = []
    
    for i in range(iters):
        dj_dw, dj_db = compute_gradient(x, y, w, b)
        
        # 更新参数
        w = w - alpha * dj_dw
        b = b - alpha * dj_db
        
        # 记录代价(可选,用于调试)
        if i < 100000:  # 防止内存溢出
            cost = compute_cost(x, y, w, b)
            cost_history.append(cost)
        
        # 每100次迭代打印进度
        if i % 100 == 0:
            print(f"Iteration {i:4}: Cost {cost_history[-1]:8.2f}")
    
    return w, b, cost_history

更新规则:

  • w = w - α(∂J/∂w)
  • b = b - α(∂J/∂b)

3. 完整训练流程实现

现在我们将上述函数整合成一个完整的训练流程,包括数据生成、训练和可视化:

import numpy as np
import matplotlib.pyplot as plt

# 1. 生成模拟数据
np.random.seed(42)
m = 100  # 样本数量
x = 2 * np.random.rand(m)
y = 4 + 3 * x + np.random.randn(m)  # 真实关系: y = 4 + 3x + 噪声

# 2. 初始化参数
w_init = 0
b_init = 0
alpha = 0.1  # 学习率
iters = 1000  # 迭代次数

# 3. 运行梯度下降
w_final, b_final, cost_history = gradient_descent(
    x, y, w_init, b_init, alpha, iters, compute_cost, compute_gradient
)

print(f"最终参数: w = {w_final:.4f}, b = {b_final:.4f}")

# 4. 可视化结果
plt.figure(figsize=(12, 4))

# 4.1 数据与拟合直线
plt.subplot(1, 2, 1)
plt.scatter(x, y, label='训练数据')
plt.plot(x, w_final * x + b_final, 'r-', label='拟合直线')
plt.xlabel('x')
plt.ylabel('y')
plt.title('线性回归拟合')
plt.legend()

# 4.2 代价函数变化
plt.subplot(1, 2, 2)
plt.plot(cost_history)
plt.xlabel('迭代次数')
plt.ylabel('代价')
plt.title('代价函数变化')
plt.tight_layout()
plt.show()

4. 学习率对收敛速度的影响实验

学习率是梯度下降中最重要的超参数之一。我们通过实验观察不同学习率对收敛速度的影响:

# 测试不同学习率
learning_rates = [0.01, 0.1, 0.5, 1.0]
colors = ['b', 'g', 'r', 'c']
plt.figure(figsize=(10, 6))

for alpha, color in zip(learning_rates, colors):
    _, _, cost_history = gradient_descent(
        x, y, w_init, b_init, alpha, iters, compute_cost, compute_gradient
    )
    plt.plot(cost_history, color, label=f"alpha = {alpha}")

plt.xlabel('迭代次数')
plt.ylabel('代价')
plt.title('不同学习率下的代价函数变化')
plt.legend()
plt.show()

实验结果分析:

  • alpha=0.01 :收敛缓慢,需要更多迭代才能达到最优
  • alpha=0.1 :收敛速度适中,是较好的选择
  • alpha=0.5 :初期收敛快,但后期可能出现震荡
  • alpha=1.0 :步长过大,导致代价函数发散

5. 性能优化与向量化实现

上述实现使用了显式循环,对于大规模数据效率较低。我们可以利用NumPy的向量化操作进行优化:

def compute_cost_vectorized(x, y, w, b):
    """
    向量化实现的代价函数计算
    """
    m = x.shape[0]
    f_wb = w * x + b  # 向量化预测
    cost = np.sum((f_wb - y) ** 2) / (2 * m)
    return cost

def compute_gradient_vectorized(x, y, w, b):
    """
    向量化实现的梯度计算
    """
    m = x.shape[0]
    f_wb = w * x + b  # 向量化预测
    dj_dw = np.dot(f_wb - y, x) / m  # 向量化梯度计算
    dj_db = np.sum(f_wb - y) / m
    return dj_dw, dj_db

向量化实现的优势:

  • 代码更简洁
  • 利用NumPy底层优化,运行速度更快
  • 适合处理大规模数据集

在实际项目中,我通常会先使用向量化实现快速验证算法可行性,再根据需要添加详细注释和数学解释。这种从简单到复杂的实现方式能有效提高开发效率。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐