使用 CRF 做中文分词
使用 CRF 做中文分詞
概要
注:以上實現只針對中文分詞任務。
1. 簡述 CRF
注,以下內容需要一定的學習成本,如有不適請跳至下一節(實戰中學習)。但,建議先大概學一下理論!
學習 CRF 的路線:
- 概率有向圖模型 --> 貝葉斯網絡
- 概率無向圖模型 --> 馬爾科夫隨機場
- 概率無向圖模型的因子分解(考慮如何表示聯合概率),不求證明但求理解
- 資料
- 從零學習Belief Propagation算法1
- 從零學習Belief Propagation算法2
- 邏輯斯蒂回歸 非常簡單,它能讓我們了解什么是 對數線性模型(CRF也是哦)
- 最大熵模型
- 本質,模型的學習過程就是求解最大熵模型的過程
- 也就是,在滿足所有約束條件下,模型最后學到的是熵最大的,最公平的
- 最后,學習使用 改進的迭代尺度法 優化ME(后面 CRF 的學習也使用這個方法)
- 資料
- 《統計學習方法》第六章
- 從三個點學習 HMM (其實 CRF MEM 類似)–> 學習過程中一定要牢記這三點
- 概率問題(數學問題)
- 學習問題(學習模型)
- 預測問題(利用模型)–> 維特比算法
- HMM 的兩個假設
- 馬爾科夫性假設
- 觀測獨立性假設:任意時刻的觀測只依賴該時刻的狀態,與其他觀測以及狀態無關(☆,注意與 CRF MEMM 的不同)
- MEMM 的問題 --> 標注偏執(CRF,解決了此問題)
- 資料
- 《統計學習方法》第十章
- 【中文分詞】最大熵馬爾可夫模型MEMM
- 重點回顧 馬爾科夫隨機場
- 資料
- 從零學習Belief Propagation算法2
- CRF 通俗的說,就是條件概率分布構成一個馬爾科夫隨機場;
- CRF 參數化形式,由它的定義結合概率無向圖的因子分解可得;
- 可能會發現其與 ME 的形式,有幾分相似,但它們的思想不一樣(CRF 做序列標注,ME 做分類);
- 接下來從三個點學習 CRF
- 概率問題(重點關注求期望,在學習算法要用到)
- 學習問題(已學,改進的迭代尺度法)
- 解碼問題(已學,維特比算法)
- 資料
- 《統計學習方法》第十一章
- 【中文分詞】條件隨機場CRF
學習過程中可能對一些符合表示產生歧義,后面的中文分詞應用會有解釋。
學完后,可能還有點迷糊,不要緊,通過下面做中文分詞的應用便能完全懂了!
2. 問題描述
我們的目標是:中文分詞!OK,接下來分析此任務。
白 B // 第一列,等待分詞的輸入序列;第二列,標記序列(即分詞結果) 菜 E 清 B // B,表示詞的開始 燉 E // M,表示詞的中間 白 B // E,表示詞的結束 蘿 M // S, 表示單字成詞 卜 E 是 S 什 B 么 E 味 B 道 E ? S這里,把中文分詞視為序列標注問題。即,給每個字打上標簽,由這些標簽描述了分詞結果。上面是“白菜清燉白蘿卜是什么味道?”的分詞結果。從注釋中可以看到,一共有 4 個標簽。
通常,我們稱等待分詞的語句輸入序列(或稱為觀測序列);而稱分詞結果為輸出序列(或稱為標記序列、狀態序列)。所以,我們的目標(中文分詞)可以描述為:在給定觀測序列 XXX 下,找到概率最大的標記序列 YYY。
怎么解決呢?CRF的思想是:首先,假設條件分布 P(Y∣X)P(Y|X)P(Y∣X) 可構成馬爾科夫隨機場(即每個狀態只受相鄰狀態或輸入序列的影響);然后,我們通過訓練(統計)可以確定此分布;最后,序列標注問題(如中文分詞)是在給定條件隨機場 P(Y∣X)P(Y|X)P(Y∣X) 和輸入序列 XXX 求條件概率最大的標記序列 Y?Y^{*}Y???梢钥吹?#xff0c;最后變為一個動態規劃問題(DP)。
在我們繼續進行下去之前,需要線性鏈CRF的參數化形式(想要了解如何得到的該形式,請參考概率無向圖的因子分解)
P(Y∣X)=1Z(X)exp(∑i,kλktk(yi?1,yi,X,i)+∑i,lμlsl(yi,x,i))P(Y|X)=\frac{1}{Z(X)}exp(\sum_{i,k}\lambda_kt_k(y_{i-1},y_i,X,i)+\sum_{i,l}\mu_ls_l(y_i,x,i))P(Y∣X)=Z(X)1?exp(i,k∑?λk?tk?(yi?1?,yi?,X,i)+i,l∑?μl?sl?(yi?,x,i))
其中,tkt_ktk? sls_lsl? 分別為 轉移特征、狀態特征,而 λk\lambda_kλk? μl\mu_lμl? 為它們對應的權重(學習問題就是去學習這些權重)。而 Z(X)Z(X)Z(X) 是規范化因子,求和是在所有可能的輸出序列上進行(如何理解這句話,其實就是遍歷 YYY 的全排列,比如這里一共有 4句子長度4^{句子長度}4句子長度 個可能)
Z(X)=∑Yexp(∑i,kλktk(yi?1,yi,X,i)+∑i,lμlsl(yi,x,i))Z(X) = \sum_Yexp(\sum_{i,k}\lambda_kt_k(y_{i-1},y_i,X,i)+\sum_{i,l}\mu_ls_l(y_i,x,i))Z(X)=Y∑?exp(i,k∑?λk?tk?(yi?1?,yi?,X,i)+i,l∑?μl?sl?(yi?,x,i))
可以看到,Z(X)Z(X)Z(X) 起到的是全局歸一化,而在 MEMM 中只使用了局部歸一化,所以出現局部偏執問題。
在下一節,我們將關注 狀態特征 與 轉移特征。
3. 構建特征函數
在線性鏈CRF參數化形式中,未知的只有特征與對應參數(或稱為權重),而參數是在學習問題中關注的,所以只剩下特征了。怎樣定義或構建特征呢?
我們的目標是:中文分詞!所以,針對中文分詞任務我們有獨特的特征構造方式(借鑒于CRF++)。首先,明確特征函數的含義:它描述是否滿足指定特征,滿足返回1否則返回0;再來看特征模板
# Unigram --> 構造狀態特征的模板 U00:%x[-2,0] U01:%x[-1,0] U02:%x[0,0] U03:%x[1,0] U04:%x[2,0] U05:%x[-2,0]/%x[-1,0]/%x[0,0] U06:%x[-1,0]/%x[0,0]/%x[1,0] U07:%x[0,0]/%x[1,0]/%x[2,0] U08:%x[-1,0]/%x[0,0] U09:%x[0,0]/%x[1,0]# Bigram --> 構造轉移特征的模板 B我們的特征有兩種形式:狀態特征 & 轉移特征。下面結合特征模板分別介紹這兩種特征。
狀態特征函數 sl(yi,X,i))s_l(y_i,X,i))sl?(yi?,X,i)) ,它的輸入為當前狀態、整個觀測序列以及當前位置。上面特征模板中 U01~U09 用于生成狀態特征。以 U00:%x[-2,0] 為例,-2 代表取觀測序列中相對當前位置的倒數第2個,0 在此處無意義。所以,用 U00~U09 做模板在 我愛我的祖國。 第3個位置下可構建的狀態特征為
我 愛 我 <-- 當前處于此位置 的 祖 國 # 下面為生成的狀態特征 U00:我 U01:愛 U02:我 U03:的 U04:祖 U05:我/愛/我 U06:愛/我/的 U07:我/的/祖 U08:愛/我 U09:我/的 注:每行代表 4 個狀態特征,因為這里有 4 標簽(BMES)。所以,這里一共產生 40 個狀態特征。 注:請注意,這只是在第 3 個位置下產生的。轉移特征 tk(yi?1,yi,X,i)t_k(y_{i-1},y_i,X,i)tk?(yi?1?,yi?,X,i),它的輸入為上一狀態、當前狀態、整個觀測序列以及當前位置。上面特征模板中,B 表示生成所有的轉移特征。因而,一共可產生 16 種轉移特征(無視觀測序列)。它們為
BB --> 表示從上一個狀態 B 轉移到當前狀態 B BM BE BS MB MM ME MS EB EM EE ES SB SM SE SSOK,了解了這兩種特征的構造過程,我們便可以通過掃描訓練集來構建大量的特征(注意,轉移特征固定為16個)。我的 25M 訓練集下,大約可以生成 560w+ 個特征函數(只取頻率大于等于3的)。
下面看程序中如何定義特征,首先看 Feature 類
import java.io.Serializable; import java.util.concurrent.atomic.AtomicInteger;/*** @author iwant* @date 19-6-13 09:31* @desc 特征函數*/ public class Feature implements Serializable, Cloneable {// 特征函數數字標識private int id;// 特征函數標識(含特征函數的內容) --> 比如,featureId="U00:我"private String featureId; // 出現頻率 --> 數組大小為 4 ,從這里可以看出一個該類對象對應 4 個狀態特征private AtomicInteger[] freqs = new AtomicInteger[4];// 權重 --> 數組大小為 4 ,從這里可以看出一個該類對象對應 4 個狀態特征// 注:對應四個標記(BMES)private double[] weights = new double[4];public Feature(int id, String featureId) {this.id = id;this.featureId = featureId;for (int i = 0; i < weights.length; i++)this.freqs[i] = new AtomicInteger();}public Feature(String featureId) {this(-1, featureId);}}從注釋中我們可以看到,一個Feature 對象包含 4 個狀態特征。
我們把所有的特征放在一個 Map<String,Feature> 集合中,其中 key 即 featureId 便于后續查找某一特征,而 value 自然就是對應的 Feature 對象了。
注:因這里的轉移特征無視觀測序列,所以在用 Feature 對象描述轉移特征時,一個對象對應一個轉移特征。因而此時規定對象中的數組只有下標 0 處有意義。
這里就不列出掃描訓練集構造所有特征函數的代碼,具體請在源碼中 Utils.java 中查找。
有了特征函數我們便可關注參數了,也就是下一節中學習算法做的事。
4. CRF 學習算法
首先,我們需要明確學習的是什么。在線性鏈CRF的參數化形式中原有兩個未知,一個是特征函數另一個是參數(亦稱為權重)。在上一節,我們已經構造了所有的特征函數。所以現在只剩下它們對應的參數就可以確定模型了。自然而然,CRF 學習的目標就是更新這些參數!
至于如何更新這些參數,是這一小節的重點。前面已經提到,我們將會使用改進的迭代尺度法來優化CRF。
可能你會問為什么這么學習。首先要明確我們已知訓練數據集,由此可知經驗概率分布 P~(X,Y)\tilde{P}(X,Y)P~(X,Y) (統計學習的最基本假設)。現在求條件分布,便可以通過 極大化 訓練數據的 對數似然函數 來求模型參數。改進的迭代尺度法通過迭代的方法不斷優化對數似然函數改變量的下界,達到極大化對數似然函數的目的。通俗地講,為了求條件分布,數學家給我們提供了一個NB的工具 – 極大化對數似然函數,這樣就變成了優化問題。針對該優化問題呢,數學家經過對原函數不斷放縮、求導給我們推導出了許多優化算法,改進的迭代尺度法(IIS)便是其中一個,其他的還有 擬牛頓法 梯度下降法 等等。(注:這里不再詳述推導過程。感興趣的,可以參考《統計學習方法》第六章)
說個題外話,再來談談為什么CRF學習算法學習的是參數。我們還可以這么理解,由不同的參數可以定義不同的模型。所有的參數可能構成了 模型空間(或稱為 模型集合)。學習算法的目的就是在模型空間中找尋最能擬合訓練數據的模型。
迭代尺度法的基本思想:我們有種方法能夠使得對數似然函數每次增加一點點,所以我們可以不斷地使用(迭代)該方法慢慢的增大對數似然函數??傆心敲匆粋€時刻,它會收斂,于是達到了極大化對數似然函數的目的。
OK,是時候看算法描述
下面將結合中文分詞任務解釋上面的算法描述。
不難發現整個算法中最難的就是步驟(a)中的方程求解。怎么求,公式11.41、11.44已經說明了。下面以公式11.41為例來說明。
- 先要明確它是針對轉移特征的,且 δk\delta_kδk? 只針對下標為 kkk 這一轉移特征;
- 再來看 SSS,它是一個常數,是一個足夠大的特征總數。這里,我們每次訓練都要選一批句子,而每個句子都有個特征總數(即該句子滿足的狀態特征與轉移特征總數),足夠大的特征總數可以為這些特征總數中最大的那個。
- 再來看 Ep~[tk]E_{\tilde{p}}[t_k]Ep~??[tk?] ,表示在經驗概率分布下 P~(X,Y)\tilde{P}(X,Y)P~(X,Y),轉移特征函數 tkt_ktk? 的期望值。聽起來很高大上,其實就是統計該特征函數在當前批量句子中出現的次數;
- 最后有 Ep[tk]E_{p}[t_k]Ep?[tk?] ,表示特征函數在經驗分布為 P~(X)\tilde{P}(X)P~(X) 下關于聯合分布 P(X,Y)P(X,Y)P(X,Y) 的數學期望。公式 (11.42) 說明了怎么求。但是相當繁瑣!我們需要知道 alphaalphaalpha MMM β\betaβ。
- 先來解釋公式11.42,最外面的求和是針對所有的句子(比如,我實現時每次訓練要用 500 個句子);從 1~n+1 的求和是遍歷一個句子(注意一個句子的長度為 n);最里面的求和是針對所有的“前后狀態”(在中文分詞任務中,有4中標簽,所以一共有16個前后狀態);
- MMM (參考《統計學習方法》公式11.23)
Mi(X)=[exp(∑k=1K(∑i,kλktk(yi?1,yi,X,i)+∑i,lμlsl(yi,x,i)))]M_i(X)=[exp(\sum_{k=1}^K(\sum_{i,k}\lambda_kt_k(y_{i-1},y_i,X,i)+\sum_{i,l}\mu_ls_l(y_i,x,i)))]Mi?(X)=[exp(k=1∑K?(i,k∑?λk?tk?(yi?1?,yi?,X,i)+i,l∑?μl?sl?(yi?,x,i)))]
- alphaalphaalpha (參考《統計學習方法》公式11.28)
αiT(X)=αi?1T(X)Mi(X)\alpha_i^T(X)=\alpha_{i-1}^T(X)M_i(X)αiT?(X)=αi?1T?(X)Mi?(X)
- betabetabeta (參考《統計學習方法》公式11.31)
βi(X)=Mi+1(X)βi+1(X)\beta_i(X)=M_{i+1}(X)\beta_{i+1}(X)βi?(X)=Mi+1?(X)βi+1?(X)
- 注意,在計算它們是要注意下標!每個句子都要計算 alphaalphaalpha MMM β\betaβ 一次。
上面只是理解計算過程,而在具體的實現中需要優秀的設計來提高性能。比如,我們的特征有幾百萬個,每次訓練要遍歷這么多特征?顯然,這樣行不通。我們能不能率先找出該批量句子中所含有的特征?再比如,考慮這一系列的計算過程那些能用多線程?
這些設計很吃個人的經驗,建議在 先弄懂算法流程后,再設計,最后動手。大家先自己想想怎么設計,看看有沒有奇淫技巧。如果沒想到,可以看看我的代碼(篇幅有限,就不再描述我是如何實現的了)。我的肯定不是最好的,但還能湊合。
5. CRF 預測算法
最后的預測算法,用于解決解碼問題,說白了就是能夠對輸入的句子進行分詞。
現在已經有條件隨機場 P(Y∣X)P(Y|X)P(Y∣X)(由上面得到的)以及輸入序列(句子),要求條件概率最大的輸出序列(標記序列)Y?Y^*Y?。最后會發現這就是一個最優路徑問題,要用到 動態規劃。此處便用的是著名的 維特比算法。
具體過程不打算寫了。請參考《統計學習方法》中的 206 頁。很簡單的!書中已經給出遞歸公式,實現時按照公式來即可。
6. 結果
我是在 人民日報語料(2014版) 語料上做的實驗。模型大約訓練了一個半小時,最后的準確率為 94.20%。還算可以。
[INFO] 開始初始化! [INFO] 開始解析特征模板... [INFO] 特征模板解析完畢! [INFO] 開始加載模型... [INFO] 一共加載了 5656572 個特征! [INFO] 初始化完畢! [INFO] 評測使用的文件:data/save/test.data ! [INFO] 結果將會保存在:data/save/result.data ! [INFO] 分詞已結束,正在評估準確率... [INFO] 評測結果如下:wTotal: 2042582sTotal: 47884wError: 118489, 5.80% --> 94.20%sError: 29377, 61.35%代碼(Github)
總結
以上是生活随笔為你收集整理的使用 CRF 做中文分词的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: rk3288_5.1_BOX 调整HDM
- 下一篇: SocksCap64全局代理设置教程