NLP(三十一)短語的語序問題

  所謂的短語的語序問題,即給定一個打亂順序的短語,我們要按照語義信息將其重新組合,新的語序通順的短語。
  舉個簡單例子,比如我們在識別驗證碼中的文字的時候,識別出來的文字分別爲“哲”,“思”,“學”,“想”,那麼重合調整語序後形成的短語應該爲“哲學思想”。
  這樣的問題也會經常出現,除了驗證碼識別,還有語音識別等。解決這類的語序問題,我們通常會用到統計方面的語言模型(Language Model,LM),常見的有N-gram問題等。
  下面將講述n-gram問題的解決辦法。

原理篇

  N-gram模型是一種語言模型,語言模型是一個基於概率的判別模型,它的輸入是一句話(單詞的順序序列),輸出是這句話的概率,即這些單詞的聯合概率(joint probability)。
  假設一個句子由n個詞組成:\(S=(w_{1}, w_{2}, ...,w_{n-1}, w_{n})\),如何衡量這些詞的聯合概率呢?我們不妨假設每一個詞語\(w_{i}\)都依賴於前\(i-1\)個詞語的影響,則聯合概率如下:

\[p(S)=p(w_{1}w_{2}...w_{n-1}w_{n})=p(w_{1})p(w_{2}|w_{1})...p(w_{n}|w_{1}w_{2}...w_{n-1}) \]

上述的假設是合情合理的,但實際我們在計算的過程中,會發現參數空間過大和數據稀疏等問題,尤其是\(i\)值越大,前\(i-1\)個的組合情況越少,甚至爲0。
  爲了避免上述問題,我們需要馬爾科夫假設,即一個詞的出現僅與它之前的N個詞有關。如果一個詞的出現僅依賴於前一個詞,那麼爲Bi-gram的情形(N=2),公式如下:

\[p(S)=p(w_{1}w_{2}...w_{n-1}w_{n})=p(w_{1})p(w_{2}|w_{1})...p(w_{n}|w_{n-1}) \]

如果一個詞的出現僅依賴於前兩個詞,那麼爲Tri-gram的情形(N=3),公式如下:

\[p(S)=p(w_{1}w_{2}...w_{n-1}w_{n})=p(w_{1})p(w_{2}|w_{1})p(w_{3}|w_{2}w_{1})...p(w_{n}|w_{n-1}w_{n-2}) \]

在實際我們計算上述等式最後面的值時,可以用頻數來代替(這是根據條件概率得到的),比如:

\[p(w_{n}|w_{n-1})=C(w_{n-1}w_{n})/C(w_{n-1}) \]

\[p(w_{n}|w_{n-1}w_{n-2})=C(w_{n-2}w_{n-1}w_{n})/C(w_{n-2}w_{n-1}) \]

其中,\(C(w_{n-1}w_{n})\)表示\(w_{n-1}w_{n}\)一起在文章中出現的概率,其餘類似。

實戰篇

  根據上面的原理,我們來解決短語的語序問題。
  首先,我們需要語料,語料就用人民日報的NER語料,前幾行如下:

海釣比賽地點在廈門與金門之間的海域。
這座依山傍水的博物館由國內一流的設計師主持設計,整個建築羣精美而恢宏。
在發達國家,急救保險十分普及,已成爲社會保障體系的重要組成部分。
日俄兩國國內政局都充滿變數,儘管日俄關係目前是歷史最佳時期,但其脆弱性不言自明。
克馬爾的女兒讓娜今年讀五年級,她所在的班上有30多名同學,該班的“家委會”由10名家長組成。
  解決短語的語序問題的腳本代碼如下:

# -*- coding: utf-8 -*-
# author: Jclian91
# place: Pudong Shanghai
# time: 2020/5/18 4:04 下午
from itertools import permutations
from pprint import pprint
from operator import itemgetter

# read corpus data in sentence format
with open("corpus.txt", "r", encoding="utf-8") as f:
    content = [_.strip() for _ in f.readlines()]

# random characters input order
string = "哲學思想"
# string = "景德鎮陶瓷"
# string = "突出貢獻"
# string = "哈薩克斯坦"
# string = "管理局"
# string = "博物館"
# string = "北京大學"
# string = "浦東發展銀行"
# string = "世界盃決賽"
word_list = set(list(string))
print("模擬輸入:", word_list)
candidate_list = list(permutations(word_list, r=len(word_list)))

# check if a character in the corpus
for word in word_list:
    if word not in "".join(content):
        raise Exception("%s不在語料庫中!" % word)

# Language Model
word_prob_dict = {}
for candidate in candidate_list:
    candidate = list(candidate)
    prob = "".join(content).count(candidate[0])/len("".join(content))
    # 2-gram
    # prob = Count(W_{i}W_{i-1})/Count(W_{i-1})
    for i in range(1, len(candidate)):
        char_cnt = "".join(content).count(candidate[i-1])
        word_cnt = "".join(content).count("".join(candidate[i-1:i+1]))
        prob *= (word_cnt/char_cnt)
        if prob == 0:
            break

    word_prob_dict["".join(candidate)] = prob

# recognize result
# pprint(word_prob_dict)
print("最終輸出結果:")
print(sorted(word_prob_dict.items(), key=itemgetter(1), reverse=True)[0])

輸出結果如下:

模擬輸入: {'思', '想', '哲', '學'}
最終輸出結果:
('哲學思想', 4.851640701745029e-08)

模擬輸入: {'陶', '鎮', '德', '瓷', '景'}
最終輸出結果:
('景德鎮陶瓷', 4.402324066467249e-12)

模擬輸入: {'貢', '出', '突', '獻'}
最終輸出結果:
('突出貢獻', 4.4461494672771407e-07)

模擬輸入: {'坦', '克', '薩', '斯', '哈'}
最終輸出結果:
('哈薩克斯坦', 4.65260719191311e-09)

模擬輸入: {'理', '局', '管'}
最終輸出結果:
('管理局', 4.5911913512038205e-06)

模擬輸入: {'館', '物', '博'}
最終輸出結果:
('博物館', 5.9318486186358995e-06)

模擬輸入: {'大', '京', '北', '學'}
最終輸出結果:
('北京大學', 3.491890788613219e-06)

模擬輸入: {'銀', '行', '發', '東', '浦', '展'}
最終輸出結果:
('浦東發展銀行', 2.8403362134844498e-11)

模擬輸入: {'決', '世', '界', '杯', '賽'}
最終輸出結果:
('世界盃決賽', 6.28999814981479e-07)

總結

  上面的代碼只是給出瞭如何解決短語的語序問題的一個實現思路,實際我們在應用的過程中還需要考慮以下問題:

  • 語料大小,本文示例語料較小,只有3萬多句話,語料越大,識別的效果一般也越好;
  • 短語的長度,一般短語越長,識別的時間也越長,如何優化識別算法,從而使得識別時間更短;
  • 平滑處理,本例中不考慮分母爲0的情形,實際應用中我們還需要考慮分母爲0的情形,此時需要做平滑處理。

  本次分享到此結束,感謝大家的閱讀~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章