机器学习(二)
上一篇主要把机器学习入门、监督学习、线性回归模型和代价函数顺了一遍。那时候我已经知道,训练线性回归大概就是找到合适的 $w$ 和 $b$,让代价函数 $J(w,b)$ 尽可能小。
这一篇就接着往下走:模型不是靠我手动试 $w$ 和 $b$,而是用梯度下降一点点自己调整参数。然后再从单特征扩展到多维特征,看看向量化、特征缩放和多项式回归为什么会出现。
我现在对这部分的理解是:梯度下降是训练的核心方法,多维特征让模型能处理更真实的问题,特征缩放和向量化则是在让训练更顺、更高效。
梯度下降:模型怎么自己变好
梯度下降(Gradient Descent,梯度下降)可以先理解成:沿着代价函数下降的方向,一步一步调整参数。
在线性回归里,参数更新公式是:
这里:
- $w,b$:模型参数。
- $J(w,b)$:代价函数,用来衡量预测错多少。
- $\alpha$:学习率,控制每一步走多大。
- $\frac{\partial J}{\partial w}$、$\frac{\partial J}{\partial b}$:告诉参数应该往哪个方向调整。
:=表示更新赋值。
我现在把它想成下坡:当前位置是当前参数,地形高度是代价函数。梯度告诉我坡往哪里下,学习率决定我每一步迈多大。
这里还有一个细节:$w$ 和 $b$ 应该同步更新。也就是说,这一轮更新 $w$ 和 $b$ 时,都应该基于同一组旧参数来计算,而不是先更新了 $w$,再拿新的 $w$ 去更新 $b$。
梯度下降用于线性回归
线性回归模型是:
代价函数是:
这里 $m$ 是训练样本数量,$x^{(i)}$ 是第 $i$ 个样本的输入,$y^{(i)}$ 是对应的真实值。
线性回归里,梯度可以写成:
我现在先不强行记推导,更关注它在代码里做了什么:
1 | def compute_gradient(x, y, w, b): |
这段代码是在计算当前 $w,b$ 下,代价函数分别对 $w$ 和 $b$ 的斜率。
error是预测值和真实值之间的差。dj_dw会把误差和输入特征一起考虑。dj_db只累加误差。- 最后除以
m,得到平均意义上的梯度。
有了梯度以后,就可以写梯度下降:
1 | def gradient_descent(x, y, w_init, b_init, alpha, num_iters): |
这段代码的主线就是:
- 计算当前参数的梯度。
- 根据学习率更新参数。
- 记录当前代价函数。
- 重复很多次。
J_history 对我来说很有用,因为它可以让我看训练是不是在往好的方向走。如果代价函数一直下降,说明梯度下降大概率在工作;如果忽高忽低,甚至越来越大,就要检查学习率或代码。
批梯度下降
课程里这里用的是批梯度下降(Batch Gradient Descent,批梯度下降)。
它的意思是:每次更新参数时,都使用全部训练样本来计算梯度。
我现在先这样理解:
- Batch:一次看完整个训练集。
- 优点:更新方向比较稳定。
- 缺点:如果数据特别大,每次都看完整训练集会比较慢。
现在入门阶段用它比较合适,因为逻辑最清楚:每一轮都是基于全部数据算一次平均方向。
从单特征到多维特征
上一篇和前面代码里,一个样本只有一个输入特征:
但真实任务通常不只靠一个特征。比如预测房价时,可能会看:
- 面积
- 卧室数量
- 楼层
- 房龄
这时模型就变成多维特征线性回归:
也可以写成向量形式:
这里:
- $\mathbf{x}$:一个样本的所有特征。
- $\mathbf{w}$:每个特征对应的参数。
- $n$:特征数量。
- $m$:训练样本数量。
代码里可以这样表示数据:
1 | import numpy as np |
这里:
X_train是二维数组。- 每一行是一套房子。
- 每一列是一个特征。
X_train.shape是(m, n)。y_train是每套房子对应的真实价格。
多维特征不是换了一个完全陌生的模型,而是把一个输入扩展成多个输入。
向量化:让代码更接近公式
如果不用向量化,可以用循环算一个样本的预测:
1 | def predict_loop(x, w, b): |
这段代码很直观:每个特征乘上对应权重,再全部加起来。
但用 NumPy 可以写得更短:
1 | def predict(x, w, b): |
np.dot(x, w) 对应数学里的点积:
我现在把向量化(Vectorization,向量化)理解成:用数组运算替代手写循环。
它的好处是:
- 代码更短。
- 计算更快。
- 更适合多特征。
- 和数学公式更一致。
后面到了神经网络,向量化会更常见,所以这里先建立感觉很重要。
多维特征的代价函数和梯度
多维特征下,代价函数还是衡量预测值和真实值之间的差:
向量化代码可以写成:
1 | def compute_cost(X, y, w, b): |
这里:
X @ w + b一次算出所有样本的预测值。errors是所有样本的预测误差。np.sum(errors ** 2)把误差平方加起来。
梯度也可以向量化:
1 | def compute_gradient(X, y, w, b): |
这里最关键的是 X.T @ error。它一次性算出了所有特征对应的梯度。
我现在先把它理解成:每个特征都要看自己和预测误差之间的关系,X.T @ error 就是在同时做这件事。
特征缩放
特征缩放(Feature Scaling,特征缩放)是多维特征里很重要的一步。
原因很直观:不同特征的数值范围可能差很多。
比如:
- 面积可能是几百到几千。
- 卧室数量可能只有 1 到 5。
- 房龄可能是几十。
如果这些特征直接放进模型,梯度下降可能会变慢,因为不同参数更新时面对的尺度差别太大。
常见特征缩放方法有几种。
最大值归一化:
均值归一化:
Z-score 标准化:
这里我重点先记 Z-score。它的意思是:每个特征先减去自己的平均值,再除以自己的标准差。
代码是:
1 | def zscore_normalize_features(X): |
这里:
mu是每一列特征的平均值。sigma是每一列特征的标准差。axis=0表示按列计算。X_norm是标准化后的特征矩阵。
标准化后,不同特征会处在更接近的范围里,梯度下降通常会更稳定。
学习率怎么选
学习率 $\alpha$ 决定每次更新参数走多大一步。
我现在还是用“下坡步长”来理解它:
- $\alpha$ 太小:下降很慢。
- $\alpha$ 合适:代价函数稳定下降。
- $\alpha$ 太大:可能越过最低点,甚至发散。
调试梯度下降时,可以看 $J$ 是否随着迭代次数下降。
如果 J_history 下降得很慢,学习率可能太小;如果 J_history 越来越大,学习率可能太大。
特征缩放也会影响学习率选择。特征范围比较接近时,学习率通常更容易选。
特征工程
特征工程(Feature Engineering,特征工程)可以理解成:人为构造更有用的输入特征。
比如房价不一定和面积完全线性相关。原来只有一个面积特征 $x$,可以加入面积的平方:
这不是让模型自己从零发现所有东西,而是我根据对问题的理解,给模型提供更合适的输入。
现在我对特征工程的感觉是:它提醒我,机器学习不只是调公式,数据怎么表示也很重要。
多项式回归
多项式回归(Polynomial Regression,多项式回归)就是一个特征工程的例子。
模型可以写成:
代码里可以构造多项式特征:
1 | x = np.arange(0, 20, 1) |
这段代码做了两件事:
np.c_[x, x**2, x**3]把一个特征扩展成多个多项式特征。zscore_normalize_features(X)对扩展后的特征做标准化。
虽然模型里出现了 $x^2$、$x^3$,但它对参数 $w_1,w_2,w_3,b$ 仍然是线性的,所以还可以使用线性回归那套训练方法。
这里我先不展开过拟合问题。多项式次数太高会带来什么风险,放到下一篇和逻辑回归、正则化一起整理。
我现在容易混淆的点
第一,梯度下降不是一个新的模型,而是一种优化方法。它负责帮模型找到更合适的参数。
第二,批梯度下降每次更新会看完整个训练集。它方向比较稳定,但数据很大时会慢。
第三,多维特征不是把线性回归推翻,而是把输入从一个数字扩展成多个数字。
第四,向量化不是为了写得高级,而是为了让代码更接近公式,也更适合大规模计算。
第五,特征缩放不会改变标签含义,它主要是让输入特征的范围更适合训练。
第六,多项式回归虽然能拟合更复杂的趋势,但复杂度变高后也会带来新的问题。
这一篇的总结
这一篇从梯度下降开始,把线性回归往实际训练推进了一步。
我现在理解到:
- 梯度下降是让模型参数自动变好的核心方法。
- 学习率决定每一步走多大。
- 批梯度下降每次用全部训练样本计算梯度。
- 多维特征让线性回归可以处理更真实的问题。
- 向量化让公式和代码更统一。
- 特征缩放能让梯度下降更稳定、更快。
- 特征工程和多项式回归让线性回归可以拟合更复杂的趋势。
下一篇准备进入分类问题,也就是逻辑回归。同时也要继续整理过拟合和正则化,因为模型变复杂以后,训练集效果好不一定代表模型真的好。





