基于正规方程的波士顿房价预测
实验报告:基于正规方程的波士顿房价预测
一、 实验目的
理解解析解原理:掌握正规方程的数学推导,理解如何通过矩阵运算直接求解线性回归的最优权重 。
掌握偏置项处理:学习在特征矩阵中手动注入全 1 列(Bias term)以统一矩阵运算 。
算法效率对比:通过计时监控,观察正规方程在小规模数据集上无需迭代、计算迅速的特性 。
二、 环境配置
平台: AI Studio (Python 3.10) 。
核心库:paddle (矩阵运算), pandas (数据解析), numpy (矩阵构建), sklearn (指标评估) 。
数据集:波士顿房价数据集 。
库安装说明
在 Notebook 环境中,必须在命令前加上感叹号 !。这是因为单元格默认运行 Python 代码,而感叹号告诉系统将该行作为 Linux Shell 命令 执行 。
# --no-user 避免与系统默认路径冲突,-t 指定持久化安装目录
!python -m pip install -U pandas numpy matplotlib scikit-learn joblib
!pip install scikit-learn -t /home/aistudio/external-libraries --no-user
# 1. 创建目录
!mkdir -p data/datasets
# 2. 下载数据(这里以一个常见的波士顿房价链接为例,如果没有链接可以跳过)
!wget https://archive.ics.uci.edu/ml/machine-learning-databases/housing/housing.data -O data/datasets/house.csv
# 3. 检查文件
!ls -lh data/datasets/house.csv
!head -n 5 data/datasets/house.csv
#所有的命令字的原理
# =========================================
# 一、安装后续实验会用到的 Python 第三方库
# =========================================
# 这条命令的意思是:
# 用当前这个 Python 解释器去执行 pip 安装命令,
# 安装或升级 pandas、numpy、matplotlib、scikit-learn、joblib 这些常用库。
#
# 为什么不用 "!pip install ...",而写成 "!python -m pip install ..."?
# 因为这样可以确保:使用的是“当前 notebook 对应的 Python 环境”里的 pip,
# 不容易出现“明明装了库,但当前环境还是找不到”的问题。
#
# 参数说明:
# -U 表示 upgrade,即“如果已经安装了旧版本,就升级到新版本”
#
# 这些库分别是做什么的?
# pandas : 处理表格数据,比如 csv 文件
# numpy : 进行数值计算、矩阵运算
# matplotlib : 画图
# scikit-learn : 机器学习工具库,常用于数据预处理、评价指标等
# joblib : 常用于保存模型或做并行加速
!python -m pip install -U pandas numpy matplotlib scikit-learn joblib
# 这条命令继续安装 scikit-learn,
# 但这次是“指定安装到某个固定目录”中,而不是默认装到系统环境里。
#
# -t /home/aistudio/external-libraries
# 表示 target(目标路径),把库安装到这个目录下。
#
# --no-user
# 表示“不安装到用户默认目录中”。
#
# 为什么要这样设计?
# 因为在 AI Studio / Notebook 平台中,
# 有时默认环境是临时的、权限有限,或者不同环境之间容易冲突。
# 所以把库安装到一个你自己指定的目录中,更稳定、更方便管理。
#
# 简单理解:
# 第一条命令:先正常安装/升级常用库
# 第二条命令:把关键库额外安装到一个你能控制的位置,减少路径冲突问题
!pip install scikit-learn -t /home/aistudio/external-libraries --no-user
# =========================================
# 二、创建数据集存放目录
# =========================================
# mkdir 是 Linux 系统里“创建文件夹”的命令
# -p 的意思是:
# 1. 如果上一级目录不存在,就一起创建
# 2. 如果这个目录已经存在,也不会报错
#
# data/datasets 表示:
# 在当前工作目录下,创建一个 data 文件夹,
# 再在 data 文件夹里面创建一个 datasets 文件夹。
#
# 为什么要先创建目录?
# 因为后面下载的数据文件要有地方存放。
# 提前把目录结构整理好,代码会更清晰,也更方便后续管理数据。
!mkdir -p data/datasets
# =========================================
# 三、下载实验用数据
# =========================================
# wget 是 Linux 下常见的“下载文件”命令。
#
# 这条命令的作用:
# 从 UCI 数据集网站下载 housing.data 文件,
# 并把它保存成 data/datasets/house.csv
#
# 参数说明:
# -O 不是字母 o,而是大写字母 O
# 它表示:把下载下来的内容保存成指定文件名
#
# 为什么原文件叫 housing.data,保存时却改名为 house.csv?
# 因为后面我们通常会把它当作表格数据来读,
# 改成更容易理解的名字,自己看代码时更直观。
#
# 注意:
# 严格来说,这个文件原始格式并不是标准 csv,
# 只是为了方便管理,把它保存成了 .csv 这个名字。
# 后面读取时,通常还需要指定分隔方式。
#
# 如果你的实验平台已经提供了数据,或者没有网络,
# 这一步也可以跳过,直接使用本地已有数据。
!wget https://archive.ics.uci.edu/ml/machine-learning-databases/housing/housing.data -O data/datasets/house.csv
# =========================================
# 四、检查下载是否成功
# =========================================
# ls 是“列出文件”的命令
# -l 表示用更详细的格式显示文件信息
# -h 表示以更适合人阅读的方式显示文件大小
# 比如 KB、MB,而不是单纯显示很长的字节数
#
# 这条命令的作用:
# 查看刚刚下载的文件是否真的存在,
# 同时看一下文件大小是否正常。
#
# 为什么要检查?
# 因为有时候下载会失败,或者下载到一个空文件,
# 提前检查可以避免后面读数据时报错,却不知道问题出在哪里。
!ls -lh data/datasets/house.csv
# head 命令表示“查看文件前几行内容”
# -n 5 表示只看前 5 行
#
# 这条命令的作用:
# 快速预览数据文件内容,确认:
# 1. 文件是不是我们想要的数据
# 2. 数据格式大概是什么样
# 3. 后面该怎么用 pandas 去读取
#
# 为什么只看前 5 行?
# 因为数据文件可能很大,
# 没必要一开始全部打开,只看前几行就能大致了解结构。
!head -n 5 data/datasets/house.csv
三、 实验原理
3.1 正规方程 (Normal Equation)
对于线性回归模型,我们可以不通过梯度下降迭代,而是通过求导令导数为 0 直接得到闭式解:
θ=(XTX)−1XTy\theta = (X^T X)^{-1} X^T yθ=(XTX)−1XTy
下面是我按照**吴恩达课程里“从损失函数 → 求导 → 闭式解 → 与梯度下降对比”**的思路,为你整理的一份 Markdown 讲解版。
你可以直接复制到 notebook、实验报告或者 Obsidian 里使用。
线性回归的闭式解:为什么可以直接令导数为 0 求出参数?
1. 问题背景
在线性回归中,我们希望找到一组参数 θ\thetaθ,使得模型的预测值尽可能接近真实值。
线性回归模型可以写成:
y^=Xθ \hat{y} = X\theta y^=Xθ
其中:
- XXX:输入特征矩阵
- θ\thetaθ:模型参数
- y^\hat{y}y^:预测值
我们的目标是:
找到一个最优的 θ\thetaθ,让预测误差最小。
2. 线性回归的损失函数
在线性回归中,最常见的损失函数是 均方误差(Mean Squared Error, MSE)。
为了推导方便,通常写成下面这个形式:
J(θ)=12m(Xθ−y)T(Xθ−y) J(\theta) = \frac{1}{2m}(X\theta - y)^T (X\theta - y) J(θ)=2m1(Xθ−y)T(Xθ−y)
其中:
- mmm:样本数
- XθX\thetaXθ:所有样本的预测结果
- yyy:真实标签
- (Xθ−y)(X\theta - y)(Xθ−y):预测误差
3. 为什么要写成矩阵形式?
吴恩达课程里特别强调:
当样本很多、特征很多时,用矩阵表示会非常简洁。
如果逐个样本写:
J(θ)=12m∑i=1m(hθ(x(i))−y(i))2 J(\theta) = \frac{1}{2m}\sum_{i=1}^{m}(h_\theta(x^{(i)}) - y^{(i)})^2 J(θ)=2m1i=1∑m(hθ(x(i))−y(i))2
这是标量形式。
把所有样本一次性写出来,就变成:
J(θ)=12m(Xθ−y)T(Xθ−y) J(\theta) = \frac{1}{2m}(X\theta - y)^T(X\theta - y) J(θ)=2m1(Xθ−y)T(Xθ−y)
这样做的好处是:
- 写法更紧凑
- 便于推导
- 更适合程序实现
- 能直接得到闭式解
4. 什么叫闭式解?
闭式解(Closed-form Solution)指的是:
不需要一步一步迭代更新参数,
而是通过数学推导,直接写出最优参数的公式。
对于线性回归,闭式解是:
θ=(XTX)−1XTy \theta = (X^T X)^{-1}X^T y θ=(XTX)−1XTy
这也叫做:
- 正规方程(Normal Equation)
- 最小二乘解(Least Squares Solution)
5. 闭式解是怎么推出来的?
下面是核心推导过程。
6. 从损失函数开始
我们从这个目标函数出发:
J(θ)=12m(Xθ−y)T(Xθ−y) J(\theta) = \frac{1}{2m}(X\theta - y)^T(X\theta - y) J(θ)=2m1(Xθ−y)T(Xθ−y)
我们的目标是:
minθJ(θ) \min_\theta J(\theta) θminJ(θ)
也就是说,要找到让损失最小的 θ\thetaθ。
7. 为什么令导数等于 0?
这是高数里的基本思想:
如果一个函数在某个点取得极小值,并且这个点可导,那么在这个点的一阶导数通常为 0。
就像一元函数中:
f′(x)=0 f'(x)=0 f′(x)=0
可能对应极大值、极小值或驻点。
在线性回归中,损失函数 J(θ)J(\theta)J(θ) 是一个关于 θ\thetaθ 的凸二次函数,它像一个“碗”。
因此:
- 它只有一个全局最小值
- 导数为 0 的地方,就是最优解
所以我们只要做:
∇θJ(θ)=0 \nabla_\theta J(\theta)=0 ∇θJ(θ)=0
就可以求出最优参数。
8. 先把损失函数展开
原式:
J(θ)=12m(Xθ−y)T(Xθ−y) J(\theta) = \frac{1}{2m}(X\theta - y)^T(X\theta - y) J(θ)=2m1(Xθ−y)T(Xθ−y)
先展开:
J(θ)=12m(θTXTXθ−θTXTy−yTXθ+yTy) J(\theta) = \frac{1}{2m}\left( \theta^T X^T X \theta - \theta^T X^T y - y^T X \theta + y^T y \right) J(θ)=2m1(θTXTXθ−θTXTy−yTXθ+yTy)
注意这里:
- θTXTy\theta^T X^T yθTXTy 和 yTXθy^T X \thetayTXθ 都是标量
- 两者其实相等
所以可以进一步写成:
J(θ)=12m(θTXTXθ−2θTXTy+yTy) J(\theta) = \frac{1}{2m}\left( \theta^T X^T X \theta - 2\theta^T X^T y + y^T y \right) J(θ)=2m1(θTXTXθ−2θTXTy+yTy)
9. 对 θ\thetaθ 求导
现在对 J(θ)J(\theta)J(θ) 关于 θ\thetaθ 求梯度。
我们用几个常见矩阵求导公式:
(1)二次型求导
∂∂θ(θTAθ)=2Aθ \frac{\partial}{\partial \theta}(\theta^T A \theta)=2A\theta ∂θ∂(θTAθ)=2Aθ
当 AAA 对称时成立。
这里 XTXX^T XXTX 一定是对称矩阵,所以可以直接用。
(2)线性项求导
∂∂θ(bTθ)=b \frac{\partial}{\partial \theta}(b^T\theta)=b ∂θ∂(bTθ)=b
因此:
∂∂θ(θTXTXθ)=2XTXθ \frac{\partial}{\partial \theta}(\theta^T X^T X \theta)=2X^T X\theta ∂θ∂(θTXTXθ)=2XTXθ
∂∂θ(2θTXTy)=2XTy \frac{\partial}{\partial \theta}(2\theta^T X^T y)=2X^T y ∂θ∂(2θTXTy)=2XTy
而 yTyy^T yyTy 与 θ\thetaθ 无关,导数为 0。
于是:
∇θJ(θ)=12m(2XTXθ−2XTy) \nabla_\theta J(\theta) = \frac{1}{2m}\left(2X^T X\theta - 2X^T y\right) ∇θJ(θ)=2m1(2XTXθ−2XTy)
化简得:
∇θJ(θ)=1m(XTXθ−XTy) \nabla_\theta J(\theta) = \frac{1}{m}(X^T X\theta - X^T y) ∇θJ(θ)=m1(XTXθ−XTy)
10. 令梯度等于 0
根据最小值条件:
∇θJ(θ)=0 \nabla_\theta J(\theta)=0 ∇θJ(θ)=0
所以:
1m(XTXθ−XTy)=0 \frac{1}{m}(X^T X\theta - X^T y)=0 m1(XTXθ−XTy)=0
两边乘以 mmm:
XTXθ−XTy=0 X^T X\theta - X^T y=0 XTXθ−XTy=0
移项:
XTXθ=XTy X^T X\theta = X^T y XTXθ=XTy
如果 XTXX^T XXTX 可逆,那么左右两边同时乘以 (XTX)−1(X^T X)^{-1}(XTX)−1:
θ=(XTX)−1XTy \theta = (X^T X)^{-1}X^T y θ=(XTX)−1XTy
这就是线性回归的闭式解。
11. 这个公式到底表示什么?
这个公式的含义是:
在所有可能的参数 θ\thetaθ 中,
这个解能让平方误差和最小。
也就是说,它找到了一个最合适的超平面,使得:
∣Xθ−y∣2 |X\theta - y|^2 ∣Xθ−y∣2
尽可能小。
这就是最小二乘法的本质。
12. 从几何角度理解
吴恩达课程通常更偏工程直觉。
这里可以加一个几何理解帮助你真正吃透。
我们想求:
Xθ≈y X\theta \approx y Xθ≈y
但很多时候,yyy 不一定恰好能被写成 XθX\thetaXθ。
所以我们其实是在找一个 XθX\thetaXθ,使它成为 yyy 在 XXX 的列空间上的最佳逼近。
换句话说:
- XθX\thetaXθ 在 XXX 的列空间里
- 我们希望它离 yyy 最近
- 这个“最近”就是误差平方和最小
因此,闭式解本质上是一个投影问题。
13. 为什么它叫正规方程?
因为在推导中我们得到了:
XTXθ=XTy X^T X\theta = X^T y XTXθ=XTy
这就是所谓的 Normal Equation。
这里的 “normal” 不是“普通”的意思,而是“正交”的意思。
因为最优解满足:
XT(Xθ−y)=0 X^T(X\theta - y)=0 XT(Xθ−y)=0
也就是说,残差向量 (Xθ−y)(X\theta - y)(Xθ−y) 与 XXX 的列空间正交。
这就是“正规方程”名字的来源。
14. 闭式解和梯度下降有什么区别?
这是吴恩达课程里很常讲的一点。
1)闭式解
直接用公式:
θ=(XTX)−1XTy \theta = (X^T X)^{-1}X^T y θ=(XTX)−1XTy
优点:
- 不需要迭代
- 不需要学习率
- 一步就能得到最优解
缺点:
- 需要计算矩阵逆
- 当特征维度很大时,计算代价高
- 如果 XTXX^T XXTX 不可逆,就会有问题
2)梯度下降
通过不断更新参数:
θ:=θ−α∇θJ(θ) \theta := \theta - \alpha \nabla_\theta J(\theta) θ:=θ−α∇θJ(θ)
优点:
- 适合大规模数据
- 不需要矩阵求逆
- 更容易扩展到复杂模型(如神经网络)
缺点:
- 需要选择学习率
- 需要多次迭代
- 收敛速度依赖超参数设置
15. 为什么深度学习里不用闭式解?
这是一个非常关键的问题。
在线性回归中,损失函数关于参数是一个二次函数,所以能直接求导并写出解析解。
但在深度学习中:
- 模型是多层非线性的
- 激活函数引入了复杂非线性
- 参数之间高度耦合
- 损失函数通常没有解析解
所以深度学习中几乎都依赖:
- 梯度下降
- 随机梯度下降(SGD)
- Adam、RMSProp 等优化算法
也就是说:
线性回归之所以能有闭式解,是因为它“太规整了”;
神经网络太复杂,通常只能靠迭代优化。
16. XTXX^T XXTX 为什么有时不可逆?
闭式解成立有一个前提:
XTX 可逆 X^T X \text{ 可逆} XTX 可逆
但有些情况下它可能不可逆,比如:
(1)特征之间线性相关
例如:
- 第二列 = 第一列的 2 倍
- 某个特征可以由其他特征线性表示
这会导致矩阵退化。
(2)特征数太多
如果特征数量大于样本数量,也可能导致 XTXX^T XXTX 不满秩。
17. 如果不可逆怎么办?
常见做法有三种:
方法一:删除冗余特征
去掉重复或强线性相关的特征。
方法二:使用伪逆
把公式改成:
θ=X+y \theta = X^+ y θ=X+y
其中 X+X^+X+ 是 Moore-Penrose 伪逆。
在实际编程中,这很常见。
方法三:加入正则化
例如岭回归:
θ=(XTX+λI)−1XTy \theta = (X^T X + \lambda I)^{-1}X^T y θ=(XTX+λI)−1XTy
这样通常就可逆了。
18. 和吴恩达课程思路对应起来怎么记?
你可以按下面这个逻辑去记忆:
第一步:写出模型
y^=Xθ \hat{y}=X\theta y^=Xθ
第二步:写出损失函数
J(θ)=12m(Xθ−y)T(Xθ−y) J(\theta)=\frac{1}{2m}(X\theta-y)^T(X\theta-y) J(θ)=2m1(Xθ−y)T(Xθ−y)
第三步:对参数求导
∇θJ(θ)=1m(XTXθ−XTy) \nabla_\theta J(\theta)=\frac{1}{m}(X^T X\theta - X^T y) ∇θJ(θ)=m1(XTXθ−XTy)
第四步:令梯度为 0
XTXθ=XTy X^T X\theta = X^T y XTXθ=XTy
第五步:解方程
θ=(XTX)−1XTy \theta=(X^T X)^{-1}X^T y θ=(XTX)−1XTy
你只要牢牢记住这五步,闭式解推导就不会乱。
19. 一个最核心的直觉总结
线性回归本质上是在做这件事:
找到一组参数 θ\thetaθ,
让预测值 XθX\thetaXθ 与真实值 yyy 的距离最小。
因为这个目标函数是一个标准的凸二次函数,
所以我们可以像高数里求极小值一样,直接:
- 求导
- 令导数为 0
- 解出参数
于是得到闭式解:
θ=(XTX)−1XTy \theta = (X^T X)^{-1}X^T y θ=(XTX)−1XTy
20. 一段适合写进笔记/作业的简洁总结
在线性回归中,模型预测可表示为 $ \hat{y}=X\theta $。为了使预测值尽可能接近真实值,通常定义均方误差损失函数为:
J(θ)=12m(Xθ−y)T(Xθ−y) J(\theta)=\frac{1}{2m}(X\theta-y)^T(X\theta-y) J(θ)=2m1(Xθ−y)T(Xθ−y)
由于该损失函数关于参数 θ\thetaθ 是一个凸二次函数,因此可以通过对其求梯度并令梯度为 0,直接求得最优解。对损失函数求导可得:
∇θJ(θ)=1m(XTXθ−XTy) \nabla_\theta J(\theta)=\frac{1}{m}(X^T X\theta - X^T y) ∇θJ(θ)=m1(XTXθ−XTy)
令其等于 0,有:
XTXθ=XTy X^T X\theta = X^T y XTXθ=XTy
当 XTXX^T XXTX 可逆时,可进一步解得:
θ=(XTX)−1XTy \theta = (X^T X)^{-1}X^T y θ=(XTX)−1XTy
该解称为线性回归的闭式解或正规方程解。与梯度下降相比,闭式解不需要迭代和学习率,但当特征维度较高时,计算矩阵逆的代价较大,因此在大规模问题和深度学习中通常仍采用梯度下降方法进行优化。
3.2 截距项(Bias)处理
为了将方程 y=wx+by = wx + by=wx+b 矩阵化,实验在特征矩阵 XXX 的右侧拼接了一列全为 1 的向量 。这使得参数 θ\thetaθ 的最后一个元素自动对应偏置 bbb 。
四、 实验代码实现
import time
import paddle
from paddle.io import DataLoader, Dataset
import pandas as pd
import numpy as np
# --- 1. 定义数据集类 ---
# [cite_start]原理:解析原始文本,并执行 Min-Max 归一化以确保数值稳定性 [cite: 21]。
class HouseDataset(Dataset):
def __init__(self, file_path):
super(HouseDataset, self).__init__()
# [cite_start]使用正则表达式处理变长空格分隔符 [cite: 8]
df = pd.read_csv(file_path, sep='\s+', header=None)
# [cite_start]特征取前 13 列,标签取最后 1 列 [cite: 20]
self.data = df.iloc[:, :13].values.astype('float32')
self.label = df.iloc[:, 13:].values.astype('float32')
# [cite_start]Min-Max 归一化处理 [cite: 21]
self.data_max = self.data.max(axis=0)
self.data_min = self.data.min(axis=0)
self.data = (self.data - self.data_min) / (self.data_max - self.data_min + 1e-7)
def __getitem__(self, idx):
return self.data[idx], self.label[idx]
def __len__(self):
return len(self.data)
# --- 2. 正规方程求解函数 ---
def normal_equation(X, y):
"""
实现核心数学公式: theta = (X^T * X)^-1 * X^T * y
"""
X_t = paddle.t(X) # 矩阵转置
# [cite_start]按照解析解逻辑进行矩阵运算 [cite: 17]
theta = paddle.matmul(
paddle.linalg.inv(paddle.matmul(X_t, X)),
paddle.matmul(X_t, y)
)
return theta
# --- 3. 实验运行流程与计时 ---
total_start_time = time.perf_counter()
# 数据加载
dataset = HouseDataset('data/datasets/house.csv')
# [cite_start]构造偏置项:拼接全 1 向量 [cite: 22]
raw_X = paddle.to_tensor(dataset.data)
ones = paddle.ones([raw_X.shape[0], 1])
X_with_bias = paddle.concat([raw_X, ones], axis=1) # 维度变为 [506, 14]
y = paddle.to_tensor(dataset.label)
# 核心求解:直接获取最优解析解
theta_optimal = normal_equation(X_with_bias, y)
# 参数注入:将结果存入模型参数中
model = paddle.nn.Linear(in_features=13, out_features=1)
model.weight.set_value(theta_optimal[:-1].reshape([13, 1]))
model.bias.set_value(theta_optimal[-1])
# --- 4. 预测验证 ---
test_x, test_y = dataset[0]
pred_y = model(paddle.to_tensor(test_x).unsqueeze(0))
print(f"正规方程求解完成。")
print("-" * 30)
print(f"真实房价: {test_y[0]:.2f}")
print(f"预测房价: {pred_y.numpy()[0][0]:.2f}")
print("-" * 30)
print(f"总运行耗时: {time.perf_counter() - total_start_time:.6f} 秒")
五、 结果分析与结论
5.1 性能指标对比
计算速度:正规方程在本实验中几乎瞬时完成 。相比需要 2000 次迭代的梯度下降法,它极大地缩短了训练时间 。
准确性:由于得到了全局最优解析解,模型在测试样本上的预测值与真实值非常接近。
5.2 实验结论
算法优劣性:正规方程不需要设置学习率 α\alphaα 或迭代次数,避免了参数选择不当导致的收敛失败问题 。
适用场景:实验证明,对于波士顿房价这种特征维度(13维)较小的数据集,正规方程是最优选择;但在处理超高维度数据时,求逆运算的计算开销将成为瓶颈 。
特征处理:归一化处理仍是必要的,它保证了数据在矩阵求逆运算中的数值稳定性 。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)