是由 Fudan NLP 实验室的邱锡鹏老师开源的一套 Java 写就的中文 NLP 工具包,提供诸如分词、词性标注、文本分类、依存句法分析等功能。
【开源中文分词工具探析】系列:
类似于,FNLP 也是采用线性模型(linear model)作为基础分词模型。与对数线性模型(log-linear model)HMM/CRF 所不同的是,线性模型没有归一化因子而直接建模 Score 函数:
\[S(X,Y) = \sum_s w_s * \Phi_s(X,Y) \]
则序列标注问题对应于求解:
\[\mathop{\arg \max}_{Y} S(X,Y) \]
THULAC 是采用感知器来学习参数 \(w_s\),而 FNLP 则是采用在线学习算法 Passive-Aggressive(PA) [2]。PA 算法结合感知器与 SVM 的优点,学习速度快;损失函数为 hinge loss:
\[loss(W;(X,Y)) = \left \{ {\matrix {{0,} & {\gamma (W;(X,Y)) \ 1} \cr {1- \gamma (W;(X,Y))} & {otherwise} \cr } } \right. \]
其中,\(\gamma (W;(X,Y))\) 为边际距离,定义为:
\[\gamma (W;(X,Y)) = S(X,Y) - S(X,\hat{Y}) \]
以下源码分析基于 fnlp-2.1 版本。
中文分词的训练模型为
,由两个类
- seg.m
与
- TempletGroup
序列化压缩而成:
- Linear
- ObjectInputStream in = new ObjectInputStream(new BufferedInputStream(
- new GZIPInputStream(new FileInputStream("models/seg.m"))));
- TempletGroup templets = (TempletGroup) in.readObject();
- Linear cl = (Linear) in.readObject();
其中,类
定义了特征模板,
- TempletGroup
包含了特征模板、特征及其偏移量、权重数组:
- Linear
- // main field
- public Inferencer inferencer;
- protected AlphabetFactory factory;
- // details about `factory` field
- .factory: AlphabetFactory.maps {
- "LABELS" - >LabelAlphabet(data: {
- S = 0,
- M = 2,
- E = 3,
- B = 1
- })"FEATURES" - >StringFeatureAlphabet(data: TObjectIntCustomHashMap)
- }
- // StringFeatureAlphabet记录了feature在weights数组中的偏移
- // details about `inferencer` field
- .inferencer: LinearViterbi protected float[] weights;
- public TempletGroup templets;
类
的变量 data 为一个 TObjectIntMap,K 为特征,V 为偏移量,如下所示:
- StringFeatureAlphabet
- 0: 32
- 1:供/ 414540
- 2:O/ 14372
- 2:L/ 131248
- 3:煞/C/ 147492
- 5:呼/ 20032
- 8:哈/钦/ 419968
- 12:拉/杰/沙/ 350972
- 13:L/文/C/ 1324032
Map 的 size 为 441006,即为特征总数(感觉 FNLP 的训练语料太少)。其中,特征有 index + 特征值组成;容易发现特征共有 14 种。至于特征具体是怎么定义,且看下一小节。
中文分词对应的解码类为
,主要的 field 如下:
- CWSTagger
- private Linear cl; //
- protected Pipe prePipe = null; // String2Sequence, 初步切分成char array形式
- protected Pipe featurePipe; // Sequence2FeatureSequence, 计算特征数组
- protected AlphabetFactory factory;
- protected TempletGroup templets; // lis of BaseTemplet, 特征模板
- protected LabelAlphabet labels; // 对应于factory.maps中的LABELS,即S,M,E,B
解码同 CRF、结构化感知器 SP 一样为 Viterbi 算法,具体实现在类
,在此不再赘述。
- LinearViterbi
特征模板共定义了 14 个特征(对应于上面的训练模型),如下所示:
- %y[-1]%y[0] // index 0
- %x[0,0]%y[0]
- %x[0,1]%y[0] // index 2
- %x[0,0]%x[0,1]%y[0]
- %x[-1,0]%y[0] // index 4
- %x[1,0]%y[0]
- %x[-2,0]%y[0]
- %x[2,0]%y[0]
- %x[-2,0]%x[-1,0]%y[0] // index 8
- %x[-1,0]%x[0,0]%y[0]
- %x[0,0]%x[1,0]%y[0]
- %x[1,0]%x[2,0]%y[0]
- %x[-1,0]%x[0,0]%x[1,0]%y[0]
- %x[-1,1]%x[0,0]%x[1,1]%y[0] // index 13
特征模板格式与相类似;从上可以看出,有 1 个类别转移特征(index 0),5 个 unigram 字符状态特征(index 1, 4, 5, 6, 7),4 个 bigram 字符状态特征(index 8, 9, 10, 11),1 个 trigram 字符状态特征(index 12),3 个字符状态与类型的混合特征(index 2, 3, 13)。其中,FNLP 的 enum
定义了 5 种字符类型如下(与训练模型有稍许区别):
- Chars.CharType
- D // 数字
- L // 字母
- C // 汉字
- O // 其他,例如标点等
- B_ // 空格
- public enum CharType {
- C,
- L,
- D,
- P,
- // 标点
- B
- }
其实,字符类型特征对于分词的贡献不是太大,可以不用。接下来,我们来感受下 FNLP 的分词效果:
- CWSTagger segger = new CWSTagger("models/seg.m");
- segger.setEnFilter(true);
- String sentence = "小明硕士毕业于中国科学院计算所,后在日本京都大学深造";
- List words = segger.tag2List(sentence);
- // [小明, 硕士, 毕业于, 中国, 科学院, 计算, 所, ,, 后, 在, 日本, 京都, 大学, 深造]
可以看出,FNLP 分词的粒度不均匀,准确性不是太高;应该是跟训练语料太少有关系,训练不充分而导致的。
[1] Qiu, Xipeng, Qi Zhang, and Xuanjing Huang. "FudanNLP: A Toolkit for Chinese Natural Language Processing." ACL (Conference System Demonstrations). 2013.
[2] Crammer, Koby, et al. "Online passive-aggressive algorithms." Journal of Machine Learning Research 7.Mar (2006): 551-585.
[3] 邱锡鹏, "自然语言处理原理与实现", 2014.
来源: http://www.cnblogs.com/en-heng/p/6559327.html