用导数找函数最小值
用数值差分估计导数,沿着导数的反方向一步步移动,直观感受「导数指引搜索方向」。
学完本案例你将理解:导数告诉你往哪走函数值会上升,反方向走就会下降——这就是梯度下降的雏形。
生活引入
大雾天蒙眼下山
伸手不见五指的大雾天,你要从山顶走到山谷。你看不见整个山势,只能感受到脚下的坡度。你的策略很简单:感受坡度最陡的方向 → 往最陡下坡方向走一小步 → 停下来 → 重新感受 → 再走一小步。
「感受坡度」就是求导数,「走一小步」就是用导数的反方向更新位置。这个方法不需要知道整座山的形状,只需要知道脚下的坡度。
直观理解
对于函数 \(f(x) = (x-3)^2 + 2\),最小值在 \(x=3\) 处,最小值是 \(f(3) = 2\)。
从 \(x=10\) 出发,怎么走到 \(x=3\)?不需要解方程,只需要一个策略:
- 如果导数 \(f'(x) > 0\)(函数在上升),就往左走(减小 x)
- 如果导数 \(f'(x) < 0\)(函数在下降),就往右走(增大 x)
- 导数的绝对值越大,说明坡越陡,可以走远一点
数学定义
数值导数(中心差分)
\[ f'(x) \approx \frac{f(x+h) - f(x-h)}{2h} \]不需要推导求导公式。用两个非常接近的点的函数值差除以距离,就得到了导数的近似值。\(h\) 取一个极小的数,如 \(10^{-5}\)。
朴素下山算法
\[ x_{t+1} = x_t - \eta \cdot f'(x_t) \]其中 \(\eta\) 是步长(学习率),控制每一步走多远。
Python 动手实践
实例
import numpy as np
# 目标函数:f(x) = (x-3)^2 + 2,最小值在 x=3
def f(x):
return (x - 3) ** 2 + 2
# 数值导数——用中心差分,不依赖符号求导
def numerical_derivative(f, x, h=1e-5):
"""估计 f 在 x 处的导数值"""
return (f(x + h) - f(x - h)) / (2 * h)
# 朴素下山算法:沿着导数的反方向移动
x = 10.0 # 起点,故意选得离最优点很远
step_size = 0.1 # 每次移动的步长
history = [x]
for i in range(50):
grad = numerical_derivative(f, x)
x = x - step_size * grad # 导数为正就往左,为负就往右
history.append(x)
print("RUNOOB 下山算法结果:")
print(f"起点 x = {history[0]:.1f}")
print(f"终点 x = {history[-1]:.4f} (真实最小值点 x=3)")
print(f"终点 f(x) = {f(history[-1]):.4f} (真实最小值 f(3)=2)")
print("\nRUNOOB 前 10 步移动轨迹:")
print(f"{'步数':<6} {'x':<10} {'f(x)':<10} {'导数':<10} {'方向'}")
for i, xi in enumerate(history[:10]):
grad = numerical_derivative(f, xi)
direction = "往左" if grad > 0 else "往右"
print(f"{i:<6} {xi:<10.4f} {f(xi):<10.4f} {grad:<10.4f} {direction}")
# 目标函数:f(x) = (x-3)^2 + 2,最小值在 x=3
def f(x):
return (x - 3) ** 2 + 2
# 数值导数——用中心差分,不依赖符号求导
def numerical_derivative(f, x, h=1e-5):
"""估计 f 在 x 处的导数值"""
return (f(x + h) - f(x - h)) / (2 * h)
# 朴素下山算法:沿着导数的反方向移动
x = 10.0 # 起点,故意选得离最优点很远
step_size = 0.1 # 每次移动的步长
history = [x]
for i in range(50):
grad = numerical_derivative(f, x)
x = x - step_size * grad # 导数为正就往左,为负就往右
history.append(x)
print("RUNOOB 下山算法结果:")
print(f"起点 x = {history[0]:.1f}")
print(f"终点 x = {history[-1]:.4f} (真实最小值点 x=3)")
print(f"终点 f(x) = {f(history[-1]):.4f} (真实最小值 f(3)=2)")
print("\nRUNOOB 前 10 步移动轨迹:")
print(f"{'步数':<6} {'x':<10} {'f(x)':<10} {'导数':<10} {'方向'}")
for i, xi in enumerate(history[:10]):
grad = numerical_derivative(f, xi)
direction = "往左" if grad > 0 else "往右"
print(f"{i:<6} {xi:<10.4f} {f(xi):<10.4f} {grad:<10.4f} {direction}")
RUNOOB 下山算法结果: 起点 x = 10.0 终点 x = 3.0000 (真实最小值点 x=3) 终点 f(x) = 2.0000 (真实最小值 f(3)=2) RUNOOB 前 10 步移动轨迹: 步数 x f(x) 导数 方向 0 10.0000 51.0000 14.0000 往左 1 8.6000 33.3600 11.2000 往左 2 7.4800 22.0704 8.9600 往左 3 6.5840 14.8042 7.1680 往左 4 5.8672 10.1942 5.7344 往左
AI 中的应用场景
| 场景 | 与本案的联系 |
|---|---|
| 梯度下降训练 | 神经网络训练的本质:用损失函数对每个参数的偏导数指引参数更新方向 |
| 数值梯度检查 | 调试反向传播时,用数值梯度验证解析梯度的正确性——正是本案的中心差分法 |
| 超参数搜索 | 贝叶斯优化等调参方法,也是在未知函数曲面上沿着「导数」方向搜索最小值 |
