机器学习入门:用 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()
绘制散点图,显示结果如下:

1.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 拟合直线与数据散点图
下图展示了原始数据点与训练得到的线性回归模型。模型很好地捕捉了人口与利润之间的正向趋势。

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

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
数学原理:
参数 :
- alpha : 学习率(步长)
- iters : 迭代次数
第 2-4 行 : 初始化
- temp : 使用临时变量
temp存储新参数,避免更新过程中相互干扰 - parameters : 参数个数(2 个)
- cost : 记录每次迭代的代价
第 6 行 : 外层循环 - 迭代 iters 次
第 7 行 : 计算所有样本的误差
- 以向量形式,一次性计算所有样本的预测误差
第 9-11 行: 内层循环梯度计算 - 更新每个 \(\theta_j\) (对每个参数 j,term = 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 的数组,记录每次迭代后的代价
最终假设函数:
计算最终训练后的代价
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 次已充分。
四、总结
通过本实验,我们完整实现了单变量线性回归的梯度下降算法:
- 数据加载与探索:Pandas 读取数据,散点图确认线性关系。
- 代价函数:向量化计算均方误差。
- 批量梯度下降:循环迭代,同步更新参数,记录代价历史。
- 结果分析:最终参数、预测值、拟合直线及代价收敛曲线。
- 关键接口:掌握 Pandas 数据处理、NumPy 矩阵运算、超参数调优。
梯度下降是机器学习优化的基石,掌握本实验将为日后其他学习打下坚实基础。
注1:为什么需要
theta.T?因为 theta 是 1×2,需要转置为 2×1 才能与 X 的 2 列进行矩阵乘法。详见 NumPy 基础
注2:为什么除以 2m 而不是 m?方便梯度求导时抵消系数,使用 m 也不影响最终结果。
附:完整代码及数据可从 gitee 仓库 获取。
📢 声明:本文借助AI辅助工具进行资料整理与初稿生成,所有内容均经过作者本人的详细核对、修改与编排,文责自负。

浙公网安备 33010602011771号