实验报告:基于正规方程的波士顿房价预测

一、 实验目的

理解解析解原理:掌握正规方程的数学推导,理解如何通过矩阵运算直接求解线性回归的最优权重 。

掌握偏置项处理:学习在特征矩阵中手动注入全 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^=

其中:

  • 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(y)T(y)

其中:

  • mmm:样本数
  • XθX\theta:所有样本的预测结果
  • yyy:真实标签
  • (Xθ−y)(X\theta - y)(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=1m(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(y)T(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(y)T(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(y)T(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(θTXTθTXTyyT+yTy)

注意这里:

  • θTXTy\theta^T X^T yθTXTyyTXθy^T X \thetayT 都是标量
  • 两者其实相等

所以可以进一步写成:

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(θTXT2θ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 θ(θTXT)=2XT

∂∂θ(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(2XT2XTy)

化简得:

∇θJ(θ)=1m(XTXθ−XTy) \nabla_\theta J(\theta) = \frac{1}{m}(X^T X\theta - X^T y) θJ(θ)=m1(XTXTy)


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(XTXTy)=0

两边乘以 mmm

XTXθ−XTy=0 X^T X\theta - X^T y=0 XTXTy=0

移项:

XTXθ=XTy X^T X\theta = X^T y XT=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 y2

尽可能小。

这就是最小二乘法的本质。


12. 从几何角度理解

吴恩达课程通常更偏工程直觉。
这里可以加一个几何理解帮助你真正吃透。

我们想求:

Xθ≈y X\theta \approx y y

但很多时候,yyy 不一定恰好能被写成 XθX\theta

所以我们其实是在找一个 XθX\theta,使它成为 yyyXXX 的列空间上的最佳逼近。

换句话说:

  • XθX\thetaXXX 的列空间里
  • 我们希望它离 yyy 最近
  • 这个“最近”就是误差平方和最小

因此,闭式解本质上是一个投影问题


13. 为什么它叫正规方程?

因为在推导中我们得到了:

XTXθ=XTy X^T X\theta = X^T y XT=XTy

这就是所谓的 Normal Equation

这里的 “normal” 不是“普通”的意思,而是“正交”的意思。

因为最优解满足:

XT(Xθ−y)=0 X^T(X\theta - y)=0 XT(y)=0

也就是说,残差向量 (Xθ−y)(X\theta - y)(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^=

第二步:写出损失函数

J(θ)=12m(Xθ−y)T(Xθ−y) J(\theta)=\frac{1}{2m}(X\theta-y)^T(X\theta-y) J(θ)=2m1(y)T(y)

第三步:对参数求导

∇θJ(θ)=1m(XTXθ−XTy) \nabla_\theta J(\theta)=\frac{1}{m}(X^T X\theta - X^T y) θJ(θ)=m1(XTXTy)

第四步:令梯度为 0

XTXθ=XTy X^T X\theta = X^T y XT=XTy

第五步:解方程

θ=(XTX)−1XTy \theta=(X^T X)^{-1}X^T y θ=(XTX)1XTy

你只要牢牢记住这五步,闭式解推导就不会乱。


19. 一个最核心的直觉总结

线性回归本质上是在做这件事:

找到一组参数 θ\thetaθ
让预测值 XθX\theta 与真实值 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(y)T(y)

由于该损失函数关于参数 θ\thetaθ 是一个凸二次函数,因此可以通过对其求梯度并令梯度为 0,直接求得最优解。对损失函数求导可得:

∇θJ(θ)=1m(XTXθ−XTy) \nabla_\theta J(\theta)=\frac{1}{m}(X^T X\theta - X^T y) θJ(θ)=m1(XTXTy)

令其等于 0,有:

XTXθ=XTy X^T X\theta = X^T y XT=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维)较小的数据集,正规方程是最优选择;但在处理超高维度数据时,求逆运算的计算开销将成为瓶颈 。

特征处理:归一化处理仍是必要的,它保证了数据在矩阵求逆运算中的数值稳定性 。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐