机器学习入门:用 Python 实现梯度下降线性回归

本文基于经典的 单变量线性回归 练习 - 吴恩达 Machine Learning 课程 ex1 作业,使用 Python 完整实现梯度下降算法。项目完整代码及数据可从 gitee 仓库获取,文章末尾附链接。实验数据来自 ex1data1.txt,包含 城市人口 与 食品货车利润 两列,目标是利用人口预测利润。本文将依据本实验逐步讲解数据处理、代价函数定义、梯度下降优化、结果可视化及预测应用。

机器学习线性回归实验:基于真实数据的梯度下降实践

项目假设您是一个运输食物的货车公司的 CEO,现在需要扩张一家线下实体店。根据公司已经有的实体店,来决定在哪个城市开设新的店。 数据 ex1data1.txt :第一列是城市的人口规模,第二列是该城市每个食物货车的利润。


一、执行结果

1.1 数据概览

1.1.1 data.head()

前五行数据读取,显示结果如下:

Population Profit
0 6.1101 17.5920
1 5.5277 9.1302
2 8.5186 13.6620
3 7.0032 11.8540
4 5.8598 6.8233

1.1.2 data.describe()

数据集共 97 个样本,人口(Population)和利润(Profit)的统计信息如下:

Population Profit
count 97.000000 97.000000
mean 8.159800 5.839135
std 3.869884 5.510262
min 5.026900 -2.680700
25% 5.707700 1.986900
50% 6.589400 4.562300
75% 8.578100 7.046700
max 22.203000 24.147000

1.1.3 plt.show()

绘制散点图,显示结果如下:

image

1.2 代价函数初始值

根据创建的以参数 \(θ\) 为特征函数的代价函数

\[J\left( \theta  \right)=\frac{1}{2m}\sum\limits_{i=1}^{m}{{{\left( {{h}_{\theta }}\left( {{x}^{(i)}} \right)-{{y}^{(i)}} \right)}^{2}}} \]

其中:\({{h}_{\theta }}\left( x \right)={{\theta }^{T}}X={{\theta }_{0}}+{{\theta }_{1}}{{x}}\)

在初始参数 θ = [0, 0] 下,代价函数值 J(θ) 为:

32.072733877455676

1.3 梯度下降结果

  • 学习率 α = 0.01
  • 迭代次数 iters = 1000
  • 最终参数
    θ₀ (截距) = -3.24140214
    θ₁ (斜率) =  1.1272942
    
  • 最终代价
    4.515955503078914
    

1.4 拟合直线与数据散点图

下图展示了原始数据点与训练得到的线性回归模型。模型很好地捕捉了人口与利润之间的正向趋势。

image

1.5 代价下降曲线

随着迭代次数增加,代价函数 J(θ) 从 32.07 平稳下降至约 4.52,曲线呈典型凸函数收敛形态,证明梯度下降工作正常。

image

1.6 预测示例

  • 人口为 35,000(即 3.5 万)时,预测利润:
    Profit = -3.2414 + 1.1273 × 3.5 ≈ 0.7042(万美元)= 7042 美元
    
  • 人口为 70,000(7.0 万)时,预测利润:
    Profit = -3.2414 + 1.1273 × 7.0 ≈ 4.6497(万美元)= 46497 美元
    

二、代码执行过程详解

2.1 导入所需库

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
  • NumPy:科学计算,矩阵运算
  • Pandas:数据读取与处理
  • Matplotlib:数据可视化

2.2 加载并查看数据

path = 'ex1data1.txt'
data = pd.read_csv(path, header=None, names=['Population', 'Profit'])
data.head()
  • header=None:原始数据无列名
  • names=[...]:手动指定列名
  • head() 查看前 5 行,确认数据加载正确。

2.3 数据探索

data.describe()

快速获取数据分布(均值、标准差、分位数等),判断是否需要归一化(本数据集量纲一致,无需归一化)。

2.4 数据可视化

data.plot(kind='scatter', x='Population', y='Profit', figsize=(12,8))
plt.show()

绘制散点图,直观确认人口与利润存在线性正相关趋势。

2.5 定义代价函数

def computeCost(X, y, theta):
    inner = np.power(((X * theta.T) - y), 2)
    return np.sum(inner) / (2 * len(X))
  • 输入:特征矩阵 X(m×n),目标向量 y(m×1),参数向量 theta(1×n) (见注1)
  • 计算:预测值 h = X·θᵀ,误差平方和,除以 2m (见注2)
  • 输出:标量代价

2.6 准备特征矩阵与目标变量

data.insert(0, 'Ones', 1)          # 添加截距列(全1)
cols = data.shape[1]
X = data.iloc[:, 0:cols-1]         # 特征:Ones, Population
y = data.iloc[:, cols-1:cols]      # 目标:Profit
  • (1) 插入全 1 列用于向量化计算截距项 θ₀

    • 在数据第 0 列插入值为 1 的列
    • 目的:使假设函数 \({{h}_{\theta }}\left( x \right)={{\theta }_{0}}+{{\theta }_{1}}{{x}}\) 可以写成向量形式 \({{\theta }^{T}}X\) , 这样 \({{\theta }_{0}}\) ​ 就对应截距项
  • (2) iloc 按位置切片,得到特征矩阵 X(97 行 ×2 列)和 y(97 行 ×1 列)

    • cols 获取列数(3 列)
    • X : 取前两列(Ones, Population)作为特征
    • y : 取最后一列(Profit)作为目标

2.7 转换为 NumPy 矩阵并初始化参数

X = np.matrix(X.values)
y = np.matrix(y.values)
theta = np.matrix(np.array([0, 0]))
  • 将 pandas DataFrame 转换为 NumPy 的 matrix 类型以便进行矩阵运算
  • 初始参数为 [0, 0]

2.8 验证维度

X.shape, theta.shape, y.shape
# 输出:((97, 2), (1, 2), (97, 1))
  • X.shape : (97, 2) - 97 个样本,2 个特征
  • theta.shape : (1, 2) - 共 1 行,2 个参数
  • y.shape : (97, 1) - 97 个目标值

确保矩阵维度可进行 X * theta.T(得到 97×1 预测值)。

2.9 初始代价计算

computeCost(X, y, theta)   # 32.072733877455676
  • \(θ = [ 0 , 0 ]\) 时,计算初始代价
  • 结果:32.07(误差很大,因为还没训练)

2.10 批量梯度下降实现 ⭐ 核心

def gradientDescent(X, y, theta, alpha, iters):
    temp = np.matrix(np.zeros(theta.shape))
    parameters = int(theta.ravel().shape[1])
    cost = np.zeros(iters)
    
    for i in range(iters):
        error = (X * theta.T) - y
        
        for j in range(parameters):
            term = np.multiply(error, X[:,j])
            temp[0,j] = theta[0,j] - ((alpha / len(X)) * np.sum(term))
        
        theta = temp
        cost[i] = computeCost(X, y, theta)
        
    return theta, cost

数学原理

\[\theta_j := \theta_j - \alpha \frac{\partial}{\partial \theta_j} J(\theta) \]

\[\frac{\partial}{\partial \theta_j} J(\theta) = \frac{1}{m}\sum_{i=1}^{m}(h_\theta(x^{(i)}) - y^{(i)})x_j^{(i)} \]

参数 :

  • alpha : 学习率(步长)
  • iters : 迭代次数

第 2-4 行 : 初始化

  • temp : 使用临时变量 temp 存储新参数,避免更新过程中相互干扰
  • parameters : 参数个数(2 个)
  • cost : 记录每次迭代的代价

第 6 行 : 外层循环 - 迭代 iters 次

第 7 行 : 计算所有样本的误差

  • 以向量形式,一次性计算所有样本的预测误差

第 9-11 行: 内层循环梯度计算 - 更新每个 \(\theta_j\) (对每个参数 jterm = error * X[:,j],求和后乘以 α/m

  • np.multiply(error, X[:,j]): 误差乘以第 \(j\) 个特征
  • np.sum(term): 求和
  • (alpha / len(X)) * np.sum(term): 计算梯度 \(\alpha \cdot \frac{1}{m}\sum\)
  • 更新公式:\(\theta_j = \theta_j - \text{梯度}\)

第 13 行: 同时更新所有 \(\theta\)(批量梯度下降)

第 14 行 : 记录当前代价

  • 每次迭代后计算并存储代价,用于绘制收敛曲线

2.11 设置超参数并运行

alpha = 0.01
iters = 1000
g, cost = gradientDescent(X, y, theta, alpha, iters)
  • 学习率 \(α = 0.01\) : 控制每次更新的步长
  • 迭代次数 = 1000: 训练 1000 轮
  • g 为训练后的参数矩阵 ,最终训练结果为:[[-3.24140214, 1.1272942]]
  • cost 为长度为 1000 的数组,记录每次迭代后的代价

最终假设函数

\[\text{Profit} = -3.24 + 1.13 \times \text{Population} \]

计算最终训练后的代价

computeCost(X, y, g)
  • 结果:4.52(相比初始的 32.07 大幅下降)
  • 说明模型拟合效果很好

2.12 绘制拟合直线

x = np.linspace(data.Population.min(), data.Population.max(), 100)
f = g[0, 0] + (g[0, 1] * x)

fig, ax = plt.subplots(figsize=(12,8))
ax.plot(x, f, 'r', label='Prediction')
ax.scatter(data.Population, data.Profit, label='Training Data')
ax.legend(loc=2)
plt.show()
  • 生成 100 个等间距的 x 值(人口范围)- linspace 生成连续的 x 值
  • 用训练好的参数计算预测值 \(f = \theta_0 + \theta_1 \times x\)
  • 绘制散点图和拟合直线

2.13 绘制代价收敛曲线

fig, ax = plt.subplots(figsize=(12,8))
ax.plot(np.arange(iters), cost, 'r')
ax.set_xlabel('Iterations')
ax.set_ylabel('Cost')
plt.show()
  • 横轴:迭代次数 - np.arange(iters) 生成迭代次数序列
  • 纵轴:代价值
  • 可以看到代价随迭代逐渐下降,最终收敛;曲线单调递减,最终趋于平缓

三、关键接口与参数详解

3.1 Pandas 接口

接口 作用 示例
pd.read_csv() 读取 CSV 文件 pd.read_csv(path, header=None, names=[...])
data.insert(loc, col, value) 在指定位置插入列 data.insert(0, 'Ones', 1)
df.iloc[rows, cols] 按位置索引 X = data.iloc[:, 0:cols-1]

3.2 NumPy 核心操作

接口 作用 示例
np.matrix(data) 转换为矩阵类型 np.matrix(X.values)
X * theta.T 矩阵乘法(需维度匹配) 计算预测值 h
np.power(arr, 2) 逐元素平方 计算误差平方
np.sum(arr) 求和 误差平方和
np.zeros(shape) 创建全零数组 初始化临时参数

3.3 梯度下降函数参数

参数 含义 本实验取值
X 特征矩阵(含截距列) shape (97,2)
y 目标向量 shape (97,1)
theta 初始参数 [0, 0]
alpha 学习率 0.01
iters 迭代次数 1000
  • 学习率选择:可通过观察代价曲线(若震荡则减小,若收敛太慢则增大),选择恰当的学习率。常用范围 0.001 ~ 0.1。
  • 迭代次数:需足够使代价曲线平坦,本实验 1000 次已充分。

四、总结

通过本实验,我们完整实现了单变量线性回归的梯度下降算法:

  1. 数据加载与探索:Pandas 读取数据,散点图确认线性关系。
  2. 代价函数:向量化计算均方误差。
  3. 批量梯度下降:循环迭代,同步更新参数,记录代价历史。
  4. 结果分析:最终参数、预测值、拟合直线及代价收敛曲线。
  5. 关键接口:掌握 Pandas 数据处理、NumPy 矩阵运算、超参数调优。

梯度下降是机器学习优化的基石,掌握本实验将为日后其他学习打下坚实基础。


注1:为什么需要 theta.T?因为 theta 是 1×2,需要转置为 2×1 才能与 X 的 2 列进行矩阵乘法。详见 NumPy 基础
注2:为什么除以 2m 而不是 m?方便梯度求导时抵消系数,使用 m 也不影响最终结果。

附:完整代码及数据可从 gitee 仓库 获取。

📢 声明:本文借助AI辅助工具进行资料整理与初稿生成,所有内容均经过作者本人的详细核对、修改与编排,文责自负。

posted @ 2026-04-13 17:21  Lyn_Li  阅读(9)  评论(0)    收藏  举报