个人主页: > for_ever_love__ < (欢迎各位大佬莅临😊)
其他栏目: > 我想学python了 <
其他栏目: > iOS项目总结大全 <
其他栏目: > iOS UI <
文章目录
- 机器学习入门------手写线性回归与梯度下降
-
- 一、从"预测"说起
- 二、模型与损失函数
-
- [1. 模型假设](#1. 模型假设)
- [2. 损失函数:量化"预测得有多差"](#2. 损失函数:量化"预测得有多差")
- [3. 梯度下降:下山的过程](#3. 梯度下降:下山的过程)
- 三、动手实现
-
- [1. 生成模拟数据](#1. 生成模拟数据)
- [2. 训练循环](#2. 训练循环)
- 四、三个必须理解的关键点
-
- [1. 为什么要"平方"?](#1. 为什么要"平方"?)
- [2. 梯度方向的直觉](#2. 梯度方向的直觉)
- [3. 特征尺度问题](#3. 特征尺度问题)
- 五、常见坑与排查方法
- [六、对比:手写 vs 用 sklearn](#六、对比:手写 vs 用 sklearn)
- 七、小结
机器学习入门------手写线性回归与梯度下降
一、从"预测"说起
假设你手里有一组「房子面积 → 房价」的数据,现在来了个 90 平的房子,让你估价。你会怎么做?最朴素的想法:在图上把已知数据点画出来,画一条最贴合的直线,用这条直线去预测。
这条直线,就是最简单的机器学习模型------线性回归 。而"找出最贴合的直线"的过程,就是训练。
今天我们不用任何框架,只用 NumPy(数据处理那篇刚好派上用场),把线性回归和梯度下降从零写一遍。写完你就会明白:所谓"模型训练",本质就是一个求函数最小值的迭代过程。
二、模型与损失函数
1. 模型假设
线性回归假设输入和输出之间是线性关系:
ŷ = w·x + b
x:输入(房子面积)w:权重(斜率),b:偏置(截距)------这两个就是模型要学的参数ŷ:模型预测值(读作 y-hat,区别于真实值 y)
2. 损失函数:量化"预测得有多差"
预测值和真实值的差距用均方误差(MSE)衡量:
L = (1/N) · Σ (ŷᵢ - yᵢ)²
把 N 个样本的预测误差平方后取平均。平方有两个好处:放大偏差、且处处可导(方便求梯度)。
训练目标:找到让 L 最小的 w 和 b。
3. 梯度下降:下山的过程
想象你蒙着眼站在山上,想走到最低点(损失最小的位置)。策略很简单:
- 用脚感受当前位置的坡度(求梯度)
- 朝最陡的下坡方向走一小步(沿负梯度更新参数)
- 重复,直到坡度接近 0
写成公式:
w ← w - η · ∂L/∂w
b ← b - η · ∂L/∂b
η 是学习率:步子太大容易跨过谷底来回震荡,太小则收敛极慢。
| 学习率 | 行为 |
|---|---|
| 太大(如 1.0) | 损失上下震荡甚至发散 |
| 合适(如 0.01) | 平滑下降、稳定收敛 |
| 太小(如 1e-8) | 下降极慢,像没训练 |
三、动手实现
1. 生成模拟数据
python
import numpy as np
np.random.seed(42)
N = 200
x = np.random.rand(N) * 100 # 房子面积 0~100
y = 3.0 * x + 2.0 + np.random.randn(N) * 10 # 真实关系 y=3x+2,加噪声
2. 训练循环
python
w, b = 0.0, 0.0 # 参数初始化为 0
lr = 1e-4 # 学习率
epochs = 1000 # 迭代轮数
for epoch in range(epochs):
# 前向:算预测值
y_pred = w * x + b
# 损失:均方误差
loss = np.mean((y_pred - y) ** 2)
# 反向:算梯度(对 MSE 求导的结果)
grad_w = 2 * np.mean((y_pred - y) * x)
grad_b = 2 * np.mean(y_pred - y)
# 更新参数
w -= lr * grad_w
b -= lr * grad_b
if epoch % 100 == 0:
print(f"epoch={epoch:4d} loss={loss:10.2f} w={w:.3f} b={b:.3f}")
print(f"最终结果: w={w:.3f} (真实值 3.0), b={b:.3f} (真实值 2.0)")
运行输出(节选):
epoch= 0 loss= 30584.67 w=1.527 b=0.030
epoch= 100 loss= 1452.95 w=2.527 b=0.842
epoch= 500 loss= 98.03 w=2.984 b=1.914
epoch= 900 loss= 95.31 w=3.002 b=2.003
最终结果: w=3.004 (真实值 3.0), b=2.008 (真实值 2.0)
可以看到:损失从 3 万多一路降到 95 左右,参数 w、b 收敛到真实值附近。剩下的 95 就是数据里噪声本身的最小误差------模型不可能把随机噪声也"学掉"。
四、三个必须理解的关键点
1. 为什么要"平方"?
如果用绝对值误差 |ŷ-y|,函数在 0 点不可导,梯度法走不动;平方函数处处光滑,梯度信息完整。这是 MSE 流行的核心原因。
2. 梯度方向的直觉
grad_w = 2·mean((y_pred-y)·x):当预测值普遍偏高(误差为正),梯度为正,w 减小 → 预测下调;偏低则反之。梯度永远指向"损失上升最快的方向",所以我们减它。
3. 特征尺度问题
如果 x 的取值是 0~100000 而不是 0~100,同样 lr=1e-4 会直接发散。原因是梯度里带着 x,输入越大梯度越大,步长失控。解决方案是特征归一化:
python
x_norm = (x - x.mean()) / x.std() # 转成均值 0、标准差 1
这也是为什么深度学习里输入数据几乎都要做标准化------梯度下降对尺度极其敏感。
五、常见坑与排查方法
| 现象 | 可能原因 | 解决 |
|---|---|---|
| loss 变 NaN / 巨大 | 学习率太大 | 缩小 lr(每次除以 10 试试) |
| loss 几乎不动 | lr 太小 / 参数初始化离谱 | 调大 lr,或归一化输入 |
| loss 收敛但预测差 | 数据有异常值 | 画散点图排查,MSE 对离群点极敏感 |
| 每次结果不一样 | 忘记固定随机种子 | np.random.seed(42) |
调试口诀:先看 loss 曲线,再怀疑一切。loss 不降是参数问题,loss 降但预测差是数据问题。
六、对比:手写 vs 用 sklearn
| 对比项 | 手写 NumPy | sklearn |
|---|---|---|
| 理解原理 | ✅ 彻底搞懂梯度下降 | ❌ 黑盒 |
| 代码量 | ~20 行 | ~3 行 |
| 灵活性 | 任意改动 | 受限于 API |
| 生产适用性 | 低 | 高 |
生产中当然用现成库,但学习阶段手写一遍的价值无可替代------PyTorch 的自动求导做的正是我们今天手动算梯度的那一步。
七、小结
- 线性回归 =
ŷ = wx + b,训练 = 找让 MSE 最小的 w、b - 梯度下降三步:前向算预测 → 反向算梯度 → 沿负梯度更新
- 学习率是第一优先级超参数:大了发散、小了龟速
- 特征尺度不一致时必须归一化
- loss 曲线是最重要的调试工具
明天进入深度学习与 PyTorch:让"自动求导"替我们完成今天手动推的梯度,训练第一个真正的神经网络。
一句话总结:所谓训练,就是让参数沿着"损失下降最快的方向",一小步一小步地走到谷底。