简单词性标注实战
文章目錄
- 詞性標注實戰
- 講解
- 代碼實現
- 讀取詞性標注數據集
- 構建上述三個特征數組
- 進行詞性標注
- 查看一下路徑對應的詞性,以及錯誤詞性的dp值
- 學自NLPCamp
詞性標注實戰
講解
對于一段文本我們要知道其中每個詞的詞性。形式如下:
# Z為詞性總和:{z1,z2,...,zn},S為句子(詞總和):{w1,w2,...,wn} # S中每個詞的詞性肯能有多種,而組合起來的Z就更多了所以對于要求的P(Z|S),S是一個小概率,Z是大概率。所以其是求不出來的,使用貝葉斯公式求。
我們要求的是:從S的所有詞的詞性組合中選擇一個概率最大的詞性組合(也就是這個詞性最符合語法)
P(Z|S) = P(S|Z) * P(Z) / P(S) # S為已知量,Z為變量。所以省略常數分母 P(Z|S) = P(S|Z) * P(Z)可以看出這就是一個噪聲信道模型。
由于Z是S中所有詞的詞性的組合,S是S中所有詞的列表。其上式可寫成:
argmax P(Z|S) = P(z1,z2,...,zn|w1,w2,...,wn)# Translation Model * Language Model= P(w1,w2,...,wn|z1,z2,...,zn) * P(z1,z2,...,zn)# 假設獨立= P(w1|z1)*P(w2|z2)*...*P(wn|zn) * P(z1,z2,...,zn)# 使用BiGram= P(w1|z1)*P(w2|z2)*...*P(wn|zn) * P(z1)*P(z2|z1)*...*P(zn|zn-1)= ∏(1<=i<=n)P(wi|zi) * P(z1) * ∏(2<=i<=n)P(zi|zi-1)- 假設獨立:假設w之間是獨立的,z之間是獨立的。但是w與z之間不是獨立的,每個w與它對應的z相關。
加個log防止溢出:
argmaxZ∈S中每個單詞詞性的所有組合 logP(Z|S) = ∑1<=i<=nlogP(wi|zi) + logP(z1) + ∑2<=i<=nlogP(zi|zi-1)
- Z={z1,z2,…,zn},S={w1,w2,…,wn}
上述公式可以得到一下兩個矩陣+一個向量。
這三個矩陣分別對應:
- A
- logP(wi|zi)
- 大小為n * m。n為詞典庫大小,m為詞標注表大小
- B
- logP(z1)
- 大小為m。m為詞標注表大小
- C
- logP(zi|zi-1)
- 大小為m * m。m為詞標注表大小
通過掃描語料庫構建這三個矩陣。三個矩陣中的每一項都是模型的一個參數。
由于Z是句子中所有單詞詞性的一個組合。所以我們可以將組合問題優化為動態規劃問題(Viterbi算法的思想)。
通過動態規劃五部曲來解答問題:
-
DP數組下標及其含義是什么?
- DP[i] [j]:第 i 個單詞選擇第 j 個詞性時,從第一個單詞到第 i 個單詞的概率大小。
-
遞推公式是什么?
- dp[i][j] = max for z in range(0, m): dp[i-1][z] + a[j][i] + b[j] + c[j-1][j]
-
怎樣初始化?
- 第一行是第一個單詞,所以對其進行初始化。dp[0][j] = a[j][0] + b[j]
-
遍歷順序是什么?
- 從左到右遍歷,從上到下遍歷
-
終止條件是什么?
- 遍歷到dp數組尾部
代碼實現
使用人人網詞性標注數據集。
import jieba from collections import defaultdict, Counter import numpy as np import math讀取詞性標注數據集
讀取詞性標注數據集,并且獲取詞及其詞性。做特征提取并進行token化。
words = [] tags = [] is_head = [] with open('sources/TagData/PeopleDaily199801.txt', 'r', encoding='utf8') as f:while True:line = f.readline()if not line:breakline = line.strip()# 用于組合詞combine_word = ''combine_tag = ''combine_flag = Falsefor i, word in enumerate(line.split()):if i == 0:# 去掉開頭的報紙日期continueif i == 1:is_head.append(1)else:is_head.append(0)if word.find('[') != -1:combine_word += word.split('[')[1].split('/')[0]combine_flag = Truecontinueelif word.rfind(']') != -1:combine_word += word.split(']')[0].split('/')[0]combine_tag = word.split(']')[1]words.append(combine_word)tags.append(combine_tag)combine_word = ''combine_flag = Falsecontinueif combine_flag:combine_word += word.split('/')[0]continuewords.append(word.split('/')[0])tags.append(word.split('/')[1])ids_to_word = list(set(words)) ids_to_tag = list(set(tags)) words_len = len(ids_to_word) tags_len = len(ids_to_tag)word_to_ids = dict((w, i) for i, w in enumerate(ids_to_word)) tag_to_ids = dict((t, i) for i, t in enumerate(ids_to_tag)) word_to_tag = dict((w, t) for w, t in zip(words, tags))構建上述三個特征數組
A = [[0 for j in range(words_len)] for i in range(tags_len)] B = [0 for i in range(tags_len)] C = [[0 for j in range(tags_len)] for i in range(tags_len)]# 構建A數組 pre_word, pre_tag, pre_head = 0, 0, 0 for word, tag, head in zip(words, tags, is_head):if head:B[tag_to_ids[tag]] += 1else:A[tag_to_ids[tag]][word_to_ids[word]] += 1C[tag_to_ids[pre_tag]][tag_to_ids[tag]] += 1pre_word, pre_tag, pre_head = word, tag, headA = np.array(A, dtype=float) B = np.array(B, dtype=float) C = np.array(C, dtype=float)for i in range(A.shape[0]):summ = A[i].sum()for j in range(A.shape[1]):A[i][j] = (A[i][j] + 1) / (summ + A.shape[1])summ = B.sum() for i in range(B.shape[0]):B[i] = (B[i] + 1) / (summ + B.shape[0])for i in range(C.shape[0]):summ = C[i].sum()for j in range(C.shape[1]):C[i][j] = (C[i][j] + 1) / (summ + C.shape[1])最后三個for循環是為了進行歸一化、平滑化。防止0的出現。
進行詞性標注
正確詞性:19980103-02-013-001/m 云南/ns 全面/ad 完成/v 黨報/n 黨刊/n 發行/vn 任務/n
content = "云南 全面 完成 黨報 黨刊 發行 任務".split()dp = [[0 for j in range(tags_len)] for i in range(len(content))] path = [0 for i in range(len(content))]res = [] for tag in range(tags_len):dp[0][tag] = -math.log(A[tag][word_to_ids[content[0]]]) - math.log(B[tag])for i in range(1, len(content)):minn = float('inf')minn_tag = ''for z in range(tags_len):if dp[i-1][z] < minn:minn, minn_tag = dp[i-1][z], zpath[i-1] = minn_tagfor j in range(tags_len):dp[i][j] = -math.log(A[j][word_to_ids[content[i]]]) - math.log(C[minn_tag][j]) + minnminn = float('inf') minn_tag = '' for z in range(tags_len):if dp[-1][z] < minn:minn, minn_tag = dp[-1][z], z path[-1] = minn_tag print(path) [6, 16, 7, 30, 30, 7, 30]查看一下路徑對應的詞性,以及錯誤詞性的dp值
for i in path:print(ids_to_tag[i], end=' ') print() print(sorted(list(enumerate(dp[-2])), key=lambda i: i[1])) ns ad v n n v n [(7, 62.52403804697192), (18, 63.54269168062274), (31, 66.31538434533402), (12, 66.84356020041032), (30, 66.98141761571875), (29, 67.15506401995253), (42, 67.2579442909328), (41, 67.32280128709618), (21, 67.76834136366989), (35, 67.77954985376014), (22, 68.1156403237701), (13, 68.16223680506411), (4, 68.89857138751013), (16, 68.90836896977878), (15, 69.2393456247602), (25, 69.27639029481048), (3, 69.32057529175151), (5, 69.333718912759), (24, 69.38224259821669), (17, 69.5266120576496), (0, 69.69754138240673), (38, 69.96903098819377), (6, 69.97206357901055), (19, 70.0542828964721), (9, 70.06770949242326), (2, 70.20966944201218), (26, 70.30691706175277), (10, 70.40364083385651), (43, 70.97207322317294), (40, 71.36526036755518), (8, 72.02383165009198), (28, 72.2356456654909), (39, 72.31657495384712), (32, 72.34134999730088), (23, 72.62285124657133), (20, 73.24008077749231), (14, 74.84844776211168), (34, 74.84877086114435), (1, 75.25396389279734), (33, 75.2545589639259), (11, 75.94695799781421), (27, 75.94695799781421), (36, 75.94695799781421), (37, 75.94706005077978)]我們再看一下tag與其對應的id:
print(tag_to_ids) {'j': 0, 'Rg': 1, 'y': 2, 'i': 3, 'r': 4, 'l': 5, 'ns': 6, 'v': 7, 'q': 8, 's': 9, 'Vg': 10, 'Yg': 11, 'u': 12, 'm': 13, 'Bg': 14, 't': 15, 'ad': 16, 'Ng': 17, 'vn': 18, 'nt': 19, 'o': 20, 'p': 21, 'a': 22, 'Tg': 23, 'b': 24, 'k': 25, 'z': 26, 'na': 27, 'Dg': 28, 'd': 29, 'n': 30, 'w': 31, 'Ag': 32, 'h': 33, 'e': 34, 'nr': 35, 'vvn': 36, 'Mg': 37, 'an': 38, 'nx': 39, 'nz': 40, 'c': 41, 'f': 42, 'vd': 43}我們可以看到輸出錯誤的那個詞性的是動詞,我們再詳細看一下矩陣中的值:
print("發行作為v的概率:", A[7][word_to_ids['發行']]) print("發行作為vn的概率:", A[18][word_to_ids['發行']]) print("n作為前詞性v作為當前詞性的概率:", C[30][7]) print("n作為前詞性vn作為當前詞性的概率:", C[30][18]) 發行作為v的概率: 0.0003537863463443465 發行作為vn的概率: 0.00035066275260241855 n作為前詞性v作為當前詞性的概率: 0.1478553505803919 n作為前詞性vn作為當前詞性的概率: 0.05386328343799666可以看到,發行這個詞作為v和vn的概率差別不大。但是在整個語料中v作為n的后詞的概率要比vn大,而vn作為n的后詞在語料中出現的次數少。所以受C矩陣影響將“發行”這個詞作為了v詞性。
總結
- 上一篇: MapReduce作业提交流程
- 下一篇: linux编译lame,lame mp3