凸优化中的瑞士军刀:次梯度方法在不可微场景下的5种步长选择策略
在机器学习和运筹优化的世界里,我们常常被那些光滑、可微的函数所包围,梯度下降法也因此成为了工程师和研究者手中的“标准答案”。然而,现实世界远非处处光滑。当我们处理带有L1正则项的稀疏模型、ReLU激活的神经网络,或是某些特定的损失函数时,函数的“尖角”和“棱线”让经典的梯度无处可寻。此时,次梯度方法便从工具箱的角落里被重新拾起,它像一把瑞士军刀,虽不似专用工具那般精致高效,却能以惊人的鲁棒性应对各种非光滑的复杂地形。
次梯度方法的核心思想优雅而直接:既然梯度不存在,我们就找一个能“支撑”住函数图像的线性近似——次梯度。但这份优雅背后藏着一个棘手的难题:步长。与梯度下降不同,负次梯度方向不一定是下降方向,这意味着我们无法依赖线搜索来动态确定步长。步长的选择,从自适应计算退化为预先设定,这直接决定了算法是收敛于最优解附近,还是在解空间里漫无目的地徘徊。对于运筹学研究者、算法工程师以及任何需要处理非光滑凸优化问题的人来说,理解并驾驭这五种经典的步长策略,是从理论走向实践的关键一步。本文将深入剖析固定步长、固定步长范数、平方可和但不可和、不可和的递减步长以及不可和的递减步长范数这五种策略,结合收敛性理论与数值实验的洞察,为你揭示在不同场景下如何做出明智的选择。
1. 次梯度方法:当梯度“缺席”时的替补队员
在光滑函数的优化中,梯度指向了函数局部上升最快的方向,其反方向则是下降方向。这个性质是梯度下降法及其变种(如动量法、Adam)能够有效工作的基石。然而,对于凸但不可微的函数,比如在零点不可微的绝对值函数 f(x)=|x|,我们无法定义唯一的梯度。次梯度的概念应运而生,它本质上是所有可能“支撑”函数图像的直线斜率的集合。
次梯度 g 在点 x 处的正式定义是,对于定义域内所有的 y,都满足: f(y) ≥ f(x) + g^T (y - x) 这个不等式意味着,以 g 为斜率的线性函数在 x 处是 f 的一个全局下界估计。对于可微点,次微分集合 ∂f(x) 就是梯度 {∇f(x)};对于不可微点,它是一个集合。以 f(x)=|x| 为例:
- 当
x > 0时,∂f(x) = {1} - 当
x < 0时,∂f(x) = {-1} - 当
x = 0时,∂f(x) = [-1, 1](整个区间)
次梯度方法的形式与梯度下降惊人地相似: x^{(k+1)} = x^{(k)} - α_k * g^{(k)} 其中 g^{(k)} 是 f 在 x^{(k)} 处的任意一个次梯度,α_k > 0 是第 k 步的步长。正是这个“任意一个”带来了根本性的不同。由于 g^{(k)} 可能并非指向下降方向,迭代过程中目标函数值 f(x^{(k)}) 并非单调递减。因此,算法必须额外维护一个“历史最佳点” x_best,记录迭代过程中遇到的最小函数值对应的解。
注意:次梯度方法通常只能保证收敛到最优值的一个邻域内,且收敛速率一般为
O(1/ε²),这比梯度下降在光滑凸函数上的O(1/ε)速率要慢。这是处理非光滑性所付出的代价。
1.1 一个简单的Python示例:感受非单调性
让我们用绝对值函数这个最简单的例子,直观感受次梯度方法的非单调性。我们采用一个固定的次梯度选择规则:在 x=0 时,随机从 [-1,1] 中选取一个值。
import numpy as np
import matplotlib.pyplot as plt
def f(x):
"""目标函数:绝对值函数"""
return np.abs(x)
def subgradient_f(x):
"""次梯度。在x=0处,我们随机返回-1或1来模拟从集合中选取。"""
if x > 0:
return 1.0
elif x < 0:
return -1.0
else:
# 在零点,从次微分[-1,1]中随机选一个。这里简化为以相等概率选-1或1。
return np.random.choice([-1.0, 1.0])
def subgradient_descent(x0, stepsize, max_iter=50):
"""次梯度下降算法"""
x = x0
history = {'x': [x], 'f': [f(x)], 'best_f': [f(x)], 'best_x': [x]}
for k in range(max_iter):
g = subgradient_f(x)
x = x - stepsize * g # 更新
current_f = f(x)
history['x'].append(x)
history['f'].append(current_f)
# 更新历史最佳
if current_f < history['best_f'][-1]:
history['best_f'].append(current_f)
history['best_x'].append(x)
else:
history['best_f'].append(history['best_f'][-1])
history['best_x'].append(history['best_x'][-1])
return history
# 运行实验
x0 = 2.5
stepsize = 0.5
history = subgradient_descent(x0, stepsize, max_iter=30)
# 绘制结果
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
iterations = range(len(history['f']))
axes[0].plot(iterations, history['f'], 'b-o', label='f(x_k)', markersize=4)
axes[0].plot(iterations, history['best_f'], 'r--s', label='Best f so far', markersize=4)
axes[0].axhline(y=0, color='k', linestyle=':', alpha=0.5) # 最优值线
axes[0].set_xlabel('Iteration k')
axes[0].set_ylabel('Function Value')
axes[0].set_title('Non-monotonic Behavior o

6617

被折叠的 条评论
为什么被折叠?



