FNLP是由Fudan NLP實驗室的邱錫鵬老師開源的一套Java寫就的中文NLP工具包,提供諸如分詞、詞性標注、文本分類、依存句法分析等功能。
【開源中文分詞工具探析】系列:
- 開源中文分詞工具探析(一):ICTCLAS (NLPIR)
- 開源中文分詞工具探析(二):Jieba
- 開源中文分詞工具探析(三):Ansj
- 開源中文分詞工具探析(四):THULAC
- 開源中文分詞工具探析(五):FNLP
- 開源中文分詞工具探析(六):Stanford CoreNLP
- 開源中文分詞工具探析(七):LTP
1. 前言
類似於THULAC,FNLP也是采用線性模型(linear model)分詞。較於對數線性模型(log-linear model)HMM/CRF所不同的是,線性模型沒有歸一化因子而直接建模Score函數:
則序列標注問題對應於求解:
THULAC是采用感知器來學習參數\(w_s\),而FNLP則是采用在線學習算法Passive-Aggressive(PA) [2]。PA算法結合感知器與SVM的優點,學習速度快;損失函數為hinge loss:
其中,\(\gamma (W;(X,Y))\)為邊際距離,定義為:
\(\hat{Y}\)為錯誤序列標注中得分最高(score函數最大值)的標簽。關於參數更新策略的細節請參看FNLP Book [3].
2. 分解
以下源碼分析基於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<String>)}
// StringFeatureAlphabet記錄了feature在weights數組中的偏移
// details about `inferencer` field
.inferencer: LinearViterbi
protected float[] weights;
public TempletGroup templets;
類StringFeatureAlphabet
的變量data為一個TObjectIntMap,K為特征,V為偏移量,如下所示:
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種。至於特征模板是如何定義,且看下下一小節。
解碼
中文分詞對應的解碼類為CWSTagger
,主要的field如下:
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個特征(對應於上面的訓練模型),如下所示:
0: %y[-1]%y[0]
1: %x[0,0]%y[0]
2: %x[0,1]%y[0]
3: %x[0,0]%x[0,1]%y[0]
4: %x[-1,0]%y[0]
5: %x[1,0]%y[0]
6: %x[-2,0]%y[0]
7: %x[2,0]%y[0]
8: %x[-2,0]%x[-1,0]%y[0]
9: %x[-1,0]%x[0,0]%y[0]
10: %x[0,0]%x[1,0]%y[0]
11: %x[1,0]%x[2,0]%y[0]
12: %x[-1,0]%x[0,0]%x[1,0]%y[0]
13: %x[-1,1]%x[0,0]%x[1,1]%y[0]
特征模板格式與CRF++相類似;從上可以看出,有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 Chars.CharType
定義了5種字符類型如下(與訓練模型有稍許區別)。其實,字符類型特征對於分詞來說比較雞肋,可以不用。
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<String> words = segger.tag2List(sentence);
// [小明, 碩士, 畢業於, 中國, 科學院, 計算, 所, ,, 后, 在, 日本, 京都, 大學, 深造]
可以看出,FNLP分詞的粒度不均勻,准確性不是太高;應該是跟訓練語料太少有關系,訓練不充分而導致的。
3. 參考文獻
[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.