1. 程式人生 > >開源中文分詞工具探析(四):THULAC

開源中文分詞工具探析(四):THULAC

THULAC是一款相當不錯的中文分詞工具,準確率高、分詞速度蠻快的;並且在工程上做了很多優化,比如:用DAT儲存訓練特徵(壓縮訓練模型),加入了標點符號的特徵(提高分詞準確率)等。

【開源中文分詞工具探析】系列:

1. 前言

THULAC所採用的分詞模型為結構化感知器(Structured Perceptron, SP),屬於兩種CWS模型中的Character-Based Model,將中文分詞看作為一個序列標註問題:對於字元序列\(C=c_1^n\),找出最有可能的標註序列\(Y=y_1^n\)。定義score函式\(S(Y,C)\)為在\(C\)的情況下標註序列為\(Y\)的得分。SP以最大熵準則建模score函式,分詞結果則等同於最大score函式所對應的標註序列。記在時刻\(t\)

的狀態為\(y\)的路徑\(y_1^{t}\)所對應的score函式最大值為

\[ \delta_t(y) = \max S(y_1^{t-1}, C, y_t=y) \]

那麼,則有遞推式

\[ \delta_{t+1}(y) = \max_{y'} \ \{ \delta_t(y') + w_{y',y} + F(y_{t+1}=y,C) \} \]

其中,\(w_{y',y}\)為轉移特徵\((y',y)\)所對應的權值,\(F(y_{t+1}=y, C)\)為特徵模板的特徵值的加權之和:

\[ F(y_{t+1}=y,C) = \sum_{i=1}^7 \alpha_i f_i(y_{t+1}=y,C) \]

其中,\(\alpha_i\)為特徵\(f_i(y_{t+1}=y,C)\)所對應的權重。

2. 分解

以下原始碼分析基於最近lite_v1_2版本THULAC-Java

訓練模型

seg_only模式(只分詞沒有POS)對應的訓練模型的資料包括三種:權重資料cws_model.bin、序列標註類別資料cws_label.txt、特徵資料cws_dat.bin。權重資料對應於類character.CBModel,其格式如下:

int l_size (size of the labels): 4
int f_size (size of the features): 2453880
int[] ll_weights // weights of (label, label):
-42717
39325
-33792
...
-31595
26794
int[] fl_weights //weights of (feature, label):
-4958
2517
5373
-2930
-286
0
...

訓練模型中的標註類別(label)共有4類“0”, “2”, “3”, “1”(見檔案cws_label.txt),分別對應於中文分詞中的標註類別B、E、S、M;這一點可以在C++版的thulac_base.h找到映照:

enum POC{
    kPOC_B='0',
    kPOC_M='1',
    kPOC_E='2',
    kPOC_S='3'
};

但是,THULAC在解碼時用到的label,則是BMES在特徵資料檔案的索引位置,因此標註類別B、M、E、S對映到整數0、3、1、2;這些對映可以在後面的構建label轉移矩陣labelTransPre及Viterbi解碼的程式碼中找到印證。那麼,則B轉移到B的權重\(w_{B,B}=w_{0,0}=-42717\),B轉移到E的權重\(w_{B,E}=w_{0,1}=39325\),B轉移到S的權重\(w_{B,S}=w_{0,2}=-33792\)。此即與實際情況相對應,B只能會轉移到M和E,而不可能轉移到S。

權重資料中標識對應於特徵模板的feature共有2453880個。那麼,label與label之間的組合共有4×4=16種,即ll_weights的長度為16;feature與label之間的組合共有4×2453880=9815520種,即fl_weights的長度為9815520。

特徵資料則是用雙陣列Trie樹DAT來儲存的,對應於類base.Dat,其格式如下:

int datSize: 7643071
Vector<Entry> dat:
0 0
0 1
0 51
0 52
...
25053 0
5 25088
...

datSizedat的長度,等於7643071;類Entry表示雙陣列中的的base與check值。那麼,問題來了:一是特徵長什麼樣?二是知道特徵如何得到對應的權重?三是為什麼DAT的長度遠大於字元特徵的個數2453880?

特徵

首先,我們來看看特徵長什麼樣,參看特徵生成方法CBNGramFeature::featureGeneration

public void featureGeneration(String seq, Indexer<String> indexer, Counter<String> bigramCounter){
  ...
  for(int i = 0; i < seq.length(); i ++){
    mid = seq.charAt(i);
    left = (i > 0) ? (seq.charAt(i-1)) : (SENTENCE_BOUNDARY);
    ...
    key = ((char)mid)+((char)SEPERATOR) + "1";
    key = ((char)left)+((char)SEPERATOR) + "2";
    key = ((char)right)+((char)SEPERATOR) + "3";
    key = ((char)left)+((char)mid)+((char)SEPERATOR) + "1";
    key = ((char)mid)+((char)right)+((char)SEPERATOR) + "2";
    key = ((char)left2)+((char)left)+((char)SEPERATOR) + "1"; // should be + "3"
    key = ((char)right)+((char)right2)+((char)SEPERATOR) + "1"; // should be + "4"
    ...
  }
}

特徵模板共定義7個字元特徵:3個unigram字元特徵與4個bigram字元特徵。在處理特徵時,字元後面加上了空格,然後在加上標識1、2、3、4,用以區分特徵的種類。值得指出的是Java版作者寫錯了最後兩個bigram特徵,應該是加上數字3、4;在C++版的函式NGramFeature::feature_generation可找到印證。通過特徵模板定義,我們發現THULAC既考慮到了前面2個字元(各種組合)對當前字元標註的影響,也考慮到了後面2個字元的影響。

接下來,為了解決第二個問題,我們來看看用Viterbi演算法解碼前的程式碼——THULAC先將特徵值的加權之和\(F(t_i,C)\)計算出來,然後按label次序逐個放入values[i*4+label] 中。主幹程式碼如下:

/**
 * @param datSize DAT size
 * @param ch1 first character
 * @param ch2 second character
 * @return [unigram字元特徵的base + " ", bigram字元特徵的base + " "]
 */
public Vector<Integer> findBases(int datSize, int ch1, int ch2) {
  uniBase = dat.get(ch1).base + SEPERATOR;
  biBase = dat.get(ind).base + SEPERATOR;
}

/**
 * 按label 0,1,2,3 將特徵值的加權之和放入values陣列中
 * @param valueOffset values陣列偏置量,在putValues中呼叫時按步長4遞增
 * @param base unigram字元特徵或bigram字元特徵的base加上空格的index
 * @param del 標識'1', '2', '3', '4' -> 49, 50, 51, 52
 * @param pAllowedLable null
 */
private void addValues(int valueOffset, int base, int del, int[] pAllowedLable) {
  int ind = dat.get(base).base + del; // 加上標識del後特徵的index
  int offset = dat.get(ind).base; // 特徵的base
  int weightOffset = offset * model.l_size; // 特徵陣列的偏移量
  int allowedLabel;
  if (model.l_size == 4) {
    values[valueOffset] += model.fl_weights[weightOffset];
    values[valueOffset + 1] += model.fl_weights[weightOffset + 1];
    values[valueOffset + 2] += model.fl_weights[weightOffset + 2];
    values[valueOffset + 3] += model.fl_weights[weightOffset + 3];
  }
}

public int putValues(String sequence, int len) {
  int base = 0;
  for (int i = 0; i < len; i++) {
    int valueOffset = i * model.l_size;
    if ((base = uniBases[i + 1]) != -1) {
      addValues(valueOffset, base, 49, null); // c_{i}t_{i}
    }
    if ((base = uniBases[i]) != -1) {
      addValues(valueOffset, base, 50, null); // c_{i-1}t_{i}
    }
    if ((base = uniBases[i + 2]) != -1) {
      addValues(valueOffset, base, 51, null); // c_{i+1}t_{i}
    }
    if ((base = biBases[i + 1]) != -1) {
      addValues(valueOffset, base, 49, null); // c_{i-1}c_{i}t_{i}
    }
    if ((base = biBases[i + 2]) != -1) {
      addValues(valueOffset, base, 50, null); // c_{i}c_{i+1}t_{i}
    }
    if ((base = biBases[i]) != -1) {
      addValues(valueOffset, base, 51, null); // c_{i-2}c_{i-1}t_{i}
    }
    if ((base = biBases[i + 3]) != -1) {
      addValues(valueOffset, base, 52, null); // c_{i+1}c_{i+2}t_{i}
    }
  }
}

注意:49對應於數字1的unicode值,50對應於數字2等。從上述程式碼中,我們發現特徵陣列fl[4*i+j]對應於特徵的base為i,label為j。拼接特徵的流程如下:先得到unigram或bigram字元特徵,然後加空格,再加標識。在拼接過程中,按照DAT的轉移方程進行轉移:

base[r] + c = s
check[s] = r

最後,我們回到第三個問題——為什麼DAT的長度遠大於特徵數?這是因為在構建DAT時,需要儲存很多的中間結果。

解碼

THULAC用於解碼的類character.CBTaggingDecoder,Viterbi演算法實現對應於方法AlphaBeta::dbDecode;CBTaggingDecoder的主要欄位如下:

public char separator;
private int maxLength; // 定義的最大句子長度20000
private int len; // 待分詞句子的長度
private String sequence; // 待分詞句子的深拷貝
private int[][] allowedLabelLists; // 根據標點符號及句子結構,判斷當前字元允許的label: int[len][]
private int[][] pocsToTags; // index -> allow-labels: int[16][2,3,4]

private CBNGramFeature nGramFeature;
private Dat dat; // 字元特徵DAT
private CBModel model; // 權重

private Node[] nodes; // 分詞DAG鄰接表
private int[] values; // 特徵模板F(t_i,X)的加權之和: int[i*4+label]
private AlphaBeta[] alphas; // 解碼時用來記錄path, [i, j]指c_{i}的標註為t_{j}的前一結點
private int[] result; // 分詞後的標註陣列
private String[] labelInfo; // ["0", "2", "3", "1"]
private int[] labelTrans; // 
private int[][] labelTransPre; // 可能情況的前labels: int[4][3]
public int threshold;
private int[][] labelLookingFor;

其中,二維陣列pocsToTags為index與allow labels之間的對映,內容如下:

[null, [0, -1], [3, -1], [0, 3, -1], 
[1, -1], [0, 1, -1], [1, 3, -1], [0, 1, 3, -1], 
[2, -1], [0, 2, -1], [2, 3, -1], [0, 2, 3, -1], 
[1, 2, -1], [0, 1, 2, -1], [1, 2, 3, -1], [0, 1, 2, 3, -1]]

該陣列的index表示了當前字元具有某些性質,比如:

  • 1([0])表示詞的開始,即標註B;
  • 2([3])表示詞的中間字元,即標註M;
  • 4([1])表示詞的結尾,即標註E;
  • 8([2])表示標點符號,即標註S;
  • 9([0,2])表示某一個詞的開始,或者能單獨成詞,即標註BS;
  • 12([1,2])表示詞的結束,即標註ES;
  • 15([0,1,2,3])為預設值。

在分詞前,THULAC根據標點符號等特徵,給一些字元加入allow labels以提高分詞準確性。比如,根據書名號確定裡面為一個詞,

val sentence = "倒模,替身算什麼?鍾漢良、ab《孤芳不自賞》摳圖來充數"
val poc_cands = new POCGraph
val tagged = new TaggedSentence
val segged = new SegmentedSentence
val segmenter = new CBTaggingDecoder
val preprocesser = new Preprocesser
val prefix = "models/"
segmenter.init(prefix + "cws_model.bin", prefix + "cws_dat.bin", prefix + "cws_label.txt")
segmenter.setLabelTrans()
segmenter.segment(raw, poc_cands, tagged)
segmenter.get_seg_result(segged)
println(segged.mkString(" "))
// 倒模 , 替身 算 什麼 ? 鍾漢良 、 ab 《 孤芳不自賞 》 摳 圖 來 充數

3. 參考資料

[1] Li, Z., & Sun, M. (2009). Punctuation as implicit annotations for Chinese word segmentation. Computational Linguistics, 35(4), 505-512.

相關推薦

開源中文工具THULAC

THULAC是一款相當不錯的中文分詞工具,準確率高、分詞速度蠻快的;並且在工程上做了很多優化,比如:用DAT儲存訓練特徵(壓縮訓練模型),加入了標點符號的特徵(提高分詞準確率)等。 【開源中文分詞工具探析】系列: 1. 前言 THULAC所採用的分詞模型為結構化感知器(Structured Percep

開源中文工具Stanford CoreNLP

inf git deb seq 效果 analysis stream fix sps CoreNLP是由斯坦福大學開源的一套Java NLP工具,提供諸如:詞性標註(part-of-speech (POS) tagger)、命名實體識別(named entity recog

開源中文工具LTP

LTP是哈工大開源的一套中文語言處理系統,涵蓋了基本功能:分詞、詞性標註、命名實體識別、依存句法分析、語義角色標註、語義依存分析等。 【開源中文分詞工具探析】系列: 1. 前言 同THULAC一樣,LTP也是基於結構化感知器(Structured Perceptron, SP),以最大熵準則建模標註序列

開源中文工具FNLP

FNLP是由Fudan NLP實驗室的邱錫鵬老師開源的一套Java寫就的中文NLP工具包,提供諸如分詞、詞性標註、文字分類、依存句法分析等功能。 【開源中文分詞工具探析】系列: 1. 前言 類似於THULAC,FNLP也是採用線性模型(linear model)分詞。較於對數線性模型(log-linea

開源中文工具Ansj

Ansj是由孫健(ansjsun)開源的一箇中文分詞器,為ICTLAS的Java版本,也採用了Bigram + HMM分詞模型(可參考我之前寫的文章):在Bigram分詞的基礎上,識別未登入詞,以提高分詞準確度。雖然基本分詞原理與ICTLAS的一樣,但是Ansj做了一些工程上的優化,比如:用DAT高效地實現檢

中文工具ICTCLAS (NLPIR)

【開源中文分詞工具探析】系列: 1. 前言 ICTCLAS是張華平老師推出的中文分詞系統,於2009年更名為NLPIR。ICTCLAS是中文分詞界元老級工具了,作者開放出了free版本的原始碼(1.0整理版本在此). 作者在論文[1] 中宣稱ICTCLAS是基於HHMM(Hierarchical Hid

中文工具Jieba

【開源中文分詞工具探析】系列: 1. 前言 Jieba是由fxsjy大神開源的一款中文分詞工具,一款屬於工業界的分詞工具——模型易用簡單、程式碼清晰可讀,推薦有志學習NLP或Python的讀一下原始碼。與採用分詞模型Bigram + HMM 的ICTCLAS 相類似,Jieba採用的是Unigram +

北大開源全新中文工具準確率遠超THULAC、結巴

選自GitHub,作者:羅睿軒、許晶晶、孫栩,機器之心編輯。 最近,北大開源了一箇中文分詞工具包,它在多個分詞資料集上都有非常高的分詞準確率。其中廣泛使用的結巴分詞誤差率高達 18.55% 和 20.42,而北大的 pkuseg 只有 3.25% 與 4.32%。 pkuseg 是由北京

在PyCharmPython整合開發環境中安裝jieba中文工具

PyCharm IDE中,可以直接引入各種工具包。jieba中文分詞工具包安裝非常方便。 1、開啟Pycharm,點選左上角  >>File  >>Settings。 2、在settings介面中點選Project :***(專案名稱)  >

PyNLPIR python中文工具

命名 hub 兩個 工具 ict mage ret wid tty 官網:https://pynlpir.readthedocs.io/en/latest/ github:https://github.com/tsroten/pynlpir NLPIR分詞系

7個優秀的開源中文庫推薦

中文分詞是中文文字處理的基礎步驟,也是中文人機自然語言互動的基礎模組。由於中文句子中沒有詞的界限,因此在進行中文自然語言處理時,通常需要先進行分詞。 縱觀整個開源領域,陸陸續續做中文分詞的也有不少,不過目前仍在維護的且質量較高的並不多。下面整理了一些個人認為比較優秀的中文分詞庫,以供大家

Hanlp等七種優秀的開源中文庫推薦

中文分詞是中文文字處理的基礎步驟,也是中文人機自然語言互動的基礎模組。由於中文句子中沒有詞的界限,因此在進行中文自然語言處理時,通常需要先進行分詞。 縱觀整個開源領域,陸陸續續做中文分詞的也有不少,不過目前仍在維護的且質量較高的並不多。下面整理了一些個人認為比較優秀的中文分

10大Java開源中文器的使用方法和效果對比

原文地址:http://my.oschina.net/apdplat/blog/412921 本文的目標有兩個: 1、學會使用10大Java開源中文分詞器 2、對比分析10 大Java開源中文分詞器的分詞效果 本文給出了10大Java開源中文分詞的使用方法以及分詞

【NLP】11大Java開源中文器的使用方法和效果對比

本文的目標有兩個: 1、學會使用11大Java開源中文分詞器 2、對比分析11大Java開源中文分詞器的分詞效果 本文給出了11大Java開源中文分詞的使用方法以及分詞結果對比程式碼,至於效果哪個好,那要用的人結合自己的應用場景自己來判斷。 11大Java開源中文分詞器,不同的分詞器

Solr與開源中文(ansj)整合

1. ansj分詞原始碼及jar包下載地址 原始碼: https://github.com/NLPchina/ansj_seg jar包: http://maven.nlpcn.org/org/ansj/ http://maven.nlpcn.org/org/nlpcn/n

中文工具thulac4j釋出

1. 介紹 thulac4j是THULAC的Java 8工程化實現,具有分詞速度快、準、強的特點;支援 自定義詞典 繁體轉簡體 停用詞過濾 若想在專案中使用thulac4j,可新增依賴: <dependency> <groupId>io.github.yizhiru</g

乾貨 | 史上最全中文工具整理

作者 | fendouai 一.中文分詞  分詞服務介面列表 二.準確率評測: THULAC:與代表性分詞軟體的效能對比 我們選擇LTP-3.2.0 、ICTCLAS(2015版) 、jieba(C++版)等國內具代表性的分詞軟體與THULAC做效能

中文工具

THULAC 四款python中中文分詞的嘗試。嘗試的有:jieba、SnowNLP(MIT)、pynlpir(大資料搜尋挖掘實驗室(北京市海量語言資訊處理與雲端計算應用工程技術研究中心))、thulac(清華大學自然語言處理與社會人文計算實驗室) 四款都

python中文工具結巴jieba

結巴分詞jieba特點    支援三種分詞模式:        精確模式,試圖將句子最精確地切開,適合文字分析;        全模式,把句子中所有的可以成詞的詞語都掃描出來, 速度非常快,但是不能解決歧義;        搜尋引擎模式,在精確模式的基礎上,對長詞再次切分,提

jieba.NET是jieba中文的.NET版本C#實現

jieba.NET是jieba中文分詞的.NET版本(C#實現)。 當前版本為0.38.2,基於jieba 0.38,提供與jieba一致的功能與介面,以後可能會在jieba基礎上提供其它擴充套件功能。關於jieba的實現思路,可以看看這篇wiki裡提到的資料。 如果