量化投資的利器:隱馬爾可夫模型(三)

之前幾篇有關(guān)HMM模型的文章(隱馬爾可夫模型(一),隱馬爾可夫模型(二))主要討論了這個模型的理論部分,從這篇文章開始,我們從實際的應(yīng)用場景入手,看看應(yīng)該如何使用HMM模型以及它的代碼實現(xiàn)。

與傳統(tǒng)的機器學(xué)習(xí)模型分為界限明確的監(jiān)督式學(xué)習(xí)和非監(jiān)督式學(xué)習(xí)不同,HMM可以處理這兩種場景的問題(這其實是所謂生成式模型的優(yōu)點)。而這篇文章將先討論監(jiān)督式的場景,下一篇文章(隱馬爾可夫模型(四))將討論非監(jiān)督式的場景

:在Github上可以下載本文所使用的數(shù)據(jù)和模型代碼。

一、中文分詞:監(jiān)督式學(xué)習(xí)

在對中文進行自然語言處理時,分詞是其中很重要的一步,也是隱馬爾可夫模型很典型的應(yīng)用場景(監(jiān)督式學(xué)習(xí))。下面就來詳細討論中文分詞的詳細步驟。

所謂中文分詞就是將句子里的一個或多個文字組合在一起構(gòu)成中文里面的語義單元,也就是日常生活中詞的概念。比如“我愛北京天安門”這句話就分為4個語義單元,分布是“我”“愛”“北京”和“天安門”。

為了能到達上面的分詞效果,我們首先需要將其轉(zhuǎn)換為模型能處理的分類問題。具體地,假設(shè)文字一共有4種狀態(tài),分別以字母B、M、E、S表示。

  • 字母S表示當(dāng)前文字單獨成一個語義單元。比如上面例子里的“我”和“愛”字。
  • 字母B表示當(dāng)前文字是一個語義單元的開頭,且該語義單元包含多個文字。比如上面例子里的“北”和“天”字。
  • 字母M表示當(dāng)前文字在一個語義單元的內(nèi)部,比如上面例子里的“安”字。
  • 字母E表示當(dāng)前文字在一個語義單元的結(jié)尾,比如上面例子里的“京”和“門”字。

經(jīng)過這樣的定義后,任何一個中文句子都對應(yīng)著一串字母序列,比如“我愛北京天安門”這句話就對應(yīng)著序列SSBEBME。反過來,如果知道了一個句子所對應(yīng)的字母序列,那么分詞就是一件很簡單的事情了,比如從字母序列SSBEBME可以很容易得到分詞的結(jié)果:S/S/BE/BME。因此,只需搭建模型對每個文字進行分類即可。這樣就把中文分詞這件看似無從下手的問題轉(zhuǎn)換為了熟悉的多元分類問題[1],如圖1所示。

圖1

顯然在這個問題中,文字之間不是相互獨立的,比如如果一個字的狀態(tài)是B,那么緊接著它的文字不可能是狀態(tài)S,于是使用隱馬爾可夫模型來解決這個分類問題。

具體地,用變量y_i表示第i個字的狀態(tài),根據(jù)上面的假設(shè)y_i可能的取值有4個;用變量x_i表示第個字具體是什么文字。根據(jù)隱馬爾可夫模型(二)中的討論,現(xiàn)搭建模型如下。

  • 先驗概率P(x_i | y_i):在已知文字狀態(tài)的情況下,每個漢字出現(xiàn)的概率。針對這部分概率,使用多項式模型,假設(shè)P(x_i = k | y_i = l) = p_{k, l}。
  • 初始分布P(y_0):表示每句話第一個字的狀態(tài)分布,這個比較直接,假設(shè)P(y_0 = l) = a_l
  • 轉(zhuǎn)移矩陣P(y_i | y_{i - 1}):表示4種狀態(tài)間相互轉(zhuǎn)移的可能性,這部分也比較直接,假設(shè)矩陣的元素為P(y_i = l | y_{i - 1} = t) = b_{l, t}。

在中文分詞這個問題中,我們能夠觀察到被預(yù)測量y_i(在實際應(yīng)用里,字的狀態(tài)來源于人工標(biāo)注)。因此根據(jù)最大似然估計法的原則,容易得到上述參數(shù)估計值的解析表達式。

\hat{p}_{k, l} = \frac{C(x = k, y = l)}{C(y = l)} \\ \hat{a}_l = \frac{C(y_1 = l)}{C(y_1)} \\ \hat_{l, t} = \frac{\sum_i C(y_i = l, y_{i - 1} = t)}{\sum_i C(y_{i - 1} = t)} \tag{1}

公式(1)中C表示事件在訓(xùn)練數(shù)據(jù)中出現(xiàn)的次數(shù),比如C(y = B)表示在訓(xùn)練文本中,狀態(tài)B出現(xiàn)的次數(shù)(不管它的具體位置是什么)。由此可見,這個隱馬爾可夫模型的參數(shù)估計值與多項式模型非常類似,都是具體事件在訓(xùn)練數(shù)據(jù)里的占比[2],因此在學(xué)術(shù)上這個模型被稱為用于監(jiān)督式學(xué)習(xí)的multinomial HMM。

二、中文分詞之代碼實現(xiàn)

雖然用于中文分詞的multinomial HMM模型比較簡單,但遺憾的是并沒有成熟的開源解決方案,大多數(shù)開源算法都是用于非監(jiān)督式學(xué)習(xí)的multinomial HMM。不過如果理解了模型的原理,用Python實現(xiàn)這個模型并不困難。限于篇幅,代碼的細節(jié)在此就不展開了(對工程實現(xiàn)比較感興趣的讀者請參考隨書配套的代碼yahmm/hmm[3]),僅討論在模型的工程實現(xiàn)中比較重要的幾點。

1.單元測試

就程序本身而來,模型的實現(xiàn)并不復(fù)雜。通常只需要幾百或者幾千行代碼,但與普通的程序相比,模型本身包含了比較復(fù)雜的數(shù)學(xué)邏輯,很難保證編寫的代碼就是想要的模型邏輯。因此單元測試就顯得極其重要了,否則很容易陷入這樣的困境—“當(dāng)模型效果不好時,不知道是因為自己的模型搭得不好,還是因為代碼寫錯了”[4]。而且做單元測試時,不能只簡單地測試功能是否實現(xiàn),更需要用人工計算的模型結(jié)果去驗證代碼的邏輯是否有問題。

對代碼做單元測試通常會用到一些自動化的輔助工具,在Python中,nosetests是一個不錯的選擇。

2.Cython

Python雖然很簡單易學(xué),但它最大的問題是運行速度太慢,用Python來做機器學(xué)習(xí)的模型運算幾乎不可行。為了解決這個問題,比較成熟的第三方庫通常用C語言實現(xiàn)具體的復(fù)雜運算,并將其封裝好,供Python調(diào)用(之前章節(jié)一直使用的scikit-learn就是這樣的實現(xiàn)架構(gòu),只是這些細節(jié)對用戶是不可見的,所以容易造成是Python在計算模型的假象)。

正因為如此,自己實現(xiàn)算法時也需要用C語言來封裝模型的核心算法,比如隱馬爾可夫模型中的Viterbi算法。但問題是C語言比較復(fù)雜,實現(xiàn)起來難度較大,有沒有比較簡單的實現(xiàn)方法呢?答案是使用Cython,它能將大部分Python代碼自動地“翻譯”成C代碼,并將后者封裝好,供Python調(diào)用。

在隨書配套的代碼中,用Python和Cython分別實現(xiàn)了Viterbi算法,它們的代碼路徑分別是viterbipy.pyviterbi.pyx。如果比較兩個腳本會發(fā)現(xiàn),絕大部分代碼都是一樣的,但經(jīng)過編譯[5]后,Cython版的算法比Python版的快10~100倍。有關(guān)Cython的具體細節(jié)超出了這篇文章的范圍,請讀者參考其他相關(guān)資料。

回到隱馬爾可夫模型,討論上面多次提到的Viterbi算法[5],該算法負責(zé)求解模型預(yù)測結(jié)果,公式(2)。

\hat{Y} = argmax_Y P(y_0, y_1, ..., y_T | X_1, X_2, ..., X_T) \tag{2}

這個算法的思路是利用動態(tài)規(guī)劃(dynamic programming),將最初的問題轉(zhuǎn)換為相似的子問題來解決。具體地,記S_T(y_T) = max_{y_0, ..., y_T}P(y_0, ..., y_T | X_1, ..., X_T),也就是說S_T(y_T)表示針對自變量{X_1, ..., X_T},在已知第T個狀態(tài)的情況下,所能達到的最大條件概率。數(shù)學(xué)上可以證明,這個問題可以按如圖2所示的方式將其拆解為子問題,這就是Viterbi算法的核心。其中符號\propto表示正比于,這樣書寫是為了避免那些對結(jié)果沒有影響但又極其繁瑣的數(shù)學(xué)公式,在機器學(xué)習(xí)領(lǐng)域很常用。至于Viterbi算法實現(xiàn)的具體代碼,限于篇幅,在此就不做討論了。

圖2

最后,使用實現(xiàn)好的multinomial HMM模型對中文進行分詞,訓(xùn)練模型的文本來自GitHub中的liwenzhu/corpusZh。這份數(shù)據(jù)是詞性標(biāo)注數(shù)據(jù),并沒有標(biāo)準(zhǔn)的分詞標(biāo)記。因此在訓(xùn)練模型前,需要對其做數(shù)據(jù)轉(zhuǎn)換,具體的可參考segmentation_example.py。模型的效果如圖3所示,對中文分詞的準(zhǔn)確度在80%左右。

segmentation_example.py代碼片段

def train_model(X, y, lengths):
    """
    訓(xùn)練模型
    """
    vect = CountVectorizer(token_pattern=r"(?u)\b\w+\b")
    XX = vect.fit_transform(X)
    model = MultinomialHMM(alpha=0.01)
    model.fit(XX, y, lengths)
    return vect, model


def chinese_segmentation(data_path):
    """
    對中文分詞,并評估模型效果
    """
    train_set, test_set = read_data(data_path, 0.2)
    train_X, train_Y, train_lengths = extract_feature(train_set)
    test_X, test_Y, test_lengths = extract_feature(test_set)
    vect, model = train_model(train_X, train_Y, train_lengths)
    pred = model.predict(vect.transform(test_X), test_lengths)
    print(classification_report([i[0] for i in test_Y], [i[0] for i in pred]))
    # 在Python3中,str不需要decode
    if sys.version_info[0] == 3:
        exampl_str = list("我愛北京天安門")
    else:
        exampl_str = list("我愛北京天安門".decode("utf-8"))
    print_segmentation(exampl_str, model.predict(vect.transform(exampl_str)))
圖3

三、廣告時間

這篇文章的大部分內(nèi)容參考自我的新書《精通數(shù)據(jù)科學(xué):從線性回歸到深度學(xué)習(xí)》。

李國杰院士和韓家煒教授在讀過此書后,親自為其作序,歡迎大家購買。

另外,與之相關(guān)的免費視頻課程請關(guān)注這個鏈接


  1. 在實際生產(chǎn)中,對中文分詞并不會只將文字狀態(tài)分為4類,因為這樣的分類方法太“粗糙”,并不足以解構(gòu)復(fù)雜的中文,導(dǎo)致模型效果并不好。在實際中,通常會將分詞和詞性標(biāo)注(名詞、動詞等)結(jié)合在一起。比如在正文的例子里,用字母V表示動詞,則“愛”字的狀態(tài)為“S+V”。經(jīng)過這樣的轉(zhuǎn)換后,雖然表面上增加了模型的預(yù)測難度(被預(yù)測量的類別更多),但實際上,分詞效果會明顯上升。
    當(dāng)然這兩種分類方法對模型而言是沒有任何差別的,因此為了表述簡潔,在正文里只討論較為直接的第一種方法。 ?

  2. 與多項式模型類似,在實際應(yīng)用中,常在公式(1)的基礎(chǔ)上加入平滑項,由平滑系數(shù)控制。 ?

  3. 這個模型API設(shè)計參考自GitHub上的larsmans/seqlearn。雖然后者是scikit-learn的相關(guān)項目,但其在實現(xiàn)上有很嚴(yán)重且致命的bug,而且無人維護,不推薦讀者使用。 ?

  4. 引自美國伯克利加州大學(xué)Michael Irwin Jordan教授的觀點。 ?

  5. 該算法是由美國工程師安德魯·詹姆斯·維特比(Andrew James Viterbi)發(fā)明的,因此以他的名字為算法命名。維特比不僅是一名杰出的工程師,還是一名成功的商人,他是芯片制造商高通公司的聯(lián)合創(chuàng)始人 ? ?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容