所謂的短語的語序問題,即給定一個打亂順序的短語,我們要按照語義信息將其重新組合,新的語序通順的短語。
舉個簡單例子,比如我們在識別驗證碼中的文字的時候,識別出來的文字分別爲“哲”,“思”,“學”,“想”,那麼重合調整語序後形成的短語應該爲“哲學思想”。
這樣的問題也會經常出現,除了驗證碼識別,還有語音識別等。解決這類的語序問題,我們通常會用到統計方面的語言模型(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\)個詞語的影響,則聯合概率如下:
上述的假設是合情合理的,但實際我們在計算的過程中,會發現參數空間過大和數據稀疏等問題,尤其是\(i\)值越大,前\(i-1\)個的組合情況越少,甚至爲0。
爲了避免上述問題,我們需要馬爾科夫假設
,即一個詞的出現僅與它之前的N個詞有關。如果一個詞的出現僅依賴於前一個詞,那麼爲Bi-gram
的情形(N=2),公式如下:
如果一個詞的出現僅依賴於前兩個詞,那麼爲Tri-gram
的情形(N=3),公式如下:
在實際我們計算上述等式最後面的值時,可以用頻數來代替(這是根據條件概率得到的),比如:
其中,\(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的情形,此時需要做平滑處理。
本次分享到此結束,感謝大家的閱讀~