3.2 凸集与凸函数
3.1 节把凸性列为问题分类的第一维度,并说明了它在优化中的特殊地位:凸问题的一阶稳定点就是全局最优,算法由此获得全局收敛保证。本节给出凸性的完整内容:凸集的定义与常见例子,分离定理与对偶锥,投影与最近点问题,凸函数的判定条件与保凸运算,共轭函数与 Fenchel 对偶,以及光滑性、强凸性与次梯度。
这些内容有一个共同的目标:把几何直觉变成可验证的判据。判断一个集合是否凸,可以检查两点之间的线段是否仍在集合内;判断一个函数是否凸,可以看它的图像上方图是否为凸集,或检查 Hessian 是否半正定。这些判据都能在具体问题上机械地执行,得到的是确定的结论而不是猜测。后续各节的分析工具多数在本节建立:3.3 节的收敛速率用到光滑性与强凸性给出的二次上下界,3.6 节的对偶理论用到分离定理与共轭函数,3.7 节的近端方法用到投影与次微分。
3.2.1 凸集与凸组合
定义
集合
直观含义是:集合中任意两点的连线整段都在集合内。空集、单点集与整个
反复应用定义得到凸组合(convex combination)的封闭性:若
常见凸集
超平面与半空间。集合
多面体与单纯形。有限个半空间与超平面的交称为多面体(polyhedron),它是线性规划(3.6 节)的可行域。单纯形(simplex)是一类特殊多面体:给定仿射无关的点集
范数球与范数锥。对任意范数
二阶锥。集合
称为二阶锥(second-order cone),它是欧氏范数的范数锥。二阶锥规划的可行域由若干个二阶锥与仿射约束组成(3.6 节)。
半正定锥。对称矩阵集合
称为半正定锥,其中
仿射集是凸集的特例:集合中任意两点的连线延长后仍在集合内,即
保凸运算与凸包
从已知凸集构造新凸集的运算清单,省去了逐个验证定义。
交:任意多个凸集的交集是凸集(对凸组合封闭性在交运算下保持)。这一条最常用:多面体是半空间的交,可行域通常是若干凸集的交。
仿射映射的像与原像:
笛卡尔积与部分变量最小化:两个凸集的笛卡尔积是凸集;若
凸包:任意集合
多面体有一个对偶描述。有界多面体等于它的极点(顶点)的凸包,而无界多面体等于极点的凸包加上回收锥。线性规划的最优解总可以在极点处取到(3.6 节),这一性质把连续优化问题转化为对有限个顶点的搜索,单纯形法正是沿顶点移动的算法。
3.2.2 分离定理与对偶锥
分离定理
凸集最有力的几何工具是分离定理(separation theorem):两个不相交的凸集可以用一个超平面分开。
定理:设
若进一步假设两个集合之一为闭集、另一个为紧集,则上面的不等号可以写成严格不等号。
证明基于最近点问题:取两个集合之间的最近点对
分离定理是凸分析的支柱,后续两处直接使用。其一,3.6 节的 KKT 条件:在最优点处,目标函数的负梯度方向与积极约束的梯度方向构成的锥之间存在分离超平面,分离的系数就是拉格朗日乘子。其二,对偶理论:原问题与对偶问题之间的间隙为零时,两者的最优解由同一个超平面联系,这一超平面就是支撑超平面(
与之配套的还有 Farkas 引理,它处理线性不等式系统的可解性。
Farkas 引理:对矩阵
引理的表述看起来抽象,含义是:线性约束系统无解时,可以给出一个显式的无解证明。第二种情形的
对偶锥与极锥
对集合
称为
几个常见例子:非负象限的对偶锥是自身;二阶锥的对偶锥是自身(自对偶);半正定锥也是自对偶的,
对偶锥刻画可行方向。若约束写成
3.2.3 投影与最近点问题
投影算子
给定闭凸集
投影有两个等价的刻画,它们在算法分析中反复使用。
变分不等式:
几何含义是:从投影点指向
非扩张性:对任意
投影不会放大距离。这条性质是投影梯度法与近端方法收敛性证明的关键(3.3 与 3.7 节)。
常见凸集上的投影
盒子
单纯形
仿射集
半正定锥:把对称矩阵做特征分解后截断负特征值,
下列代码用数值方法演示分离定理与投影的三条性质:先求两个凸集之间的最近点对,用连接方向构造分离超平面并在采样点上验证;再检验投影的非扩张性与变分不等式。
import numpy as np
from scipy import optimize
# 两个不相交凸集:单位圆盘 A,盒子 B = [1.5, 3] x [-0.5, 0.5]
def closest_pair():
"""求两个凸集之间的最近点对(约束优化)"""
def dist(v):
a, b = v[:2], v[2:]
return np.sum((a - b) ** 2)
cons = [
{'type': 'ineq', 'fun': lambda v: 1 - v[0] ** 2 - v[1] ** 2}, # A 在单位圆盘内
{'type': 'ineq', 'fun': lambda v: v[2] - 1.5}, # B 的坐标范围
{'type': 'ineq', 'fun': lambda v: 3 - v[2]},
{'type': 'ineq', 'fun': lambda v: v[3] + 0.5},
{'type': 'ineq', 'fun': lambda v: 0.5 - v[3]},
]
res = optimize.minimize(dist, [0, 0, 2, 0], constraints=cons)
return res.x[:2], res.x[2:]
a, b = closest_pair()
w = b - a # 分离方向取两个最近点之差
c = 0.5 * w @ (a + b) # 超平面过中点
print(f"最近点对:a = {np.round(a, 4)}(圆盘),b = {np.round(b, 4)}(盒子)")
print(f"分离超平面:w·a = {w @ a:.4f} <= c = {c:.4f} <= w·b = {w @ b:.4f}")
rng = np.random.default_rng(0)
th = rng.random(20000) * 2 * np.pi
r = np.sqrt(rng.random(20000))
ptsA = np.stack([r * np.cos(th), r * np.sin(th)], axis=1) # 圆盘内采样
ptsB = np.stack([rng.uniform(1.5, 3, 20000), rng.uniform(-0.5, 0.5, 20000)], axis=1)
print(f"采样验证:A 侧最大值 = {np.max(ptsA @ w):.4f},B 侧最小值 = {np.min(ptsB @ w):.4f}")
# 盒子投影的非扩张性
def proj_box(x, lo, hi):
return np.clip(x, lo, hi)
X = rng.normal(size=(20000, 2)) * 2
Y = rng.normal(size=(20000, 2)) * 2
num = np.linalg.norm(proj_box(X, -0.5, 0.5) - proj_box(Y, -0.5, 0.5), axis=1)
den = np.linalg.norm(X - Y, axis=1)
mask = den > 1e-9
print(f"盒子投影:最大距离比 = {np.max(num[mask] / den[mask]):.6f}(不超过 1)")
# 单纯形投影:排序法求阈值
def proj_simplex(v):
u = np.sort(v)[::-1]
css = np.cumsum(u) - 1
idx = np.arange(1, len(v) + 1)
cond = u - css / idx > 0
rho = idx[cond][-1]
theta = css[cond][-1] / rho
return np.maximum(v - theta, 0)
for x in [np.array([0.4, 1.2, -0.3]), np.array([2.0, 2.0, 2.0])]:
p = proj_simplex(x)
Z = rng.dirichlet(np.ones(3), 2000)
print(f"x = {x} -> P(x) = {np.round(p, 4)},变分不等式最大偏离 = {np.max((x - p) @ (Z - p).T):.2e}")
# 分离超平面把两个集合分开,采样点在两侧;盒子投影的距离比恰好取到 1
# 单纯形投影满足非负性和和为一,变分不等式在机器精度内成立输出验证了三条性质。分离超平面的两侧分别容纳两个集合(圆盘侧最大值
3.2.4 凸函数与判定条件
定义
函数
是凸集。等价的定义用 Jensen 不等式表述:对任意
函数值在两点之间的取值不超过端点取值的线性插值。取
下水平集(sublevel set)
判定条件
三个判据从不同角度刻画凸性,可以按问题的可计算性选择。
一阶条件(
函数图像处处位于任意一点处切平面的上方。这条不等式还给出一个有用的推论:若
二阶条件(
单调梯度:
严格凸要求 Jensen 不等式在
强凸性给出曲率的下界,是线性收敛速率的来源(下一小节与 3.3 节)。
保凸运算
从简单凸函数构造复杂凸函数的清单。
非负加权和保持凸性;凸函数与仿射映射的复合
几个常用函数的凸性可以直接从清单推出:范数、
下列代码对四个函数验证凸性的三个判定条件:Hessian 的最小特征值、一阶条件在随机点对上的余量、Jensen 不等式在随机点对与随机权重上的违背次数。
import numpy as np
def f_quad(x): return 0.5 * (2 * x[0] ** 2 + 3 * x[1] ** 2 + x[0] * x[1])
def g_quad(x): return np.array([2 * x[0] + 0.5 * x[1], 3 * x[1] + 0.5 * x[0]])
def h_quad(x): return np.array([[2.0, 0.5], [0.5, 3.0]])
def f_neglog(x): return -np.log(x[0])
def g_neglog(x): return np.array([-1 / x[0], 0.0])
def h_neglog(x): return np.array([[1 / x[0] ** 2, 0.0], [0.0, 0.0]])
def f_lse(x): return np.log(np.exp(x[0]) + np.exp(x[1]))
def g_lse(x):
e = np.exp(x - np.max(x))
return e / e.sum()
def h_lse(x):
p = g_lse(x)
return np.diag(p) - np.outer(p, p)
def f_bad(x): return x[0] ** 2 - 3 * x[0] * x[1] + x[1] ** 2 # 不定二次型
g_bad = lambda x: np.array([2 * x[0] - 3 * x[1], -3 * x[0] + 2 * x[1]])
h_bad = lambda x: np.array([[2.0, -3.0], [-3.0, 2.0]])
cases = {
"二次型": (f_quad, g_quad, h_quad, np.array([0.5, 0.5])),
"负对数": (f_neglog, g_neglog, h_neglog, np.array([4.0, 0.0])),
"log-sum-exp": (f_lse, g_lse, h_lse, np.array([-1.0, 1.0])),
"不定二次型": (f_bad, g_bad, h_bad, np.array([0.3, 0.7])),
}
rng = np.random.default_rng(1)
for name, (f, g, H, x0) in cases.items():
min_eig = np.linalg.eigvalsh(H(x0)).min()
viol = 0
for _ in range(20000):
x = x0 + rng.normal(scale=0.8, size=2)
y = x + rng.normal(scale=0.8, size=2)
if name == "负对数" and (x[0] <= 0.05 or y[0] <= 0.05):
continue
if f(y) - f(x) - g(x) @ (y - x) < -1e-10: # 一阶条件
viol += 1
jviol = 0
for _ in range(20000):
x = x0 + rng.normal(scale=0.8, size=2)
y = x0 + rng.normal(scale=0.8, size=2)
if name == "负对数" and (x[0] <= 0.05 or y[0] <= 0.05):
continue
t = rng.random()
if f(t * x + (1 - t) * y) > t * f(x) + (1 - t) * f(y) + 1e-10: # Jensen
jviol += 1
print(f"{name:12s}: Hessian 最小特征值 = {min_eig:+.3f},一阶条件违背 {viol:5d} 次,Jensen 违背 {jviol:5d} 次")
# 三个凸函数的违背次数为零(负对数与 log-sum-exp 的 Hessian 最小特征值为 0,对应平坦方向与平移不变性)
# 不定二次型的 Hessian 最小特征值为 -1,两个判据都被大量违背输出把凸性与非凸性的差别显示得很直接。三个凸函数的两个判据在
3.2.5 共轭函数与 Fenchel 对偶
共轭函数
函数
右端的量
三个基本事实把共轭函数与前面的工具连接起来。
Fenchel 不等式:对任意
等号成立当且仅当
二次共轭:
共轭次梯度:Fenchel 不等式取等号的条件可以写成一个对称的对偶关系,
几个常用函数的共轭有闭式:
| 指示函数 | 支撑函数 |
最后一行的对偶关系说明:集合的支撑函数就是其指示函数的共轭,因此闭凸集可以由它的支撑函数完全描述。这一事实把集合的包含、交运算转化为支撑函数的不等式,是凸集计算的基础工具。
Fenchel 对偶
共轭函数给出一种对偶形式,它与 3.6 节的拉格朗日对偶是同一件事的两种表达:
右端是左端的下界。凸性与约束规范成立时左右两端相等,右端的最优解
下列代码验证共轭函数的两条性质:Fenchel 不等式在随机点上成立,以及二次共轭在闭凸函数上恢复原函数、在非凸函数上给出凸包络。
import numpy as np
xs = np.linspace(-3, 3, 2001)
# f(x) = |x| 的共轭:|y| <= 1 时等于 0,|y| > 1 时为 +inf(数值上随网格扩大而增大)
print("f(x) = |x| 的共轭 sup_x (y x - |x|):")
for y in [-2.0, -1.0, -0.5, 0.0, 0.5, 1.0, 2.0]:
print(f" y = {y:+.1f}: 数值上确界 = {np.max(y * xs - np.abs(xs)):8.3f}")
# f(x) = 0.5 x^2 的二次共轭恢复原函数
print("\nf(x) = 0.5 x^2 的二次共轭:")
for x in [-2.0, -0.5, 0.0, 1.5]:
biconj = np.max(x * xs - 0.5 * xs ** 2) # f*(y) = 0.5 y^2
print(f" x = {x:+.1f}: f(x) = {0.5 * x ** 2:.4f},f**(x) = {biconj:.4f}")
# f 为 {-1, 1} 上的指示函数时 f*(y) = |y|,二次共轭为 [-1, 1] 的指示函数(凸包络)
print("\nf(x) = 0(x = ±1),+inf(其他)的二次共轭:")
for x in [-2.0, -0.5, 0.5, 2.0]:
biconj = np.max(x * xs - np.abs(xs))
print(f" x = {x:+.1f}: f**(x) = {biconj:.4f}(区间外随网格扩大而增大,对应 +inf)")
# Fenchel 不等式:f(x) + f*(y) - x y = 0.5 (x - y)^2 >= 0
rng = np.random.default_rng(2)
X, Y = rng.normal(size=5000), rng.normal(size=5000)
gap = 0.5 * X ** 2 + 0.5 * Y ** 2 - X * Y
print(f"\nFenchel 不等式最小余量 = {gap.min():.3e},等于 0.5 (x - y)^2 的最小值")
# 二次函数的二次共轭与原函数逐点相同;指示函数的二次共轭是区间的指示函数
# |x| 的共轭在 |y| > 1 时的数值随网格扩大,对应上确界为无穷输出依次验证三条性质。绝对值的共轭在
3.2.6 光滑性、强凸性与次梯度
光滑性与强凸性
凸性刻画曲率的符号,光滑性与强凸性进一步给出曲率的上下界。
强凸(
称为条件数,它决定一阶方法的收敛速度:条件数越大,收敛越慢。二次函数上
次梯度与次微分
可微性不是凸性的必要条件。
全部次梯度构成的集合称为次微分(subdifferential),记作
次微分继承单调性:凸函数的次微分是单调算子,即对任意
这一性质使凸优化与非光滑分析可以用统一的算子语言处理(3.7 节)。次微分还给出最优性条件:
与光滑性和强凸性并列的还有一个概念:自协调性(self-concordance),它限制三阶导数相对二阶导数的比例,是牛顿法复杂度分析(3.6 节内点法)的工具。对
下列代码估计光滑常数与强曲常数(用采样上确界与下确界),并验证次梯度的定义式。
import numpy as np
Q = np.array([[4.0, 1.0], [1.0, 2.0]])
print(f"二次函数 0.5 x'Qx:真实 L = {np.linalg.eigvalsh(Q).max():.6f},m = {np.linalg.eigvalsh(Q).min():.6f}")
rng = np.random.default_rng(3)
grad = lambda x: Q @ x
L_est, m_est = 0.0, np.inf
for _ in range(200000):
x, y = rng.normal(scale=3, size=2), rng.normal(scale=3, size=2)
d = x - y
nd = np.linalg.norm(d)
if nd < 1e-6:
continue
L_est = max(L_est, np.linalg.norm(grad(x) - grad(y)) / nd) # 梯度差之比的上确界
m_est = min(m_est, (grad(x) - grad(y)) @ d / nd ** 2) # 二次下界的下确界
print(f"采样估计:L ≈ {L_est:.6f},m ≈ {m_est:.6f}(条件数 kappa = {L_est / m_est:.4f})")
# |x| 在 0 处的次微分:检验 f(y) >= f(0) + g y 对全体 y 成立的 g
ygrid = np.linspace(-5, 5, 40001)
print("\n|x| 在 0 处的次梯度检验:")
for g in [-1.0, -0.5, 0.0, 0.5, 1.0, 1.5]:
ok = np.all(np.abs(ygrid) >= g * ygrid)
print(f" g = {g:+.1f}:定义式对全体 y 成立:{ok}")
# 采样比值从下方逼近真实常数:L 的估计不超过 4.414214 的有效上确界
# g 在 [-1, 1] 内都满足定义式,g = 1.5 失败,次微分恰为区间 [-1, 1]输出显示采样估计的
3.2.7 本节小结
凸性的几何与判据
本节建立了凸性工具箱。凸集对凸组合封闭,常见例子包括超平面、半空间、多面体、单纯形、范数球、二阶锥与半正定锥,保凸运算(交、仿射映射、部分变量最小化、凸包)使新集合的凸性可以归结为已知成分。分离定理给出凸集的几何核心:不相交的凸集之间存在分离超平面,线性系统的可解性由 Farkas 引理刻画,对偶锥描述可行方向的结构。投影是最近点问题的解,由变分不等式唯一刻画,并满足非扩张性。凸函数的三个判定条件是上方图的凸性、Jensen 不等式、一阶切平面条件与 Hessian 半正定;严格凸与强凸分别保证最优解唯一与线性收敛速率,光滑性与强凸性给出二次上下界,条件数
本节内容以判据与结论为主,下一节开始使用它们。3.3 节把一阶与二阶最优性条件完整列出,用光滑性与强凸性的二次界推导梯度法的收敛速率,并讨论步长选择与梯度的计算方式。
练习题
第 1 题 概念推导
证明两条结论:其一,有限个凸集的交集是凸集;其二,
参考答案
交集的凸性:设
逐点最大值的凸性:记
第一步用了
log-sum-exp 的夹逼:设
三边取对数得
第 2 题 计算推理
参考答案
常数与条件数:Hessian 恒为
共轭函数:由表中的二次函数公式,
梯度下降的行为:梯度为
坐标缩放:令
第 3 题 代码验证
用 3.2.4 节的判定代码检查三个新函数:
参考答案
import numpy as np
rng = np.random.default_rng(5)
def f1(x): return np.abs(x).sum() # L1 范数
def g1(x): # 次梯度:非零坐标取 sign,零坐标取 0
return np.where(np.abs(x) > 1e-12, np.sign(x), 0.0)
def f2(x): return np.log(np.exp(x).sum())
def g2(x):
e = np.exp(x - x.max())
return e / e.sum()
def h2(x):
p = g2(x)
return np.diag(p) - np.outer(p, p)
def f3(x): return x[0] ** 2 * x[1] ** 2
def h3(x): return np.array([[2 * x[1] ** 2, 4 * x[0] * x[1]], [4 * x[0] * x[1], 2 * x[0] ** 2]])
# f1:一阶条件(用次梯度)
x0 = np.array([1.0, -2.0, 0.5])
viol = 0
for _ in range(20000):
y = x0 + rng.normal(size=3)
if f1(y) < f1(x0) + g1(x0) @ (y - x0) - 1e-10:
viol += 1
print("f1 一阶条件违背次数 =", viol)
# f2:Hessian 最小特征值(随机点)
mins = [np.linalg.eigvalsh(h2(rng.normal(size=3))).min() for _ in range(2000)]
print(f"f2 Hessian 最小特征值的最小值 = {np.min(mins):.3e}(非负)")
# f3:Hessian 半正定性随区域变化(行列式 = -12 x1^2 x2^2 < 0,除坐标轴外处处不定)
for pt in [(1.0, 1.0), (1.0, -1.0), (0.0, 2.0)]:
H = h3(np.array(pt))
print(f"f3 在 {pt}:Hessian 特征值 = {np.round(np.linalg.eigvalsh(H), 4)}")输出(节选):
f1 一阶条件违背次数 = 0
f2 Hessian 最小特征值的最小值 = -1.793e-16(非负)
f3 在 (1.0, 1.0):Hessian 特征值 = [-2. 6.]
f3 在 (1.0, -1.0):Hessian 特征值 = [-2. 6.]
f3 在 (0.0, 2.0):Hessian 特征值 = [0. 8.]结论:
关于凸性判定的方法选择:
第 4 题 综合应用
某推荐系统要把物品的打分向量约束到单纯形上(非负且和为一),并希望投影操作足够快。给定打分
参考答案
投影计算:排序后的分量为
和为一,非负,且合理:第二个分量明显最大,投影把全部质量分配给它。
复杂度:排序的复杂度为
不精确投影的问题:截断后重新归一化不满足变分不等式,也不满足非扩张性。取
常见错误
错误 1 · 用下水平集的凸性推断函数的凸性
原因:凸函数的下水平集是凸集,但反过来不成立。
解决:使用三个严格判据之一:上方图的凸性、Jensen 不等式、一阶或二阶条件。对可微函数优先用二阶条件(Hessian 半正定)或一阶条件的数值检验(3.2.4 节的抽样方法)。需要利用下水平集结构时,明确标注结论适用于拟凸情形,并说明拟凸问题需要不同的算法(如二分法求解下水平集)。
错误 2 · 混淆严格凸与强凸
原因:两个概念都强化了凸性,但强度不同。严格凸保证最优解唯一,不保证线性收敛速率;强凸要求二次下界,才给出线性收敛与条件数。
解决:报告问题时给出可验证的常数:光滑常数为
错误 3 · 认为次梯度总是可以作为下降方向
原因:次梯度方向不一定是下降方向。负次梯度
解决:非光滑问题优先使用近端方法(3.7 节)而不是原始次梯度法,近端算子把非光滑部分精确处理,光滑部分用梯度,收敛速率提高到
错误 4 · 把共轭函数当普通函数的对偶形式使用而忽略凸性前提
原因:共轭函数
解决:使用共轭与 Fenchel 对偶前先确认