feat: 完整中文翻译 maths-cs-ai-compendium(数学·计算机科学·AI 知识大全)
翻译自英文原版 maths-cs-ai-compendium,共 20 章全部完成。 第01章 向量 | 第02章 矩阵 | 第03章 微积分 第04章 统计学 | 第05章 概率论 | 第06章 机器学习 第07章 计算语言学 | 第08章 计算机视觉 | 第09章 音频与语音 第10章 多模态学习 | 第11章 自主系统 | 第12章 图神经网络 第13章 计算与操作系统 | 第14章 数据结构与算法 第15章 生产级软件工程 | 第16章 SIMD与GPU编程 第17章 AI推理 | 第18章 ML系统设计 第19章 应用人工智能 | 第20章 前沿人工智能 翻译说明: - 所有数学公式 $...$ / $$...$$、代码块、图片引用完整保留 - mkdocs.yml 配置中文导航 + language: zh - README.md 已翻译为中文(兼 docs/index.md) - docs/ 目录包含指向各章文件的 symlink - 约 29,000 行中文内容,排除 .cache/ 构建缓存
This commit is contained in:
@@ -0,0 +1,205 @@
|
||||
# 微分
|
||||
|
||||
*微分学研究瞬时变化率。本节涵盖极限、导数、微分法则、链式法则(反向传播的基础),以及机器学习中常用的导数。*
|
||||
|
||||
- 在前面的章节中,我们学会了如何将数据表示为向量,并用矩阵对其进行变换。但现实世界中的许多现象并非静止不变的。汽车在加速,股价在波动,神经网络的损失随着权重的更新而变化。**微积分**是研究变化的数学。
|
||||
|
||||
- 微积分回答两个问题:某个量在当前时刻变化得有多快?(微分学)以及它在一段时间内累积了多少?(积分学)。本节讨论的是"多快"的问题。
|
||||
|
||||
- 想象一下你正在开车,瞥了一眼车速表。上面显示 60 km/h。这个数字不是你整个行程的平均速度,而是你在这一瞬间的瞬时速度。微分学为我们提供了计算这种瞬时变化率的工具。
|
||||
|
||||
- 但首先,让我们回顾一下直线方程:$y = mx + b$。
|
||||
|
||||
- 这是两个量之间最简单的关系。
|
||||
|
||||
- $b$ 是 **y 截距**,即直线与 y 轴的交点(当 $x = 0$ 时的起始值)。
|
||||
- $m$ 是**斜率**,即变化率:$x$ 每增加 1 个单位,$y$ 就变化 $m$ 个单位。
|
||||
- 如果 $m = 3$,直线陡峭上升;如果 $m = 0$,直线水平;如果 $m = -2$,直线下降。
|
||||
|
||||
- 斜率计算公式为 $m = \frac{\Delta y}{\Delta x} = \frac{y_2 - y_1}{x_2 - x_1}$,即"$y$ 变化了多少"与"$x$ 变化了多少"的比值。
|
||||
|
||||

|
||||
|
||||
- 一旦知道了 $m$ 和 $b$,就可以计算任意 $x$ 对应的 $y$ 值。
|
||||
|
||||
- 例如,若 $m = 2$ 且 $b = 3$,则在 $x = 5$ 处:$y = 2(5) + 3 = 13$。
|
||||
|
||||
- 这两个参数完全决定了这条直线,预测任何输出只需代入公式即可。
|
||||
|
||||
- 对于直线,斜率处处相同。
|
||||
|
||||
- 这一思想可以推广到直线之外。任何函数都是一个将输入映射到输出的规则,一旦知道了它的公式(参数和形状),就可以计算任意输入对应的输出,并将结果绘制成图。
|
||||
|
||||
- $y = x^2$ 给出抛物线,$y = \sin(x)$ 给出波形,$y = e^x$ 给出指数增长。每个公式都定义了一条特定的曲线,能够熟练地将函数理解为一种形状,对于后续内容至关重要。
|
||||
|
||||
- 对于直线,斜率处处相同。但大多数有趣的函数都是弯曲的,因此斜率在不同点处各不相同。微积分给了我们一种方法来求曲线上任意一点的斜率。
|
||||
|
||||
- 我们还需要**极限**的概念。极限描述的是当输入越来越接近某个目标值时,函数趋近于什么值,而不一定非要达到该值。
|
||||
|
||||
$$\lim_{x \to a} f(x) = L$$
|
||||
|
||||
- 这读作:"当 $x$ 趋近于 $a$ 时,$f(x)$ 趋近于 $L$。"函数在 $x = a$ 处不一定等于 $L$,只需无限接近即可。
|
||||
|
||||
- 例如,考虑 $f(x) = \frac{x^2 - 1}{x - 1}$。如果直接代入 $x = 1$,会得到 $\frac{0}{0}$,这是未定义的。
|
||||
|
||||
- 但尝试接近 1 的值:$f(0.9) = 1.9$,$f(0.99) = 1.99$,$f(1.01) = 2.01$。输出显然朝着 2 靠近。
|
||||
|
||||
- 从代数角度看,我们可以理解原因:将分子因式分解为 $(x-1)(x+1)$,约去 $(x-1)$ 项,对于所有 $x \neq 1$ 得到 $f(x) = x + 1$。因此当 $x \to 1$ 时,$f(x) \to 2$。
|
||||
|
||||
- 该函数在 $x = 1$ 处有一个空洞,但极限仍然存在。
|
||||
|
||||
- 极限是微积分中其他一切内容的基础。
|
||||
|
||||
- 函数 $f(x)$ 在点 $x = a$ 处的**导数**衡量的是瞬时变化率。从几何角度看,它是该点处曲线切线的斜率。
|
||||
|
||||

|
||||
|
||||
- 要计算这个斜率,我们首先在曲线上取两个点,计算通过这两个点的直线(**割线**)的斜率。然后让第二个点逐渐靠近第一个点,观察割线的斜率趋近于什么值。这就是**差商**:
|
||||
|
||||
$$f'(a) = \lim_{h \to 0} \frac{f(a + h) - f(a)}{h}$$
|
||||
|
||||

|
||||
|
||||
- 分子 $f(a+h) - f(a)$ 是输出的变化量。分母 $h$ 是输入的变化量。它们的比值是在一个极小区间上的平均变化率。当 $h \to 0$ 时,这个平均值就变成了瞬时变化率。
|
||||
|
||||
- 例如,设 $f(x) = x^2$。在 $x = 3$ 处:
|
||||
|
||||
$$f'(3) = \lim_{h \to 0} \frac{(3+h)^2 - 9}{h} = \lim_{h \to 0} \frac{9 + 6h + h^2 - 9}{h} = \lim_{h \to 0} (6 + h) = 6$$
|
||||
|
||||
- 因此在 $x = 3$ 处,函数 $x^2$ 以每单位输入变化 6 单位输出的速率增加。
|
||||
|
||||
- 如果这个极限存在,则称函数在该点是**可微**的。要做到这一点,函数必须连续(没有跳跃)、光滑(没有尖角),并且在点附近有定义。
|
||||
|
||||
- 如果你能笔不离纸地画出曲线,且没有任何折点,那么它在该点很可能是可微的。
|
||||
|
||||
- 每次都从极限定义出发计算导数会很繁琐。幸运的是,少数几条法则就能让我们快速微分几乎任何函数。
|
||||
|
||||
- **常数法则**:常数的导数为零。若 $f(x) = 5$,则 $f'(x) = 0$。水平线的斜率为零。
|
||||
|
||||
- **幂法则**:微分的主力法则。将指数提到前面,然后将指数减一:
|
||||
|
||||
$$\frac{d}{dx} x^n = n x^{n-1}$$
|
||||
|
||||
- 例如:$\frac{d}{dx} x^3 = 3x^2$。三次函数变成了二次函数。该法则适用于任何实数指数,包括负数和分数:$\frac{d}{dx} x^{-1} = -x^{-2}$ 以及 $\frac{d}{dx} \sqrt{x} = \frac{d}{dx} x^{1/2} = \frac{1}{2}x^{-1/2}$。
|
||||
|
||||
- **和/差法则**:逐项求导。
|
||||
|
||||
$$\frac{d}{dx}[f(x) \pm g(x)] = f'(x) \pm g'(x)$$
|
||||
|
||||
- **乘积法则**:当两个函数相乘时,导数并非简单地将各自的导数相乘。而是:
|
||||
|
||||
$$\frac{d}{dx}[f(x) \cdot g(x)] = f'(x)g(x) + f(x)g'(x)$$
|
||||
|
||||
- 可以这样理解:"第一个的变化率乘以第二个,加上第一个乘以第二个的变化率。"例如,$\frac{d}{dx}[x^2 \sin x] = 2x \sin x + x^2 \cos x$。
|
||||
|
||||
- **商法则**:对于函数的比值:
|
||||
|
||||
$$\frac{d}{dx}\left[\frac{f(x)}{g(x)}\right] = \frac{f'(x)g(x) - f(x)g'(x)}{[g(x)]^2}$$
|
||||
|
||||
- 一个有用的记忆口诀:"上导下不导减去上不导下导,除以分母的平方。"
|
||||
|
||||
- **链式法则**:对机器学习最重要的法则。当函数复合(一个函数嵌套在另一个函数内部)时,导数等于沿链各导数的乘积:
|
||||
|
||||
$$\frac{d}{dx} f(g(x)) = f'(g(x)) \cdot g'(x)$$
|
||||
|
||||
- 可以把它想象成剥洋葱。先对外层函数求导(内层函数保持不变),然后乘以内层函数的导数。
|
||||
|
||||

|
||||
|
||||
- 例如,$\frac{d}{dx} (3x + 1)^5 = 5(3x+1)^4 \cdot 3 = 15(3x+1)^4$。外层函数是 $(\cdot)^5$,内层是 $3x+1$。
|
||||
|
||||
- 链式法则是神经网络中**反向传播**的数学基础。一个深层网络就是一个由多个复合函数组成的长链。要计算损失相对于每个权重的变化率,我们从输出层开始逐层向输入层反复应用链式法则,将每一步的局部导数相乘。
|
||||
|
||||
- 以下是你会遇到的最常见导数。每一个都可以从极限定义推导出来,但熟记它们可以节省时间:
|
||||
|
||||
| 函数 | 导数 | 备注 |
|
||||
|---|---|---|
|
||||
| $e^x$ | $e^x$ | 唯一一个导数等于自身的函数 |
|
||||
| $a^x$ | $a^x \ln a$ | 指数函数的一般形式 |
|
||||
| $\ln x$ | $\frac{1}{x}$ | 自然对数 |
|
||||
| $\log_a x$ | $\frac{1}{x \ln a}$ | 一般对数 |
|
||||
| $\sin x$ | $\cos x$ | |
|
||||
| $\cos x$ | $-\sin x$ | 注意负号 |
|
||||
| $\tan x$ | $\sec^2 x$ | |
|
||||
|
||||
- 指数函数 $e^x$ 非常特别:它是唯一一个导数等于自身的函数。这就是为什么 $e$ 在机器学习中无处不在,从 softmax 激活函数到概率分布都能见到它的身影。
|
||||
|
||||
- **洛必达法则**用于处理形如 $\frac{0}{0}$ 或 $\frac{\infty}{\infty}$ 的不定式极限。当直接代入得到这类形式时,可以分别对分子和分母求导,然后再次尝试求极限:
|
||||
|
||||
$$\lim_{x \to a} \frac{f(x)}{g(x)} = \lim_{x \to a} \frac{f'(x)}{g'(x)}$$
|
||||
|
||||
- 条件:$f$ 和 $g$ 都必须在 $a$ 附近可微,并且 $g'(x)$ 在 $a$ 附近(可能除去 $a$ 本身)不为零。原极限必须是不定式。
|
||||
|
||||
- 例如:$\lim_{x \to 0} \frac{\sin x}{x}$。直接代入得到 $\frac{0}{0}$。应用洛必达法则:$\lim_{x \to 0} \frac{\cos x}{1} = 1$。这个极限是基础的,在信号处理和傅里叶分析中都会出现。
|
||||
|
||||
- 如果结果仍然是不定式,可以反复应用该法则。例如,$\lim_{x \to 0} \frac{1 - \cos x}{x^2}$ 得到 $\frac{0}{0}$。第一次应用:$\lim_{x \to 0} \frac{\sin x}{2x}$,仍然是 $\frac{0}{0}$。第二次应用:$\lim_{x \to 0} \frac{\cos x}{2} = \frac{1}{2}$。
|
||||
|
||||
- 如果两个函数是可微的,那么它们的和、差、积、复合以及商(分母不为零时)也是可微的。这就是为什么我们可以自信地对由简单部分组成的复杂表达式进行微分。
|
||||
|
||||
## 编程练习(使用 CoLab 或 notebook)
|
||||
|
||||
1. 可视化常见函数。在同一张图中绘制 $x^2$、$\sin(x)$ 和 $e^x$,建立对不同公式产生不同形状的直观感受。尝试修改参数(例如 $2x^2$、$\sin(2x)$),观察曲线如何变化。
|
||||
```python
|
||||
import jax.numpy as jnp
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
x = jnp.linspace(-3, 3, 300)
|
||||
|
||||
fig, axes = plt.subplots(1, 3, figsize=(12, 3))
|
||||
axes[0].plot(x, x**2, color="#e74c3c")
|
||||
axes[0].set_title("x² (抛物线)")
|
||||
axes[1].plot(x, jnp.sin(x), color="#3498db")
|
||||
axes[1].set_title("sin(x) (波形)")
|
||||
axes[2].plot(x, jnp.exp(x), color="#27ae60")
|
||||
axes[2].set_title("eˣ (指数函数)")
|
||||
for ax in axes:
|
||||
ax.axhline(0, color="gray", linewidth=0.5)
|
||||
ax.axvline(0, color="gray", linewidth=0.5)
|
||||
plt.tight_layout()
|
||||
plt.show()
|
||||
```
|
||||
|
||||
2. 使用 JAX 的自动微分计算 $f(x) = x^3 - 2x + 1$ 在若干点处的导数,并与解析导数 $f'(x) = 3x^2 - 2$ 进行比较。
|
||||
```python
|
||||
import jax
|
||||
import jax.numpy as jnp
|
||||
|
||||
f = lambda x: x**3 - 2*x + 1
|
||||
df = jax.grad(f)
|
||||
|
||||
for x in [0.0, 1.0, 2.0, -1.0]:
|
||||
print(f"x={x:5.1f} 自动微分: {df(x):.4f} 解析解: {3*x**2 - 2:.4f}")
|
||||
```
|
||||
|
||||
2. 数值验证链式法则。定义 $f(x) = \sin(x^2)$,通过 `jax.grad` 计算其导数,并与解析结果 $2x\cos(x^2)$ 进行比较。
|
||||
```python
|
||||
import jax
|
||||
import jax.numpy as jnp
|
||||
|
||||
f = lambda x: jnp.sin(x**2)
|
||||
df = jax.grad(f)
|
||||
|
||||
for x in [0.5, 1.0, 2.0]:
|
||||
auto = df(x)
|
||||
analytical = 2*x * jnp.cos(x**2)
|
||||
print(f"x={x:.1f} 自动微分: {auto:.6f} 解析解: {analytical:.6f}")
|
||||
```
|
||||
|
||||
3. 可视化导数。将 $f(x) = x^3 - 3x$ 与其导数 $f'(x) = 3x^2 - 3$ 绘制在同一张图上。观察 $f'(x) = 0$ 的位置与 $f$ 的峰谷之间的对应关系。
|
||||
```python
|
||||
import jax
|
||||
import jax.numpy as jnp
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
f = lambda x: x**3 - 3*x
|
||||
# jax.grad 用于标量;jax.vmap 将其向量化,可同时处理一组输入
|
||||
df = jax.vmap(jax.grad(f))
|
||||
|
||||
x = jnp.linspace(-2.5, 2.5, 200)
|
||||
plt.plot(x, jax.vmap(f)(x), label="f(x)")
|
||||
plt.plot(x, df(x), label="f'(x)", linestyle="--")
|
||||
plt.axhline(0, color="gray", linewidth=0.5)
|
||||
plt.legend()
|
||||
plt.title("函数及其导数")
|
||||
plt.show()
|
||||
```
|
||||
@@ -0,0 +1,110 @@
|
||||
# 积分学
|
||||
|
||||
*积分学在区间上累积量,将局部变化率还原为总量。本文涵盖定积分与不定积分、微积分基本定理、积分技巧,以及在机器学习中与概率密度和期望值的应用。*
|
||||
|
||||
- 微分告诉我们单个点的变化率。积分则相反:它将许多微小片段累积起来,计算出一个总量。
|
||||
|
||||
- 如果导数回答的是"多快?",那么积分回答的是"多少?"
|
||||
|
||||
- 理解积分最简单的方式是将其视为**曲线下的面积**。如果绘制出函数 $f(x)$ 的图像,并将从 $x = a$ 到 $x = b$ 之间曲线与 x 轴之间的区域涂上阴影,积分给出的就是该区域的有符号面积。
|
||||
|
||||

|
||||
|
||||
- 为什么是"有符号"的?在 x 轴上方的区域贡献正面积,在下方的区域贡献负面积。这在物理上是有意义的:如果 $f(x)$ 代表速度,积分给出的是净位移(正向减去反向),而不是总路程。
|
||||
|
||||
- 为了计算这个面积,想象将区域切成 $n$ 个细长的竖直矩形,每个矩形的宽度为 $\Delta x$。每个矩形的高度是该切片内某一点的函数值。将它们求和:
|
||||
|
||||
$$\text{面积} \approx \sum_{i=1}^{n} f(x_i^\ast) \, \Delta x$$
|
||||
|
||||
- 当我们让矩形越来越薄时($n \to \infty$,$\Delta x \to 0$),这个和就变得精确。这个极限过程定义了**定积分**:
|
||||
|
||||
$$\int_a^b f(x)\, dx = \lim_{n \to \infty} \sum_{i=1}^{n} f(x_i^\ast) \, \Delta x$$
|
||||
|
||||
- $\int$ 符号是拉长的"S",代表"求和"(Sum)。$dx$ 提醒我们,我们是在沿 x 轴方向对无穷薄的切片求和。
|
||||
|
||||
- **不定积分**(或**原函数**)是一个函数 $F(x)$,其导数为 $f(x)$。我们写作:
|
||||
|
||||
$$\int f(x)\, dx = F(x) + C$$
|
||||
|
||||
- $+ C$ 是**积分常数**。因为任何常数的导数都是零,所以存在无穷多个仅相差一个常数的原函数。例如,$\int 2x\, dx = x^2 + C$,因为 $x^2 + 7$ 或 $x^2 - 3$ 的导数仍然是 $2x$。
|
||||
|
||||
- **微积分基本定理**是连接微分与积分的桥梁。它包含两部分:
|
||||
|
||||
- **第一部分**:如果 $F(x)$ 是 $f(x)$ 的一个原函数,那么定积分等于 $F$ 在端点处的值之差:
|
||||
|
||||
$$\int_a^b f(x)\, dx = F(b) - F(a)$$
|
||||
|
||||
- 这非常实用。我们不再需要计算一个和的极限(这很困难),而是找到一个原函数并在两点处求值(这通常很简单)。
|
||||
|
||||
- **第二部分**:如果我们定义 $F(x) = \int_a^x f(t)\, dt$,那么 $F'(x) = f(x)$。微分与积分是互逆运算,它们相互抵消。
|
||||
|
||||
- 例如,计算 $\int_1^3 x^2\, dx$:$x^2$ 的原函数是 $\frac{x^3}{3}$。所以 $\int_1^3 x^2\, dx = \frac{27}{3} - \frac{1}{3} = \frac{26}{3} \approx 8.67$。
|
||||
|
||||
- 正如微分有运算法则一样,积分也有相应的逆向运算法则:
|
||||
|
||||
| 函数 | 积分 | 条件 |
|
||||
|---|---|---|
|
||||
| $x^n$ | $\frac{x^{n+1}}{n+1} + C$ | $n \neq -1$ |
|
||||
| $\frac{1}{x}$ | $\ln\|x\| + C$ | |
|
||||
| $e^x$ | $e^x + C$ | |
|
||||
| $a^x$ | $\frac{a^x}{\ln a} + C$ | |
|
||||
| $\sin x$ | $-\cos x + C$ | |
|
||||
| $\cos x$ | $\sin x + C$ | |
|
||||
| $k$(常数) | $kx + C$ | |
|
||||
|
||||
- **和/差法则**同样适用:$\int [f(x) \pm g(x)]\, dx = \int f(x)\, dx \pm \int g(x)\, dx$。常数可以提出来:$\int k\, f(x)\, dx = k \int f(x)\, dx$。
|
||||
|
||||
- 当一个函数太复杂而无法直接积分时,我们有简化它的技巧。
|
||||
|
||||
- **换元积分法(u 代换)**是链式法则的逆过程。如果发现一个复合函数 $f(g(x))$ 乘以 $g'(x)$,则令 $u = g(x)$,于是 $du = g'(x)\, dx$,积分得以简化。
|
||||
|
||||
- 例如:$\int 2x \cos(x^2)\, dx$。令 $u = x^2$,则 $du = 2x\, dx$。积分变为 $\int \cos(u)\, du = \sin(u) + C = \sin(x^2) + C$。
|
||||
|
||||
- **分部积分法**是乘积法则的逆过程。如果被积函数是两个函数的乘积:
|
||||
|
||||
$$\int u\, dv = uv - \int v\, du$$
|
||||
|
||||
- 策略性地选择 $u$ 和 $dv$,使得剩下的积分 $\int v\, du$ 比原来的更简单。选择 $u$ 的常用助记法是 **LIATE**:对数函数(Logarithmic)、反三角函数(Inverse trig)、代数函数(Algebraic)、三角函数(Trigonometric)、指数函数(Exponential)(优先从靠前的类别中选择 $u$)。
|
||||
|
||||
- 例如:$\int x\, e^x\, dx$。令 $u = x$(代数函数)和 $dv = e^x\, dx$。则 $du = dx$,$v = e^x$。因此:$\int x\, e^x\, dx = x\, e^x - \int e^x\, dx = x\, e^x - e^x + C = e^x(x - 1) + C$。
|
||||
|
||||
- 在机器学习中,积分出现在概率论中(通过对密度函数积分来计算概率)、期望值中(连续分布上的加权平均),以及计算 ROC 曲线下的面积。虽然在实际中我们很少手动积分,但理解积分的含义有助于解释这些量。
|
||||
|
||||
## 编程练习(使用 CoLab 或 notebook)
|
||||
|
||||
1. 使用黎曼和,用不断增加数量的矩形来数值逼近 $\int_0^1 x^2\, dx$。与精确答案 $\frac{1}{3}$ 进行比较。
|
||||
```python
|
||||
import jax.numpy as jnp
|
||||
|
||||
for n in [10, 100, 1000, 10000]:
|
||||
x = jnp.linspace(0, 1, n, endpoint=False)
|
||||
dx = 1.0 / n
|
||||
area = jnp.sum(x**2 * dx)
|
||||
print(f"n={n:5d} approx: {area:.6f} exact: {1/3:.6f}")
|
||||
```
|
||||
|
||||
2. 数值验证微积分基本定理。定义 $F(x) = \int_0^x t^2\, dt = \frac{x^3}{3}$,并验证其导数(通过 `jax.grad` 计算)等于 $x^2$。
|
||||
```python
|
||||
import jax
|
||||
import jax.numpy as jnp
|
||||
|
||||
F = lambda x: x**3 / 3
|
||||
dF = jax.grad(F)
|
||||
|
||||
for x in [0.5, 1.0, 2.0, 3.0]:
|
||||
print(f"x={x:.1f} F'(x)={dF(x):.4f} x^2={x**2:.4f}")
|
||||
```
|
||||
|
||||
3. 可视化 $f(x) = \sin(x)$ 从 $0$ 到 $\pi$ 的曲线下面积。使用 `plt.fill_between` 填充该区域,并用黎曼和数值计算面积。
|
||||
```python
|
||||
import jax.numpy as jnp
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
x = jnp.linspace(0, jnp.pi, 500)
|
||||
y = jnp.sin(x)
|
||||
|
||||
plt.plot(x, y, color="purple", linewidth=2)
|
||||
plt.fill_between(x, y, alpha=0.2, color="purple")
|
||||
plt.title(f"Area = {jnp.sum(jnp.sin(x) * (jnp.pi / 500)):.4f} (exact: 2.0)")
|
||||
plt.show()
|
||||
```
|
||||
@@ -0,0 +1,219 @@
|
||||
# 多元微积分
|
||||
|
||||
*多元微积分将导数和积分扩展到多变量函数,这对于机器学习模型拥有数百万参数的情形至关重要。本章涵盖偏导数、梯度、雅可比矩阵、海森矩阵以及使反向传播成为可能的多变量链式法则。*
|
||||
|
||||
- 到目前为止,我们的函数都只接受单个输入 $x$ 并产生单个输出 $f(x)$。但在机器学习中,我们几乎从不只处理一个变量。
|
||||
|
||||
- 考虑一个双变量函数,例如 $f(x, y) = x^2 + y^2$。它在三维空间中定义了一个曲面,形状像一个碗。我们想知道:如果我们在保持 $y$ 固定的同时稍微调整 $x$,$f$ 会如何变化?这就是**偏导数**。
|
||||
|
||||
- $f$ 对 $x$ 的**偏导数**,记作 $\frac{\partial f}{\partial x}$,将其他所有变量视为常数,然后对 $x$ 正常求导。
|
||||
|
||||
- 对于 $f(x, y) = x^2y + 3x - 2y$:
|
||||
|
||||
$$\frac{\partial f}{\partial x} = 2xy + 3 \qquad \frac{\partial f}{\partial y} = x^2 - 2$$
|
||||
|
||||
- 计算 $\frac{\partial f}{\partial x}$ 时,我们将 $y$ 视为常数,因此 $x^2y$ 求导得 $2xy$,$3x$ 求导得 $3$,$-2y$ 求导得 $0$。
|
||||
|
||||
- 计算 $\frac{\partial f}{\partial y}$ 时,我们将 $x$ 视为常数,因此 $x^2y$ 求导得 $x^2$,$3x$ 求导得 $0$,$-2y$ 求导得 $-2$。
|
||||
|
||||
- 从几何上看,对 $x$ 求偏导数就像用一个平行于 $xz$ 平面的平面(在固定的 $y$ 值处)切割三维曲面,然后求所得曲线的斜率。
|
||||
|
||||

|
||||
|
||||
- **梯度**将所有偏导数收集到一个向量中:
|
||||
|
||||
$$\nabla f = \left(\frac{\partial f}{\partial x_1}, \frac{\partial f}{\partial x_2}, \ldots, \frac{\partial f}{\partial x_n}\right)$$
|
||||
|
||||
- 对于 $f(x, y) = x^2 + y^2$:$\nabla f(x, y) = (2x, 2y)$。在点 $(1, 2)$ 处:$\nabla f(1, 2) = (2, 4)$。
|
||||
|
||||
- 梯度有两个关键性质:
|
||||
|
||||
- **方向**:它指向上升最陡的方向。想象一位登山者在山上。他们所在位置的梯度指向正上方,沿着最陡的路径。
|
||||
|
||||
- **大小**:$\|\nabla f\|$ 给出了最陡方向上的变化率。梯度大意味着地形陡峭;梯度小意味着地形近乎平坦。
|
||||
|
||||

|
||||
|
||||
- 由于梯度指向上坡,沿相反方向($-\nabla f$)移动就是下坡,走向更低的值。这个简单的想法是**梯度下降**的基础,我们将在后续章节中详细探讨这种优化技术。现在,关键要点是:梯度告诉你哪个方向是"上坡",以及攀登的陡峭程度。
|
||||
|
||||
- **方向导数**推广了偏导数。它不问"$f$ 沿 $x$ 轴如何变化?",而是问"$f$ 沿任意方向 $\mathbf{u}$ 如何变化?"它通过梯度与单位向量的点积来计算:
|
||||
|
||||
$$D_{\mathbf{u}} f = \nabla f \cdot \mathbf{u}$$
|
||||
|
||||
- 对于 $f(x, y) = x^2 + y^2$ 在点 $(1, 2)$ 处,沿方向 $\mathbf{v} = (3, 4)$:首先归一化得到 $\mathbf{u} = (3/5, 4/5)$,然后 $D_{\mathbf{u}} f = (2, 4) \cdot (3/5, 4/5) = 6/5 + 16/5 = 22/5$。
|
||||
|
||||
- 偏导数是方向导数的特例,其中方向沿着坐标轴。如果方向导数在某个方向上为零,则函数在该点沿该方向是平坦的。
|
||||
|
||||
- **等高线**(或水平曲线)连接函数值相等的点。对于 $f(x, y) = x^2 + y^2$,等高线是以原点为中心的圆:对应不同 $c$ 值的 $x^2 + y^2 = c$。
|
||||
|
||||
- 等高线永不相交(一个点不可能有两个不同的函数值)。
|
||||
|
||||
- 梯度始终垂直于等高线,从低值指向高值。
|
||||
|
||||
- 等高线密集表示地形陡峭;等高线稀疏表示坡度平缓。
|
||||
|
||||
- 到目前为止,我们的函数都只产生单个输出。但许多函数会产生多个输出。函数 $\mathbf{F}: \mathbb{R}^n \to \mathbb{R}^m$ 接收 $n$ 个输入并产生 $m$ 个输出。**雅可比矩阵**组织了这样一个向量值函数的所有偏导数:
|
||||
|
||||
```math
|
||||
J = \begin{bmatrix} \frac{\partial f_1}{\partial x_1} & \cdots & \frac{\partial f_1}{\partial x_n} \\ \vdots & \ddots & \vdots \\ \frac{\partial f_m}{\partial x_1} & \cdots & \frac{\partial f_m}{\partial x_n} \end{bmatrix}
|
||||
```
|
||||
|
||||
- 雅可比矩阵的每一行是一个输出分量的梯度。对于一个有 3 个输入和 2 个输出的函数,雅可比矩阵是一个 $2 \times 3$ 矩阵。
|
||||
|
||||
- 雅可比矩阵将导数推广到向量值函数。
|
||||
|
||||
- 就像标量函数的导数告诉你每单位输入变化对应的输出变化量一样,雅可比矩阵告诉你每个输出相对于每个输入的变化情况。
|
||||
|
||||
- **雅可比行列式**衡量一个变换局部拉伸或压缩空间的程度。
|
||||
|
||||
- 如果行列式为 2,小区域的面积加倍。如果行列式为 0,该变换将空间压缩到更低维度(回想我们在矩阵章节中学到的:行列式为零意味着奇异变换,不可逆)。
|
||||
|
||||
- 当多个变换组合在一起(一个变换的输出作为下一个变换的输入)时,整体映射的雅可比矩阵是各个雅可比矩阵的乘积。我们将在后续章节中看到这个思想变得至关重要。
|
||||
|
||||
- 梯度捕获一阶信息(斜率),而**海森矩阵**捕获二阶信息(曲率)。
|
||||
|
||||
- 对于标量函数 $f(x_1, \ldots, x_n)$,海森矩阵是所有二阶偏导数的 $n \times n$ 矩阵:
|
||||
|
||||
```math
|
||||
H = \begin{bmatrix} \frac{\partial^2 f}{\partial x_1^2} & \frac{\partial^2 f}{\partial x_1 \partial x_2} & \cdots \\ \frac{\partial^2 f}{\partial x_2 \partial x_1} & \frac{\partial^2 f}{\partial x_2^2} & \cdots \\ \vdots & \vdots & \ddots \end{bmatrix}
|
||||
```
|
||||
|
||||
- 对于 $f(x, y) = x^3 + 2xy^2 - y^3$,梯度为 $(3x^2 + 2y^2,\; 4xy - 3y^2)$,海森矩阵为:
|
||||
|
||||
```math
|
||||
H = \begin{bmatrix} 6x & 4y \\ 4y & 4x - 6y \end{bmatrix}
|
||||
```
|
||||
|
||||
- 对角线元素($6x$ 和 $4x - 6y$)告诉你 $x$ 方向的斜率随 $x$ 移动如何变化,$y$ 方向同理。
|
||||
|
||||
- 非对角线元素($4y$)告诉你一个方向的斜率随另一个方向的移动如何变化。
|
||||
|
||||
- **克莱罗定理**保证:对于具有连续二阶导数的函数,混合偏导数相等:$\frac{\partial^2 f}{\partial x \partial y} = \frac{\partial^2 f}{\partial y \partial x}$。
|
||||
|
||||
- 这意味着海森矩阵是对称的,这(正如我们在矩阵章节中看到的)保证了实特征值和正交特征向量。
|
||||
|
||||
- 海森矩阵告诉我们临界点(梯度为零的点)附近函数的形状:
|
||||
|
||||
- 如果 $H$ 是正定的(所有特征值为正),则该点是**局部极小值点**,曲面像碗一样向各个方向向上弯曲。
|
||||
- 如果 $H$ 是负定的(所有特征值为负),则该点是**局部极大值点**,曲面像倒扣的碗一样向各个方向向下弯曲。
|
||||
- 如果 $H$ 同时具有正负特征值,则该点是**鞍点**,曲面在某些方向上向上弯曲,在另一些方向上向下弯曲,就像山坳一样。
|
||||
|
||||
- **多变量链式法则**将链式法则扩展到多变量函数。如果 $z = f(x, y)$,其中 $x = g(t)$ 且 $y = h(t)$,则:
|
||||
|
||||
$$\frac{dz}{dt} = \frac{\partial f}{\partial x}\frac{dx}{dt} + \frac{\partial f}{\partial y}\frac{dy}{dt}$$
|
||||
|
||||
- 从 $t$ 到 $z$ 的每条路径都贡献一项:沿该路径的偏导数乘以中间变量对 $t$ 的导数。
|
||||
|
||||
- 例如,如果 $z = x^2 y + 3x - y^2$,$x = \cos(t)$,$y = \sin(t)$:
|
||||
|
||||
$$\frac{dz}{dt} = (2xy + 3)(-\sin t) + (x^2 - 2y)(\cos t)$$
|
||||
|
||||
- 除了手动计算导数,还有三种方法:
|
||||
|
||||
- **数值微分**:用 $f'(x) \approx \frac{f(x+h) - f(x-h)}{2h}$(取很小的 $h$)来近似。简单但有噪声且不精确。
|
||||
- **符号微分**:通过代数地应用求导法则产生精确表达式。可能导致表达式呈指数级膨胀。
|
||||
- **自动微分(autodiff)**:跟踪运算链并高效地计算精确导数。JAX、PyTorch 和 TensorFlow 都使用这种方法。它能给出精确的数值(而非近似值),且不会产生臃肿的符号表达式。
|
||||
|
||||
## 编程练习(使用 CoLab 或 notebook)
|
||||
|
||||
1. 使用 `jax.grad` 计算函数 $f(x, y) = x^2 y + 3x - 2y$ 在点 $(1, 2)$ 处的梯度。由于 $f$ 接收向量输入,请使用带 `argnums` 参数的 `jax.grad`。
|
||||
```python
|
||||
import jax
|
||||
import jax.numpy as jnp
|
||||
|
||||
def f(x, y):
|
||||
return x**2 * y + 3*x - 2*y
|
||||
|
||||
df_dx = jax.grad(f, argnums=0)
|
||||
df_dy = jax.grad(f, argnums=1)
|
||||
|
||||
x, y = 1.0, 2.0
|
||||
print(f"∂f/∂x = {df_dx(x, y):.4f} (期望: {2*x*y + 3:.4f})")
|
||||
print(f"∂f/∂y = {df_dy(x, y):.4f} (期望: {x**2 - 2:.4f})")
|
||||
```
|
||||
|
||||
2. 使用 `jax.jacobian` 计算向量值函数的雅可比矩阵,并与手动计算结果进行比较。
|
||||
```python
|
||||
import jax
|
||||
import jax.numpy as jnp
|
||||
|
||||
def F(x):
|
||||
return jnp.array([x[0]**2 + x[1], x[0] * x[1]**2])
|
||||
|
||||
J = jax.jacobian(F)
|
||||
x = jnp.array([1.0, 2.0])
|
||||
print(f"在 (1,2) 处的雅可比矩阵:\n{J(x)}")
|
||||
# 期望: [[2*x[0], 1], [x[1]**2, 2*x[0]*x[1]]] = [[2, 1], [4, 4]]
|
||||
```
|
||||
|
||||
3. 使用 `jax.hessian` 计算 $f(x, y) = x^3 + 2xy^2 - y^3$ 的海森矩阵,并验证其对称性。
|
||||
```python
|
||||
import jax
|
||||
import jax.numpy as jnp
|
||||
|
||||
def f(xy):
|
||||
x, y = xy[0], xy[1]
|
||||
return x**3 + 2*x*y**2 - y**3
|
||||
|
||||
H = jax.hessian(f)
|
||||
point = jnp.array([1.0, 2.0])
|
||||
hess = H(point)
|
||||
print(f"海森矩阵:\n{hess}")
|
||||
print(f"是否对称: {jnp.allclose(hess, hess.T)}")
|
||||
# 期望: [[6x, 4y], [4y, 4x-6y]] = [[6, 8], [8, -8]]
|
||||
```
|
||||
|
||||
4. 从头构建一个极简的自动微分引擎。
|
||||
- 每个 `Var` 追踪其值以及如何通过链式法则反向传播梯度。
|
||||
- 尝试扩展更多运算(除法、幂运算等)。
|
||||
- 这是 JAX、PyTorch 和 Numpy 的设计基础。
|
||||
```python
|
||||
class Var:
|
||||
def __init__(self, val, children=(), backward_fn=None):
|
||||
self.val = val
|
||||
self.grad = 0.0
|
||||
self.children = children
|
||||
self.backward_fn = backward_fn
|
||||
|
||||
def __add__(self, other):
|
||||
out = Var(self.val + other.val, children=(self, other))
|
||||
def _backward():
|
||||
self.grad += out.grad # d(a+b)/da = 1
|
||||
other.grad += out.grad # d(a+b)/db = 1
|
||||
out.backward_fn = _backward
|
||||
return out
|
||||
|
||||
def __mul__(self, other):
|
||||
out = Var(self.val * other.val, children=(self, other))
|
||||
def _backward():
|
||||
self.grad += other.val * out.grad # d(a*b)/da = b
|
||||
other.grad += self.val * out.grad # d(a*b)/db = a
|
||||
out.backward_fn = _backward
|
||||
return out
|
||||
|
||||
def backward(self):
|
||||
# 拓扑排序,然后传播梯度
|
||||
# 我们将在数据结构与算法章节中详细介绍
|
||||
order, visited = [], set()
|
||||
def topo(v):
|
||||
if v not in visited:
|
||||
visited.add(v)
|
||||
for c in v.children:
|
||||
topo(c)
|
||||
order.append(v)
|
||||
topo(self)
|
||||
self.grad = 1.0
|
||||
for v in reversed(order):
|
||||
if v.backward_fn:
|
||||
v.backward_fn()
|
||||
|
||||
# f(x, y) = x*x*y + x 在 (3, 2) 处
|
||||
x = Var(3.0)
|
||||
y = Var(2.0)
|
||||
f = x * x * y + x # = 3*3*2 + 3 = 21
|
||||
|
||||
f.backward()
|
||||
print(f"f = {f.val}") # 21.0
|
||||
print(f"df/dx = {x.grad}") # 2*x*y + 1 = 13.0
|
||||
print(f"df/dy = {y.grad}") # x*x = 9.0
|
||||
```
|
||||
@@ -0,0 +1,143 @@
|
||||
# 函数逼近
|
||||
|
||||
*函数逼近用足够接近原函数的简单函数来替代复杂函数。本文涵盖线性化、泰勒级数、多项式逼近、傅里叶级数以及通用逼近定理——这些是神经网络能够学习任意映射的理论基础。*
|
||||
|
||||
- 我们遇到的许多函数都过于复杂,无法直接处理。例如,在纸上计算 $e^{0.1}$、预测卫星轨迹等,都涉及没有简单封闭形式答案的函数。
|
||||
|
||||
- **函数逼近**用更简单的函数来替代复杂函数,使其在关心区域内"足够接近"原函数。
|
||||
|
||||
- 最自然的逼近是多项式。多项式只是 $x$ 的幂次与系数的和,易于求值、微分和积分。
|
||||
|
||||
- 但为什么多项式作为逼近器如此有效?看看 $x$ 的每个幂次贡献了什么。
|
||||
|
||||
- 常数项 $a_0$ 设定基准值。
|
||||
- $a_1 x$ 项增加斜率。
|
||||
- $a_2 x^2$ 项增加曲率。
|
||||
- 更高的幂次则捕捉函数形状的更多细节。
|
||||
|
||||

|
||||
|
||||
- 通过选择合适的系数,我们可以逐次匹配函数在某一点的值、斜率、曲率以及高阶行为。
|
||||
|
||||
- 当项数足够时,多项式几乎可以模仿任何光滑函数。
|
||||
|
||||
- 问题在于:如何找到正确的系数?
|
||||
|
||||
- **线性化**是最简单的逼近。在点 $x = a$ 附近,我们用函数的切线来代替它:
|
||||
|
||||
$$L(x) = f(a) + f'(a)(x - a)$$
|
||||
|
||||
- 这是一阶**泰勒逼近**。它的思路是:从已知值 $f(a)$ 出发,然后加上斜率乘以距离 $a$ 的偏移量。
|
||||
|
||||
- 例如,在 $x = 0$ 处对 $\sin(x)$ 线性化:$f(0) = 0$,$f'(0) = \cos(0) = 1$,所以 $L(x) = x$。在零附近,$\sin(x) \approx x$。试试看:$\sin(0.1) = 0.0998\ldots \approx 0.1$。
|
||||
|
||||
- 但线性化仅在非常接近 $a$ 的地方有效。离得稍远,逼近就失效了。为了做得更好,我们需要引入高阶项。
|
||||
|
||||
- **泰勒级数**将函数表示为无穷多个多项式项的和,每一项都捕捉到函数在点 $a$ 附近行为的更精细细节:
|
||||
|
||||
$$f(x) = \sum_{n=0}^{\infty} \frac{f^{(n)}(a)}{n!}(x - a)^n = f(a) + f'(a)(x-a) + \frac{f''(a)}{2!}(x-a)^2 + \frac{f'''(a)}{3!}(x-a)^3 + \cdots$$
|
||||
|
||||

|
||||
|
||||
- 每一项依次增加一个修正项。第一项匹配函数值,第二项匹配斜率,第三项匹配曲率,依此类推。包含的项越多,逼近精确的区域就越大。
|
||||
|
||||
- 分母中的 $n!$ 并非随意选择。当你对 $(x - a)^n$ 恰好微分 $n$ 次时,会得到 $n!$。阶乘抵消了这个结果,从而确保泰勒多项式的 $n$ 阶导数在 $x = a$ 处与原函数的 $n$ 阶导数相等。
|
||||
|
||||
- **麦克劳林级数**就是中心在 $a = 0$ 的泰勒级数:
|
||||
|
||||
$$f(x) = \sum_{n=0}^{\infty} \frac{f^{(n)}(0)}{n!} x^n$$
|
||||
|
||||
- 一些著名的麦克劳林级数:
|
||||
|
||||
$$e^x = 1 + x + \frac{x^2}{2!} + \frac{x^3}{3!} + \cdots$$
|
||||
|
||||
$$\sin x = x - \frac{x^3}{3!} + \frac{x^5}{5!} - \frac{x^7}{7!} + \cdots$$
|
||||
|
||||
$$\cos x = 1 - \frac{x^2}{2!} + \frac{x^4}{4!} - \frac{x^6}{6!} + \cdots$$
|
||||
|
||||
- 注意 $\sin x$ 只有奇次幂(它是奇函数),而 $\cos x$ 只有偶次幂(它是偶函数)。交替的符号使得逼近在真实值周围振荡,从两侧同时收敛。
|
||||
|
||||
- 让我们用四项来逼近 $e^{0.5}$:$1 + 0.5 + \frac{0.25}{2} + \frac{0.125}{6} = 1 + 0.5 + 0.125 + 0.02083 \approx 1.6458$。真实值为 $1.6487\ldots$,因此四项已经给出了三个正确的小数位。
|
||||
|
||||
- 并非所有泰勒级数都处处收敛。**收敛半径**告诉我们,在距离中心 $a$ 多远的范围内,级数给出有效的结果。在此半径内,通过增加项数,多项式逼近可以达到任意所需的精度。超出此半径,级数发散。
|
||||
|
||||
- **幂级数**的一般形式是:$\sum_{n=0}^{\infty} a_n (x - c)^n$。泰勒级数是系数由导数确定的幂级数。其他幂级数可能由其他规则定义。**比值判别法**用于判定收敛性:计算 $\lim_{n \to \infty} \left|\frac{a_{n+1}}{a_n}\right|$。如果该极限为 $L$,则收敛半径为 $R = 1/L$。
|
||||
|
||||
- 将泰勒级数截断到 $n$ 项时,会产生误差。**拉格朗日余项**给出了这个误差的界限:
|
||||
|
||||
$$R_n(x) = \frac{f^{(n+1)}(c)}{(n+1)!}(x-a)^{n+1}$$
|
||||
|
||||
- 这里 $c$ 是 $a$ 和 $x$ 之间的某个未知点。我们无法确切知道 $c$,但通常可以限定 $|f^{(n+1)}(c)|$ 来得到最坏情况下的误差估计。分母中的 $(n+1)!$ 增长极快,因此随着项数增加,误差迅速减小(对于收敛半径内的函数而言)。
|
||||
|
||||
- 对于多变量函数,泰勒展开包含混合偏导数。$f(\mathbf{x})$ 在点 $\mathbf{a}$ 附近的二阶逼近为:
|
||||
|
||||
$$f(\mathbf{x}) \approx f(\mathbf{a}) + \nabla f(\mathbf{a})^T (\mathbf{x} - \mathbf{a}) + \frac{1}{2} (\mathbf{x} - \mathbf{a})^T H(\mathbf{a}) (\mathbf{x} - \mathbf{a})$$
|
||||
|
||||
- 第一项是函数值,第二项使用梯度(向量,如我们在多元微积分中看到的),第三项使用海森矩阵(捕捉曲率)。这直接将我们的矩阵章节与微积分联系起来:海森矩阵是一个由二阶导数组成的矩阵,描述了函数表面的形状。
|
||||
|
||||
- 这种多变量二阶逼近是牛顿法和其他二阶优化技术的基础,我们将在下一个文件中看到。
|
||||
|
||||
- 除了多项式,还有其他值得了解的逼近方法:
|
||||
|
||||
- **样条插值**:不用单个高次多项式,而是将多个低次多项式光滑拼接在一起。这避免了高次多项式可能产生的剧烈振荡。
|
||||
- **傅里叶级数**:将周期函数逼近为正弦和余弦的和。在信号处理和音频中至关重要。
|
||||
- **神经网络**:通用函数逼近器。只要有足够的神经元,它们可以任意精度逼近任何连续函数。这就是深度学习的理论基础。
|
||||
|
||||
- 如果一个函数具有使逼近可靠的性质——连续性(无跳跃)、可微性(无尖角)、光滑性(所有阶导数都存在)和有界性(输出保持有限),我们就称其为"行为良好"的函数。
|
||||
|
||||
- 多项式、指数函数和三角函数都属于行为良好的函数。函数行为越好,获得良好逼近所需的泰勒项数就越少。
|
||||
|
||||
## 编程练习(使用 CoLab 或 Jupyter Notebook)
|
||||
|
||||
1. 用递增数量的泰勒项逼近 $e^x$,并可视化逼近效果如何改善。
|
||||
```python
|
||||
import jax.numpy as jnp
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
x = jnp.linspace(-2, 3, 300)
|
||||
plt.plot(x, jnp.exp(x), "k-", linewidth=2, label="eˣ (精确值)")
|
||||
|
||||
colors = ["#e74c3c", "#3498db", "#27ae60", "#9b59b6"]
|
||||
for n, color in zip([1, 2, 4, 8], colors):
|
||||
approx = sum(x**k / jnp.array(float(jnp.prod(jnp.arange(1, k+1)) if k > 0 else 1))
|
||||
for k in range(n+1))
|
||||
plt.plot(x, approx, color=color, linestyle="--", label=f"{n} 项")
|
||||
|
||||
plt.ylim(-2, 15)
|
||||
plt.legend()
|
||||
plt.title("eˣ 的泰勒逼近")
|
||||
plt.show()
|
||||
```
|
||||
|
||||
2. 计算拉格朗日余项,以限定用不同数量的泰勒项逼近 $\sin(1)$ 时的误差。
|
||||
```python
|
||||
import jax.numpy as jnp
|
||||
|
||||
x = 1.0
|
||||
exact = jnp.sin(x)
|
||||
|
||||
taylor = 0.0
|
||||
for n in range(8):
|
||||
sign = (-1)**n
|
||||
factorial = float(jnp.prod(jnp.arange(1, 2*n+2)))
|
||||
taylor += sign * x**(2*n+1) / factorial
|
||||
error = abs(exact - taylor)
|
||||
bound = x**(2*n+3) / float(jnp.prod(jnp.arange(1, 2*n+4)))
|
||||
print(f"项数={n+1} 近似值={taylor:.10f} 误差={error:.2e} 界限={bound:.2e}")
|
||||
```
|
||||
|
||||
3. 比较在 $x=0$ 附近 $\cos(x)$ 的线性化逼近与二次泰勒逼近。在同一张图上绘制两个逼近和真实函数,观察各自精确的范围。
|
||||
```python
|
||||
import jax.numpy as jnp
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
x = jnp.linspace(-3, 3, 300)
|
||||
plt.plot(x, jnp.cos(x), "k-", linewidth=2, label="cos(x)")
|
||||
plt.plot(x, jnp.ones_like(x), "--", color="#e74c3c", label="线性: 1")
|
||||
plt.plot(x, 1 - x**2/2, "--", color="#3498db", label="二次: 1 - x²/2")
|
||||
plt.plot(x, 1 - x**2/2 + x**4/24, "--", color="#27ae60", label="四阶")
|
||||
plt.ylim(-2, 2)
|
||||
plt.legend()
|
||||
plt.title("cos(x) 的泰勒逼近")
|
||||
plt.show()
|
||||
```
|
||||
@@ -0,0 +1,145 @@
|
||||
# 优化
|
||||
|
||||
*优化是模型训练的数学核心——寻找使损失函数最小的参数。本文涵盖驻点、凸性、梯度下降、牛顿法、带拉格朗日乘数的约束优化,以及驱动现代深度学习的主流优化器(SGD、Adam)。*
|
||||
|
||||
- 训练神经网络、拟合回归线、调优超参数:几乎所有机器学习算法的核心都是一个**优化**问题。
|
||||
|
||||
- 我们有一个函数(损失函数、代价函数、目标函数),希望找到使其尽可能小(或大)的输入。
|
||||
|
||||
- 在优化之前,我们需要理解函数的**零点**(或根)。$f(x)$ 的零点是指满足 $f(x) = 0$ 的 $x$ 值。从图形上看,这些点就是与 x 轴的交点。
|
||||
|
||||
- 例如,$f(x) = x^2 - 3x + 2 = (x-1)(x-2)$ 的零点在 $x = 1$ 和 $x = 2$ 处。在两个零点之间,函数为负($f(1.5) = -0.25$);在零点之外,函数为正。零点将数轴分割成若干个区域,在每个区域中函数保持相同符号。
|
||||
|
||||
- 零点的**重数**是指对应因式出现的次数。
|
||||
|
||||
- 在单零点(重数为 1)处,图像穿过 x 轴。在二重零点(重数为 2)处,图像接触 x 轴但反弹回去而不穿过,在该点处看起来是"平坦"的。
|
||||
|
||||
- 寻找零点之所以重要,是因为导数 $f'(x)$ 的零点正是 $f(x)$ 的**驻点**——即极大值或极小值的候选点。
|
||||
|
||||
- 在极大值或极小值处,切线是水平的(斜率为 0),因此 $f'(x) = 0$。
|
||||
|
||||

|
||||
|
||||
- 但并非每个驻点都是极大值或极小值。$f'(x) = 0$ 的点也可能是**拐点**(如 $f(x) = x^3$ 在 $x = 0$ 处),函数在该点暂时变平但并未改变方向。
|
||||
|
||||
- **二阶导数检验**可以解决这个问题。在驻点 $x = c$(即 $f'(c) = 0$)处:
|
||||
|
||||
- 若 $f''(c) > 0$:曲线向下凸(碗状),因此 $c$ 是**局部极小值**。
|
||||
- 若 $f''(c) < 0$:曲线向上凸(山丘状),因此 $c$ 是**局部极大值**。
|
||||
- 若 $f''(c) = 0$:检验无效,需要使用更高阶导数或其他方法。
|
||||
|
||||
- 例如,$f(x) = x^3 - 3x$。导数为 $f'(x) = 3x^2 - 3 = 3(x-1)(x+1)$,因此驻点在 $x = -1$ 和 $x = 1$ 处。二阶导数为 $f''(x) = 6x$。在 $x = -1$ 处:$f''(-1) = -6 < 0$(局部极大值)。在 $x = 1$ 处:$f''(1) = 6 > 0$(局部极小值)。
|
||||
|
||||
- 如果连接函数图像上任意两点的线段位于图像之上(或与之重合),则该函数是**凸的**。可以想象成一个碗形,处处向上弯曲。数学上,若对所有 $x$ 有 $f''(x) \geq 0$,则 $f$ 是凸函数。
|
||||
|
||||

|
||||
|
||||
- 凸性的强大之处在于凸函数有一个卓越的性质:每个局部极小值同时也是**全局最小值**。不存在会让人陷入的欺骗性局部低谷。如果你把一个球滚入凸碗中,它总是会到达底部。
|
||||
|
||||
- 若 $-f$ 是凸的,则函数是**凹的**(向下弯曲)。函数从凹性过渡到凸性的点称为**拐点**,出现在 $f''(x) = 0$ 处。
|
||||
|
||||
- **牛顿法**利用切线寻找函数的零点(进而也可用于寻找其导数的驻点)。从初始猜测 $x_0$ 出发,迭代更新:
|
||||
|
||||
$$x_{n+1} = x_n - \frac{f(x_n)}{f'(x_n)}$$
|
||||
|
||||

|
||||
|
||||
- 其思想是:在 $x_n$ 处画出切线,找到它与 x 轴的交点,该交点即为 $x_{n+1}$。对于性质良好且初始点选取恰当的函数,牛顿法收敛非常快(二次收敛,即每步正确位数大致翻倍)。
|
||||
|
||||
- 例如,求 $\sqrt{5}$(即 $f(x) = x^2 - 5$ 的零点):$f'(x) = 2x$,因此 $x_{n+1} = x_n - \frac{x_n^2 - 5}{2x_n}$。从 $x_0 = 2$ 开始:$x_1 = 2.25$,$x_2 = 2.2361\ldots$,已精确到小数点后四位。
|
||||
|
||||
- 如果初始猜测离根太远、根附近 $f'(x) = 0$,或函数在附近有拐点,牛顿法可能会失败。此外,它还需要计算导数,这可能代价高昂。
|
||||
|
||||
- 对于优化(寻找极小值而非零点),我们将牛顿法应用于 $f'(x) = 0$,得到更新公式:
|
||||
|
||||
$$x_{n+1} = x_n - \frac{f'(x_n)}{f''(x_n)}$$
|
||||
|
||||
- 在多维情形下,这变为 $\mathbf{x}_{n+1} = \mathbf{x}_n - H^{-1} \nabla f(\mathbf{x}_n)$,其中 $H$ 是 Hessian 矩阵。这正是上一节中二阶泰勒近似的实际应用:将函数近似为二次型,跳到该二次型的极小值点,然后重复。
|
||||
|
||||
- **拉格朗日乘数**用于求解**约束优化**:在约束条件 $g(x, y) = c$ 下求 $f(x, y)$ 的最优值。我们不是在 $\mathbb{R}^n$ 中全域搜索,而是限制在约束条件成立的集合(一条曲线或曲面)上。
|
||||
|
||||
- 关键见解是几何层面的:在约束最优解处,$f$ 的梯度必须与 $g$ 的梯度平行。如果它们不平行,我们可以沿着约束条件朝某个方向移动,从而继续改进 $f$ 的值,这意味着还没有达到最优。
|
||||
|
||||
- 我们引入一个新变量 $\lambda$(拉格朗日乘数),定义**拉格朗日函数**:
|
||||
|
||||
$$\mathcal{L}(x, y, \lambda) = f(x, y) - \lambda(g(x, y) - c)$$
|
||||
|
||||
- 令所有偏导数为零,得到一个方程组,其解即为约束最优解:
|
||||
|
||||
$$\frac{\partial \mathcal{L}}{\partial x} = 0, \quad \frac{\partial \mathcal{L}}{\partial y} = 0, \quad \frac{\partial \mathcal{L}}{\partial \lambda} = 0$$
|
||||
|
||||

|
||||
|
||||
- 例如,在 $x^2 + y^2 = 1$ 的约束下最大化 $f(x,y) = x^2 y$。拉格朗日函数为 $\mathcal{L} = x^2 y - \lambda(x^2 + y^2 - 1)$。求偏导:
|
||||
|
||||
$$2xy - 2\lambda x = 0, \quad x^2 - 2\lambda y = 0, \quad x^2 + y^2 = 1$$
|
||||
|
||||
- 由第一个方程(假设 $x \neq 0$):$\lambda = y$。代入第二个方程:$x^2 = 2y^2$。结合约束条件:$2y^2 + y^2 = 1$,得 $y = \frac{1}{\sqrt{3}}$。最大值为 $f = \frac{2}{3\sqrt{3}}$。
|
||||
|
||||
- 对于不等式约束($g(x,y) \leq c$ 而非 $= c$),**Karush-Kuhn-Tucker(KKT)条件**推广了拉格朗日乘数法。约束要么是激活的(有效约束,按等式处理),要么是非激活的(解在内部,约束无关紧要)。
|
||||
|
||||
- 在实践中,我们很少手工进行优化。以下是主要的算法家族:
|
||||
|
||||
- **一阶方法**(仅使用梯度):梯度下降、随机梯度下降(SGD)、Adam。这些方法每步计算成本低,但收敛可能较慢,尤其是在病态问题上。
|
||||
|
||||
- **二阶方法**(使用梯度和 Hessian 矩阵):牛顿法收敛快,但计算和求逆 Hessian 矩阵代价高昂(对于 $n$ 个参数为 $O(n^3)$)。**拟牛顿法**(如 BFGS 和 L-BFGS)仅利用梯度信息近似 Hessian 矩阵,比一阶方法收敛更快,又无需承担完全的二阶方法计算成本。
|
||||
|
||||
- **共轭梯度法**:适用于大型稀疏系统,仅需矩阵-向量乘积,无需存储完整的 Hessian 矩阵。
|
||||
|
||||
- **高斯-牛顿法**和**莱文贝格-马夸尔特法**:专门用于最小二乘问题(在回归中常见),通过 Jacobian 矩阵近似 Hessian 矩阵。
|
||||
|
||||
- **自然梯度下降**:利用 Fisher 信息矩阵考虑参数空间的几何结构,对概率模型可能更有效。
|
||||
|
||||
- 优化器的选择取决于具体问题。对于深度学习,一阶方法(尤其是 Adam)占主导地位,因为参数量巨大(数百万到数十亿),计算 Hessian 矩阵不切实际。对于目标函数光滑的小规模问题,二阶方法可能快得多。
|
||||
|
||||
## 编程练习(在 CoLab 或 notebook 中完成)
|
||||
|
||||
1. 实现牛顿法求 $\sqrt{7}$(即 $f(x) = x^2 - 7$ 的零点)。观察其快速收敛。
|
||||
```python
|
||||
import jax.numpy as jnp
|
||||
|
||||
f = lambda x: x**2 - 7
|
||||
df = lambda x: 2*x
|
||||
|
||||
x = 3.0 # 初始猜测
|
||||
for i in range(6):
|
||||
x = x - f(x) / df(x)
|
||||
print(f"step {i+1}: x = {x:.10f} (error: {abs(x - jnp.sqrt(7.0)):.2e})")
|
||||
```
|
||||
|
||||
2. 使用梯度下降最小化 $f(x, y) = (x - 3)^2 + (y + 1)^2$。最小值在 $(3, -1)$ 处。尝试不同的学习率。
|
||||
```python
|
||||
import jax
|
||||
import jax.numpy as jnp
|
||||
|
||||
def f(params):
|
||||
x, y = params
|
||||
return (x - 3)**2 + (y + 1)**2
|
||||
|
||||
grad_f = jax.grad(f)
|
||||
params = jnp.array([0.0, 0.0])
|
||||
lr = 0.1
|
||||
|
||||
for i in range(20):
|
||||
g = grad_f(params)
|
||||
params = params - lr * g
|
||||
if i % 5 == 0 or i == 19:
|
||||
print(f"step {i:2d}: ({params[0]:.4f}, {params[1]:.4f}) loss={f(params):.6f}")
|
||||
```
|
||||
|
||||
3. 数值求解约束优化问题。在 $x + y = 10$ 的约束下最大化 $f(x,y) = xy$,通过参数化 $y = 10 - x$ 并求单变量函数的最优值。
|
||||
```python
|
||||
import jax
|
||||
import jax.numpy as jnp
|
||||
|
||||
# 代入约束条件:y = 10 - x,所以 f = x(10 - x) = 10x - x²
|
||||
f = lambda x: x * (10 - x)
|
||||
df = jax.grad(f)
|
||||
|
||||
# 梯度上升(我们要求最大值,所以加上梯度)
|
||||
x = 1.0
|
||||
lr = 0.1
|
||||
for i in range(20):
|
||||
x = x + lr * df(x)
|
||||
print(f"x={x:.4f}, y={10-x:.4f}, f={f(x):.4f}") # 应为 x=5, y=5, f=25
|
||||
```
|
||||
Reference in New Issue
Block a user