2. 稀疏约束优化的变体#

除了标准的稀疏约束优化(SCO)问题外,skscope 还提供了对几种有用的SCO变体的支持。

2.1. 分组结构参数#

在某些情况下,我们可能会遇到分组结构的参数,其中所有参数被划分为不重叠的组。这种情况的例子包括线性模型下的组变量选择[1]多任务学习等。

在处理具有组结构的参数时,我们将每个参数组视为一个单元,同时选择或取消选择组中的所有参数。这个问题被称为组SCO(GSCO)。

例如,如果我们的目标是从总共\(q\)个参数组中选择\(s\)组参数,GSCO问题可以表述如下:

\[\min_{\theta \in R^p} f(x),\operatorname{ s.t. } \sum_{i=1}^q I({\|\theta}_{g_i}\|\neq 0) \leq s,\]

其中 \(G=\{g_1, \dots, g_q\}\) 是满足以下条件的索引集的分区:

  • \(g_i \subseteq \{1, \dots, p\}\) 对于 \(i=1, \dots, q\),

  • \(g_i \cap g_j = \emptyset\) 对于 \(i \neq j\),

  • \(\bigcup_{i=1}^q g_i = \{1, \dots, p\}\).

特别是,如果 \(q=p\)\(g_i = \{i\}\)(对于 \(i=1, \dots, q\)),那么 GSCO 等同于 SCO。因此,GSCO 是 SCO 的推广。

当遇到GSCO时,我们使用skscope提供的求解器中的group参数。group参数是一个从0开始无间隙的递增整数数组,长度为维度数。这意味着同一组内的变量必须是相邻的,并且它们将一起被选择或取消选择。以下是一些无效的group参数数组示例:

  • group = [0, 2, 1, 2](非递增),

  • group = [1, 2, 3, 3] (不从0开始),

  • group = [0, 2, 2, 3] (包含一个间隔).

在使用skscope解决GSCO时,请注意

  • the dimensionality 参数是所有参数的数量,

  • sparsity 参数表示 要选择的参数组数量

from skscope import ScopeSolver

q, s, m = 3, 2, 2
params_true = [1, 10, 0, 0, -1, 5]
ScopeSolver(
    dimensionality=q * m,     ## the total number of parameters
    sparsity=s,               ## the number of parameter groups to be selected
    group=[0, 0, 1, 1, 2, 2], ## specify group structure
)

2.2. 预选非稀疏参数#

在使用各种模型时,通常会有一些参数必须具有非零值。例如:

  • 在具有截距的线性模型中,截距项通常被假定为非稀疏的;

  • 在Ising模型中,对应矩阵的对角线项表示外部磁场的强度,通常被认为是非零的。

\(\mathcal{P}\) 表示预选参数的集合,广义SCO被表述为:

\[\arg\min\limits_{\theta \in R^p} f(\theta) \text{ s.t. } ||\theta_{\mathcal{P}^c}||_0 \leq s.\]

skscope 允许用户使用 preselect 参数来指定这些预选的非稀疏参数。该参数是一个整数列表,求解器将始终选择这些参数。

以下是preselect使用的一个示例:

from skscope import ScopeSolver

solver = ScopeSolver(
    dimensionality=10,      ## 10 parameters in total
    sparsity=3,             ## 3 non-sparse parameters to be selected
    preselect=[0, 1],   ## always select the first two parameters as non-sparse values
)

2.3. #

Layer,即skscope.layer中定义的任何类,是目标函数的“装饰器”。 参数在进入目标函数之前将由Layer处理。 不同的层可以实现不同的效果, 并且它们可以顺序连接在一起形成一个更大的层, 从而实现更复杂的功能。

在实践中,可能有一些参数的要求可以通过修改目标函数来实现。 例如,如果我们希望未选择特征对应的参数等于一个常数而不是零,即

\[\arg\min_{\theta \in R^p} f(\theta) \text{ s.t. } ||\theta - \mu||_0 \leq s,\]

其中 \(\mu \in R^p\) 是一个偏移向量。为此,我们可以将原始问题重新表述如下:

\[\arg\min_{\theta' \in R^p} f(L(\theta')) \text{ s.t. } ||\theta'||_0 \leq s,\]

其中 \(L(\theta') = \theta' + \mu\) 是一个向参数添加常数偏移的层。

skscope中,我们可以通过稀疏求解器的solve方法中的layers参数来实现这一点。

from skscope import ScopeSolver
import skscope.layer as Layer
from jax import numpy as jnp

X = jnp.array([[1, 2, 3], [4, 5, 6]])
y = jnp.array([1, 2])

def loss(params):
    return jnp.sum((X @ params - y) ** 2)

solver = ScopeSolver(3, 1)

solver.solve(
    loss,
    layers=[Layer.OffsetSparse(dimensionality=3, offset=1)],
)

print(solver.get_estimated_params())

params 在进入 loss 之前将通过一个偏移层 OffsetSparse。 这样,未选择特征对应的参数将等于1而不是零。

此外,我们可以同时使用多个层来实现更复杂的功能。 layers 是一个层的列表,参数在进入 loss 之前会按顺序通过这些层。

solver.solve(
    loss,
    layers=[
        Layer.LinearConstraint(dimensionality=3, coef=jnp.array([[1, 1, 1]])),
        Layer.NonNegative(dimensionality=3),]
)

这将为参数添加一个线性约束 \(\theta_1 + \theta_2 + \theta_3 = 1\) 和一个非负约束。

skscope.layer中,我们提供了几种用于重新参数化的层:NonNegativeLinearConstraintSimplexConstraintBoxConstraint。 此外,用户还可以通过继承skscope.layer.Identity类来定义自己的层。

2.4. 灵活的优化接口#

对于skscope中的所有求解器(除了IHTSolver),这些求解器中不可或缺的一步是解决一个优化问题:

\[\arg\min_{\theta \in R^s} f(\theta),\]

其中

  • \(\theta\) 是一个 \(s\) 维参数向量(注意 \(s\) 是 SCO 中所需的稀疏性)

  • \(f(\theta)\) 是目标函数;

所有在skscope中的求解器都使用scipy.optimize中的L-BFGS-B算法作为此问题的默认数值优化求解器。

在某些情况下,\(\theta\)的内在结构可能会有额外的约束,这些约束可以表述为一个集合\(\mathcal{C}\)

\[\arg\min_{\theta \in R^s, \theta \in \mathcal{C}} f(\theta).\]

一个典型的例子是用于连续随机变量的高斯图模型,它将\(\theta\)限制在对称正定空间上(参见此示例gaussian precision matrix)。尽管默认的数值求解器无法解决这个问题,skscope提供了一个灵活的接口,允许替换它。具体来说,用户可以通过正确设置求解器中的numeric_solver来更改默认的数值优化求解器。

> 请注意,numeric_solver 的接受输入应与 skscope.numeric_solver.convex_solver_LBFGS 具有相同的接口。

from skscope import ScopeSolver
def custom_numeric_solver(*args, **kwargs):
    params = []
    # do something about params
    return params

p, k = 10, 3
solver = ScopeSolver(p, k, numeric_solver=custom_numeric_solver)

此功能显著扩展了skscope的应用范围,使其能够与Python中的其他强大优化工具包合作。 我们将简要介绍一些示例:

  • cvxpy: 一个开源的Python嵌入式建模语言,用于凸优化问题。其官方网站提供了强大的功能(如半定规划)。

  • scipy.optimize: 包含非线性问题、线性规划、约束和非线性最小二乘法、根查找和曲线拟合的求解器。其文档可以在这里找到。

2.5. 参考#

  • [1] 张, Y., 朱, J., 朱, J., & 王, X. (2023). 一种拼接方法用于最佳子集组选择. INFORMS 计算杂志, 35(1), 104-119.