目錄
viterbi算法是學習自然語言處理的基礎算法,已經會有很多博客寫了關於viterbi算法的數學介紹。
但是對於在nlp中實踐應用的博客很少,導致很多朋友在學習viterbi算法之後,對於viterbi算法在nlp領域的應用還有一些困惑。
那麼這也是我寫這篇博客的意圖,現在對於英文詞性標註都可以通過調取很多包來完成(nltk,pyhanlp,jieba之類的)
但是自己實現英文詞性標註可以增加對於底層的理解,
那麼這篇博客就講解英文詞性標註中的viterbi算法和動態規劃,提供所有語料庫和代碼。
一、viterbi算法
viterbi算法其實就是多步驟每步多選擇模型的最優選擇問題,其在每一步的所有選擇都保存了前續所有步驟到當前步驟當前選擇的最小總代價(或者最大價值)以及當前代價的情況下前繼步驟的選擇。依次計算完所有步驟後,通過回溯的方法找到最優選擇路徑。符合這個模型的都可以用viterbi算法解決。
二、英文分詞基礎知識
2.1場景
英文詞性標註語料庫:https://download.csdn.net/download/qq_35883464/11463932
代碼:https://download.csdn.net/download/qq_35883464/11463943
場景:給定一個英文句子S=’I like …’其中每個單詞由w表示,z表示那個單詞對應的詞性。
目標:給定一個句子S=w1,w2,w3,w4,w5 .可以得出每個單詞對應的詞性
2.2 公式推導(naisy channel model)
naisy channel model:
我們這裏的語言模型只考慮bigram,就是2元語言模型。
因爲只是比較概率的大小,所以最後一步加了log,雖然公司推導很多,但是靜下心來看還是很簡單的。
最後我們得出在給定單詞求出詞性的概率公式:
爲了方便,我們定義Pi,A,B 要做的就是求這3個值。
A:給定詞性z,出現單詞w的概率,A就是一個M*N的矩陣(M是句子中所有單詞數量,N是所有詞性數量)
B:給定詞性Zn-1,下一個出現Zn詞性的概率,B就是一個N*N的矩陣(N是所有詞性數量)
Pi:每個詞性出現在句子開頭的概率,Pi就是一個1*N的矩陣(N是所有詞性數量)
現在應該知道了這3個參數的意思,等下用程序實現,
2.3 動態規劃
圖示路徑:
爲了記錄動態規劃最短路徑的值,使用一個N*t矩陣dp來記錄最短路徑的值(N詞性數量,t單詞序列數量)
若t單詞的詞性是dp[ i ][ j ]這一點,爲了記錄 t 單詞詞性從從哪條路徑來的,使用一個N*t矩陣ptr來記錄t -1單詞詞性。
例子:ptr[ i ][ j ]儲存的是前一個單詞(j-1)的詞性
如果只有dp矩陣,我們只可以記錄最短路徑的值,不知道最短路徑具體路徑是如何走的,ptr矩陣記錄了從哪條路徑來的,解決了這個問題
公式表示:
三、代碼
3.1數據處理
tag2id, id2tag = {}, {} # tag2id:{'VB':0, 'NNP':1,....} id2tag = {0:'VB', 2:'NNP'..}
word2id, id2word = {}, {} # word2id:{'i':0, 'he':2,...}
with open('traindata.txt') as f:
for line in f:
items = line.split('/')
word, tag = items[0], items[1].rstrip()
if word not in word2id:
word2id[word] = len(word2id)
id2word[len(id2word)] = word
if tag not in tag2id:
tag2id[tag] = len(tag2id)
id2tag[len(id2tag)] = tag
M = len(word2id) # 詞典大小
N = len(tag2id) # 詞性的種類個數
3.2 平滑處理
def log(v):
if v==0:
return np.log(0.00001)
return np.log(v)
我這就偷懶在log的時候就做很簡單的平滑處理了,你可增加加一些平滑處理
3.3 計算模型參數
# 構建pi A B
import numpy as np
pi = np.zeros(N) # 每個詞性出現在句子中第一個位置的概率
A = np.zeros((N, M)) # A[i][j]:給定詞性i,出現單詞j的概率
B = np.zeros((N, N)) # B[i][j]:之前是狀態i,之後是狀態j的概率
# 計算模型參數
pre_tag = ''
with open('traindata.txt') as f:
for line in f:
items = line.split('/')
wordid,tagid = word2id[items[0]], tag2id[items[1].rstrip()]
if pre_tag == '': # 句子的開始
pi[tagid] += 1
A[tagid][wordid] += 1
else:
A[tagid][wordid] += 1
B[tag2id[pre_tag]][tagid] += 1
if items[0] == '.':
pre_tag = ''
else:
pre_tag = items[1].rstrip()
# 轉化概率
pi = pi/sum(pi)
for i in range(N): # A B 每個元素是除以一行的和
A[i] /= sum(A[i])
B[i] /= sum(B[i])
3.4 viterbi算法
def viterbi(x, pi, A, B):
'''
x: 輸入的句子 X: l like playing soccer
pi:詞性出現在句子中第一個位置的概率
A[i][j]:給定詞性i,出現單詞j的概率
B[i][j]:之前是狀態i,之後是狀態j的概率
'''
x = [word2id[word] for word in x.split(' ')] # x:[453, 354,12, ...]
T = len(x)
dp = np.zeros((T,N)) # dp[i][j]: w1...wi, 表示wi詞性是第j個tag
ptr = np.array([[0 for x in range(N)] for y in range(T)]) # 動態規劃路徑記錄T*N
# 填充矩陣
for j in range(N): # dp矩陣的第一列,x中的第一個單詞
dp[0][j] = log(pi[j]) + log(A[j][x[0]])
# dp的第一列之後的列
for i in range(1,T): # 當前i單詞
for j in range(N): # i單詞要考慮j詞性
dp[i][j] = -99999 # 先賦值很小的可能性
for k in range(N): # 前面一個單詞的每個詞性 到i單詞的j詞性的路徑個數,既k
score = dp[i-1][k] + log(B[k][j]) + log(A[j][x[i]])
if score > dp[i][j]:
dp[i][j] = score
ptr[i][j] = k
# 把最好的詞性打印出來
best_seq = [0]*T # best_seq = [1,52,23,4,...]
# step1:找出最後一個單詞對應概率最大的詞性
best_seq[T-1] = np.argmax(dp[T-1])
# step2:通過從後到前的循環依次求出詞性
for i in range(T-2, -1, -1): # T-2,T-3,...1,0
best_seq[i] = ptr[i+1][best_seq[i+1]]
# best_seq錯放了對應於X的詞性序列
for i in range(len(best_seq)):
print(id2tag[best_seq[i]])
以上就是所有的算法代碼了,大家可以試一試,親測試無誤。