查看全课大纲(第 5 / 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 回传
凸函数:从向量求导的矩阵记号到好优化的函数形状
约 11 分钟
小象实战讲义 · 机器学习算法精讲
第一章收官。前几节把函数、向量、矩阵、特征值都过了一遍,本节补上最后两块基石:一是当输入从单个数变成向量时,求导该怎么记——Jacobian 矩阵与 Hessian 矩阵;二是机器学习最爱的一类”好优化”的函数形状——凸函数与凹函数。这两件事在神经网络与模型优化里天天见面,第 4 章讲梯度下降时会正式兑现”凸为什么好优化”。
💡 核心导读
- 向量求导的矩阵记号:一阶偏导排成 Jacobian,二阶偏导排成 Hessian——参数动辄上百维,矩阵”把所有可能性都考虑到”。
- 凸集与凸函数:集合内任两点连线不出界叫凸集;函数图像上任两点的弦始终在图像上方,叫凸函数。
- t 权重插值: 按”权重”在两点之间取点;凸性比较的是”插值点的函数值”与”函数值的插值”谁大谁小。
- 凹凸依赖区间:同一个函数换个区间可能凹凸互换——判定前必须先给定区间。
- 凸=优化友好:log 凹、 凸;凸函数容易被优化、容易沿着梯度找到最优值,第 4 章展开。
输入是向量时怎么求导:Jacobian 与 Hessian
机器学习里的函数,输入往往不是一个数,而是一个向量 。这时”求导”不再是求一条导数——要对它的变量求导,就得对每一个分量都求一遍。
先看标量函数 。把 对所有分量的一阶偏导排成一列,就是它的梯度(gradient):
把这套想法推广到”输出也是向量”的函数 :每一个 都要对每一个分量 求一次偏导, 个偏导排成一张矩阵——这就是雅可比矩阵(Jacobian matrix),第 行第 列的元素是
标量函数的 Jacobian 只有一行,正好是梯度的转置——Jacobian 是”一阶导数”的完整家谱,梯度是它的特例。
再往下一步——不求一阶,求二阶。对标量函数 ,把所有二阶偏导 排成 矩阵,就是海森矩阵(Hessian matrix):
- 对角线上 :对同一个变量连着求两次导,就是普通的二阶导数 ;
- 对角线外 :先对 求导、再对 求导,两次求导分别落在两个不同分量上——这就是混合偏导。二阶偏导连续时,“先 后 “与”先 后 “结果相同,所以 Hessian 是对称矩阵。
讲师强调这套记号的用武之地:尤其到了神经网络的部分,“每一个部分的表述全是向量”——成百上千个参数要逐个求导,一个个分量去抠太琐碎;写成矩阵,“就把所有的可能性都考虑到”,用一种特别漂亮的方式一次表达完——虽然看着有点复杂。至于这套记号怎么发力:导数、微分与优化的正式讨论在第 4 章(梯度下降与牛顿法)。
下面的代码用”数值差分”实算一次:对某个分量挪一小步、看函数值变多少,近似出偏导,把 Jacobian 与 Hessian 都算出来。J 的解析式是 [[2*x1, 0], [x2, x1]],代入点 应得 [[2, 0], [2, 1]];Hessian 的解析式请自己动手求一遍二阶偏导,再与数值版对照:
def jacobian_numeric(F, x, h=1e-4):
"""中心差分:向量值函数 F 的 Jacobian(第 i 行是 F_i 对各分量的偏导)。"""
n = len(x)
m = len(F(x))
J = []
for i in range(m):
row = []
for j in range(n):
ej = [1.0 if k == j else 0.0 for k in range(n)]
F_p = F(tuple(v + h * w for v, w in zip(x, ej)))
F_m = F(tuple(v - h * w for v, w in zip(x, ej)))
row.append((F_p[i] - F_m[i]) / (2 * h))
J.append(row)
return J
def hessian_numeric(f, x, h=1e-4):
"""中心差分:标量函数 f 的 Hessian(所有二阶偏导,含混合偏导)。"""
n = len(x)
H = []
for i in range(n):
row = []
for j in range(n):
ei = [1.0 if k == i else 0.0 for k in range(n)]
ej = [1.0 if k == j else 0.0 for k in range(n)]
f_pp = f(tuple(v + h * wi + h * wj for v, wi, wj in zip(x, ei, ej)))
f_pm = f(tuple(v + h * wi - h * wj for v, wi, wj in zip(x, ei, ej)))
f_mp = f(tuple(v - h * wi + h * wj for v, wi, wj in zip(x, ei, ej)))
f_mm = f(tuple(v - h * wi - h * wj for v, wi, wj in zip(x, ei, ej)))
row.append((f_pp - f_pm - f_mp + f_mm) / (4 * h * h))
H.append(row)
return H
F = lambda p: (p[0] ** 2, p[0] * p[1])
f = lambda p: p[0] ** 2 + 3 * p[1] ** 2 + 2 * p[0] * p[1]
print("F(x) = [x1^2, x1*x2] 在点 (1, 2) 处的 Jacobian:")
for r in jacobian_numeric(F, (1.0, 2.0)):
print(f" {[round(v, 4) for v in r]}")
print("f(x) = x1^2 + 3*x2^2 + 2*x1*x2 在点 (1, 1) 处的 Hessian:")
for r in hessian_numeric(f, (1.0, 1.0)):
print(f" {[round(v, 4) for v in r]}")F(x) = [x1^2, x1*x2] 在点 (1, 2) 处的 Jacobian:
[2.0, 0.0]
[2.0, 1.0]
f(x) = x1^2 + 3*x2^2 + 2*x1*x2 在点 (1, 1) 处的 Hessian:
[2.0, 2.0]
[2.0, 6.0]数值版与解析解严丝合缝,Hessian 还恰好对称——混合偏导”先 后 等于先 后 “的直观印证。
凸集与凸函数:先集合,后函数
词源先立正:convex 一般叫凸,concave 叫凹,两者方向相反。先看集合的版本——凸集(convex set):一个集合里任取两个点,它们连线上的所有点如果全都还在集合之内,这就是凸集;反过来,只要存在两个点、连线有一段跑出集合,那它就不是凸集合。讲师特意提醒用词:反面不叫”凹集合”——凹凸是函数层面的说法,对集合只说凸与非凸。实心圆、正方形都是凸集;带月牙形缺口的那类集合不是。
集合的凸性用得不多,函数才是主角——凸函数(convex function)为什么值得专门学?讲师一句话点题:凸函数”比较容易被优化”,第 4 章就会讲怎么去优化凸函数,但首先得知道什么是凸函数。中文”凸”的字面容易让人想到向上鼓包,不少同学在这里把方向搞反——判别只认一条铁律:弦在图像上方 = 凸,弦在图像下方 = 凹。
凸函数的严格定义:t 权重与两步比较
怎么把”弦在上方”写成数学?分三步。
第一步,在两个输入之间取一个点。 给定 、,用参数 混合出一个中间点:
完全可以当权重理解: 时结果就是 ; 时就是 ; 时各取一半、正好中点; 时是 0.7 份 加 0.3 份 ——谁的权重大,点就离谁近。讲师原话:“ 呢就像一个权重一样,不断地通过 的大小,在 0 到 1 的范围调整它们之间的距离。”
第二步,把中间点代入函数,得 。
第三步,两端的函数值用同一个 再插一次,得 。
比较第二步与第三步谁小,凸凹立判:
左边不大于右边,是凸函数;不等号反过来(),就是凹函数。为什么第三步是”弦”?讲师把这一点讲透了:、 是图像上的两个端点,第三步按 的比例在两个函数值之间取点——由于 方向的刻度是均匀的,取出来的点与两个端点必然共线; 从 0 走到 1,恰好把连接两端点的这条直线(弦)扫了一遍。所以这套比较的几何本质就一句话,讲师原话:“在两个点之间我们画一条直线,看直线上的点比函数的点是大了还是小了。”
于是哪怕公式全忘了,也能现场判定:给一个函数、给一个区间,取两端点连一条直线——弦在图像上方:凸;弦在图像下方:凹;弦一段在上一段在下:这个区间既不凸也不凹。
凹凸依赖区间:log 凹、 凸
凹凸性是跟着区间走的。 讲师在课上画了一条”不老实”的曲线:A、B 之间看它是凸函数;A、D 之间(跨过 C)看,它又是凹的;还有些区间里弦有上有下——既非严格凸也非严格凹。所以”这函数凸不凸”这个问法本身不完整,必须问”在哪个区间上凸不凸”。判定方法倒是一点不复杂,就是上面那句画弦口诀。
两个要刻进肌肉记忆的判例,讲师课上直接带大家过了一遍:
- log 是凹函数——先在脑子里调出它的形状:增速越来越缓的那条曲线,两端点一连,弦整个被图像压在下面。
- 是凸函数——越来越陡的那条,弦始终浮在图像上方。
再往机器学习里接一句。讲师指出,很多激活函数都喜欢用指数形式来表示,比如 sigmoid,“因为它是个凸函数,容易去求导,而且容易进行优化,容易去沿着梯度找到最优值”。这里按”正确性优先”做一处修正:全局凸的是指数函数 本身;sigmoid 是 S 形——左半段凸、右半段凹,并不是全局凸函数。指数形式给激活函数带来的是”处处光滑、求导方便”,“容易沿着梯度找最优”的大方向没错,但严格判定凹凸,还是得回到画弦。
用代码把定义逐字验一遍:在区间上均匀撒点、两两配对、再取三个 值,检查凸性不等式是否处处成立。返回”最大违反值”——负数(哪怕只负一点点)说明所有弦都守在图像上方,凸性成立;正数说明有弦被图像顶了下去:
import math
def max_chord_violation(f, a, b, m=9):
"""在 [a, b] 上均匀取 m+1 个点,两两配对、再取三个 t 值,
检查凸性定义 f(t*x1+(1-t)*x2) <= t*f(x1)+(1-t)*f(x2)。
返回最大违反值:<= 0 表示所有弦都在图像上方(凸),> 0 表示存在违反。"""
xs = [a + (b - a) * k / m for k in range(m + 1)]
worst = -float('inf')
for i in range(len(xs)):
for j in range(i + 1, len(xs)):
for t in (0.25, 0.5, 0.75):
x_mid = t * xs[i] + (1 - t) * xs[j]
left = f(x_mid)
right = t * f(xs[i]) + (1 - t) * f(xs[j])
worst = max(worst, left - right)
return worst
cases = [
("f(x)=x^2 区间 [0.5, 2]", lambda x: x * x, 0.5, 2.0),
("f(x)=e^x 区间 [-2, 2]", math.exp, -2.0, 2.0),
("f(x)=log x 区间 [0.5, 4]", math.log, 0.5, 4.0),
("f(x)=-log x 区间 [0.5, 4]", lambda x: -math.log(x), 0.5, 4.0),
]
for name, g, a, b in cases:
v = max_chord_violation(g, a, b)
verdict = "凸性成立(弦在图像上方)" if v <= 1e-9 else "凸性被违反(弦被图像顶下去)"
print(f"{name}:最大违反值 = {v:.4f} → {verdict}")f(x)=x^2 区间 [0.5, 2]:最大违反值 = -0.0052 → 凸性成立(弦在图像上方)
f(x)=e^x 区间 [-2, 2]:最大违反值 = -0.0030 → 凸性成立(弦在图像上方)
f(x)=log x 区间 [0.5, 4]:最大违反值 = 0.4917 → 凸性被违反(弦被图像顶下去)
f(x)=-log x 区间 [0.5, 4]:最大违反值 = -0.0010 → 凸性成立(弦在图像上方)结果一目了然: 与 全部通过——弦贴着图像上方;log 的弦被顶开 0.4917——凹得明明白白;而给 log 加个负号, 的每条弦又稳稳落回图像上方——凹函数取负号就是凸函数。这四行输出,正是”画弦口诀”的数值版。
📝 动手练一练
- 概念辨析:已知 在区间 上是凸函数,在 上也是凸函数,那么 在整个 上一定是凸函数吗?
👉 点击查看参考答案
不一定。反例:。它在 上是 (线性,凸),在 上是常数 1(也凸);但在 整体上不凸——取 、、:插值点 ,,而弦的高度 ,图像把弦顶了上去,违反凸性定义。症结在拼接点:左段斜率是 1、右段斜率是 0,斜率在 处”变缓”,弦就守不住图像上方。判定凹凸没有捷径,最终还是回到讲师那句话:看连接两端点的弦是否始终在图像上方。
- 代码实践:写一个函数,在给定区间上找出”违反凸性最严重”的一组点对与 值,用它检验 在 与 两个区间上的表现,并解释你看到的现象。
👉 点击查看参考答案
import math
def worst_chord_pair(f, a, b, m=11):
"""扫描 [a, b] 上的网格点对与若干 t 值,返回凸性违反最严重的一组样例。"""
xs = [a + (b - a) * k / m for k in range(m + 1)]
worst = None
for i in range(len(xs)):
for j in range(i + 1, len(xs)):
for t in (0.2, 0.4, 0.5, 0.6, 0.8):
x_mid = t * xs[i] + (1 - t) * xs[j]
viol = f(x_mid) - (t * f(xs[i]) + (1 - t) * f(xs[j]))
if worst is None or viol > worst[-1]:
worst = (xs[i], xs[j], t, viol)
return worst
for name, a, b in [("sin(x) 在 [0, pi]", 0.0, math.pi),
("sin(x) 在 [pi, 2*pi]", math.pi, 2 * math.pi)]:
xa, xb, t, v = worst_chord_pair(math.sin, a, b)
print(f"{name}:最大违反值 = {v:.4f}")
print(f" 最差点对 x1={xa:.4f}, x2={xb:.4f}, t={t}")
x_mid = t * xa + (1 - t) * xb
chord = t * math.sin(xa) + (1 - t) * math.sin(xb)
print(f" 插值点 x={x_mid:.4f}, f(x)={math.sin(x_mid):.4f}, 弦上高度={chord:.4f}")sin(x) 在 [0, pi]:最大违反值 = 1.0000
最差点对 x1=0.0000, x2=3.1416, t=0.5
插值点 x=1.5708, f(x)=1.0000, 弦上高度=0.0000
sin(x) 在 [pi, 2*pi]:最大违反值 = -0.0007
最差点对 x1=3.1416, x2=3.4272, t=0.8
插值点 x=3.1987, f(x)=-0.0571, 弦上高度=-0.0563上最大违反值恰好是 1.0000,最差点对是 、、——连接两个零点的弦就贴在 x 轴上,而 sin 在中间鼓到 1,弦被整个压在图像下方:凹。换到 ,最大违反值变成负数(-0.0007)——所有弦都守在图像上方:凸。同一个 sin,换半程凹凸互换,这就是”凹凸依赖区间”最直观的演示。
本章小结
第一章到这里收官:线性代数(向量、矩阵、特征值)与函数(包括凸函数凹函数)背后那些直观的道理都过了一遍——讲师原话,“这些以后会随着机器学习用得更多”。带走三件事:输入是向量时,一阶偏导排成 Jacobian、二阶偏导排成对称的 Hessian,是神经网络场景的标配记号;凸函数的判定靠”t 权重插值 + 两步比较”,几何口诀是弦在图像上方=凸、下方=凹,且必须先给定区间;log 凹、 凸两个判例入门,而”凸函数容易被优化、容易沿着梯度找到最优值”的完整兑现,第 4 章梯度下降见。至于概率与统计——讲师原话,“我们就留着下一课再讲”。
📋 行动清单
- 任选一个函数(比如 ),先在不同区间上用”画弦”目测凹凸,再用本节的检查代码验证你的判断
- 把”两段都凸、拼起来不凸”的 反例讲给同学听,说清楚拼接点处发生了什么
- 手算一遍本节 Hessian demo 的解析式(对 、 各求两次偏导),与代码数值输出对照
- 预习下一章概率与统计:从下一节《概率问题》开始
—— 小象教研组
配套资料 · 直接下载
课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。