查看全课大纲(第 5 / 82 节)
  1. 1.函数与数据泛化
  2. 2.向量:从几何直觉到高维空间
  3. 3.矩阵与张量
  4. 4.特征值与特征向量:矩阵变换的不动方向
  5. 5.凸函数:从向量求导的矩阵记号到好优化的函数形状
  6. 6.概率与统计:机器学习的数学基础
  7. 7.独立性与随机变量:贝叶斯公式与二项分布
  8. 8.期望、方差与协方差:随机变量的数字画像
  9. 9.联合概率:当一条轴不够用的时候
  10. 10.概率分布(一):从伯努利到伽马函数
  11. 11.概率分布(二):贝塔、泊松、正态与两个贝叶斯故事
  12. 12.机器学习的模型:找输入与输出的关系
  13. 13.机器学习的应用
  14. 14.过拟合
  15. 15.机器学习的分类
  16. 16.有监督学习:从房价预测说起
  17. 17.损失函数
  18. 18.梯度下降(上):更新规则与学习速率
  19. 19.梯度下降(2)
  20. 20.牛顿方法
  21. 21.KNN简介
  22. 22.距离测算
  23. 23.朴素贝叶斯
  24. 24.朴素贝叶斯的实际应用
  25. 25.信息熵:决策树构建的度量基础
  26. 26.信息增益与决策树的构建
  27. 27.ID3算法
  28. 28.决策树节点的划分
  29. 29.CART树
  30. 30.Bagging
  31. 31.Boosting:自适应提升算法
  32. 32.Gradient Boosting
  33. 33.线性模型:回归与分类的同一副骨架
  34. 34.最小二乘:把最优参数直接解出来
  35. 35.最小二乘法的几何与概率视角
  36. 36.极大似然:给最小二乘一个概率出身
  37. 37.逻辑回归(1):把线性模型接到分类上
  38. 38.逻辑回归(2):损失函数与多分类
  39. 39.---- 手造数据: 5 个样本, 2 个特征; x2 几乎 = 2*x1 (偏差手工指定, 无随机) ----
  40. 40.讲师例: 向量 "23014" -- 三把范数尺子量同一个向量
  41. 41.优化方法(1):坐标下降与前向选择
  42. 42.优化方法(2):前向策略与最小角回归
  43. 43.拉格朗日乘子法(1):从无约束到等式约束
  44. 44.拉格朗日乘子法(2):等式约束下的平行条件与拉格朗日函数
  45. 45.拉格朗日乘子法(3):不等式约束与 KKT 条件
  46. 46.最大间隔:把分类问题变成优化问题
  47. 47.拉格朗日对偶问题:把 w 和 b 全部换成 α
  48. 48.支持向量:边界上的少数派
  49. 49.SVM 计算过程:一笔到底算完,再走进核方法
  50. 50.贝叶斯公式:概率即信念
  51. 51.隐含变量和概率模型:给看不见的原因建模
  52. 52.EM 算法:期望与最大化的迭代
  53. 53.琴生不等式:EM 的数学钥匙
  54. 54.三个硬币问题:EM 的复盘与衔接
  55. 55.高斯混合模型:用隐变量解释双峰数据
  56. 56.隐马尔可夫模型
  57. 57.隐马尔可夫模型(2):前向算法与动态混合模型
  58. 58.隐马尔可夫模型在 NLP 中的运用
  59. 59.EM 算法:把「为什么收敛」推到底
  60. 60.HMM 在 NLP 中的运用:从分词到机器翻译
  61. 61.HMM 参数估计:Baum-Welch 的三块拉格朗日
  62. 62.高维下的高斯模型
  63. 63.主题模型:从词袋到隐含主题
  64. 64.Dirichlet 分布(1)
  65. 65.Dirichlet 分布(2)
  66. 66.统计学习内容回顾
  67. 67.K-means 算法
  68. 68.PCA 降维
  69. 69.SVD 降维
  70. 70.从聚类到流形
  71. 71.局部线性嵌入
  72. 72.随机邻域嵌入:SNE 与 t-SNE
  73. 73.无参模型:让数据自己说话
  74. 74.高斯过程:函数空间里的正态分布
  75. 75.高斯过程(2):噪声、正则化与核的线性组合
  76. 76.神经网络简介:从感知器到自编码器
  77. 77.反向传播:把误差一层层传回去
  78. 78.卷积神经网络(CNN):让特征自己学出来
  79. 79.循环神经网络(RNN):给网络装上记忆
  80. 80.深度学习回顾:静态一类,动态一类
  81. 81.增强学习(1):蒙特卡洛、树搜索与 Q-learning
  82. 82.强化学习(2):从乌鸦吃核桃到井字棋的 value 回传

凸函数:从向量求导的矩阵记号到好优化的函数形状

约 11 分钟

📺 正在播放小象官方高清录播(支持倍速与清晰度调节)

小象实战讲义 · 机器学习算法精讲

第一章收官。前几节把函数、向量、矩阵、特征值都过了一遍,本节补上最后两块基石:一是当输入从单个数变成向量时,求导该怎么记——Jacobian 矩阵与 Hessian 矩阵;二是机器学习最爱的一类”好优化”的函数形状——凸函数与凹函数。这两件事在神经网络与模型优化里天天见面,第 4 章讲梯度下降时会正式兑现”凸为什么好优化”。

💡 核心导读

  • 向量求导的矩阵记号:一阶偏导排成 Jacobian,二阶偏导排成 Hessian——参数动辄上百维,矩阵”把所有可能性都考虑到”。
  • 凸集与凸函数:集合内任两点连线不出界叫凸集;函数图像上任两点的弦始终在图像上方,叫凸函数。
  • t 权重插值:tx1+(1−t)x2t\mathbf{x}_1+(1-t)\mathbf{x}_2 按”权重”在两点之间取点;凸性比较的是”插值点的函数值”与”函数值的插值”谁大谁小。
  • 凹凸依赖区间:同一个函数换个区间可能凹凸互换——判定前必须先给定区间。
  • 凸=优化友好:log 凹、exe^x 凸;凸函数容易被优化、容易沿着梯度找到最优值,第 4 章展开。

输入是向量时怎么求导:Jacobian 与 Hessian

机器学习里的函数,输入往往不是一个数,而是一个向量 x=[x1,x2,…,xn]T\mathbf{x} = [x_1, x_2, \dots, x_n]^T。这时”求导”不再是求一条导数——要对它的变量求导,就得对每一个分量都求一遍。

先看标量函数 ff。把 ff 对所有分量的一阶偏导排成一列,就是它的梯度(gradient):

∇f(x)=[∂f∂x1,…,∂f∂xn]T\nabla f(\mathbf{x}) = \left[ \frac{\partial f}{\partial x_1}, \dots, \frac{\partial f}{\partial x_n} \right]^T

把这套想法推广到”输出也是向量”的函数 F(x)=[F1(x),…,Fm(x)]T\mathbf{F}(\mathbf{x}) = [F_1(\mathbf{x}), \dots, F_m(\mathbf{x})]^T:每一个 FiF_i 都要对每一个分量 xjx_j 求一次偏导,m×nm \times n 个偏导排成一张矩阵——这就是雅可比矩阵(Jacobian matrix),第 ii 行第 jj 列的元素是

Jij=∂Fi∂xjJ_{ij} = \frac{\partial F_i}{\partial x_j}

标量函数的 Jacobian 只有一行,正好是梯度的转置——Jacobian 是”一阶导数”的完整家谱,梯度是它的特例。

再往下一步——不求一阶,求二阶。对标量函数 ff,把所有二阶偏导 ∂2f/∂xi∂xj\partial^2 f / \partial x_i \partial x_j 排成 n×nn \times n 矩阵,就是海森矩阵(Hessian matrix)HH:

  • 对角线上 i=ji = j:对同一个变量连着求两次导,就是普通的二阶导数 ∂2f/∂xi2\partial^2 f / \partial x_i^2;
  • 对角线外 i≠ji \ne j:先对 xix_i 求导、再对 xjx_j 求导,两次求导分别落在两个不同分量上——这就是混合偏导。二阶偏导连续时,“先 ii 后 jj“与”先 jj 后 ii“结果相同,所以 Hessian 是对称矩阵。

讲师强调这套记号的用武之地:尤其到了神经网络的部分,“每一个部分的表述全是向量”——成百上千个参数要逐个求导,一个个分量去抠太琐碎;写成矩阵,“就把所有的可能性都考虑到”,用一种特别漂亮的方式一次表达完——虽然看着有点复杂。至于这套记号怎么发力:导数、微分与优化的正式讨论在第 4 章(梯度下降与牛顿法)。

下面的代码用”数值差分”实算一次:对某个分量挪一小步、看函数值变多少,近似出偏导,把 Jacobian 与 Hessian 都算出来。J 的解析式是 [[2*x1, 0], [x2, x1]],代入点 (1,2)(1, 2) 应得 [[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 还恰好对称——混合偏导”先 ii 后 jj 等于先 jj 后 ii“的直观印证。

凸集与凸函数:先集合,后函数

词源先立正:convex 一般叫凸,concave 叫凹,两者方向相反。先看集合的版本——凸集(convex set):一个集合里任取两个点,它们连线上的所有点如果全都还在集合之内,这就是凸集;反过来,只要存在两个点、连线有一段跑出集合,那它就不是凸集合。讲师特意提醒用词:反面不叫”凹集合”——凹凸是函数层面的说法,对集合只说凸与非凸。实心圆、正方形都是凸集;带月牙形缺口的那类集合不是。

集合的凸性用得不多,函数才是主角——凸函数(convex function)为什么值得专门学?讲师一句话点题:凸函数”比较容易被优化”,第 4 章就会讲怎么去优化凸函数,但首先得知道什么是凸函数。中文”凸”的字面容易让人想到向上鼓包,不少同学在这里把方向搞反——判别只认一条铁律:弦在图像上方 = 凸,弦在图像下方 = 凹。

凸函数的严格定义:t 权重与两步比较

怎么把”弦在上方”写成数学?分三步。

第一步,在两个输入之间取一个点。 给定 x1\mathbf{x}_1、x2\mathbf{x}_2,用参数 tt 混合出一个中间点:

t x1+(1−t) x2,0≤t≤1t\,\mathbf{x}_1 + (1-t)\,\mathbf{x}_2, \quad 0 \le t \le 1

tt 完全可以当权重理解:t=1t=1 时结果就是 x1\mathbf{x}_1;t=0t=0 时就是 x2\mathbf{x}_2;t=0.5t=0.5 时各取一半、正好中点;t=0.7t=0.7 时是 0.7 份 x1\mathbf{x}_1 加 0.3 份 x2\mathbf{x}_2——谁的权重大,点就离谁近。讲师原话:“tt 呢就像一个权重一样,不断地通过 tt 的大小,在 0 到 1 的范围调整它们之间的距离。”

第二步,把中间点代入函数,得 f(t x1+(1−t) x2)f\big(t\,\mathbf{x}_1+(1-t)\,\mathbf{x}_2\big)。

第三步,两端的函数值用同一个 tt 再插一次,得 t f(x1)+(1−t) f(x2)t\,f(\mathbf{x}_1)+(1-t)\,f(\mathbf{x}_2)。

比较第二步与第三步谁小,凸凹立判:

f(t x1+(1−t) x2)≤t f(x1)+(1−t) f(x2)f\big(t\,\mathbf{x}_1 + (1-t)\,\mathbf{x}_2\big) \le t\,f(\mathbf{x}_1) + (1-t)\,f(\mathbf{x}_2)

左边不大于右边,是凸函数;不等号反过来(≥\ge),就是凹函数。为什么第三步是”弦”?讲师把这一点讲透了:(x1,f(x1))(\mathbf{x}_1, f(\mathbf{x}_1))、(x2,f(x2))(\mathbf{x}_2, f(\mathbf{x}_2)) 是图像上的两个端点,第三步按 tt 的比例在两个函数值之间取点——由于 yy 方向的刻度是均匀的,取出来的点与两个端点必然共线;tt 从 0 走到 1,恰好把连接两端点的这条直线(弦)扫了一遍。所以这套比较的几何本质就一句话,讲师原话:“在两个点之间我们画一条直线,看直线上的点比函数的点是大了还是小了。”

于是哪怕公式全忘了,也能现场判定:给一个函数、给一个区间,取两端点连一条直线——弦在图像上方:凸;弦在图像下方:凹;弦一段在上一段在下:这个区间既不凸也不凹。

凹凸依赖区间:log 凹、exe^x 凸

凹凸性是跟着区间走的。 讲师在课上画了一条”不老实”的曲线:A、B 之间看它是凸函数;A、D 之间(跨过 C)看,它又是凹的;还有些区间里弦有上有下——既非严格凸也非严格凹。所以”这函数凸不凸”这个问法本身不完整,必须问”在哪个区间上凸不凸”。判定方法倒是一点不复杂,就是上面那句画弦口诀。

两个要刻进肌肉记忆的判例,讲师课上直接带大家过了一遍:

  • log 是凹函数——先在脑子里调出它的形状:增速越来越缓的那条曲线,两端点一连,弦整个被图像压在下面。
  • exe^x 是凸函数——越来越陡的那条,弦始终浮在图像上方。

再往机器学习里接一句。讲师指出,很多激活函数都喜欢用指数形式来表示,比如 sigmoid,“因为它是个凸函数,容易去求导,而且容易进行优化,容易去沿着梯度找到最优值”。这里按”正确性优先”做一处修正:全局凸的是指数函数 exe^x 本身;sigmoid 是 S 形——左半段凸、右半段凹,并不是全局凸函数。指数形式给激活函数带来的是”处处光滑、求导方便”,“容易沿着梯度找最优”的大方向没错,但严格判定凹凸,还是得回到画弦。

用代码把定义逐字验一遍:在区间上均匀撒点、两两配对、再取三个 tt 值,检查凸性不等式是否处处成立。返回”最大违反值”——负数(哪怕只负一点点)说明所有弦都守在图像上方,凸性成立;正数说明有弦被图像顶了下去:

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 → 凸性成立(弦在图像上方)

结果一目了然:x2x^2 与 exe^x 全部通过——弦贴着图像上方;log 的弦被顶开 0.4917——凹得明明白白;而给 log 加个负号,−log⁡x-\log x 的每条弦又稳稳落回图像上方——凹函数取负号就是凸函数。这四行输出,正是”画弦口诀”的数值版。

📝 动手练一练

  1. 概念辨析:已知 ff 在区间 [a,c][a, c] 上是凸函数,在 [c,b][c, b] 上也是凸函数,那么 ff 在整个 [a,b][a, b] 上一定是凸函数吗?
👉 点击查看参考答案

不一定。反例:f(x)=min⁡(x,1)f(x) = \min(x, 1)。它在 [0,1][0, 1] 上是 f(x)=xf(x) = x(线性,凸),在 [1,2][1, 2] 上是常数 1(也凸);但在 [0,2][0, 2] 整体上不凸——取 x1=0x_1=0、x2=2x_2=2、t=0.5t=0.5:插值点 x=1x=1,f(1)=1f(1)=1,而弦的高度 =0.5⋅f(0)+0.5⋅f(2)=0.5⋅0+0.5⋅1=0.5= 0.5 \cdot f(0) + 0.5 \cdot f(2) = 0.5 \cdot 0 + 0.5 \cdot 1 = 0.5,图像把弦顶了上去,违反凸性定义。症结在拼接点:左段斜率是 1、右段斜率是 0,斜率在 cc 处”变缓”,弦就守不住图像上方。判定凹凸没有捷径,最终还是回到讲师那句话:看连接两端点的弦是否始终在图像上方。

  1. 代码实践:写一个函数,在给定区间上找出”违反凸性最严重”的一组点对与 tt 值,用它检验 sin⁡(x)\sin(x) 在 [0,π][0, \pi] 与 [π,2π][\pi, 2\pi] 两个区间上的表现,并解释你看到的现象。
👉 点击查看参考答案
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

[0,π][0, \pi] 上最大违反值恰好是 1.0000,最差点对是 x1=0x_1=0、x2=πx_2=\pi、t=0.5t=0.5——连接两个零点的弦就贴在 x 轴上,而 sin 在中间鼓到 1,弦被整个压在图像下方:凹。换到 [π,2π][\pi, 2\pi],最大违反值变成负数(-0.0007)——所有弦都守在图像上方:凸。同一个 sin,换半程凹凸互换,这就是”凹凸依赖区间”最直观的演示。

本章小结

第一章到这里收官:线性代数(向量、矩阵、特征值)与函数(包括凸函数凹函数)背后那些直观的道理都过了一遍——讲师原话,“这些以后会随着机器学习用得更多”。带走三件事:输入是向量时,一阶偏导排成 Jacobian、二阶偏导排成对称的 Hessian,是神经网络场景的标配记号;凸函数的判定靠”t 权重插值 + 两步比较”,几何口诀是弦在图像上方=凸、下方=凹,且必须先给定区间;log 凹、exe^x 凸两个判例入门,而”凸函数容易被优化、容易沿着梯度找到最优值”的完整兑现,第 4 章梯度下降见。至于概率与统计——讲师原话,“我们就留着下一课再讲”。

📋 行动清单

  • 任选一个函数(比如 f(x)=x3−3xf(x) = x^3 - 3x),先在不同区间上用”画弦”目测凹凸,再用本节的检查代码验证你的判断
  • 把”两段都凸、拼起来不凸”的 min⁡(x,1)\min(x, 1) 反例讲给同学听,说清楚拼接点处发生了什么
  • 手算一遍本节 Hessian demo 的解析式(对 x1x_1、x2x_2 各求两次偏导),与代码数值输出对照
  • 预习下一章概率与统计:从下一节《概率问题》开始

—— 小象教研组

配套资料 · 直接下载

课件、代码、数据集等配套资料全部免费,点击即可直接下载,无需分享或解锁。

  • 第1章课件:机器学习的数学基础 I
    下载
  • 第1章笔记:机器学习导论
    下载
  • 第1章笔记:行列式的几何解释
    下载
  • 第1章笔记:Lp 范数
    下载