Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

中文分词:高级篇

引入 CRF

前面章节的分词方法容易理解,也有很高的执行效率,但面对歧义、新词和复杂上下文时,仅靠局部匹配往往难以作出稳定判断。更进一步的做法,是把中文分词看成一个字序列标注问题:为句子中的每个字预测其在词语中的位置,再根据标签序列恢复词语边界。

条件随机场(Conditional Random Field,CRF)正是解决这类问题的经典模型,也是 Wapiti 使用的核心模型。给定字序列 \(x = (x_1, x_2, \ldots, x_n)\),CRF 不再逐字独立判断,而是为完整的标签序列 \(y = (y_1, y_2, \ldots, y_n)\) 计算条件概率,并从中选择整体得分最高的序列。这使模型既能利用当前字及其上下文,也能约束相邻标签之间的组合关系。

CRF 用于中文分词的关键能力

  • 条件建模:直接建模 \(P(y\mid x)\),关注已知句子时标签序列出现的概率
  • 全局解码:联合考虑整句话的标签,不以某个字的局部最优结果代替全局最优结果
  • 特征组合:可以同时使用字、上下文、字符类型和标签转移等特征

举例,“南京市长江大桥”只看局部可能会把“市长”识别成一个词,但结合完整上下文,更合理的切分是“南京市 / 长江大桥”。常用的 BMES 标签体系以 B、M、E、S 分别表示词首、词中、词尾和单字成词。这个切分可以表示为:

南  京  市  长  江  大  桥
B   M   E   B   M   M   E

这样,分词问题就变成了为字序列寻找最佳标签序列的问题。

目标函数

概率公式

CRF 的条件概率定义为:

$$ P( y|x ) = \frac{1}{Z(x)}\times\exp\left(\sum_{i}\sum_{k}\lambda_k f_k(y_{i-1},y_i,x,i)\right) $$

关键组成部分

  • \(Z(x)\):归一化因子,确保概率和为1
  • \(f_k(y_{i-1},y_i,x,i)\):特征函数
  • \(\lambda_k\):特征权重参数

特征函数类型

一元特征(Unigram Features) $$ f_1(y_i,x,i) = \begin{cases} 1 & \text{if } (y_i,x,i) \text{满足指定的一元特征模板} \\ 0 & \text{otherwise} \end{cases} $$

二元特征(Bigram Features) $$ f_2(y_{i-1},y_i,x,i) = \begin{cases} 1 & \text{if } (y_{i-1},y_i,x,i) \text{满足指定的二元特征模板} \\ 0 & \text{otherwise} \end{cases} $$

势函数 为简化表示,引入势函数: $$ \psi_t(y’,y,x) = \exp\left(\sum_k \lambda_k f_k(y’,y,x,t)\right) $$

则条件概率可重写为: $$ P( y| x) = \frac{1}{Z(x)} \times \prod_t \psi_t(y_{t-1},y_t,x) $$

归一化因子

归一化因子需要对全部可能的标签序列求和:

$$ Z(x) = \sum_y \prod_t \psi_t(y_{t-1},y_t,x) $$

问题:如果有 \(T\) 个位置,每个位置有 \(L\) 个可能标签,则需要计算 \(L^T\) 个序列!

方案:引入动态规划算法高效计算。

标注过程

维特比

目标:找到最优标签序列,使得 \(P(y|x)\) 最大。

等价于最大化: $$ \text{score}(y, x) = \sum_i \sum_k \lambda_k f_k(y_{i-1}, y_i, x, i) = \sum_i \log \psi_i(y_{i-1}, y_i, x) $$

动态规划

状态定义 $$ \delta_t(y) = \max_{y_1,…,y_{t-1}} \text{score}(y_1,…,y_{t-1},y, x_1,…,x_t) $$

\(\delta_t(y)\) 表示到位置\(t\)标签为\(y\)的最优路径得分。

递推公式 $$ \delta_1(y) = \log \psi_1(\text{START}, y, x) $$ $$ \delta_t(y) = \max_{y’} \left[\delta_{t-1}(y’) + \log \psi_t(y’, y, x)\right] $$

回溯指针 $$ \phi_t(y) = \arg\max_{y’} \left[\delta_{t-1}(y’) + \log \psi_t(y’, y, x)\right] $$

梯度推导

训练目标

目标是最大化对数似然函数:

$$ L(\lambda) = \sum_s \log P(y^{(s)}|x^{(s)}) - R(\lambda) $$

其中:

  • \(s\) 是训练样本索引
  • \(y^{(s)}\) 和 \(x^{(s)}\) 分别是第 \(s\) 个样本的真实标签序列和观察序列
  • \(R(\lambda)\) 是正则化项

概率公式

$$ P(y|x) = \frac{1}{Z(x)} \exp \left(\sum_i \sum_k \lambda_k f_k(y_{i-1},y_i,x,i)\right) $$

其中:

  • \(Z(x)\) 是归一化因子
  • \(f_k(y_{i-1},y_i,x,i)\) 是第\(k\) 个特征函数
  • \(\lambda_k\) 是对应的权重参数

推导过程

第一步:目标函数展开

把概率公式带入目标函数: $$ \log P(y|x) = \sum_i \sum_k \lambda_k f_k(y_{i-1},y_i,x,i) - \log Z(x) $$

目标函数变为: $$ L(\lambda) = \sum_s \left[\sum_i \sum_k \lambda_k f_k(y_{i-1}^{(s)},y_i^{(s)},x^{(s)},i) -\log Z(x^{(s)})\right] - R(\lambda) $$

第二步:对参数求偏导

对参数 \(\lambda_k\) 求偏导:

$$ \frac{\partial L}{\partial \lambda_k} = \sum_s \left[\sum_i f_k(y_{i-1}^{(s)},y_i^{(s)},x^{(s)},i) - \frac{\partial \log Z(x^{(s)})}{\partial \lambda_k}\right] - \frac{\partial R(\lambda)}{\partial \lambda_k} $$

分析各项:

  • 第一项: \(\sum_i f_k(y_{i-1}^{(s)},y_i^{(s)},x^{(s)},i)\) 是真实标签序列下的特征值总和(经验期望
  • 第二项: \(\frac{\partial \log Z(x^{(s)})}{\partial \lambda_k}\) 需要详细推导(模型期望
  • 第三项: 正则化项的导数,L1 会特殊些,要专门来处理

关键在于计算第二项!

第三步:计算 \(\frac{\partial \log Z(x)}{\partial \lambda_k}\)

使用链式法则:

$$ \frac{\partial \log Z(x)}{\partial \lambda_k} = \frac{1}{Z(x)} \cdot \frac{\partial Z(x)}{\partial \lambda_k} $$

第四步:计算 \(\frac{\partial Z(x)}{\partial \lambda_k}\)

归一化因子的定义:

$$ Z(x) = \sum_y \exp\left(\sum_i \sum_k \lambda_k f_k(y_{i-1},y_i,x,i)\right) $$

用势函数表示:

$$ Z(x) = \sum_y \prod_t \psi_t(y_{t-1},y_t,x) $$

其中势函数:

$$ \psi_t(y_{t-1},y_t,x) = \exp\left(\sum_k \lambda_k f_k(y_{t-1},y_t,x,t)\right) $$

对 \(\lambda_k\) 求偏导:

$$ \frac{\partial Z(x)}{\partial \lambda_k} = \sum_y \frac{\partial}{\partial \lambda_k} \prod_t \psi_t(y_{t-1},y_t,x) $$

第五步:势函数的偏导数

由于 \(\psi_t\) 是指数函数:

$$ \frac{\partial \psi_t}{\partial \lambda_k} = \psi_t \cdot f_k(y_{t-1},y_t,x,t) $$

使用乘积法则,对于乘积 \(\prod_t \psi_t\):

$$ \frac{\partial}{\partial \lambda_k} \prod_t \psi_t = \sum_i \left[\prod_{t \ne i} \psi_t \right] \cdot \frac{\partial \psi_i}{\partial \lambda_k} $$ $$ = \sum_i \left[\prod_{t \ne i} \psi_t \right] \cdot \psi_i \cdot f_k(y_{i-1},y_i,x,i) $$ $$ = \prod_t \psi_t \cdot \sum_i f_k(y_{i-1},y_i,x,i) $$

关键洞察:最后一步从各项中提取了公共因子 \(\prod_t \psi_t\),将表达式化为“连乘 × 连加”

第六步:代入求和

把结果代入 \(Z(x)\) 的偏导数:

$$ \frac {\partial Z(x)}{\partial \lambda_k} = \sum_y \prod_t \psi_t(y_{t-1},y_t,x) \cdot \sum_i f_k(y_{i-1},y_i,x,i) $$ $$ = \sum_y \left[\sum_i f_k(y_{i-1},y_i,x,i)\right] \cdot \prod_t \psi_t(y_{t-1},y_t,x) $$

第七步:转化为概率形式

关键转化:注意到

$$ \prod_t \psi_t(y_{t-1},y_t,x) = P(y|x) \cdot Z(x) $$

代入得:

$$ \frac{\partial Z(x)}{\partial \lambda_k} = \sum_y \left[\sum_i f_k(y_{i-1},y_i,x,i)\right] \cdot P(y|x) \cdot Z(x) $$ $$ = Z(x) \cdot \sum_y P(y|x) \cdot \left[\sum_i f_k(y_{i-1},y_i,x,i)\right] $$

第八步:最终结果

把结果代入链式法则:

$$ \frac{\partial \log Z(x)}{\partial \lambda_k} = \frac{1}{Z(x)} \cdot Z(x) \cdot \sum_y P(y|x) \cdot \left[\sum_i f_k(y_{i-1},y_i,x,i)\right] $$

这正是模型期望:

$$ E_{P(y|x)}\left[\sum_i f_k(y_{i-1},y_i,x,i)\right] $$

第九步:梯度的最终形式

完整梯度公式

$$ \frac{\partial L}{\partial \lambda_k} = \sum_s \left[\sum_i f_k(y_{i-1}^{(s)}, y_i^{(s)},x^{(s)},i)\right] {}- \sum_s E_{P(y|x^{(s)})}\left[\sum_i f_k(y_{i-1},y_i,x^{(s)},i)\right] {}- \frac{\partial R(\lambda)}{\partial \lambda_k} $$

或者表示成

$$ \frac{\partial L}{\partial \lambda_k} = \text{经验期望} - \text{模型期望} {}- \frac{\partial R(\lambda)}{\partial \lambda_k} $$

其中:

  • 经验期望:真实数据中特征 \(f_k\) 出现的次数
  • 模型期望: 当前模型认为特征 \(f_k\) 应该出现的次数

前向后向

核心问题 对于已经推导出来的梯度公式:

$$ \frac{\partial L}{\partial \lambda_k} = \text{经验期望} - \text{模型期望} {}- \frac{\partial R(\lambda)}{\partial \lambda_k} $$

其中模型期望是:

$$ E_{P(y|x)}[f_k] = \sum_y P(y|x) \cdot \left[\sum_i f_k(y_{i-1},y_i,x,i)\right] $$

要是直接计算需要遍历 \(L^T\) 个可能的标签序列,计算量太大了!

这里与维特比算法的区别只有一个关键运算:维特比在每个状态保留最大值,用于寻找最佳切分;前向后向算法对所有路径求和,用于计算边际概率和模型期望。

突破口

对于一元特征: $$ E[f_k^{(1)}] = \sum_y P(y|x) \sum_i f_k^{(1)}(y_i,x,i) $$

交换求和顺序 $$ = \sum_i \sum_y P(y|x) \cdot f_k^{(1)}(y_i,x,i) $$

进一步分解:只有当 \(y_i\) 取特定值时 \(f_k^{(1)}\) 才为1 $$ = \sum_i \sum_{y_i} f_k^{(1)}(y_i,x,i) \sum_{y_1,…,y_{i-1},y_{i+1},…,y_T}P(y_1,…,y_T|x) $$

得到边际概率: $$ = \sum_i \sum_{y_i} f_k^{(1)}(y_i,x,i) \cdot P(y_i|x) $$

类似地,对于二元特征: $$ E[f_k^{(2)}] = \sum_i \sum_{y_{i-1}} \sum_{y_i} f_k^{(2)}(y_{i-1},y_i,x,i) \cdot P(y_{i-1},y_i|x) $$

这样,就把求 \(P(y|x)\) 转变成了怎么去计算边际概率 \(P(y_i|x)\) 和 \(P(y_{i-1},y_i|x)\) 了,问题化简了不少。

思路

关键洞察:边际概率可以分解为到达当前位置的前向分数、当前转移的势函数和离开当前位置的后向分数。这里的前向量与后向量都是未归一化分数,不是条件概率。

$$ \psi_t(y’,y,x) = \exp\left(\sum_k \lambda_k f_k(y’,y,x,t)\right) $$

前向过程

分数定义

前向分数 \(\alpha_i(y)\) 是所有以标签 \(y\) 到达位置 \(i\) 的路径势函数之和:

$$ \alpha_i(y) = \sum_{y_1,\ldots,y_{i-1}} \prod_{t=1}^{i}\psi_t(y_{t-1},y_t,x) $$

初始化

$$ \alpha_1(y)=\psi_1(\mathrm{START},y,x) $$

递推公式

$$ \alpha_i(y) = \sum_{y’}\alpha_{i-1}(y’)\psi_i(y’,y,x) $$

最后一个位置的前向分数之和就是配分函数:

$$ Z(x)=\sum_y\alpha_T(y) $$

后向过程

后向分数 \(\beta_i(y)\) 是从位置 \(i\) 的标签 \(y\) 出发,到达序列末尾的所有后缀路径势函数之和:

$$ \beta_i(y) = \sum_{y_{i+1},\ldots,y_T} \prod_{t=i+1}^{T}\psi_t(y_{t-1},y_t,x) $$

初始化

$$ \beta_T(y)=1 $$

递推公式

$$ \beta_i(y) = \sum_{y’}\psi_{i+1}(y,y’,x)\beta_{i+1}(y’) $$

边际概率

一元边际概率

$$ P(y_i=y\mid x) = \frac{\alpha_i(y)\beta_i(y)}{Z(x)} $$

二元边际概率

$$ P(y_{i-1}=y’,y_i=y\mid x) = \frac{ \alpha_{i-1}(y’) \psi_i(y’,y,x) \beta_i(y) }{Z(x)} $$

这两个公式使用同一个全局配分函数 \(Z(x)\),不需要分别定义 \(z_i\)、\(Z_{\text{unigram}}\) 或 \(Z_{\text{bigram}}\)。

数值稳定性

直接连乘势函数容易上溢或下溢。实现时可以在对数域中保存前向与后向分数,并用 \(\operatorname{logsumexp}\) 完成求和。例如:

$$ \log\alpha_i(y) = \operatorname{logsumexp}_{y’} \left( \log\alpha_{i-1}(y’) {}+ \log\psi_i(y’,y,x) \right) $$

也可以在每个位置对向量进行缩放,但前向、后向和边际概率必须使用同一组缩放因子并保持一致的定义。

L1 正则化

CRF 会产生大量特征,其中许多权重接近零,却仍然占用模型空间并参与计算。L1 正则化通过惩罚权重绝对值产生稀疏参数。Wapic 实际最小化的目标同时支持 L1 和 L2:

$$ \begin{aligned} F(\lambda) &=-L(\lambda)+r_1\sum_k|\lambda_k| \\ &\quad+\frac{r_2}{2}\sum_k\lambda_k^2 \end{aligned} $$

其中 \(r_1\) 对应命令行参数 --rho1,控制稀疏程度;\(r_2\) 对应 --rho2,为光滑部分增加 L2 惩罚。GradientComputer::RunGradientComputation 返回这个目标值,并把 \(r_2\lambda_k\) 加入普通梯度;L1 项则由优化器单独处理。

零点不可导

L1 正则化项在非零点可导,但在零点不可导。绝对值函数的次微分为:

$$ \partial |\lambda_k| = \begin{cases} {1} & \text{if } \lambda_k > 0 \\ {-1} & \text{if } \lambda_k < 0 \\ [-1,1] & \text{if } \lambda_k = 0 \end{cases} $$

标准 L-BFGS 假设目标函数光滑,不能直接处理零点处的区间次梯度。后面的 OWL-QN 会用伪梯度和象限约束解决这个问题,并让一部分参数精确变为零。

L-BFGS

L-BFGS(Limited-memory Broyden-Fletcher-Goldfarb-Shanno)是一种拟牛顿方法。它适合优化不含 L1 项的光滑 CRF 目标,并用有限的历史向量近似二阶曲率。

Wapic 用同一个 LBFGSOptimizer 承担两种模式:当 \(r_1=0\) 时执行标准 L-BFGS;当 \(r_1\neq0\) 时启用伪梯度和象限投影,实际执行后文的 OWL-QN。命令行虽然统一写作 -a l-bfgs,默认 \(r_1=0.5\),因此默认训练会进入 OWL-QN 分支。

拟牛顿法

问题基础

CRF 的参数估计本质上是一个无约束优化问题:

$$ \min_{\lambda} F(\lambda) = -\ell(\lambda) + R(\lambda) $$

其中:

  • \(\ell(\lambda)\) 是对数似然函数
  • \(R(\lambda)\) 是正则化项
  • \(\lambda\) 是 CRF 的参数向量

梯度下降只使用一阶信息:

$$ \lambda^{(k+1)} = \lambda^{(k)} -\alpha_k \nabla F(\lambda^{(k)}) $$

牛顿法则使用 Hessian 修正更新方向:

$$ \lambda^{(k+1)} = \lambda^{(k)} - [\nabla^2 F(\lambda^{(k)})]^{-1} \nabla F(\lambda^{(k)}) $$

对于高维 CRF,显式构造和求解 Hessian 的成本过高。拟牛顿法因此不直接计算 Hessian,而是逐步近似它的逆矩阵。

拟牛顿法

用 \(H_k\) 表示 Hessian 逆矩阵的近似,搜索方向为:

$$ d_k = -H_k \nabla F(\lambda^{(k)}) $$

当 \(H_k\) 正定时,\(\nabla F(\lambda^{(k)})^T d_k<0\),因此它是下降方向。线搜索随后决定沿该方向前进多远。

BFGS

BFGS 使用相邻两次迭代的参数变化和梯度变化来更新曲率近似。定义:

$$ s_k=\lambda^{(k+1)}-\lambda^{(k)} $$

$$ y_k=\nabla F(\lambda^{(k+1)})-\nabla F(\lambda^{(k)}) $$

如果 \(H_k\) 表示 Hessian 逆矩阵的近似,那么它应满足割线条件:

$$ H_{k+1}y_k=s_k $$

标准 BFGS 的逆矩阵更新为:

$$ \begin{aligned} H_{k+1} &=(I-\rho_k s_k y_k^T) H_k (I-\rho_k y_k s_k^T) \\ &\quad+\rho_k s_k s_k^T \end{aligned} $$

其中:

$$ \rho_k=\frac{1}{y_k^T s_k} $$

曲率条件

如果 \(H_k\) 正定,并且满足:

$$ y_k^T s_k>0 $$

那么 BFGS 更新得到的 \(H_{k+1}\) 仍然正定,搜索方向:

$$ d_k=-H_k\nabla F(\lambda^{(k)}) $$

就是下降方向。Wapic 的光滑分支使用 Wolfe 条件进行线搜索,并在 UpdateHistory 中直接记录 \(s_k\)、\(y_k\) 和 \(\rho_k=1/(y_k^Ts_k)\)。

L-BFGS

标准 BFGS 需要存储 \(n \times n\) 的矩阵,对于大规模问题(\(n\) 很大)并不现实。L-BFGS 不显式存储 Hessian 逆矩阵,而是保存最近的 \(m\) 组向量对 \({s_k,y_k}\),用它们隐式表示曲率信息。

核心思想:

  • 内存需求: 从\(O(n^2)\)降低到\(O(mn)\)
  • 计算复杂度: 每次迭代从\(O(n^2)\)降低到\(O(mn)\)
  • Wapic 默认值: \(m=5\),可通过 --histsz 调整

两步循环算法

算法目标

两步循环算法的目标是计算搜索方向: $$ d_k = -H_k g_k $$

其中 \(H_k\) 是 Hessian 逆矩阵的 L-BFGS 近似,\(g_k\) 是当前梯度。

关键是:我们要直接计算出 \(H_k g_k\) 的结果,而不显式构造 \(H_k\) 矩阵。

完整算法

输入

  • 当前梯度:\(g_k\)
  • 历史信息:\({s_i, y_i}_{i=k-m}^{k-1}\)(最近\(m\)个向量对)
  • 初始 Hessian 逆矩阵近似:\(H_k^0\)(通常是标量乘以单位矩阵)

输出

  • 搜索方向:\(d_k = -H_k g_k\)

算法步骤

第一步:反向循环(Backward Loop)

初始化: \(q = g_k\)

反向遍历历史信息: \(\text{for } i = k-1, k-2, \ldots, k-m:\) \(\alpha_i = \frac{s_i^T q}{y_i^T s_i}\) \(q = q - \alpha_i y_i\) \(\text{存储 } \alpha_i \text{ 供第二步使用}\)

第二步:正向循环(Forward Loop)

应用初始 Hessian 逆矩阵近似: \(r = H_k^0 q\)

正向遍历历史信息: \(\text{for } i = k-m, k-m+1, \ldots, k-1:\) \(\beta = \frac{y_i^T r}{y_i^T s_i}\) \(r = r + s_i (\alpha_i - \beta)\)

返回结果: \(\text{return } -r \quad \text{// 注意负号,因为我们要的是 } -H_k g_k\)

数学原理

每一对 \((s_i,y_i)\) 都对应一次 BFGS 逆矩阵更新。连续使用最近 \(m\) 个向量对时,矩阵中出现的是这些更新变换的有序乘积,不能把它们简单写成若干秩一矩阵的和。两步循环按照与这些更新相同的顺序执行向量运算,因此能直接得到 \(H_kg_k\),而不需要显式构造 \(H_k\)。

计算复杂度

时间复杂度

  • 每个内积操作:\(O(n)\)
  • 每个循环有 \(m\) 次迭代,每次做常数个内积
  • 总复杂度:\(O(mn)\)

空间复杂度

  • 存储 \(m\) 个 \(s_i\) 向量:\(O(mn)\)
  • 存储 \(m\) 个 \(y_i\) 向量:\(O(mn)\)
  • 临时变量:\(O(n)\)
  • 总复杂度:\(O(mn)\)

相比标准 BFGS 的 \(O(n^2)\) 存储,两步循环只执行向量运算,因而适合高维 CRF 参数。Wapic 用 syp 保存循环历史;sy 使用 float 减少内存,内积仍以 float_t 累加。

初始 Hessian 逆矩阵的选择

Wapic 使用标量矩阵作为初始近似: \(\gamma = \frac{y_{k-1}^T s_{k-1}}{y_{k-1}^T y_{k-1}}\) \(H_k^0 = \gamma I\) \(r = \gamma q\)

OWL-QN

L1 正则化的挑战

令 \(f(\lambda)\) 表示包含负对数似然和 L2 惩罚的光滑部分,OWL-QN 处理的目标函数为: $$ F(\lambda) = f(\lambda) + C \sum_k |\lambda_k| $$

在 Wapic 中,\(C=r_1\)。

问题:

  • L1 正则化项在零点不可微
  • 标准 L-BFGS 要求目标函数处处可微
  • 需要特殊处理来保持L-BFGS的收敛性质

OWL-QN 算法

OWL-QN(Orthant-Wise Limited-memory Quasi-Newton)是L-BFGS在L1 正则化上的扩展。

伪梯度的定义

OWL-QN 引入**伪梯度(pseudo-gradient)**的概念来处理不可微性: $$ \widetilde{\nabla}F(\lambda)_k = \begin{cases} \nabla f(\lambda)_k + C & \text{if } \lambda_k > 0 \\ \nabla f(\lambda)_k - C & \text{if } \lambda_k < 0 \\ \nabla f(\lambda)_k + C & \text{if } \lambda_k = 0 \text{ and } \nabla f(\lambda)_k < -C \\ \nabla f(\lambda)_k - C & \text{if } \lambda_k = 0 \text{ and } \nabla f(\lambda)_k > C \\ 0 & \text{if } \lambda_k = 0 \text{ and } |\nabla f(\lambda)_k| \leq C \end{cases} $$

这对应 ComputePseudoGradient:普通梯度保存在 g,伪梯度保存在 pg

象限约束

OWL-QN 的关键思想是象限约束(orthant constraint)

搜索方向约束: 在计算搜索方向后,逐维保留与负伪梯度一致的分量: $$ \widetilde d_{k,i} = \begin{cases} d_{k,i} & \text{if } d_{k,i}(-\widetilde{\nabla}F(\lambda^{(k)})_i) > 0 \\ 0 & \text{otherwise} \end{cases} $$

这对应 ConstrainSearchDirection:如果某一维满足 d[f] * pg[f] >= 0,就把该方向分量清零。

象限投影

在线搜索过程中,需要将候选点投影回当前象限: $$ \lambda_{\text{projected}} = \text{project}(\lambda^{(k)} + \alpha d_k, \xi) $$

其中投影操作定义为: $$ \text{project}(\lambda, \xi)_i = \begin{cases} \lambda_i & \text{if } \lambda_i \cdot \xi_i > 0 \\ 0 & \text{otherwise} \end{cases} $$

参考象限不能一律取负伪梯度。对已经非零的参数,应保持它当前所在的象限;只有参数为零时,才使用负伪梯度决定准备进入的象限:

$$ \xi_i = \begin{cases} \operatorname{sign}(\lambda_i^{(k)}) & \text{if } \lambda_i^{(k)}\neq 0 \\ \operatorname{sign}(-\widetilde{\nabla}F(\lambda^{(k)})_i) & \text{if } \lambda_i^{(k)}=0 \end{cases} $$

ProjectToOrthant 使用上一次参数 xp 确定非零参数的象限;如果原参数为零,则使用 -pg 选择准备进入的象限。

回溯线搜索

Wapic 沿用原始 Wapiti 的 OWL-QN 回溯判定。候选点经过象限投影后,代码检查: $$ F(\lambda_{\text{projected}}) < F(\lambda^{(k)}) {}+ \gamma d_k^T (\lambda_{\text{projected}}-\lambda^{(k)}) $$

其中 \(\gamma=10^{-4}\)。CheckArmijoRule 直接使用搜索方向 d 与投影后实际位移的内积;这与标准 Armijo 条件使用目标函数方向导数的写法不同,因此这里把它称为 Wapiti 的回溯判定,而不把两者视为完全相同的公式。

算法流程

Wapic 的 LBFGSOptimizer::Optimize 在 \(r_1\neq0\) 时执行以下流程:

  1. 初始化: 设置初始参数 \(\lambda^{(0)}\),记忆深度 \(m\),正则化参数 \(C\)

  2. 主循环: 对于 \(k = 0, 1, 2, \ldots\)

    a. 计算伪梯度: \(g_k=\widetilde{\nabla}F(\lambda^{(k)})\)

    b. 检查收敛: 如果 \(|g_k|\) 足够小,则停止

    c. 计算搜索方向: 使用两步循环算法计算 \(d_k = -H_k g_k\)

    d. 象限约束: 删除不与负伪梯度同向的搜索方向分量

    e. 线搜索: 使用 Wapiti 的回溯判定寻找步长 \(\alpha_k\)

    f. 象限投影: \(\lambda^{(k+1)} = \text{project}(\lambda^{(k)} + \alpha_k d_k, \xi)\)

    g. 更新历史: 令 \(s_k=\lambda^{(k+1)}-\lambda^{(k)}\),并用光滑部分的梯度差 \(y_k=\nabla f(\lambda^{(k+1)})-\nabla f(\lambda^{(k)})\) 更新历史信息

实现参数

  • --rho1:L1 强度,默认 0.5;设为 0 时关闭 OWL-QN 分支
  • --rho2:L2 强度,默认 0.0001
  • --histsz:历史向量对数量,默认 5
  • --maxls:最大线搜索次数,默认 40
  • --maxiter:最大训练轮数,默认 100

CheckConvergence 同时检查伪梯度范数和一段目标函数历史中的相对改善量。线搜索失败、梯度足够小或目标函数改善不足时,训练停止。

三节的关系可以归结为:L1 正则化定义稀疏目标;L-BFGS 高效优化光滑目标;当训练既需要 L-BFGS 的曲率信息,又需要 L1 稀疏性时,使用 OWL-QN。

至此,中文分词可以被看作两类互补方案:词典方法通过词频寻找最大概率路径,CRF 则通过特征与标签转移进行全局序列预测。它们都可以作为预切分阶段,为后续子词模型提供更稳定的输入边界。

配套实现:Ismantic/Wapic