用导数找函数最小值
用数值差分估计导数,沿着导数的反方向一步步移动,直观感受「导数指引搜索方向」。
学完本案例你将理解:导数告诉你往哪走函数值会上升,反方向走就会下降——这就是梯度下降的雏形。
生活引入
大雾天蒙眼下山
伸手不见五指的大雾天,你要从山顶走到山谷。你看不见整个山势,只能感受到脚下的坡度。你的策略很简单:感受坡度最陡的方向 → 往最陡下坡方向走一小步 → 停下来 → 重新感受 → 再走一小步。
「感受坡度」就是求导数,「走一小步」就是用导数的反方向更新位置。这个方法不需要知道整座山的形状,只需要知道脚下的坡度。
直观理解
对于函数
从
- 如果导数
(函数在上升),就往左走(减小 x) - 如果导数
(函数在下降),就往右走(增大 x) - 导数的绝对值越大,说明坡越陡,可以走远一点
数学定义
数值导数(中心差分)
不需要推导求导公式。用两个非常接近的点的函数值差除以距离,就得到了导数的近似值。
朴素下山算法
其中
Python 动手实践
实例
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}")python
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 中的应用场景
| 场景 | 与本案的联系 |
|---|---|
| 梯度下降训练 | 神经网络训练的本质:用损失函数对每个参数的偏导数指引参数更新方向 |
| 数值梯度检查 | 调试反向传播时,用数值梯度验证解析梯度的正确性——正是本案的中心差分法 |
| 超参数搜索 | 贝叶斯优化等调参方法,也是在未知函数曲面上沿着「导数」方向搜索最小值 |
AI 思考中...