简介:这份资源面向想用 Java 入门监督学习的开发者,聚焦线性回归这一最基础的机器学习模型,通过一个可运行的小程序演示如何用梯度下降与正态方程拟合数据点的“最佳拟合线”。示例以 y = Mx + B 为学习目标,从训练数据中求出常数 M 与 B,并说明选择非线性特征后也能拟合高阶多项式,训练数据取自 Andrew Ng 的 Coursera 机器学习课程。压缩包共 13 个文件,约 44KB,包含 3 个 Java 源文件、5 个 txt 训练数据、Jama 矩阵运算 jar 包及 README、工程配置等,源码与数据分离,便于对照阅读和替换数据集实验。已有 253 人学习,适合作为理解梯度下降迭代过程与正态方程解析解差异的练手项目。
1. 线性回归到底在做什么:从“猜房价”说起
你手里有一张表,左边是房子面积,右边是成交价。看了几十行之后,你心里大概会冒出一句:面积越大,价格越高。这句话本身没什么稀奇,稀奇的是——能不能把“大概”变成一个公式,让新来一套房子,你直接算出它值多少钱?线性回归干的就是这件事:找一条直线,让它尽可能贴近所有已知数据点,然后用这条直线去预测未知。它是最基础的机器学习模型之一,也是很多人入门时第一个真正动手跑起来的算法。搞懂它,不只是学会一个公式,而是理解“模型如何从数据里学规律”这件事的起点。而“梯度下降”和“正态方程”,就是求这条直线的两种截然不同的路子。
2. 为什么一条直线值得反复琢磨:线性回归的两个求解思路
2.1 直线背后的假设:输入和输出之间存在线性关系
线性回归的核心假设非常朴素:输出 y 可以近似写成输入的加权和,再加一个偏置。用一维情况说,就是 y = wx + b。这里的 w 是斜率,b 是截距。模型要学的,就是这两个数。
打个比方,你在调一个老式收音机的两个旋钮,一个控制音量,一个控制音调,目标是让声音听起来最舒服。w 和 b 就是那两个旋钮,数据就是你的耳朵,你要拧到某个位置,让整体误差最小。
多维情况下,输入不止一个特征,公式变成 y = w₁x₁ + w₂x₂ + … + wₙxₙ + b。写成向量形式就是 y = wᵀx + b。不管维度多少,本质还是“加权求和”。这个假设很强,现实数据往往不完全满足,但它是理解更复杂模型的台阶。
为什么值得反复琢磨?因为线性回归把“学习”这件事压缩到了极致:没有隐藏层,没有激活函数,没有花哨的结构,只剩下“怎么把误差压到最小”这一个问题。把这个问题的两种解法吃透,后面遇到神经网络的梯度下降,你会发现骨子里是一样的。
2.2 误差怎么量:损失函数是模型的“打分器”
直线画得好不好,得有个标准。最常用的是均方误差(MSE):把每个样本的预测值和真实值之差平方,再取平均。平方的作用有两个,一是让正负误差不互相抵消,二是放大离谱预测的惩罚。
通俗地说,这就像考试打分:错一道扣一分,错得越离谱扣得越狠。模型训练的目标,就是让这个总分越低越好。
损失函数写成 L(w, b) = (1/n) Σ (yᵢ - (wᵀxᵢ + b))²。注意它是关于 w 和 b 的函数,数据是固定的。所以“训练模型”翻译过来就是:在 w 和 b 的空间里,找一个让 L 最小的点。这个空间可能有很多维,但几何直觉不变——你在一个碗形曲面上找最低点。
提示:均方误差对异常值敏感。一个极端离群点能把整条直线拽偏,实际使用前先看看数据分布。
2.3 两条路的分岔:迭代逼近 vs 一步到位
找最低点有两条路。第一条是梯度下降:先随便站一个位置,看看脚下往哪个方向最陡,往下走一小步,重复直到走不动。第二条是正态方程:直接解一个方程,一步算出最低点的坐标。
打个比方,你要找山谷的最低处。梯度下降是蒙着眼一步步摸下去,正态方程是手里有一张地形图,直接算出谷底坐标。前者慢但通用,后者快但有门槛。
这个分岔决定了后面所有的区别:梯度下降需要调学习率、需要迭代、对特征尺度敏感;正态方程需要算矩阵逆、特征多时算不动。两者没有绝对优劣,只有适不适合。理解这个分岔,比记住哪个“更好”重要得多。
3. 梯度下降:蒙着眼下山的那套办法
3.1 梯度的直觉:为什么沿着反方向走就能下降
梯度是一个向量,指向函数上升最快的方向。所以要让损失变小,就往梯度的反方向走。这个结论不需要背,想象你站在山坡上,最陡的上坡方向就是梯度,那下坡自然就是反方向。
数学上,w 的更新规则是 w := w - α · ∂L/∂w,b 同理。α 是学习率,控制每步走多大。偏导数告诉你每个旋钮该往哪拧、拧多少。
这里有个容易翻车的地方:学习率太大会跨过谷底来回震荡,太小则走半天还在半山腰。血泪经验是,先用一个不大不小的值试,比如 0.01,观察损失曲线是否稳定下降,再决定调大还是调小。
3.2 批量、随机与小批量:三种走法各有取舍
梯度下降按每次用多少数据算梯度,分成三种。批量梯度下降用全部样本算一次梯度,方向准但每步都慢;随机梯度下降每次只用一个样本,快但方向抖;小批量梯度下降折中,每次用一小撮样本。
用生活类比:批量像每次问全班同学意见再行动,稳但慢;随机像逮着一个人就问,快但容易被带偏;小批量像问一个小组,兼顾效率和稳定。实际工程里小批量最常见,因为它在计算效率和梯度质量之间取得了平衡。
| 方式 | 每次用样本数 | 优点 | 缺点 |
|---|---|---|---|
| 批量 | 全部 | 梯度稳定 | 大数据集每步极慢 |
| 随机 | 1 | 每步极快 | 方向抖动大 |
| 小批量 | 一小批 | 平衡 | 需选批量大小 |
3.3 学习率和迭代次数:两个必须盯住的旋钮
学习率决定步长,迭代次数决定走多少步。这两个参数没有万能值,得看数据。判断方法很直接:把每次迭代的损失记下来画成曲线。正常情况曲线应该先快速下降后趋于平缓;如果上下乱跳,学习率偏大;如果几乎不降,学习率偏小或特征没标准化。
特征标准化这件事值得单独提一句。如果两个特征的量纲差很远,比如一个在 0 到 1 之间,另一个在几千的量级,损失曲面会变成狭长的椭圆,梯度下降会来回锯齿,收敛极慢。把特征缩放到相近范围,曲面接近圆形,下降就顺畅得多。这是很多人第一次跑梯度下降时遇到的坑。
# 梯度下降核心循环(伪代码风格,突出参数含义) for epoch in range(n_iterations): grad_w = (2/n) * X.T.dot(X.dot(w) + b - y) # 对 w 的偏导 grad_b = (2/n) * sum(X.dot(w) + b - y) # 对 b 的偏导 w = w - learning_rate * grad_w # 沿反方向更新 b = b - learning_rate * grad_b这段代码里,grad_w 和 grad_b 是损失对两个参数的偏导,learning_rate 就是步长。注意每次更新用的是同一批数据算出的梯度,这就是批量梯度下降。改成小批量只需每次取数据的一个子集。
4. 正态方程:一步算出答案的“后悔药”
4.1 从求导到解方程:为什么能直接得到最优解
均方误差是一个凸二次函数,形状像一个碗,只有一个最低点。对凸函数来说,导数为零的点就是全局最优。把损失对 w 求导令其为零,整理之后得到一个线性方程组,解出来就是最优的 w。
写成矩阵形式是 θ = (XᵀX)⁻¹Xᵀy,其中 θ 把 w 和 b 打包在一起,X 是加了偏置列的设计矩阵。这个公式就是正态方程。它不需要迭代,不需要学习率,一步到位。
打个比方,梯度下降是试错法找钥匙,正态方程是直接根据锁的结构配一把。配钥匙快,但你得先有锁的完整图纸,而且锁太复杂时配钥匙的机器也算不动。
4.2 矩阵逆的代价:特征一多就“算不动”
正态方程的瓶颈在 (XᵀX)⁻¹。求逆的计算量大致随特征数的立方增长。特征几十个时还好,几百个就开始吃力,上万维基本不现实。另外,如果特征之间存在完全共线性,XᵀX 不可逆,公式直接失效;接近共线时数值也不稳定。
所以正态方程适合特征数不多的场景,比如几个到几十个特征的回归问题。特征一多,梯度下降的优势就出来了——它每步只算梯度,不碰矩阵逆,代价随样本数和特征数线性增长。
注意:特征共线性不是小问题。两个特征高度相关时,正态方程给出的系数可能数值巨大且符号反常,解释性会很差。
4.3 两种解法怎么选:一张对照表说清边界
| 维度 | 梯度下降 | 正态方程 |
|---|---|---|
| 是否需要调参 | 需要学习率、迭代次数 | 不需要 |
| 特征很多时 | 可扩展 | 求逆代价高 |
| 特征少时 | 也能用,但多此一举 | 一步到位 |
| 特征共线性 | 仍可运行 | 可能不可逆 |
| 实现复杂度 | 循环逻辑 | 一行矩阵运算 |
选择逻辑不复杂:特征少、想要确定性结果、不想调参,用正态方程;特征多、数据量大、需要在线更新,用梯度下降。两者算出的最优解在理想条件下是一致的,差别在过程和适用边界。
5. 常见误区:这几个概念最容易搅在一起
5.1 线性回归和逻辑回归:名字像,任务完全不同
易混点:两者都带“回归”二字,公式形式也相似,都是加权求和。为什么混:名字误导,加上都用了类似的线性组合。怎么区分:看输出。线性回归输出连续值,用于预测数值;逻辑回归输出概率,用于分类。逻辑回归在加权和外面套了一个函数把结果压到 0 到 1 之间,本质是分类模型。记住一句话:线性回归答“多少”,逻辑回归答“哪类”。
5.2 损失函数和评估指标:训练时看的和验收时看的不是一回事
易混点:均方误差既像损失又像指标,容易以为是一回事。为什么混:数值上确实可能相同。怎么区分:损失函数是训练时用来求梯度的,必须可导;评估指标是训练后衡量模型好坏的,不参与优化。你可以用均方误差训练,却用平均绝对误差验收。两者目的不同,别混着谈。
5.3 梯度和导数:多维情况下的叫法差异
易混点:一维时叫导数,多维时叫梯度,很多人以为梯度是另一种东西。为什么混:术语切换没有明显提示。怎么区分:梯度就是把各方向的偏导数打包成向量。一维时梯度退化成导数。理解上不用分开,都是“变化最快的方向”。
5.4 特征标准化和归一化:目的相近,做法有别
易混点:两个词经常混用,有人以为完全等价。为什么混:都是把特征缩放到相近范围。怎么区分:标准化是减均值除标准差,结果均值 0 方差 1;归一化通常指缩放到固定区间如 0 到 1。对梯度下降来说,两者都能改善收敛,但标准化对异常值更稳健。选哪个看数据分布,不必纠结名词。
5.5 过拟合和欠拟合:误差大不一定是模型太简单
易混点:训练误差高就以为模型不行,训练误差低就以为模型很好。为什么混:只看一个数字容易误判。怎么区分:训练误差高、验证误差也高,是欠拟合;训练误差低、验证误差明显高,是过拟合。线性回归本身容量有限,过拟合不常见,但特征一多、样本一少,照样会发生。判断要看两组误差的差距,不是单看一组。
6. 什么时候该用线性回归:边界判断和一个具体例子
线性回归不是万能钥匙,它的适用边界很清楚。输入和输出大致呈线性关系、需要可解释的系数、数据量不大且特征不多,这几种情况下它往往是第一选择。反过来,关系明显非线性、特征维度极高、数据里异常值很多又没处理,硬上线性回归大概率翻车。
举个具体例子。某公司想根据房屋面积、房龄、楼层三个特征预测租金。特征只有三个,样本几百条,用正态方程一行矩阵运算就能出结果,系数还能直接解释为“面积每多一平米,租金平均变化多少”。这种场景用梯度下降反而多此一举。但如果特征扩展到几百个,比如加入周边设施的各种编码,正态方程的求逆就开始吃力,这时候换成小批量梯度下降更合适。
判断流程可以简化成三个问题:关系是否近似线性?特征数量是否可控?是否需要系数解释?三个都答是,线性回归直接上;有一个答否,就得考虑别的模型或先做特征工程。
# 正态方程求解(特征少时的直接解法) import numpy as np X_b = np.c_[np.ones((len(X), 1)), X] # 加一列偏置项 theta = np.linalg.inv(X_b.T.dot(X_b)).dot(X_b.T).dot(y) # theta[0] 是截距,theta[1:] 是各特征系数这段代码对应 θ = (XᵀX)⁻¹Xᵀy。np.c_ 用来拼接偏置列,inv 求逆。特征多时这行会变慢甚至报奇异矩阵错误,那时候就该换梯度下降了。
我自己的习惯是,拿到回归问题先跑一遍正态方程当基准,看系数和误差量级,再决定要不要上梯度下降做大规模版本。这个顺序帮我省过不少时间——有几次数据明明只有十几个特征,却先写了梯度下降调了半天学习率,回头一看正态方程一秒出结果。从那以后我每次遇到线性回归都先问一句:特征到底多不多?希望这个判断顺序也能帮到你。
本文还有配套的精品资源,点击获取