线性回归梯度下降 Python 3.11 实现:从零推导 3 个核心函数到完整训练循环
线性回归梯度下降 Python 3.11 实现:从零推导 3 个核心函数到完整训练循环
在机器学习的入门阶段,线性回归往往是我们接触的第一个算法。它不仅简单直观,而且包含了机器学习中许多核心概念。本文将带你从零开始,用 Python 3.11 实现线性回归的完整训练流程,重点讲解代价函数、梯度计算和参数更新这三个核心函数的数学原理与代码实现,最终构建一个包含数据生成、训练和可视化的完整可运行脚本。
1. 线性回归与梯度下降基础
线性回归是一种用于建立输入变量(特征)与输出变量(目标)之间线性关系的统计方法。给定一组训练数据 {(x⁽ⁱ⁾, y⁽ⁱ⁾)}, i=1,...,m,我们希望找到一个线性函数:
ŷ = wx + b
使得预测值 ŷ 尽可能接近真实值 y。这里的 w 是权重(斜率),b 是偏置(截距)。
梯度下降是一种优化算法,用于寻找使代价函数最小化的参数值。其核心思想是:
- 初始化参数 w 和 b
- 计算代价函数对参数的梯度
- 沿梯度反方向更新参数
- 重复步骤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底层优化,运行速度更快
- 适合处理大规模数据集
在实际项目中,我通常会先使用向量化实现快速验证算法可行性,再根据需要添加详细注释和数学解释。这种从简单到复杂的实现方式能有效提高开发效率。
更多推荐

所有评论(0)