基於特定語料庫生成HMM轉移概率分佈和發射概率分佈用於詞性標註 Python
上篇文章我們以Brown語料庫中的一個特例講解了HMM和Viterbi演算法。
那麼如何使用特定語料庫通過HMM演算法進行詞性標註呢?我們可以從HMM的五元組入手。
大致步驟:
- 得到語料庫中詞性標註種類和個數==>得到隱序列;
- 對輸入的句子進行分詞==>得到觀測序列;
- 對每個詞性標註,計算該詞性出現時的前一個詞性的次數/該詞性出現的總次數==>得到轉移概率矩陣;
- 對每個觀測狀態,計算該狀態不同詞性出現的次數/該觀測狀態出現的總次數==>得到發射概率矩陣;
- 計算各詞性初始概率:,某一個詞性出現的次數/語料庫總詞性標記數;
在實際操作中某些小細節是需要引起我們關注的。
1 如何做資料平滑?
我們要考慮並處理髮射矩陣和轉移矩陣中條件概率為0而出現整條路徑概率為0情況,因此我們需要做資料平滑。下面以他是創業大賽國賽金獎?
為例展開分析:
資料平滑的方法有很多種,一開始我使用的是加一法,在測試的時候我發現無論輸入什麼句子,詞性標註都是Rg。
他/Rg 是/Rg 創業/Rg 大賽/Rg 國/Rg 賽/Rg 金獎/Rg ?/Rg
平滑方法改進後:
他/r 是/v 創業/vn 大賽/vn 國/n 賽/vn 金獎/n ?/w
為什麼會出現這樣的情況呢?
我們回到語料庫可以發現總詞性種類有44種,而Rg只出現了10次,如果我們使用加一法平滑資料,對資料的概率影響還是比較大的,因此加一法在該語料庫中的表現上效果不是特別理想。我們可以使用Laplace平滑方法
# 加一法平滑方法
for row in range(transaction_matrix.shape[0]):
transaction_matrix[row] += 1
transaction_matrix[row] /= np.sum(transaction_matrix[row])
for row in range(emission_matrix.shape[0]):
emission_matrix[row] += 1
emission_matrix[row] /= tags_num[tags_list[row]] + emission_matrix.shape[1]
# Laplace平滑方法 l=1,p=1e-16
# 這裡也可以看做使用一個極小的數字如1e-16代替0,分母+1為避免分母為0的情況出現
for row in range(transaction_matrix.shape[0]):
n = np.sum(transaction_matrix[row])
transaction_matrix[row] += 1e-16
transaction_matrix[row] /= n + 1
for row in range(emission_matrix.shape[0]):
emission_matrix[row] += 1e-16
emission_matrix[row] /= tags_num[tags_list[row]] + 1
2 未登入詞的詞性?
這裡使用的分詞詞典是基於該語料庫的,所以分割結果都能找到對應的詞性標註,所以不存在未登入詞未知詞性的情況。
但是,如果我們使用別的分詞詞典或者分詞工具呢?那就會出現未登入詞的情況,這時候每種詞性的發射概率都相同,該未登入詞的詞性就跟前後的詞性有關了,不過對未登入詞測試結果並不是太好。
最大概率分詞:
今天/t 和/p 明天/t 我/r 不/d 用/v 再/d 做/v 自然/a 語言/n 處理/vn 作業/n 了/y !/w
其他分詞:(含未登入詞-自然語言處理)
今天/t 和/p 明天/t 我/r 不用/d 再/d 做/v 自然語言處理/vd 作業/v 了/y 。/w
3 語料庫的影響?
先看一下這個例子:
我/r 喜歡/v 學習/v 。/w
我們再看看詞典,學習有/v,/vn這兩種詞性,但是在這個例子中應該是vn才對。這是什麼情況呢?通過打印發射概率矩陣發現,學習的vn和v這兩個發射概率竟然差不多?問題可能出在這裡。
我們可以試著將語料庫中學習/v
全部替換成學習/vn
。這時候,我們的標註結果變成了:
我/r 喜歡/v 學習/vn 。/w
分析發現,出現這樣的標註差異應該是語料庫本身造成的,有趣的是我們使用LTP詞性標註測試該例子時也是第一種結果。
4 轉移概率矩陣和發射概率矩陣的儲存?
資料結構使用numpy.float64,保證有足夠小的精度儘量防止溢位,能夠小到1e-308。
轉移概率矩陣大小為44*44
,發射概率矩陣大小為44*分詞詞串數目
,兩者都是在程式執行時生成。
若在大規模詞性標註情況下建議儲存44*44
和44*60366(語料庫總詞數)
大小的轉移概率矩陣和發射概率矩陣到本地檔案中,需要時直接一次讀取該檔案存入記憶體而不需動態生成,提高效率。
5 其他問題
語料庫中有[香港/ns 特區/n 基本法/n 推廣/vn 督導/vn 委員會/n]nt
這樣的組合標記,為了處理方便,這裡特別處理這樣的詞性標註,在實際應用中需要注意處理這種情況。
在計算轉移概率時是以句子為單位進行處理的,因此,對於一段文字建議按標點分成幾個句子處理,一來能提高詞性標註的準確性,二來防止因句子長度過長出現下溢現象。
6 實現程式碼
import numpy as np
import hmm_viterbi
import max_probability_seg
def cal_hmm_matrix(observation):
# 得到所有標籤
word_pos_file = open('ChineseDic.txt').readlines()
tags_num = {}
for line in word_pos_file:
word_tags = line.strip().split(',')[1:]
for tag in word_tags:
if tag not in tags_num.keys():
tags_num[tag] = 0
tags_list = list(tags_num.keys())
# 轉移矩陣、發射矩陣
transaction_matrix = np.zeros((len(tags_list), len(tags_list)), dtype=float)
emission_matrix = np.zeros((len(tags_list), len(observation)), dtype=float)
# 計算轉移矩陣和發射矩陣
word_file = open('199801.txt').readlines()
for line in word_file:
if line.strip() != '':
word_pos_list = line.strip().split(' ')
for i in range(1, len(word_pos_list)):
tag = word_pos_list[i].split('/')[1]
pre_tag = word_pos_list[i - 1].split('/')[1]
try:
transaction_matrix[tags_list.index(pre_tag)][tags_list.index(tag)] += 1
tags_num[tag] += 1
except ValueError:
if ']' in tag:
tag = tag.split(']')[0]
else:
pre_tag = tag.split(']')[0]
transaction_matrix[tags_list.index(pre_tag)][tags_list.index(tag)] += 1
tags_num[tag] += 1
for o in observation:
# 注意' 我/','我/'的區別
if ' ' + o in line:
pos_tag = line.strip().split(o)[1].split(' ')[0].strip('/')
if ']' in pos_tag:
pos_tag = pos_tag.split(']')[0]
emission_matrix[tags_list.index(pos_tag)][observation.index(o)] += 1
for row in range(transaction_matrix.shape[0]):
n = np.sum(transaction_matrix[row])
transaction_matrix[row] += 1e-16
transaction_matrix[row] /= n + 1
for row in range(emission_matrix.shape[0]):
emission_matrix[row] += 1e-16
emission_matrix[row] /= tags_num[tags_list[row]] + 1
times_sum = sum(tags_num.values())
for item in tags_num.keys():
tags_num[item] = tags_num[item] / times_sum
# 返回隱狀態,初始概率,轉移概率,發射矩陣概率
return tags_list, list(tags_num.values()), transaction_matrix, emission_matrix
if __name__ == '__main__':
input_str = "今天和明天我彈琴。"
obs = max_probability_seg.seg(input_str).strip().split(' ')
hid, init_p, trans_p, emit_p = cal_hmm_matrix(obs)
result = hmm_viterbi.viterbi(len(obs), len(hid), init_p, trans_p, emit_p)
tag_line = ''
for k in range(len(result)):
tag_line += obs[k] + hid[int(result[k])] + ' '
print(tag_line)