在網上看到一篇關於隱馬爾科夫模型的介紹,覺得簡直不能再神奇,又在網上找到大神的一篇關於如何用隱馬爾可夫模型實現中文拼音輸入的部落格,無奈大神沒給可以執行的程式碼,只能純手動網上找到了結巴分詞的詞庫,根據此訓練得出隱馬爾科夫模型,用維特比演算法實現了一個簡單的拼音輸入法。githuh地址:https://github.com/LiuRoy/Pinyin_Demo
原理簡介
隱馬爾科夫模型
抄一段網上的定義:
隱馬爾可夫模型 (Hidden Markov Model) 是一種統計模型,用來描述一個含有隱含未知引數的馬爾可夫過程。其難點是從可觀察的引數中確定該過程的隱含引數,然後利用這些引數來作進一步的分析。
拼音輸入法中可觀察的引數就是拼音,隱含的引數就是對應的漢字。
viterbi演算法
參考https://zh.wikipedia.org/wiki/維特比演算法,思想是動態規劃,程式碼比較簡單就不贅述。
程式碼解釋
model定義
程式碼見model/table.py檔案,針對隱馬爾科夫的三個概率矩陣,分別設計了三個資料表儲存。這樣的好處很明顯,漢字的轉移概率矩陣是一個非常大的稀疏矩陣,直接檔案儲存佔用空間很大,並且載入的時候也只能一次性讀入記憶體,不僅記憶體佔用高而且載入速度慢。此外資料庫的join操作非常方便viterbi演算法中的概率計算。
資料表定義如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
class Transition(BaseModel): __tablename__ = 'transition' id = Column(Integer, primary_key=True) previous = Column(String(1), nullable=False) behind = Column(String(1), nullable=False) probability = Column(Float, nullable=False) class Emission(BaseModel): __tablename__ = 'emission' id = Column(Integer, primary_key=True) character = Column(String(1), nullable=False) pinyin = Column(String(7), nullable=False) probability = Column(Float, nullable=False) class Starting(BaseModel): __tablename__ = 'starting' id = Column(Integer, primary_key=True) character = Column(String(1), nullable=False) probability = Column(Float, nullable=False) |
模型生成
程式碼見train/main.py檔案,裡面的initstarting,initemission,init_transition分別對應於生成隱馬爾科夫模型中的初始概率矩陣,發射概率矩陣,轉移概率矩陣,並把生成的結果寫入sqlite檔案中。訓練用到的資料集是結巴分詞裡的詞庫,因為沒有訓練長句子,最後執行的結果也證明只能適用於短句輸入。
初始概率矩陣
統計初始化概率矩陣,就是找出所有出現在詞首的漢字,並統計它們出現在詞首的次數,最後根據上述資料算出這些漢字出現在詞首的概率,沒統計的漢字就認為出現在詞首的概率是0,不寫入資料庫。有一點注意的是為了防止概率計算的時候因為越算越小導致計算機無法比較,所有的概率都進行了自然對數運算。統計的結果如下:
轉移概率矩陣
此處用到的是最簡單的一階隱馬爾科夫模型,即認為在一個句子裡,每個漢字的出現只和它前面的的一個漢字有關,雖然簡單粗暴,但已經可以滿足大部分情況。統計的過程就是找出字典中每個漢字後面出現的漢字集合,並統計概率。因為這個概率矩陣非常的大,逐條資料寫入資料庫過慢,後續可以優化為批量寫入,提高訓練效率。結果如下:
上圖展示的一後面出現概率最高的十個字,也挺符合日常習慣。
發射概率矩陣
通俗點就是統計每個漢字對應的拼音以及在日常情況下的使用概率,已暴舉例,它有兩個讀音:bao和pu,難點就是找bao和pu出現的概率。此處統計用到了pypinyin模組,把字典中的短語轉換為拼音後進行概率統計,但是某些地方讀音也不完全正確,最後執行的輸入法會出現和拼音不匹配的結果。統計結果如下:
viterbi實現
程式碼建input_method/viterbi.py檔案,此處會找到最多十個區域性最優解,注意是十個區域性最優解而不是十個全域性最優解,但是這十個解中最優的那個是全域性最優解,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
def viterbi(pinyin_list): """ viterbi演算法實現輸入法 Aargs: pinyin_list (list): 拼音列表 """ start_char = Emission.join_starting(pinyin_list[0]) V = {char: prob for char, prob in start_char} for i in range(1, len(pinyin_list)): pinyin = pinyin_list[i] prob_map = {} for phrase, prob in V.iteritems(): character = phrase[-1] result = Transition.join_emission(pinyin, character) if not result: continue state, new_prob = result prob_map[phrase + state] = new_prob + prob if prob_map: V = prob_map else: return V return V |
結果展示
執行input_method/viterbi.py檔案,簡單的展示一下執行結果:
問題統計:
- 統計字典生成轉移矩陣寫入資料庫的速度太慢,執行一次要將近十分鐘。
- 發射概率矩陣資料不準確,總有一些漢字的拼音不匹配。
- 訓練集太小,實現的輸入法不適用於長句子。
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!