🤖 第10章 概率分类法
📄 一句话概括

160 个训练点、800 个测试点,二维两团高斯。 牛顿法 11 步收敛,测试准确率 95.12%,对数损失 0.1188。 概率范围 3.25e-09 到 1.000000——它是真算出来的,不是凑的。 校准五桶里预测 0.5343 的那一桶实际正类 0.5417,预测 0.9819 的那一桶实际 0.9700。 同一批 12 个错标点:Logistic 掉 0.62 个点,最小二乘掉 1.88 个点, 但两者的终点都是 93.75%——比较时必须连起点一起看。

📝 本节和前面几节的关系

第 7、8、9 章的分类器输出的全是正负一。判类只看符号,符号之外的信息一概丢掉。 可很多场合要的不是「哪一类」,是「有多确信」——比如「这个像素有 0.02 的概率是猫」, 后面才好做取舍。 这一章把输出从正负一换成一个 0 到 1 之间的数,损失也跟着换。 换完会发现两件事:一是训练可以快到 11 步就收, 二是它给出的概率真的能当频率用——这个性质叫校准。

一、从硬标签到概率

到第 9 章为止,所有分类器交出来的都是正负一。 第 7 章的最小二乘分类算出一个实数 f(x),看符号判类;f 是 0.3 还是 0.9,判类结果一模一样。 第 8 章的 SVM 输出「到边界的距离乘符号」,这个数有相对意义,但它可以是 2.7 也可以是 15.3, 没有上界,也不能当概率。 要把一个没有上界的实数压进 0 到 1,标准做法是过一层 sigmoid:

p = 1 / (1 + exp(-z))          # z = w 点乘 x + b

它的形状是单调上升的 S 形,z 等于 0 时 p 等于 0.5, 并且满足 p 关于 0.5 中心对称(p 的负 z 等于 1 减 p)。 两端很快就饱和:

z 负 4 负 2 0 2 4
p 0.01799 0.11920 0.50000 0.88080 0.98201
注意一件事:模型的形状一点没变。
决策边界还是那条直线——p 等于 0.5 等价于 z 等于 0,还是 w 点乘 x 加 b 等于 0。
变的只是「怎么解释这个数」:以前它是一个判别分数,现在它是一个概率。
实测的系数是 w 等于 (3.2909, 3.4055)、b 等于 负 0.3783。
两个系数几乎相等,因为两团点的中心在 (负 1.1, 负 1.1) 和 (1.1, 1.1) 上,
分界线正好垂直于那条对角线。
b 是负的,说明分界线从原点往正方向挪了一点点——这是两团点抽样不均匀造成的。
概率范围那一行值得单独看:最小 3.25e-09,最大 0.999999 以上。
sigmoid 在两端是饱和的,输入差 15 以上就压到 1e-7 以下。
所以「几乎肯定不是」这种判断它是真算出来的,不是把 0 硬塞进去。

二、损失换成对数似然

输出变成概率,损失就该跟着改:不再问「和正负一差多远」,而是问「给正确类留了多少概率」。 正确的写法是负的 log P(正确类)。 y 等于正一时正确概率是 p,y 等于负一时是 1 减 p,两者可以合成一个式子:

P(正确) = sigmoid(y * f)      # y 是正负一,f 是线性输出
损失     = -log P(正确) = log(1 + exp(-y * f))

这个式子叫 logistic 损失,也叫二分类的交叉熵。 它和第 8 章的 hinge 损失是邻居——两者都在惩罚「间隔不够大」,区别在尾巴:

间隔 y 乘 f hinge 损失 logistic 损失
负 2 3.000 2.127
负 1 2.000 1.313
0 1.000 0.693
+1 0.000 0.313
+2 0.000 0.127
hinge 在间隔到 1 之后精确变成 0,一分钱不罚;logistic 永远大于 0,只是很快变得很小。
这个差别在实践里意味着:hinge 只关心「够不够」,logistic 还关心「多够」。
所以 SVM 的支持向量只有边界上那几个,
而 Logistic 回归每个点都在出力——只是远处的点出的力小到可以忽略。
再和平方损失比,差别更大。
平方损失是「让输出贴近正负一」,那是把分类当回归做;它罚的是(1 减 间隔)的平方。
间隔从负 1 走到负 6,平方损失从 4.000 涨到 49.000(涨 12 倍),
交叉熵只从 1.313 涨到 6.002(涨 4.6 倍)。
一个二次增长,一个近似线性。
这句话的实际后果很硬:一个错得离谱的点,在平方损失下会拿到不成比例的话语权。
它会拽着整条边界往自己那边跑,就为了把自己那 49 的罚金降下来一点。
第 7 章那个「12 个错标点把准确率打掉 1.8 个点」的现象,根子就在这张表上。

三、牛顿法:11 步到机器精度

交叉熵是凸的、二阶连续可导,所以可以直接上牛顿法:每步解一个方程 H 乘 delta 等于 g, 然后 b 减去 delta。 梯度 g 是「设计矩阵转置 乘(预测概率减标签)」, Hessian H 是「设计矩阵转置 乘 权重 乘 设计矩阵」,权重是 p 乘 (1 减 p)。 这个 H 恰好就是加权最小二乘的法方程矩阵。 所以这套算法有个别名:迭代重加权最小二乘(IRLS)。 每一轮只是把权重换一遍,再解一次最小二乘——没有学习率要调。

轮次 平均对数似然 梯度最大值 步长最大值
1 负 0.25680 9.62e+01 7.34e-01
2 负 0.16146 2.63e+01 5.13e-01
3 负 0.12019 9.94e+00 5.33e-01
4 负 0.10231 3.84e+00 6.08e-01
5 负 0.09603 1.45e+00 6.06e-01
6 负 0.09477 4.66e-01 4.20e-01
7 负 0.09469 9.42e-02 1.34e-01
8 负 0.09469 5.98e-03 9.90e-03
9 负 0.09469 2.86e-05 4.83e-05
10 负 0.09469 6.72e-10 1.14e-09
11 负 0.09469 1.37e-15 1.39e-15
对数似然从 负 0.25680 单调涨到 负 0.09469,第 11 步步长掉到 1e-12 以下,停。
但真正该看的是「有效位数」:前 6 步就把似然从 负 0.25680 抬到 负 0.09477,
后面 5 步只多走了 0.00008。
换句话说,90% 的进展在头 6 步里就完成了。
梯度那一列更能说明问题:9.62e+01、2.63e+01、9.94e+00、3.84e+00、1.45e+00、4.66e-01、
9.42e-02、5.98e-03、2.86e-05、6.72e-10。
后半段每步不只是变小,是「平方」——5.98e-03 一步变成 2.86e-05,那正是 0.00598 的平方量级。
这就是二次收敛:靠近最优点时,每步的有效位数翻倍。
对比一下:第 8 章的 SMO 要 71 轮,第 9 章的 boosting 要两百轮。
能这么快有两个前提,缺一不可:一是这个问题的维度只有 3(两个系数加一个截距),
二是牛顿法用了二阶信息——它知道「往哪边走、走多远」。
再看「步长最大值」那一列,它前四步是 0.734、0.513、0.533、0.608——不是单调变小。
牛顿方向的长度不等于「离最优点还有多远」:方向对了,步子可能突然变大,
因为 Hessian 在那一段变小了。
所以停止条件不能只看步长,配合梯度一起看才稳。
代价必须说清楚:每步要解一个 d 乘 d 的方程组,代价是 d 的三次方。
d 等于 3 的时候无感;d 等于一万的时候一步都解不起。
所以高维问题上用的是 L-BFGS 或者随机梯度——它们只用一阶信息,步数多得多,但每步便宜。
「11 步收敛」是这个尺寸下的成绩,不能当成牛顿法的一般性能。

四、校准:概率真正的价值

准确率只回答「判对了几成」,概率还要回答「你说的几成算不算数」。 检验办法是把测试点按预测概率分桶,比较「桶里的平均预测」和「桶里实际的正类比例」。 两者贴合,就叫校准过。

预测概率区间 点数 平均预测 实际正类比例
0.0 到 0.2 370 0.0124 0.0189
0.2 到 0.4 16 0.2886 0.3750
0.4 到 0.6 24 0.5343 0.5417
0.6 到 0.8 23 0.7052 0.7826
0.8 到 1.0 367 0.9819 0.9700
两列几乎贴着走:预测 0.5343 的那一桶,实际正类占 0.5417;
预测 0.9819 的那一桶,实际占 0.9700。
这个性质不是所有分类器都有的。
第 7 章的最小二乘分类给不出——它的输出范围是 负 1.68 到 1.72,既不是概率,
也没有什么可校准的东西。
要理解校准的分量,可以想一个反例:一个模型可以对 95% 的点判对,
但它对每个点都输出 0.95——准确率满分,校准完全失效。
这种模型在「按概率排序取前 100 个」的任务里一文不值,因为它排不出序。
概率的价值不在判类,在排序和取舍。
中间三桶的点很少(16、24、23 个),那几个数波动大,别抠小数点。
两头各 370 和 367 个点,那两个数才稳。
这也是看校准表的常规姿势:先看点多的桶,点少的桶只当参考。

五、对错标点的鲁棒性

把第 7 章那 12 个位置一模一样的错标点加进训练集,两种方法各跑一遍:

做法 干净数据上 加 12 个错标点后 掉了多少
最小二乘分类 95.62% 93.75% 1.88 个点
Logistic 回归 94.38% 93.75% 0.62 个点
Logistic 掉 0.62 个点,最小二乘掉 1.88 个点,前者只有后者的三分之一。
这和第 7 章的结论一致:交叉熵对离谱点的惩罚是线性的,平方损失是二次的。
但有一件事必须看清楚:两者的终点是一样的,都是 93.75%。
Logistic 的起点低(94.38% 对 95.62%),所以它「掉得少」有一半是因为它本来就更差一点。
这就是比较两种方法时的纪律:不能只报「谁掉得多」,必须把起点一起摆出来。
只报掉幅的话,一个起点 60% 的差模型也能「掉得最少」。
为什么 Logistic 的起点会低一点?因为它优化的是对数似然,不是误分数。
这两个目标在有限样本上不完全一致:似然最大的那条线,未必是错得最少的那条。
差距 1.24 个百分点,在这个规模上属于正常的目标错位,不是实现错误。
要说明:
这两团点是按高斯造的、标准差 0.95、中心距 2.2,重叠区不小,所以准确率天花板就在 95% 上下。
换成完全可分的数据,两者都会到 100%,那时比的就是谁的概率更可信——而那正是下一节要拆的东西。

六、动手 Demo:Logistic 回归:牛顿法 11 步收敛,测试准确率 95.12%,对数损失 0.1188

造 160 个训练点和 800 个测试点:两团二维高斯,中心 (负 1.1, 负 1.1) 与 (1.1, 1.1),标准差 0.95, 一类 80 个。 模型是 p 等于 sigmoid(w 点乘 x 加 b),用牛顿法最小化平均负对数似然,每步记录似然、梯度最大值、 步长最大值。 收敛后打印系数、训练与测试准确率、对数损失、概率范围,再把测试集按预测概率分五桶做校准, 最后加 12 个错标点重跑两种方法做对照。

def fit_logit(A, yy, iters=30):
    b = np.zeros(A.shape[1])
    for t in range(1, iters + 1):
        p = sig(A @ b)                         # 当前的概率预测
        g = A.T @ (p - (yy > 0) * 1.0)          # 负对数似然的梯度
        w = p * (1.0 - p)                      # 每个点的权重
        H = A.T @ (A * w[:, None]) + 1e-10 * np.eye(len(b))
        step = np.linalg.solve(H, g)           # 牛顿方向
        b = b - step
        ll = np.mean(np.log(np.clip(sig(yy * (A @ b)), 1e-15, 1.0)))
        if np.abs(step).max() < 1e-12:          # 步长到机器精度就停
            break
    return b

实跑输出:

--------------------------------------------------------------------
Logistic 回归:牛顿法 11 步收敛,测试准确率 95.12%,对数损失 0.1188
--------------------------------------------------------------------
  数据                     160 个训练点、800 个测试点,二维,两团中心 (负 1.1, 负 1.1) 与 (1.1, 1.1),标准差 0.95
  模型                     p 等于 sigmoid(w 点乘 x 加 b),输出就是一个 0 到 1 之间的数
  损失                     交叉熵,等价于「负的对数似然」
  求解                     牛顿法:每步解一个 3 元线性方程组

  牛顿法的迭代轨迹(对数似然是每个点的平均值,越大越好):

  轮次   平均对数似然   梯度最大值   步长最大值
  --------------------------------------------
     1       -0.25680     9.62e+01     7.34e-01
     2       -0.16146     2.63e+01     5.13e-01
     3       -0.12019     9.94e+00     5.33e-01
     4       -0.10231     3.84e+00     6.08e-01
     5       -0.09603     1.45e+00     6.06e-01
     6       -0.09477     4.66e-01     4.20e-01
     7       -0.09469     9.42e-02     1.34e-01
     8       -0.09469     5.98e-03     9.90e-03
     9       -0.09469     2.86e-05     4.83e-05
    10       -0.09469     6.72e-10     1.14e-09
    11       -0.09469     1.37e-15     1.39e-15

  **对数似然从 -0.25680 单调涨到 -0.09469,第 11 步步长掉到 1e-12 以下,停。**
  **注意收敛速度:前 6 步就把似然从 -0.25680 抬到 -0.09477,后面 5 步只多走了 0.00008。**
  **这是牛顿法的典型行为:二阶信息用上了,靠近最优点时每步的有效位数翻倍。**
  **相比之下第 8 章的 SMO 要 71 轮,第 9 章的 boosting 要两百轮。**
  **能这么快,是因为这个问题的维度只有 3(两个系数加一个截距),而且牛顿法用的是二阶信息——它知道「往哪边走、走多远」。**

  系数                     w 等于 (3.2909, 3.4055),b 等于 -0.3783
  训练准确率               94.38%
  测试准确率               95.12%
  对数损失                 0.1188(每个点平均)
  概率范围                 3.25e-09 到 1.000000

  **概率范围这一行要看清楚:最小值是 3.2e-09,也就是「几乎肯定不是正类」。**
  **sigmoid 在两端是饱和的,输入差 15 以上就压到 1e-7 以下。**
  **这就是为什么它给得出「0.999999」这种数——是真算出来的,不是凑的。**

  再看校准:把测试集的 800 个点按预测概率分成五桶,比较「平均预测」和「实际的正类比例」。

  预测概率区间   点数   平均预测   实际正类比例
  --------------------------------------------
  0.0 到 0.2      370     0.0124         0.0189
  0.2 到 0.4       16     0.2886         0.3750
  0.4 到 0.6       24     0.5343         0.5417
  0.6 到 0.8       23     0.7052         0.7826
  0.8 到 1.0      367     0.9819         0.9700

  **两列几乎贴着走:预测 0.5343 的那一桶,实际正类占 0.5417;预测 0.9819 的那一桶,实际占 0.9700。**
  **这个性质叫「校准」:它说的 30% 就真的是 30%,不是随口一个数。**
  **第 7 章的最小二乘分类给不出这个——它的输出范围是负 1.68 到 1.72,既不是概率也没有校准过。**
  **注意中间三桶的点很少(16、24、23 个),所以那几个数波动大,别抠小数点。**

  为什么值得费劲做概率?因为「差得远」和「差一点点」应该罚得不一样。
  看两种损失在同一个间隔上罚多少:

  间隔   平方损失   交叉熵
  -----------------------
  -1.0      4.000    1.313
  -2.0      9.000    2.127
  -4.0     25.000    4.018
  -6.0     49.000    6.002
  +0.0      1.000    0.693
  +1.0      0.000    0.313
  +2.0      1.000    0.127

  **间隔从负 1 走到负 6,平方损失从 4 涨到 49(涨了 12 倍),交叉熵只从 1.313 涨到 6.002(涨了 4.6 倍)。**
  **前者是二次增长,后者是线性增长。**
  **所以一个错得离谱的点,在平方损失下会拿到不成比例的话语权,在交叉熵下就没那么夸张。**
  **第 7 章那个「12 个错标点把准确率打掉 1.8 个点」的现象,根子就在这张表上。**

  那就用同样的 12 个错标点试一遍:

  做法            干净数据上   加 12 个错标点后    掉了多少
  --------------------------------------------------------
  最小二乘分类        95.62%             93.75%   1.88 个点
  Logistic 回归       94.38%             93.75%   0.62 个点

  **Logistic 掉了 0.62 个点,最小二乘掉了 1.88 个点,前者只有后者的三分之一。**
  **但要看清楚一件事:两者的终点是一样的(93.75%)。**
  **Logistic 起点低(94.38% 对 95.62%),所以它「掉得少」有一半是因为它本来就更差一点。**
  **这就是为什么比较两种方法时不能只看「谁掉得多」——必须把起点一起摆出来。**
  要说明:**这两团点是按高斯造的、有真实重叠,所以准确率天花板就在 95% 上下。**
  **换成完全可分的数据,两者都会到 100%,那时比的就是谁的概率更可信。**

g = A.T @ (p - (yy > 0) * 1.0) 这一行值得记住:交叉熵配 sigmoid 的梯度, 就是「预测概率减标签」再乘设计矩阵。 形式极简,而且有直观含义——每个点的残差是「它说自己多大概率是正类」减去「它实际是不是正类」, 梯度就是这些残差按特征加权求和。 这也解释了为什么收敛后期梯度会掉得那么快:越靠近最优点,残差越接近互相抵消。 + 1e-10 * np.eye(len(b)) 这个抖动不能省。 p 一旦饱和(比如 0.999999),权重 w 等于 p 乘 (1 减 p) 就趋近于 0,H 会退化成奇异矩阵, np.linalg.solve 直接抛 LinAlgError。 加一个极小的对角项是最省事的补救,代价是稍微偏离真牛顿方向——在 1e-10 这个量级上, 偏得可以忽略。 停止条件用的是「步长最大值小于 1e-12」,不是「似然变化小于多少」。 因为似然后期变化极小(第 7 到 11 轮只动了 0.00008),拿它当判据会在第 7 轮就误判成收敛, 而那时梯度还有 9.42e-02。 判据要看那个真正会归零的量——步长或者梯度。 hist 里同时记梯度最大值和步长最大值,是为了看清两者不同步。 梯度是一直降的(26.3、9.94、3.84、1.45),步长却在中途回升(0.513、0.533、0.608)。 牛顿方向的长度不等于「离最优点还有多远」,因为 Hessian 本身也在变。 sig 里那个 np.clip(z, -60.0, 60.0) 是防溢出的。 不夹的话 exp(800) 直接变 inf,1 除以 inf 得 0,然后 log 里就会出现 log(0)。 夹到正负 60 之后,sigmoid 的下限是 1e-26 左右,双精度还兜得住。 要说明:这里的两团高斯有真实重叠,所以 95% 附近就是这份数据的天花板。 别把这个数当成「Logistic 回归的准确率」——换个分布它就是另一个数。 能跨数据比较的只有「同一份数据上两种方法的差」。

30 秒实验

  1. 把两团中心从 (正负 1.1) 拉近到 (正负 0.6),重跑。 牛顿法还要 11 步吗?对数损失涨到多少?校准那两列还贴合吗?
  2. 把牛顿法换成固定步长的梯度下降(步长取 0.5),一直跑到梯度小于 1e-6。 需要多少步?这 11 步牛顿法相当于多少步梯度下降?
  3. 把 12 个错标点加到 60 个,两种方法各重跑一遍。 终点还会一样吗?谁先崩?崩的时候是准确率掉得多还是对数损失涨得多?

常见误区

  1. 「Logistic 回归是个回归算法」。 名字里有「回归」,但它做的是分类:输出是类别概率,训练目标是分类似然。 「回归」这两个字来自它用的那个函数(logistic 函数),不来自任务类型。 同样容易误会的是「Logistic」和「log」没关系——它是「logistic」这个 S 形函数的专名。
  2. 「交叉熵和平方损失只是写法不同,效果差不多」。 形状完全不同:一个近似线性增长,一个是二次增长。 间隔走到负 6,交叉熵 6.002,平方损失 49.000,差 8 倍。 实测里 12 个错标点让平方损失掉了 1.88 个点,交叉熵只掉 0.62 个点。
  3. 「11 步收敛说明牛顿法永远比梯度下降快」。 每步要解一个 d 乘 d 的方程组,代价是 d 的三次方。 d 等于 3 的时候它快得惊人,d 等于十万的时候一步都解不起。 所以大规模问题上用的是 L-BFGS 或随机梯度——步数多,但每步便宜。 「步数」和「总代价」是两件事。
  4. 「有了概率就能当置信度用」。 得先校准过才行,而且校准是对训练分布而言的。 换个数据分布,原来贴合的校准可能立刻失效——这就是为什么部署后要重新画一张校准表, 而不是拿训练时那张交差。
自测三问(点开对答案) 1. 为什么 Logistic 回归用交叉熵,不用平方损失? —— **因为平方损失是「让输出贴近正负一」,那是把分类当回归做。** **交叉熵是「让正确类的概率尽量大」,这才是分类该问的问题。** **形状上的差别更实际:平方损失对离谱的点是二次惩罚,一个错标点能拽着边界跑很远;** **交叉熵近似线性,罚得没那么夸张。** **实测里同一批 12 个错标点,平方损失掉 1.88 个点,交叉熵只掉 0.62 个点。** 2. 牛顿法为什么 11 步就收敛,代价是什么? —— **它用了二阶信息(Hessian),靠近最优点时是二次收敛——每步的有效位数翻倍。** **看梯度那一列最清楚:5.98e-03 下一步就是 2.86e-05,正好是平方量级。** **代价是每步解一个 d 乘 d 的方程组,复杂度 d 的三次方。** **d 等于 3 时无感,d 上万时就一步都解不起,只能退回一阶方法。** 3. 校准表里两列贴合说明什么?如果不贴,说明什么? —— **贴合说明「它说的 30% 就真是 30%」,概率能直接当频率解释,可以拿去做排序、算期望、** **定阈值。** **不贴说明概率只是个「相对分数」:排序可能还对,但绝对值不能用。** **最典型的失效是一个永远输出 0.95 的模型——准确率可能很高,校准彻底崩。** **这也是为什么评估概率模型要同时看准确率和校准,缺一不可。**

牛顿法要算 Hessian、要反复迭代,11 步虽然快,但每一步都是三次方代价。 下一节换一个更省事的做法——每类拟合一个最小二乘,解一次线性方程组就完事,完全不迭代。 代价是它交出来的东西严格来说不是概率:有几百个点上「概率」是负数,得先截断再归一化才能用。

相关笔记