牛顿法 (Newton’s Method) 在优化中的应用
1. 概念的直观理解 (The “Why” and “What”)
1.1 这是什么?我们为什么需要它?
在机器学习中,我们经常需要找到一个函数的最小值或最大值。例如,训练模型时,我们通常会定义一个损失函数 (Loss Function) 来衡量模型预测与真实值之间的差距。我们的目标就是找到一组模型参数,使得这个损失函数的值最小。这个寻找最小值(或最大值)的过程就是优化 (Optimization)。
牛顿法就是一种用来寻找函数局部最小值(或最大值)的迭代优化算法。它比我们可能更熟悉的梯度下降法(Gradient Descent)通常收敛得更快,因为它利用了函数的二阶导数信息。
1.2 现实类比和几何直觉
想象一下我们在一座山顶(或者山谷底部),我们想要找到最低点。
- 梯度下降法就像是我们闭着眼睛,感受脚下的坡度(梯度),然后沿着最陡峭的方向迈出一步。这一步可能很小,也可能有点大,但我们不知道最低点到底有多远,也不知道走到最低点之前坡度会如何变化。我们只能一步步摸索。
- 牛顿法则更像是我们拥有一个“超能力”,我们不仅知道脚下的坡度(一阶导数/梯度),我们还能感知到坡度变化的趋势,即“地面的弯曲程度”(二阶导数/Hessian矩阵)。有了这些信息,我们就可以预测最低点大致在哪里,然后直接“跳”过去。如果我们的预测足够准确,我们可能只需要几次跳跃就能到达最低点。
几何直觉:用抛物线逼近
对于一个单变量函数 ,牛顿法的核心思想是:在当前点 附近,我们用一个抛物线 (quadratic function) 来近似 。我们知道抛物线有一个唯一的最低点(或最高点),这个最低点可以直接通过公式计算出来。牛顿法就是把这个抛物线的最低点作为下一次迭代的猜测值 。
想象我们在一个复杂的函数曲面上寻找最低点:
- 在当前位置 观察地面。
- 假设当前位置附近的地面是一个抛物线形状。
- 直接计算这个抛物线的最低点在哪里。
- “跳”到这个抛物线的最低点,作为我们的新位置 。
- 重复这个过程,直到我们找到真正的最低点。
这种“用局部抛物线近似”的方法使得牛顿法能够更快地收敛到最小值,因为它利用了更多的函数信息(不仅仅是坡度,还有坡度变化的速率)。
2. 预备数学知识与符号解释 (Mathematical Preliminaries & Notation)
为了理解牛顿法,我们需要回顾一些基础的微积分和线性代数概念。
2.1 导数与偏导数
- 导数 (Derivative):对于单变量函数 ,导数 或 表示函数在某一点的瞬时变化率(斜率)。如果导数为0,通常表示函数在该点有局部最大值、最小值或鞍点。
- 二阶导数 (Second Derivative): 或 表示函数变化率的变化率,即函数的弯曲程度。
- 如果 ,函数在该点是凹的(碗状向上,有局部最小值)。
- 如果 ,函数在该点是凸的(碗状向下,有局部最大值)。
- 偏导数 (Partial Derivative):对于多变量函数 ,偏导数 表示当所有其他变量保持不变时,函数随 的变化率。
2.2 梯度 (Gradient)
- 符号:
- 定义:对于多变量函数 ,其中 是一个向量,梯度是一个向量,其分量是 对每个变量的偏导数。
- 意义:梯度向量指向函数值增长最快的方向,其大小表示该方向上的最大变化率。在优化中,我们通常沿着梯度的反方向(最速下降方向)移动来寻找最小值。
2.3 Hessian 矩阵 (Hessian Matrix)
- 符号: 或
- 定义:对于多变量函数 ,Hessian 矩阵是一个方阵,包含了所有的二阶偏导数。其第 行第 列的元素是 对 和 的二阶偏导数。 即:
- 性质:如果函数 的二阶偏导数是连续的,那么混合偏导数是相等的,即 。这意味着 Hessian 矩阵通常是一个对称矩阵。
- 意义:Hessian 矩阵描述了函数曲率(弯曲程度)的性质。它告诉我们函数在某个方向上是加速增长还是减速增长,这对于判断一个点是局部最小值、最大值还是鞍点至关重要。
2.4 泰勒级数展开 (Taylor Series Expansion)
这是牛顿法的核心数学工具。它允许我们用多项式来近似一个在某点附近足够光滑的函数。
-
单变量函数 在 处的二阶泰勒展开: 这里, 是我们想要估计的函数值点, 是我们知道函数值和导数值的点。
-
多变量函数 在 处的二阶泰勒展开: 这里, 是一个向量, 表示向量或矩阵的转置。
- :函数在当前点的值。
- :这是一个点积,表示一阶导数(梯度)项。
- :这是一个二次型,表示二阶导数(Hessian)项。
2.5 矩阵逆 (Matrix Inverse)
- 对于一个方阵 ,如果存在一个矩阵 使得 (其中 是单位矩阵),则称 为 的逆矩阵。
- 在牛顿法中,我们需要计算 Hessian 矩阵的逆矩阵。
3. 详细分步推导 (Step-by-Step Derivation)
牛顿法的思想是,我们通过找到泰勒展开式的近似函数的最小值来更新我们的参数。对于一个光滑函数,其最小值点(或最大值点)处的导数(或梯度)为零。
3.1 单变量函数 的牛顿法推导
我们的目标是找到 使得 。
步骤 1:二阶泰勒展开 我们用一个抛物线来近似函数 在当前点 附近的行为。这个抛物线就是 在 处的二阶泰勒展开式: 令 为这个近似函数。
步骤 2:找到近似函数的最小值 要找到 的最小值,我们对其求导,并令导数等于零。 我们对 关于 求一阶导数: 解释: 逐项求导。
- 是常数项,求导为 0。
- 是线性项,求导得到 (因为 是一个常数)。
- 是二次项,求导利用链式法则。令 ,则 。 所以,。
结合以上,我们得到 的导数:
为了找到 的最小值点(我们称之为 ),我们将这个导数设为零:
步骤 3:解出 现在我们从上面的方程中解出 。 解释: 将 移到等式右侧。
假设 ,我们可以两边同除以 : 解释: 两边除以 。
最后,将 移到等式右侧,得到牛顿法的迭代公式: 这就是单变量函数牛顿法的核心迭代公式。
3.2 多变量函数 的牛顿法推导
我们的目标是找到 使得 。
步骤 1:二阶泰勒展开 我们用一个二次曲面来近似函数 在当前点 附近的行为。这个二次曲面就是 在 处的二阶泰勒展开式: 令 为这个近似函数。
步骤 2:找到近似函数的最小值 要找到 的最小值,我们对其求梯度,并令梯度等于零(因为最小值处的梯度为零向量)。
我们对 关于 求梯度: 解释: 逐项求梯度。
-
是常数项,求梯度为 。
-
是线性项(例如,如果 且 ,则该项为 )。对于一个向量 ,其梯度是 。所以这一项的梯度是 。
- 复习:向量微积分中的梯度规则
- 如果 是对称矩阵,则
- 复习:向量微积分中的梯度规则
-
是二次型。令 。则该项为 。 由于 是对称矩阵,根据上述梯度规则,其梯度是 。
结合以上,我们得到 的梯度:
为了找到 的最小值点(我们称之为 ),我们将这个梯度设为零向量:
步骤 3:解出 现在我们从上面的方程中解出 。 解释: 将 移到等式右侧。
假设 Hessian 矩阵 是可逆的,我们可以两边同乘以 : 解释: 两边左乘 。
由于 (单位矩阵),我们得到:
最后,将 移到等式右侧,得到多变量牛顿法的迭代公式: 这就是多变量函数牛顿法的核心迭代公式。这里的 常常被称为牛顿方向 (Newton Direction)。
总结: 无论是单变量还是多变量,牛顿法的迭代公式都是: 新的点 = 当前点 - (二阶导数的逆) * (一阶导数)
4. 实例 (Concrete Numerical Example)
让我们通过一个简单的例子来演示牛顿法如何工作。
单变量例子: 假设我们想找到函数 的最小值。 这个函数是一个抛物线,其最小值点可以通过求导并令导数等于 0 找到:。 我们用牛顿法来验证。
首先,我们需要计算一阶导数和二阶导数:
现在,我们选择一个初始点 。让我们选择 。
第一次迭代 ():
- 计算
- 计算
- 应用牛顿法更新公式: 看!我们仅用一次迭代就直接找到了最小值点 。这是因为我们选择的函数是一个二次函数,它的二阶泰勒展开式就是它本身,所以近似是完全准确的。
多变量例子: 假设我们想找到函数 的最小值。
首先,我们需要计算梯度向量 和 Hessian 矩阵 。
-
计算梯度: 所以,梯度向量是:
-
计算 Hessian 矩阵: 所以,Hessian 矩阵是: 注意到 Hessian 矩阵在这个例子中是一个常数矩阵,不依赖于 。
现在,我们选择一个初始点 。
第一次迭代 ():
-
在 处计算梯度:
-
计算 Hessian 矩阵的逆: 对于对角矩阵,逆矩阵是其对角元素取倒数:
-
应用牛顿法更新公式: 再次,我们仅用一次迭代就直接找到了最小值点 。这同样是因为我们选择的函数是一个二次函数,其二阶泰勒展开式就是它本身。 对于更复杂的非二次函数,牛顿法通常需要多次迭代才能收敛,但其收敛速度通常比梯度下降法快得多。
5. NumPy 实现 (Code Implementation)
我们将使用 Python 和 NumPy 来实现牛顿法。
import numpy as np
# --- 1. 单变量函数牛顿法示例 ---
def f_univariate(x):
"""单变量目标函数:f(x) = x^2 - 4x + 7"""
return x**2 - 4*x + 7
def df_univariate(x):
"""单变量目标函数的一阶导数:f'(x) = 2x - 4"""
return 2*x - 4
def ddf_univariate(x):
"""单变量目标函数的二阶导数:f''(x) = 2"""
return 2
def newton_method_univariate(initial_x, tol=1e-6, max_iter=100):
"""
单变量牛顿法实现
Args:
initial_x (float): 初始猜测值 x_0
tol (float): 收敛容差
max_iter (int): 最大迭代次数
Returns:
float: 找到的最小值点 x
"""
x_k = initial_x
print(f"--- 单变量牛顿法开始 (初始点 x_0 = {x_k}) ---")
for i in range(max_iter):
grad = df_univariate(x_k) # 对应数学公式中的 f'(x_k)
hess = ddf_univariate(x_k) # 对应数学公式中的 f''(x_k)
if abs(hess) < 1e-10: # 避免除以零,或者Hessian为零导致无法更新
print(f"迭代 {i+1}: 二阶导数过小,可能无法收敛或遇到鞍点。当前 x = {x_k}")
break
# 牛顿法更新公式:x_{k+1} = x_k - f'(x_k) / f''(x_k)
delta_x = grad / hess
x_new = x_k - delta_x
print(f"迭代 {i+1}: x_k={x_k:.6f}, f'(x_k)={grad:.6f}, f''(x_k)={hess:.6f}, delta_x={delta_x:.6f}, x_{{k+1}}={x_new:.6f}")
if abs(x_new - x_k) < tol:
print(f"--- 单变量牛顿法收敛于 {x_new:.6f},迭代次数:{i+1} ---")
return x_new
x_k = x_new
print(f"--- 单变量牛顿法未收敛 (达到最大迭代次数),最终结果:{x_k:.6f} ---")
return x_k
# 运行单变量牛顿法
# result_univariate = newton_method_univariate(initial_x=0.0)
# result_univariate = newton_method_univariate(initial_x=5.0)
# --- 2. 多变量函数牛顿法示例 ---
def f_multivariate(x_vec):
"""多变量目标函数:f(x1, x2) = x1^2 + 2x2^2 - 4x1 - 8x2 + 10"""
x1, x2 = x_vec[0], x_vec[1]
return x1**2 + 2*x2**2 - 4*x1 - 8*x2 + 10
def grad_multivariate(x_vec):
"""多变量目标函数的梯度向量:nabla f(x) = [2x1 - 4, 4x2 - 8]^T"""
x1, x2 = x_vec[0], x_vec[1]
return np.array([2*x1 - 4, 4*x2 - 8])
def hessian_multivariate(x_vec):
"""多变量目标函数的Hessian矩阵:H(x) = [[2, 0], [0, 4]]"""
# 这个例子中Hessian是常数,不依赖x_vec
return np.array([[2, 0], [0, 4]])
def newton_method_multivariate(initial_x_vec, tol=1e-6, max_iter=100):
"""
多变量牛顿法实现
Args:
initial_x_vec (np.array): 初始猜测向量 x_0
tol (float): 收敛容差
max_iter (int): 最大迭代次数
Returns:
np.array: 找到的最小值点 x 向量
"""
x_k = np.array(initial_x_vec, dtype=float)
print(f"\n--- 多变量牛顿法开始 (初始点 x_0 = {x_k}) ---")
for i in range(max_iter):
grad = grad_multivariate(x_k) # 对应数学公式中的 nabla f(x_k)
hess = hessian_multivariate(x_k) # 对应数学公式中的 H(x_k)
# 检查Hessian是否可逆 (正定或负定,用于最小化通常要求正定)
# 实际应用中,通常会用线性方程组求解 H * delta_x = -grad,而不是直接求逆
# 因为求逆的计算量大且数值稳定性差。这里为了清晰展示公式,我们直接求逆。
try:
hess_inv = np.linalg.inv(hess) # 对应数学公式中的 H(x_k)^-1
except np.linalg.LinAlgError:
print(f"迭代 {i+1}: Hessian矩阵不可逆。当前 x = {x_k}")
break
# 牛顿法更新公式:x_{k+1} = x_k - H(x_k)^-1 * nabla f(x_k)
# 这里的 @ 运算符是矩阵乘法
delta_x = hess_inv @ grad # 对应数学公式中的 H(x_k)^-1 * nabla f(x_k)
x_new = x_k - delta_x
print(f"迭代 {i+1}: x_k={x_k}, grad={grad}, hess={hess.tolist()}, delta_x={delta_x}, x_{{k+1}}={x_new}")
if np.linalg.norm(x_new - x_k) < tol: # 使用L2范数判断收敛
print(f"--- 多变量牛顿法收敛于 {x_new},迭代次数:{i+1} ---")
return x_new
x_k = x_new
print(f"--- 多变量牛顿法未收敛 (达到最大迭代次数),最终结果:{x_k} ---")
return x_k
# 运行多变量牛顿法
result_multivariate = newton_method_multivariate(initial_x_vec=[0.0, 0.0])代码与数学公式的关联:
df_univariate(x)和grad_multivariate(x_vec)对应数学公式中的 和 。ddf_univariate(x)和hessian_multivariate(x_vec)对应数学公式中的 和 。np.linalg.inv(hess)对应数学公式中的 。x_new = x_k - delta_x对应核心的迭代公式 和 。@运算符是 NumPy 中进行矩阵乘法的操作,等价于数学公式中的矩阵与向量的乘积。
请注意,在实际应用中,计算Hessian矩阵的逆(np.linalg.inv(hess))可能非常昂贵且不稳定,特别是对于高维问题。更常用的做法是解一个线性方程组 来找到更新方向 ,然后令 。这个过程可以通过 np.linalg.solve(hess, -grad) 来完成,它比直接求逆更有效率和数值稳定性。