前两篇主要围绕线性回归:从代价函数、梯度下降,到多维特征、向量化、特征缩放和多项式回归。它们解决的主要是回归问题,也就是预测连续值,比如房价、利润、温度。

这一篇开始进入分类问题。分类不是预测一个连续数字,而是预测类别。比如是否垃圾邮件、是否患病、是否通过审核,或者图片里是不是某个数字。

这篇也会把过拟合和正则化放进来。因为学到这里我开始意识到,模型不只是要在训练集上表现好,还要在新数据上靠谱。

从回归问题转向分类问题

回归(Regression,回归)的输出是连续值。比如预测房价时,结果可能是 232、460 这样的数字。

分类(Classification,分类)的输出是类别。最简单的是二分类问题,标签通常写成:

这里:

  • $0$:负类。
  • $1$:正类。

比如:

  • 邮件不是垃圾邮件:0。
  • 邮件是垃圾邮件:1。
  • 没有患病:0。
  • 患病:1。

我现在先把分类理解成:模型不是预测“多少”,而是预测“属于哪一类”。

为什么不能直接用线性回归做分类

线性回归的输出是:

这个输出可能小于 0,也可能大于 1。

但如果我要表达“属于正类的概率”,结果最好在 0 到 1 之间。比如 0.8 可以理解成模型认为它有 80% 的可能属于正类。

所以不能直接把线性回归的输出当作概率。我们需要一个函数,把任意实数压到 0 到 1 之间。

这个函数就是 Sigmoid。

Sigmoid 函数

Sigmoid 函数(Sigmoid Function,S 形函数)是:

其中:

逻辑回归(Logistic Regression,逻辑回归)的模型可以写成:

我现在这样理解:

  • 先用 $\mathbf{w}\cdot\mathbf{x}+b$ 得到一个线性输出。
  • 再用 Sigmoid 把这个输出压到 0 到 1。
  • 最后的结果可以看作 $y=1$ 的概率估计。

代码是:

1
2
3
4
import numpy as np

def sigmoid(z):
return 1 / (1 + np.exp(-z))

如果要对一批样本输出概率,可以写:

1
2
3
def predict_proba(X, w, b):
z = X @ w + b
return sigmoid(z)

这里:

  • X @ w + b 还是线性部分。
  • sigmoid(z) 把线性输出变成 0 到 1 之间的值。
  • 输出可以理解成属于正类的概率。

从概率变成类别

模型输出概率以后,还需要把概率转成类别。

常见做法是用 0.5 作为阈值:

预测为:

否则预测为:

代码可以写成:

1
2
3
def predict_class(X, w, b):
proba = sigmoid(X @ w + b)
return (proba >= 0.5).astype(int)

这段代码先算概率,再用阈值转成类别。

我现在先记住:逻辑回归不是直接输出 0 或 1,而是先输出概率,再根据阈值做判断。

决策边界

决策边界(Decision Boundary,决策边界)可以理解成:模型把不同类别分开的那条线或曲线。

当 Sigmoid 输出等于 0.5 时:

对应:

也就是:

这条线就是决策边界。

我现在这样理解:

  • 决策边界一边预测为 1。
  • 另一边预测为 0。
  • 如果特征是线性的,边界可能是一条直线。
  • 如果加入多项式特征,边界也可以变成曲线。

这也把上一篇的多项式特征接起来了:特征工程不只影响回归曲线,也会影响分类边界。

为什么逻辑回归不用平方误差

线性回归常用平方误差。但逻辑回归如果直接用平方误差,代价函数可能不好优化,梯度下降更难找到合适的最小值。

所以逻辑回归使用的是 Logistic Loss。

单个样本的损失可以拆开看:

当 $y=1$ 时:

当 $y=0$ 时:

合并成一个公式:

我现在先从直觉上理解:

  • 如果真实标签是 1,但模型预测概率很小,损失会很大。
  • 如果真实标签是 0,但模型预测概率很大,损失也会很大。
  • 如果模型预测和真实标签一致,损失就会比较小。

逻辑回归的代价函数

整体代价函数是:

这里:

  • $m$:训练样本数量。
  • $y^{(i)}$:第 $i$ 个样本的真实标签。
  • $f_{\mathbf{w},b}(\mathbf{x}^{(i)})$:模型预测为正类的概率。
  • 整体代价就是所有样本损失的平均。

代码可以写成:

1
2
3
4
5
6
7
8
9
10
def compute_logistic_cost(X, y, w, b):
m = X.shape[0]
z = X @ w + b
f_wb = sigmoid(z)

cost = -(1 / m) * np.sum(
y * np.log(f_wb) + (1 - y) * np.log(1 - f_wb)
)

return cost

这段代码对应的就是逻辑回归的代价函数。

f_wb 是模型预测为 1 的概率。后面两项分别处理真实标签为 1 和真实标签为 0 的情况。

逻辑回归的梯度下降

逻辑回归的梯度形式看起来和线性回归很像:

代码是:

1
2
3
4
5
6
7
8
9
def compute_logistic_gradient(X, y, w, b):
m = X.shape[0]
f_wb = sigmoid(X @ w + b)
error = f_wb - y

dj_dw = (1 / m) * (X.T @ error)
dj_db = (1 / m) * np.sum(error)

return dj_db, dj_dw

虽然形式像线性回归,但这里的 $f_{\mathbf{w},b}$ 已经不是直接的线性输出,而是经过 Sigmoid 之后的概率。

所以我不能只看公式长得像,就以为它们完全一样。模型输出和代价函数其实都变了。

欠拟合、适度拟合和过拟合

学到逻辑回归之后,过拟合问题会更明显。其实它不只出现在分类里,回归里也会出现。

欠拟合(Underfitting,欠拟合)是模型太简单,连训练数据的主要趋势都学不好。

适度拟合(Just Right,合适拟合)是模型复杂度比较合适,既能抓住主要规律,也没有过度追逐噪声。

过拟合(Overfitting,过拟合)是模型太复杂,在训练集上表现很好,但对新数据可能不好。

我现在对过拟合的理解是:模型把训练集里的细节记得太死,甚至把噪声也当成规律。

这时训练集效果好,不一定说明模型真的好。还要看它在没见过的数据上表现怎么样。

解决过拟合的常见办法

课程里提到的思路可以先记成三类:

  1. 增加训练数据。
  2. 减少特征数量。
  3. 使用正则化。

增加数据可以让模型看到更多真实情况。减少特征可以降低模型复杂度。正则化则是在不一定删除特征的情况下,限制模型参数不要太夸张。

这里我先重点整理正则化。

正则化的直觉

正则化(Regularization,正则化)就是在代价函数里增加一个惩罚项,让模型不要把参数 $w$ 调得过大。

带正则化的线性回归代价函数是:

这里:

  • 前半部分是预测误差。
  • 后半部分是正则化惩罚。
  • $\lambda$:控制惩罚强度。
  • 通常不正则化 $b$。

代码是:

1
2
3
4
5
6
7
8
def compute_linear_cost_reg(X, y, w, b, lambda_):
m = X.shape[0]
f_wb = X @ w + b

cost = (1 / (2 * m)) * np.sum((f_wb - y) ** 2)
reg_cost = (lambda_ / (2 * m)) * np.sum(w ** 2)

return cost + reg_cost

我现在把正则化理解成:模型不能只想着把训练误差压低,还要付出“参数太大”的代价。

正则化在线性回归中的梯度

加入正则化后,$w$ 的梯度会多一项:

$b$ 的梯度还是:

代码是:

1
2
3
4
5
6
7
8
9
10
def compute_linear_gradient_reg(X, y, w, b, lambda_):
m = X.shape[0]
error = X @ w + b - y

dj_dw = (1 / m) * (X.T @ error)
dj_db = (1 / m) * np.sum(error)

dj_dw = dj_dw + (lambda_ / m) * w

return dj_db, dj_dw

关键变化是:

1
dj_dw = dj_dw + (lambda_ / m) * w

这说明正则化没有改变梯度下降的大框架,只是在 $w$ 的梯度里加了惩罚项。

正则化在逻辑回归中的代价函数

逻辑回归也可以加正则化:

代码是:

1
2
3
4
5
6
7
8
9
10
11
def compute_logistic_cost_reg(X, y, w, b, lambda_):
m = X.shape[0]
f_wb = sigmoid(X @ w + b)

cost = -(1 / m) * np.sum(
y * np.log(f_wb) + (1 - y) * np.log(1 - f_wb)
)

reg_cost = (lambda_ / (2 * m)) * np.sum(w ** 2)

return cost + reg_cost

这段代码就是在逻辑回归原来的代价函数后面,加上正则化惩罚项。

前半部分负责分类错误的损失,后半部分限制参数不要过大。

正则化在逻辑回归中的梯度

逻辑回归加入正则化后,$w$ 的梯度同样多一项:

代码是:

1
2
3
4
5
6
7
8
9
10
11
def compute_logistic_gradient_reg(X, y, w, b, lambda_):
m = X.shape[0]
f_wb = sigmoid(X @ w + b)
error = f_wb - y

dj_dw = (1 / m) * (X.T @ error)
dj_db = (1 / m) * np.sum(error)

dj_dw = dj_dw + (lambda_ / m) * w

return dj_db, dj_dw

这里和线性回归的正则化梯度很像。区别在于,逻辑回归里的 error 来自 Sigmoid 后的概率输出。

lambda 怎么理解

$\lambda$ 是正则化强度。

我现在把它理解成模型的“约束力度”:

  • $\lambda = 0$:没有正则化,模型可能更容易过拟合。
  • $\lambda$ 合适:模型更平滑,泛化能力可能更好。
  • $\lambda$ 太大:模型被限制太死,可能欠拟合。

所以 $\lambda$ 不是越大越好。它要在模型太自由和太受限制之间找平衡。

阶段总结

到这里,吴恩达机器学习第一阶段里最基础的几条线,我算是先搭起了骨架。

一开始我只是知道“机器学习是让机器从数据里学规律”。现在至少能把几个核心概念串起来:

  • 监督学习里有回归和分类。
  • 线性回归让我理解了模型、参数、代价函数和梯度下降。
  • 多维特征、向量化和特征缩放让我看到,机器学习不只是公式,还有数据表示和训练效率。
  • 多项式回归和特征工程让我意识到,输入特征本身也会影响模型能学到什么。
  • 逻辑回归让我看到,分类问题不是简单换个标签,而是模型输出和损失函数都要重新设计。
  • 过拟合和正则化提醒我,训练集上效果好不等于模型真的好。

这三篇笔记不是把机器学习学完了,只是先把最基础的骨架搭起来:问题类型、模型表示、代价函数、优化方法和泛化能力。

后面继续学深度学习和联邦学习时,这些概念应该还会反复出现。到那时我再回头看,应该会比现在更能理解为什么神经网络也离不开损失函数、梯度下降和正则化这些东西。