pos Tagging
S={今天,学习,NLP}
通过已知单词,的词性,当给出一个新的句子,求解最后一个单词的词性是什么?
P ( Z ∣ S ) = P ( S ∣ Z ) ⋅ P ( Z ) = P ( w 1 w 2 . . . w N ∣ Z 1 Z 2 . . . Z N ) P ( Z 1 Z 2 . . . Z n ) = ∏ i = 1 n ⋅ P ( w i ∣ Z i ) ⋅ P ( Z 1 ) ⋅ P ( Z 2 ∣ Z 1 ) ⋅ P ( Z 3 ∣ Z 2 ) . . . P ( Z n ∣ Z n − 1 ) \begin{aligned}P(Z|S) &=P(S|Z)·P(Z) \\ &=P(w_1w_2...w_N|Z_1Z_2...Z_N)P(Z_1Z_2...Z_n)\\ &=\prod_{i=1}^{n}·P(w_i|Z_i)·P(Z_1)·P(Z_2|Z_1)·P(Z_3|Z_2)...P(Z_n|Z_{n-1}) \end{aligned} P(Z∣S)=P(S∣Z)⋅P(Z)=P(w1w2...wN∣Z1Z2...ZN)P(Z1Z2...Zn)=i=1∏n⋅P(wi∣Zi)⋅P(Z1)⋅P(Z2∣Z1)⋅P(Z3∣Z2)...P(Zn∣Zn−1)
对上面的公式进行转化,求最优解问题
Z ^ = a r g m a x P ( Z ∣ S ) = a r g m a x ∏ i = 1 n P ( W i ∣ Z i ) ⋅ P ( Z i ) ⋅ ∏ t = 2 n P ( Z t ∣ Z t − 1 ) = a r g m a x l o g ( ∏ i = 1 n P ( W i ∣ Z i ) ⋅ P ( Z i ) ⋅ ∏ t = 2 n P ( Z t ∣ Z t − 1 ) ) = a r g m a x ∏ i = 1 n l o g P ( W i ∣ Z i ) + l o g P ( Z i ) + l o g ∏ t = 2 n P ( Z t ∣ Z t − 1 ) (次向量最优解公式) \begin{aligned} \hat{Z} &=argmaxP(Z|S) \\ &=argmax\prod_{i=1}^nP(W_i|Z_i)·P(Z_i)·\prod_{t=2}^nP(Z_t|Z_{t-1}) \\ &=argmaxlog(\prod_{i=1}^nP(W_i|Z_i)·P(Z_i)·\prod_{t=2}^nP(Z_t|Z_{t-1})) \\ &=argmax\prod_{i=1}^nlogP(W_i|Z_i)+logP(Z_i)+log\prod_{t=2}^nP(Z_t|Z_{t-1}) \end{aligned} \tag{次向量最优解公式} Z^=argmaxP(Z∣S)=argmaxi=1∏nP(Wi∣Zi)⋅P(Zi)⋅t=2∏nP(Zt∣Zt−1)=argmaxlog(i=1∏nP(Wi∣Zi)⋅P(Zi)⋅t=2∏nP(Zt∣Zt−1))=argmaxi=1∏nlogP(Wi∣Zi)+logP(Zi)+logt=2∏nP(Zt∣Zt−1)(次向量最优解公式)
实现步骤:
tag2id,id2tag={},{} # map tag to id:{"VB":0,"NNp":1,...} id2tag:{0,"VB",..}
word2id,id2word = {},{} # ,aps word to id
for line in open('traindata.txt'):
items = line.split('/')
word,tag =items[0],items[1].strip() # 抽取每一行单词和词性
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) # M代表词典的大小
N = len(tag2id) # N词性的中种类个数
import numpy as np
pi = np.zeros(N) # 每个词出现在句子中第一个位置的概率
A = np.zeros((N,M)) # A[i][j]:给定tag i,出现单词j的概率
B = np.zeros((N,N)) # B[i][j]:之前状态变量i,之后转换成N;
prev_tag = "" # 类似状态
for line in open('traindata.txt'):
items = line.split("/")
wordId,tagId=word2id[items[0]],tag2id[items[1].rstrip()]
if prev_tag =="": # 这意味着句子的开始
pi[tagId]+=1
A[tagId][wordId]+=1
else: # 如果不是句子的开头
A[tagId][wordId]+=1
B[tag2id[prev_tag]][tagId]+=1
if items[0]=='.': #
prev_tag=""
else:
prev_tag=items[1].strip()
pi=pi/sum(pi)
for i in range(N):
A[i]/=sum(A[i])
B[i]/=sum(B[i])
对于上面的公式
w_i
:单词;Z_i
:词性;
可以使用二维矩阵进行描述该情景
N x M
)M: of words
N: of tags
句子 | w_1 | w_2 | … |
---|---|---|---|
名词 | w_1 单词对应n. 的概率 |
w_2 单词对应n. 的概率 |
… |
动词 | w_2 单词对应v. 的概率 |
w_2 单词对应v. 的概率 |
… |
header 1 | 名词 | 动词 | … |
---|---|---|---|
w_1 |
w_1 对应n. 的概率 |
w_1 对应v. 的概率 |
… |
N x N
)词性 | n | v | … |
---|---|---|---|
n. | n. 前面是n. 的概率 |
n. 前面是v. 的概率 |
… |
v. | n. 前面是v. 的概率 |
v. 前面是v. 的概率 |
… |
对于常规操作,这个矩阵对应的素偶有路径,时间复杂度是O(M*N*N)
为了降低时间复杂度,这里才有了viterbi
算法的出现
按照句子划分traindata.txt
给定单词,词性矩阵,对于每一个单词来说
s c o r e ( n ) = l o g P ( n ) + l o g ( w 1 ∣ n ) = l o g P ( v ∣ n ) + l o g P ( w 2 ∣ v ) = l o g ( a d j ∣ v ) + l o g P ( w 3 ∣ a d j ) (第个单词的score) \begin{aligned}score(n) & =logP(n)+log(w_1|n) \tag{第个单词的score} \\ & =logP(v|n)+logP(w_2|v) \\ & =log(adj|v)+logP(w_3|adj) \end{aligned} score(n)=logP(n)+log(w1∣n)=logP(v∣n)+logP(w2∣v)=log(adj∣v)+logP(w3∣adj)(第个单词的score)
观察上面的推到发现和之前推到的公式是一样的
因此可以使用动态规划的方法,来进行求解,生成原矩阵大小的新矩阵d[i][j]
, 那么则有下面的公式
d p [ i ] [ j ] = d p [ i − 1 ] [ 0 ] + l o g P ( a d j ∣ n ) + l o g P ( w i ∣ a d j ) + d p [ i − 1 ] [ 1 ] + l o g P ( a d j ∣ v ) + l o g P ( w i ∣ a d j ) + d p [ i − 2 ] [ 1 ] + l o g P ( a d j ∣ a d j ) + l o g P ( w i ∣ a d j ) + . . . \begin{aligned}dp[i][j]= & dp[i-1][0]+logP(adj|n)+logP(w_i|adj) \\ & +dp[i-1][1]+logP(adj|v)+logP(w_i|adj) \\ & +dp[i-2][1]+logP(adj|adj)+logP(w_i|adj) \\ & +... \end{aligned} dp[i][j]=dp[i−1][0]+logP(adj∣n)+logP(wi∣adj)+dp[i−1][1]+logP(adj∣v)+logP(wi∣adj)+dp[i−2][1]+logP(adj∣adj)+logP(wi∣adj)+...
相当于从最后一列中找到最大的,然后在到前一个着最大的
[ w 1 . . . w T ] = = > d p [ T ] [ 0 ] , d p [ T ] [ 1 ] . . . d p [ T ] [ N ] [w_1...w_T]==>dp[T][0],dp[T][1]...dp[T][N] [w1...wT]==>dp[T][0],dp[T][1]...dp[T][N]
维特比算法
给定w_1
,w_2
,…,求出Z_1
,Z_2
,…
z=argamx\sum_{i=1}^{T}logP(w_i|Z_i)+log(P(Z_1))+\sum_{t=2}^{T}logP(Z_t|Z_t)
def viterbi(x,pi,A,B):
"""
x:user input string/sentence x""I like palying soccer
pi:initeial probality of tags
A:给定tag,每个单词出现的概率
B:tag之间的转移概率
"""
x=[word2id[word] for word in x.split(" ")] # x:[2345,7543,2345,...]
T =len(x)
dp=np.zeros((T,N)) # dp[i][j]:w1,w2,w3,w4...,假设wi的tag是第i个tag
# 记录路径
ptr=np.array([[0 for x in range(N)] for y in range(T)]) # T*N
# ptr=np.zeros((T,N),dtype=np.int32)
# 第一列
for j in range(N): # basecase for DP算法
dp[0][j] = log(pi[j])+log(A[j][x[0]])
for i in range(1,T): # 每个单词
# TODO: 以下几行代码可以写成一行(vectorize的操作,会使得效率变高)
for j in range(N): # 每个词性
dp[i][j]=-99999
for k in range(N): # 从每一个k到达j
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
# decoding:把最好的结果打印出来
best_seq=[0]*T # best_seq=[1,3,5,2,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]] # 注意,当前的ptr是存在下一个节点上的
# 到目前为止,best_seq存放了对英语x的 词性序列
for i in range(len(best_seq)):
print(id2tag[best_seq[i]])
测试用例
x="I like English everyday"
viterbi(x,pi,A,B)
笔记来源于《贪心学院上课笔记》