查看全课大纲(第 38 / 82 节)
- 1.函数与数据泛化
- 2.向量:从几何直觉到高维空间
- 3.矩阵与张量
- 4.特征值与特征向量:矩阵变换的不动方向
- 5.凸函数:从向量求导的矩阵记号到好优化的函数形状
- 6.概率与统计:机器学习的数学基础
- 7.独立性与随机变量:贝叶斯公式与二项分布
- 8.期望、方差与协方差:随机变量的数字画像
- 9.联合概率:当一条轴不够用的时候
- 10.概率分布(一):从伯努利到伽马函数
- 11.概率分布(二):贝塔、泊松、正态与两个贝叶斯故事
- 12.机器学习的模型:找输入与输出的关系
- 13.机器学习的应用
- 14.过拟合
- 15.机器学习的分类
- 16.有监督学习:从房价预测说起
- 17.损失函数
- 18.梯度下降(上):更新规则与学习速率
- 19.梯度下降(2)
- 20.牛顿方法
- 21.KNN简介
- 22.距离测算
- 23.朴素贝叶斯
- 24.朴素贝叶斯的实际应用
- 25.信息熵:决策树构建的度量基础
- 26.信息增益与决策树的构建
- 27.ID3算法
- 28.决策树节点的划分
- 29.CART树
- 30.Bagging
- 31.Boosting:自适应提升算法
- 32.Gradient Boosting
- 33.线性模型:回归与分类的同一副骨架
- 34.最小二乘:把最优参数直接解出来
- 35.最小二乘法的几何与概率视角
- 36.极大似然:给最小二乘一个概率出身
- 37.逻辑回归(1):把线性模型接到分类上
- 38.逻辑回归(2):损失函数与多分类
- 39.---- 手造数据: 5 个样本, 2 个特征; x2 几乎 = 2*x1 (偏差手工指定, 无随机) ----
- 40.讲师例: 向量 "23014" -- 三把范数尺子量同一个向量
- 41.优化方法(1):坐标下降与前向选择
- 42.优化方法(2):前向策略与最小角回归
- 43.拉格朗日乘子法(1):从无约束到等式约束
- 44.拉格朗日乘子法(2):等式约束下的平行条件与拉格朗日函数
- 45.拉格朗日乘子法(3):不等式约束与 KKT 条件
- 46.最大间隔:把分类问题变成优化问题
- 47.拉格朗日对偶问题:把 w 和 b 全部换成 α
- 48.支持向量:边界上的少数派
- 49.SVM 计算过程:一笔到底算完,再走进核方法
- 50.贝叶斯公式:概率即信念
- 51.隐含变量和概率模型:给看不见的原因建模
- 52.EM 算法:期望与最大化的迭代
- 53.琴生不等式:EM 的数学钥匙
- 54.三个硬币问题:EM 的复盘与衔接
- 55.高斯混合模型:用隐变量解释双峰数据
- 56.隐马尔可夫模型
- 57.隐马尔可夫模型(2):前向算法与动态混合模型
- 58.隐马尔可夫模型在 NLP 中的运用
- 59.EM 算法:把「为什么收敛」推到底
- 60.HMM 在 NLP 中的运用:从分词到机器翻译
- 61.HMM 参数估计:Baum-Welch 的三块拉格朗日
- 62.高维下的高斯模型
- 63.主题模型:从词袋到隐含主题
- 64.Dirichlet 分布(1)
- 65.Dirichlet 分布(2)
- 66.统计学习内容回顾
- 67.K-means 算法
- 68.PCA 降维
- 69.SVD 降维
- 70.从聚类到流形
- 71.局部线性嵌入
- 72.随机邻域嵌入:SNE 与 t-SNE
- 73.无参模型:让数据自己说话
- 74.高斯过程:函数空间里的正态分布
- 75.高斯过程(2):噪声、正则化与核的线性组合
- 76.神经网络简介:从感知器到自编码器
- 77.反向传播:把误差一层层传回去
- 78.卷积神经网络(CNN):让特征自己学出来
- 79.循环神经网络(RNN):给网络装上记忆
- 80.深度学习回顾:静态一类,动态一类
- 81.增强学习(1):蒙特卡洛、树搜索与 Q-learning
- 82.强化学习(2):从乌鸦吃核桃到井字棋的 value 回传
逻辑回归(2):损失函数与多分类
约 18 分钟
小象实战讲义 · 机器学习算法精讲
上次课我们把逻辑回归的形式讲完了:Sigmoid 装着线性组合、几率的对数是线性函数、它是一个广义线性模型。但还差一点点——它还没有损失函数。没有损失,就没法训练。本节补上这最后一环:先想清楚”输出是概率”的模型该怎么算损失,再顺着代数变形把它推广到多分类(Softmax),最后回到线性模型,看一个让最小二乘的正规方程”翻车”的新问题。
💡 核心导读
- 回顾定位:逻辑回归是广义线性模型—— 里头装着线性组合 ,输出是 。
- 概率怎么算损失:输出不是”全对/全错”——每个数据被概率分成”分对的部分”与”分错的部分”两小块。
- 损失函数三要素:indicator 挑出真类别、log 把乘积变加和(极大似然的老 trick)、负号把”越大越好”翻成”越小越好”。
- 优化:整个函数关于 log 与加减乘,是凸函数——梯度下降 一路走到最优。
- Softmax:分子 、分母对全部类求和——概率之和必为 1;逻辑回归在多类上的延伸。
回顾:一个”广义”的线性模型
先把上次课的结论钉牢。逻辑斯特回归(logistic 的音译;对数几率回归是更正确的叫法,西瓜书里也这么写)本质上是线性模型,但它是 generalized linear model——广义线性模型:因为它用了 Sigmoid 函数,
把其中的 替换成我们希望的线性关系 ,就得到 。几率(odds),看谁更多谁更少。给定 输入时,它是一个分类算法:输出的是属于 的概率——大于 0.5 分为第一类,小于 0.5 分为第零类,双分类问题。
概率怎么算损失:把每个数据分成两小块
现在到关键处:它的损失函数是什么?
想清楚一件事:逻辑回归虽然是分类,它给出的不是一个直接的分类结果,而是关于类别的概率分布。这跟以前的决策树稍有差别——决策树吐出的是非黑即白的标签,这里吐出的是概率。
概率怎么算对错?讲师的读法非常直观。比如模型说”分为 1 的概率是 0.8”:
- 恰好真实的 :说明有 的部分已经分对了;
- 真实的 :那就是分错了——而且是 这一大块分错了,只剩 是分对的。
因为输出是概率的形式,任何一个数据都被分成了两个小部分,而期望(标签)是 0 或 1。我们看的,是得到的概率里与期望类别重复的那一小块——那被记为”判对的部分”。计算整个损失函数时,要充分考虑这个概率特性。
损失函数:indicator、log 与负号
形式化这个直觉,分三步走。
第一步:indicator 挑出真类别。 有 个数据、两类( 从 0 到 1),定义:
是 indicator function:括号里成立取 1、不成立取 0。所以内层求和只”点亮”样本真实类别的那一项——乘上 ,就是”它预测对的部分有多少”。把所有类别、所有数据累加:分得越对,这个数越大。
第二步:为什么是 log? 我们希望每个数据都分类正确,那”全部都分对”的概率应该是一个乘积——但概率越乘越小,很难做。老办法:在概率前面加 log,乘积就变成加和。构造极大似然值时我们一直这么干(第 8 章的正态似然、第 5 章的朴素贝叶斯,同款操作)。
第三步:为什么有负号? cost function 是损失函数,我们希望它越小越好;而里面的值(分得越对越大)是越大越好——加个负号,两件事就统一了:损失越小 里边的值越大 分得越对。顺带说破:这个 正是负的对数似然——最小化 与最大化似然是同一件事,第 8 章的老朋友换了个马甲。
对两类的特殊情况把它展开( 时 indicator 点亮第一项, 时点亮第二项):
对照一下线性回归的损失(第 4 章):——预测值减目标值的平方和。逻辑回归不能这么干:它的输出是概率,得按概率的规矩来。(这个形式在教科书里叫交叉熵损失 cross-entropy;本课沿用讲师的叫法——损失函数。)
另外注意:indicator 形式本来就是为多类准备的——把 的范围从 0~1 扩到 ,配上 Softmax 的概率,就是多分类的损失。Softmax 马上就到。
代码:把”分对的部分”算出来
先手工体验一遍损失的含义。5 个样本(注意这份数据有点”拧巴”: 是正、 却是负、 又是正),先用手选的参数 逐点检查:
import math
def sigmoid(z):
return 1.0 / (1.0 + math.exp(-z))
# 5 个样本: 特征 x 与标签 y (拧巴数据: 3 正, 4 负, 5 正)
X = [1.0, 2.0, 3.0, 4.0, 5.0]
y = [0, 0, 1, 0, 1]
# 手选参数: z = x - 4 (偏置 -4, 斜率 1), 边界在 x = 4
b, w = -4.0, 1.0
print("手选参数 z = x - 4 逐样本检查:")
total = 0.0
for xi, yi in zip(X, y):
h = sigmoid(w * xi + b)
right = h if yi == 1 else 1 - h # 与期望类别重复的部分 = 分对的部分
loss_i = -math.log(right)
total += loss_i
print(f" x={xi:.0f} (y={yi}): h={h:.3f}, 分对 {right:.3f}, 分错 {1-right:.3f}, 单点损失 {loss_i:.4f}")
print(f"平均损失 J = {total/len(X):.4f}")手选参数 z = x - 4 逐样本检查:
x=1 (y=0): h=0.047, 分对 0.953, 分错 0.047, 单点损失 0.0486
x=2 (y=0): h=0.119, 分对 0.881, 分错 0.119, 单点损失 0.1269
x=3 (y=1): h=0.269, 分对 0.269, 分错 0.731, 单点损失 1.3133
x=4 (y=0): h=0.500, 分对 0.500, 分错 0.500, 单点损失 0.6931
x=5 (y=1): h=0.731, 分对 0.731, 分错 0.269, 单点损失 0.3133
平均损失 J = 0.4990逐行读: 的负样本被压到 ,94.7% 分对,单点损失很小;最惨的是 这个正样本——模型只给了它 的概率,73.1% 的大块分错了,单点损失高达 1.3133; 恰好骑在边界上,五五开,损失正是 。手选参数的平均损失 。能不能更好?这活儿交给梯度下降。
有了损失函数,剩下的是优化
有了 ,剩下的事情大家就知道怎么做了:梯度下降,或者其他的方法。参数每步更新:
这个函数是关于 log 和乘法、加法的——肯定是一个凸函数,可以直接对它求导得到具体表达式,每次更新都逼近最好的分类结果。
import math
def sigmoid(z):
return 1.0 / (1.0 + math.exp(-z))
# 同一份数据, 从零参数开始全量梯度下降
X = [1.0, 2.0, 3.0, 4.0, 5.0]
y = [0, 0, 1, 0, 1]
def avg_loss(b, w):
s = 0.0
for xi, yi in zip(X, y):
h = sigmoid(w * xi + b)
s += -math.log(h if yi == 1 else 1 - h)
return s / len(X)
b, w = 0.0, 0.0
lr = 0.5
print(f"轮 0: b = {b:.4f}, w = {w:.4f}, J = {avg_loss(b, w):.4f}")
checkpoints = {20, 100, 500, 2000, 20000}
for epoch in range(1, 20001):
gb = gw = 0.0
for xi, yi in zip(X, y):
err = sigmoid(w * xi + b) - yi # dJ/db 累加 err, dJ/dw 累加 err*x
gb += err
gw += err * xi
b -= lr * gb / len(X) # theta := theta - alpha * dJ/dtheta
w -= lr * gw / len(X)
if epoch in checkpoints:
print(f"轮 {epoch:5d}: b = {b:.4f}, w = {w:.4f}, J = {avg_loss(b, w):.4f}")
gb = gw = 0.0
for xi, yi in zip(X, y):
err = sigmoid(w * xi + b) - yi
gb += err
gw += err * xi
print(f"收敛后梯度: dJ/db = {gb/len(X):.2e}, dJ/dw = {gw/len(X):.2e}")
print("最终模型输出:")
for xi, yi in zip(X, y):
h = sigmoid(w * xi + b)
right = h if yi == 1 else 1 - h
print(f" x={xi:.0f} (y={yi}): P(y=1) = {h:.4f}, 分对 {right:.4f}, 分错 {1-right:.4f}")轮 0: b = 0.0000, w = 0.0000, J = 0.6931
轮 20: b = -0.9719, w = 0.2939, J = 0.5889
轮 100: b = -2.7103, w = 0.7710, J = 0.4980
轮 500: b = -3.8516, w = 1.0791, J = 0.4844
轮 2000: b = -3.8940, w = 1.0904, J = 0.4844
轮 20000: b = -3.8940, w = 1.0904, J = 0.4844
收敛后梯度: dJ/db = 4.33e-16, dJ/dw = -2.00e-16
最终模型输出:
x=1 (y=0): P(y=1) = 0.0571, 分对 0.9429, 分错 0.0571
x=2 (y=0): P(y=1) = 0.1528, 分对 0.8472, 分错 0.1528
x=3 (y=1): P(y=1) = 0.3492, 分对 0.3492, 分错 0.6508
x=4 (y=0): P(y=1) = 0.6148, 分对 0.3852, 分错 0.6148
x=5 (y=1): P(y=1) = 0.8261, 分对 0.8261, 分错 0.1739三个观察。起点:零参数时所有 ——什么都不知道、全押五五开,。过程: 一路单调下降(凸函数的好处),500 轮后停在 ——比手选参数的 更优;梯度降到 量级——机器零,稳稳站在极值点上。终点:注意这份数据是”拧巴”的( 正、 负、 又正),一条单调的 S 曲线不可能让所有点都满意——最优解也只能在中间两点各让一步( 分对 34.9%、 分对 38.5%),换来两头的高置信。有些数据天生带着冲突,最优解也带着伤疤——损失函数最小,不等于损失为零。
Sigmoid 的另一种写法:为 Softmax 铺路
接下来做一个简单的变形——它看起来不起眼,却是通向多分类的桥。
,上下同乘 :分子变成 ;分母 。于是:
( 就是 1 减去 ,通分即得。)两个式子完全等价,只是换了身衣服。为什么要换?看分子:每个类别的概率,都是一个 ——两类时分子分别是 和 。如果类别超过两个,这个结构怎么推广?
多分类:Softmax 回归
classes more than two——超过两个分类。设现在有 类,每类一组自己的参数 。仿照刚才的形式, 的分子还是这一类的 ;分母呢?所有类别的概率加起来必须等于 1——所以把每一类的分子全部加起来:
这就是 Softmax,这个模型叫 Softmax Regression——它就是 Logistic Regression 在多类上的延伸。配上前面的 indicator 损失( 的范围扩成 ),就是多分类的完整训练目标。
import math
# ---- 三分类: 单特征 x, 每类一组参数 (w_j, b_j) ----
x = 2.0
W = [(0.3, -0.2), (0.9, -1.0), (0.1, 0.5)] # 类 1/2/3 的参数
scores = [w * x + b_ for w, b_ in W] # 每类的线性组合 theta_j^T x
exps = [math.exp(s) for s in scores] # 每类的"分子"
total = sum(exps) # 分母: 所有类的分子之和
probs = [v / total for v in exps]
print(f"三分类 (x = 2): 各类得分 = {[round(s,3) for s in scores]}")
for j, p in enumerate(probs):
print(f" P(y={j+1}) = {p:.4f}")
print(f" 三类概率之和 = {sum(probs):.4f}")
# ---- 两类验证: 取第 0 类参数为零向量, Softmax 应回到 Sigmoid ----
def sigmoid(z):
return 1.0 / (1.0 + math.exp(-z))
print()
for xv in [1.0, 2.0, 3.0]:
z = xv - 2.0 # 第 1 类的线性组合 (第 0 类恒为 0)
p_softmax = math.exp(z) / (math.exp(0) + math.exp(z))
p_sigmoid = sigmoid(z)
diff = abs(p_softmax - p_sigmoid)
print(f"x={xv:.0f}: Softmax P(y=1) = {p_softmax:.6f}, Sigmoid P(y=1) = {p_sigmoid:.6f}, 差 = {diff:.2e}")三分类 (x = 2): 各类得分 = [0.4, 0.8, 0.7]
P(y=1) = 0.2603
P(y=2) = 0.3883
P(y=3) = 0.3514
三类概率之和 = 1.0000
x=1: Softmax P(y=1) = 0.268941, Sigmoid P(y=1) = 0.268941, 差 = 0.00e+00
x=2: Softmax P(y=1) = 0.500000, Sigmoid P(y=1) = 0.500000, 差 = 0.00e+00
x=3: Softmax P(y=1) = 0.731059, Sigmoid P(y=1) = 0.731059, 差 = 0.00e+00两个观察。归一化:三类概率之和精确等于 1——分母干的就是这件事。“软”在哪里:得分 0.8 的类概率最大(0.3883),但 0.4 分、0.7 分的类也各分到了 26%、35%——Softmax 不是硬邦邦地只选最高分(那是 hard max),而是按 把分数变成按比例的概率,高分大概率、低分小概率。最后三行做实了”延伸”这个词:两类、且第 0 类参数取零向量时,Softmax 的公式代数上回到 Sigmoid(分子分母同除 即见),数值验证差为 0.00e+00——逐位一致。
尾声:线性模型的坏消息
逻辑回归部分到此讲完。今天还有一件重要的事——还是关于线性模型。
上次课学最小二乘法的时候,很多同学问:最后得到的估计是 ——如果这个逆求不出来呢?那整个算法不就不存在了、没法往下算了吗?
什么时候会求不出来?假设数据里特征特别多——小 特别大,甚至出现小 大于大 (特征数超过样本数)——那 多半是一个 singular matrix(奇异矩阵),没办法求逆。
这种情况下怎么办?实际上我们不是说没有办法——需要往里面加一些更多的……(本节视频恰好到这里收尾,讲师话音未落。)加的是什么?加进去会发生什么?下一节《岭回归》揭晓。
📝 动手练一练
- 押错宝的代价:模型对某样本输出 。分别计算真实标签为 与 时,该样本”分对的部分”与单点损失(,);再算 时的损失。用一句话说清:同一个 0.8,为什么两种真相下的损失差这么多?
👉 点击查看参考答案
- :分对 、分错 ,单点损失 ;
- :分对 、分错 ,单点损失 ;
- :无论真相如何,损失都是 。
同一个输出,押对宝代价 0.223、押错宝代价 1.609——差约 倍()。原因在 log:概率每往 0 压一分,真类别若是 1,损失就指数级上涨—— 最小化就是在逼模型把真实类别的概率往上抬,越自信且押对,损失越小;越自信且押错,罚得越狠。
- Softmax 手算:三类问题,某样本的三类线性组合(得分)为 、、(即 已算好)。手算三个类别的 Softmax 概率并验证和为 1(,);再验证:只保留第 1 类并把第 0 类参数视为零向量时, 的二类 Softmax 概率与 一致。
👉 点击查看参考答案
分母 ,于是:
- ;
- ;
- ;
三者之和 ✓。得分最高的类吃掉八成多概率,但另两类仍分到 11% 与 4%——softmax 的”软”:按 比例分配,而非赢家通吃。二类验证:,而 ✓——两类时 Softmax 就是逻辑回归。
本章小结
- 回顾:逻辑回归是广义线性模型——Sigmoid 装着线性组合 ,输出 ,大于 0.5 判一、小于判零。
- 损失直觉:概率输出没有全对全错——每个数据被分成”分对的部分”与”分错的部分”(0.8 且真为 1 则 0.8 分对;真为 0 则 0.8 大块分错);与决策树的硬分类不同。
- 损失函数:——indicator 挑真类、log 乘积变加和、负号把”越大越好”翻成”越小越好”;二类展开为 ;它就是负对数似然。
- 优化:凸函数,梯度下降 ;数值实证 从 一路降到 、梯度归零——拧巴数据的最优解带伤疤( 最小 为零)。
- 变形与延伸:上下同乘 得 ;推广到 类即 Softmax:,概率之和必为 1,是逻辑回归在多类上的延伸(两类特例数值验证逐位一致)。
- 尾声悬念:特征数 大于样本数 时 奇异、不可逆——正规方程翻车;解法要”往里加一些更多的……”,下一节《岭回归》揭晓。
📋 行动清单
- 从 indicator 形式出发,不看讲义展开出二类的
- 运行两块代码:把 的标签从 0 改成 1,观察最优的 、 与各点”分对部分”怎么变
- 手算 押对/押错的单点损失,体会 log 的指数级惩罚
- 手算三类 Softmax(得分 ),验证概率和为 1;再验证二类 Softmax 与 Sigmoid 一致
- 预习下一节《岭回归》: 不可逆时,往损失函数里加的那个东西是什么
—— 小象教研组
配套资料 · 直接下载
课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。