热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

番外.2.词性标注byViterbi

文章目录数据格式说明模型公式推导目标描述NoisyChannelModel代码实现问题动态规划通项代码实现小结重新再复习一下NLP,把一些内容以番外的内容记录一下。本节使用维比特算




文章目录


  • 数据格式说明
  • 模型公式推导
    • 目标描述
    • Noisy Channel Model
    • 代码实现

  • 问题
    • 动态规划通项
    • 代码实现

  • 小结


重新再复习一下NLP,把一些内容以番外的内容记录一下。本节使用维比特算法来实现了一个英文单词词性标注的模型。

公式输入请参考:
在线Latex公式


数据格式说明

数据是一个txt文件,里面包含很多句子,然后按单词(包括标点符号)进行了分词,然后每个词后面对应该词的词性。一个词在不同的语句中词性可能是不一样的。
贴一部分:
Still/RB
,/,
massive/JJ
internal/JJ
debt/NN
has/VBZ
forced/VBN
the/DT
government/NN
to/TO
borrow/VB
massively/RB
on/IN
the/DT
domestic/JJ
market/NN
and/CC
to/TO
offer/VB
inflation-adjusted/JJ
returns/NNS
of/IN
2/CD
%/NN
to/TO
3/CD
%/NN
a/DT
month/NN
just/RB
to/TO
get/VB
investors/NNS
to/TO
hold/VB
on/RP
to/TO
its/PRP$
paper/NN
./.
关于词性不多深入,大概就是有:
NN名词
VB动词
IN介词


模型公式推导

目标描述

假设有一句话




S



S


S可以表示为多个单词




w



w


w的序列





S


=



w


1




w


2




w


3







S=w_1w_2w_3\cdots


S=w1​w2​w3​⋯
每个单词在句子中都有对应的词性




z



z


z:






w


1







z


1



,



w


2







z


2



,



w


3







z


3







w_1\rightarrow z_1,w_2\rightarrow z_2,w_3\rightarrow z_3\cdots


w1​→z1​,w2​→z2​,w3​→z3​⋯
得到一个词性标注序列




Z



Z


Z:





Z


=



z


1



,



z


2



,



z


3







Z=z_1,z_2,z_3\cdots


Z=z1​,z2​,z3​⋯
对于有T个单词的句子,上面序列长度为T
我们的目标是构建一个模型,通过对语料库的训练,可以对新的一个句子





S







S'


S′:






S






=



w


1







w


2







w


3










S'=w'_1w'_2w'_3\cdots


S′=w1′​w2′​w3′​⋯
进行词性标注预测:






Z






=



z


1







z


2







z


3










Z'=z'_1z'_2z'_3\cdots


Z′=z1′​z2′​z3′​⋯


Noisy Channel Model

根据前面的
Noisy Channel Model的内容,我们可以写出模型就是要使得在给定序列




S



S


S的条件下,词性序列




Z



Z


Z出现概率最大化:





P


(


Z





S


)


=


P


(


S





Z


)





P


(


Z


)



P(Z|S)= P(S|Z)\cdot P(Z)


P(Z∣S)=P(S∣Z)⋅P(Z)
其中




P


(


S





Z


)



P(S|Z)


P(S∣Z)是翻译模型(translation model),




P


(


Z


)



P(Z)


P(Z)是语言模型(language model),展开(这里使用约等于是前面部分有独立性条件假设约束进行简化。后面部分按bigram来展开):





P


(



w


1




w


2







w


N







z


1




z


2







z


N



)





P


(



z


1




z


2







z


N



)











i


=


1



T



P


(



w


i







z


i



)





P


(



z


1



)


P


(



z


2







z


1



)


P


(



z


3







z


2



)





P


(



z


n







z



n





1




)



P(w_1w_2\cdots w_N|z_1z_2\cdots z_N)\cdot P(z_1z_2\cdots z_N)\\ \approx\prod_{i=1}^TP(w_i|z_i)\cdot P(z_1)P(z_2|z_1)P(z_3|z_2)\cdots P(z_n|z_{n-1})


P(w1​w2​⋯wN​∣z1​z2​⋯zN​)⋅P(z1​z2​⋯zN​)≈i=1∏T​P(wi​∣zi​)⋅P(z1​)P(z2​∣z1​)P(z3​∣z2​)⋯P(zn​∣zn−1​)
目标函数就是:













Z


^









=


a


r


g


max





P


(


Z





S


)
















=


a


r


g


max










i


=


1



T



P


(



w


i







z


i



)





P


(



z


1



)







j


=


2



T



P


(



z


j







z



j





1




)
















=


a


r


g


max





log






(







i


=


1



T



P


(



w


i







z


i



)





P


(



z


1



)







j


=


2



T



P


(



z


j







z



j





1




)


)

















=


a


r


g


max










i


=


1



T



P


(



w


i







z


i



)


+


log





P


(



z


1



)


+







j


=


2



T



P


(



z


j







z



j





1




)










(1)





\begin{aligned} \hat Z &=arg\max P(Z|S) \\ &=arg\max \prod_{i=1}^TP(w_i|z_i)\cdot P(z_1)\prod_{j=2}^TP(z_j|z_{j-1}) \\ &= arg\max\log\left(\prod_{i=1}^TP(w_i|z_i)\cdot P(z_1)\prod_{j=2}^TP(z_j|z_{j-1}) \right)\\ &=arg\max\sum_{i=1}^TP(w_i|z_i)+\log P(z_1)+\sum_{j=2}^TP(z_j|z_{j-1}) \end{aligned}\tag{1}


Z^​=argmaxP(Z∣S)=argmaxi=1∏T​P(wi​∣zi​)⋅P(z1​)j=2∏T​P(zj​∣zj−1​)=argmaxlog(i=1∏T​P(wi​∣zi​)⋅P(z1​)j=2∏T​P(zj​∣zj−1​))=argmaxi=1∑T​P(wi​∣zi​)+logP(z1​)+j=2∑T​P(zj​∣zj−1​)​(1)
把上面式子中的三个分项目分别记为




A


,


π


,


B



A,\pi,B


A,π,B






Z


^



=


a


r


g


max





A


+


π


+


B



\hat Z =arg\max A+\pi+B


Z^=argmaxA+π+B

整个算法的思路就是
1.计算参数




θ


=


{


A


,


π


,


B


}



\theta=\{A,\pi,B\}


θ={A,π,B}
对于




A



A


A,是一个矩阵大小为




M


×


N



M\times N


M×N,




M



M


M为词库大小,




N



N


N是词性库大小,矩阵每一行代表每个词出现为该词性的概率。可以从训练数据中计算出来。
对于




π



\pi


π,是一个向量,大小为




N



N


N,每个元素代表每个词性做为句子开始单词词性的概率。可以从训练数据中计算出来。
对于




B



B


B,是一个矩阵,是大小为




N


×


N



N\times N


N×N,每个元素代表每个词性转化到另外一个词性的概率。可以从训练数据中计算出来。

2.使用维特比算法计算





Z


^




\hat Z


Z^


代码实现

#初始化单词字典word2id, id2word、词性字典tag2id, id2tag
tag2id, id2tag = {}, {} # maps tag to id . tag2id: {"VB": 0, "NNP":1,..} , id2tag: {0: "VB", 1: "NNP"....}
word2id, id2word = {}, {} # maps word to id
for line in open('traindata.txt'):#按行读取数据
items = line.split('/')#单词和词性是用斜杠分割的,split后得到单词和词性
word, tag = items[0], items[1].rstrip() # 抽取每一行里的单词和词性,.rstrip()是去掉词性后面的换行符\n

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: 词典的大小:number of words in dictionary
N = len(tag2id) # N: 词性的种类个数: number of tags in tag set

在这里插入图片描述
可以看到这里没有过滤标点。

# 初始化 pi, A, B
import numpy as np
pi = np.zeros(N) # 词性字典中每个词性tag i出现在句子中第一个位置的概率
A = np.zeros((N,M)) # A[i][j]: 给定词性tag i, 出现单词word j的概率
B = np.zeros((N,N)) # B[i][j]: 之前的状态是词性tag i, 之后转换成转态为词性tag j的概率

prev_tag = ""#前一个单词词性
for line in open('traindata.txt'):#按行读取训练数据
items = line.split('/')
wordId, tagId = word2id[items[0]], tag2id[items[1].rstrip()]#将词和词性转化为id表示
if prev_tag == "": # 判断是否句子的开始
pi[tagId] += 1#开始词计数+1,最后算概率
A[tagId][wordId] += 1#词性对应单词计数+1
# B矩阵不用更新,因为句首单词的词性属于首状态,不是其他词性转化过来的。
else: # 如果不是句子的开头
A[tagId][wordId] += 1
B[tag2id[prev_tag]][tagId] += 1

if items[0] == ".":#句号出现表示读完一句话
prev_tag = ""#重置前一个单词词性为空
else:
prev_tag = items[1].rstrip()#将当前单词词性作为下一个单词的prev_tag
# 概率计算
pi = pi/sum(pi)
for i in range(N):
A[i] /= sum(A[i])
B[i] /= sum(B[i])

看看那个词性出现在句首概率最大
在这里插入图片描述


问题

有了上面的信息,下面来看看如何来计算一个有6个单词的句子的





Z


^




\hat Z


Z^,如果六个单词分别是





w


1



,



w


2



,



w


3



,



w


4



,



w


5



,



w


6




w_1,w_2,w_3,w_4,w_5,w_6


w1​,w2​,w3​,w4​,w5​,w6​,且根据上面的截图可以知道,目前词库中的词性字典共有54种词性,每个单词都有54种词性的可能,因此整句话六个单词的词性序列的组合排列可能性为:




5



4


6




54^6


546(种)。可以从这个例子里面看出来直接使用穷举的方法来把




5



4


6




54^6


546种排列的





Z


^




\hat Z


Z^算出来,然后求最大值是非常低效的,其时间复杂度约为




O


(


5



4


N



)



O(54^N)


O(54N)。因此我们用维特比算法来解决这个动态规划的问题。
假设句子中有




T



T


T个单词,每个单词有54种可能的词性,那么如下图所示的绿色路径





r


1




r_1


r1​的





Z


^




\hat Z


Z^值我们记为




s


c


o


r


e


(



r


1



)



score(r_1)


score(r1​),其计算公式可以根据前面的公式1推导可以得。
在这里插入图片描述
第一个单词:





log







P


(


V


B


)



π



+


log







P


(



w


1






V


B


)



A




\log \underset{\pi}{P(VB)}+\log \underset{A}{P(w_1|VB)}


logπP(VB)​+logAP(w1​∣VB)​
第二个单词:





log







P


(


N


N


P





V


B


)



B



+


log







P


(



w


2






N


N


P


)



A




\log \underset{B}{P(NNP|VB)}+\log \underset{A}{P(w_2|NNP)}


logBP(NNP∣VB)​+logAP(w2​∣NNP)​
第三个单词:





log







P


(


V


B





N


N


P


)



B



+


log







P


(



w


3






V


B


)



A




\log \underset{B}{P(VB|NNP)}+\log \underset{A}{P(w_3|VB)}


logBP(VB∣NNP)​+logAP(w3​∣VB)​
除了第一个单词之外,从第二个单词开始都只有A和B项,可以依次写出后面的公式。









s


c


o


r


e


(



r


1



)









=


log







P


(


V


B


)



π



+


log







P


(



w


1






V


B


)



A

















+


log







P


(


N


N


P





V


B


)



B



+


log







P


(



w


2






N


N


P


)



A

















+


log







P


(


V


B





N


N


P


)



B



+


log







P


(



w


3






V


B


)



A




























\begin{aligned} score(r_1)&=\log \underset{\pi}{P(VB)}+\log \underset{A}{P(w_1|VB)}\\ &+\log \underset{B}{P(NNP|VB)}+\log \underset{A}{P(w_2|NNP)}\\ &+\log \underset{B}{P(VB|NNP)}+\log \underset{A}{P(w_3|VB)}\\ &\cdots\cdots \end{aligned}


score(r1​)​=logπP(VB)​+logAP(w1​∣VB)​+logBP(NNP∣VB)​+logAP(w2​∣NNP)​+logBP(VB∣NNP)​+logAP(w3​∣VB)​⋯⋯​


动态规划通项

在这里插入图片描述
我们记在单词序列中某个单词





w


i




w_i


wi​对应词性




j



j


j的时候可以使得在该词的





Z


^




\hat Z


Z^最大,此时值为




d


p


[


i


,


j


]



dp[i,j]


dp[i,j],那么这个值有可能是从前一个单词





w



i





1





w_{i-1}


wi−1​的某个词性




j



j


j过来的。
当前一个词的词性为NNP,那么




j


=


0



j=0


j=0(第一行红点):





d


p


[


i


,


j


]


=


d


p


[


i





1


,


0


]


+


log





P


(


V


B


D





N


N


P


)


+


log





P


(



w


i






V


B


D


)



dp[i,j]=dp[i-1,0]+\log P(VBD|NNP)+\log P(w_i|VBD)


dp[i,j]=dp[i−1,0]+logP(VBD∣NNP)+logP(wi​∣VBD)
当前一个词的词性为VB,那么




j


=


1



j=1


j=1(第二行红点):





d


p


[


i


,


j


]


=


d


p


[


i





1


,


1


]


+


log





P


(


V


B


D





V


B


)


+


log





P


(



w


i






V


B


D


)



dp[i,j]=dp[i-1,1]+\log P(VBD|VB)+\log P(w_i|VBD)


dp[i,j]=dp[i−1,1]+logP(VBD∣VB)+logP(wi​∣VBD)
当前一个词的词性为VBD,那么




j


=


2



j=2


j=2(第三行左边红点):





d


p


[


i


,


j


]


=


d


p


[


i





1


,


2


]


+


log





P


(


V


B


D





V


B


D


)


+


log





P


(



w


i






V


B


D


)



dp[i,j]=dp[i-1,2]+\log P(VBD|VBD)+\log P(w_i|VBD)


dp[i,j]=dp[i−1,2]+logP(VBD∣VBD)+logP(wi​∣VBD)
以此类推,共有54种可能,那么




d


p


[


i


,


j


]



dp[i,j]


dp[i,j]需要计算54次,然后从这54中情况中得到最大值作为当前的




d


p


[


i


,


j


]



dp[i,j]


dp[i,j]
整个算法分两步
第一步就是填充下面的动态规划数组(大小为:




T


×


N



T\times N


T×N),先算第一个单词对应的所有词性的概率,然后根据第一个单词的结果算第二个单词所有词性的概率,这样按列从左到右填充完成(时间复杂度为




O


(


T


N


)



O(TN)


O(TN))。
第二步从后往前每列选出最大值




d


p



dp


dp作为当前的最佳路径(此时时间复杂度为




O


(


T



N


2



)



O(TN^2)


O(TN2))。


词性列表





w


1




w_1


w1​






w


2




w_2


w2​









\cdots








w



i





1





w_{i-1}


wi−1​






w


i




w_i


wi​









\cdots








w



T





1





w_{T-1}


wT−1​






w


T




w_T


wT​
NNP
VB
VBD












\vdots


DT
MD

代码实现

# 重写log,防止无穷大发生
def log(v):
if v == 0:
return np.log(v+0.000001)
return np.log(v)

def viterbi(x, pi, A, B):
"""
x: 要预测词性的句子,例如: "I like playing toys"
pi: 首个单词出现某个词性的概率
A: 给定tag, 每个单词出现的概率
B: tag之间的转移概率
"""
x = [word2id[word] for word in x.split(" ")] # 将x中句子按空格进行分词,然后将单词变成对应的id,这里是有点问题的,最后的句号没隔开,因此输入的句子不能带句号
T = len(x)#对应上面的公式,这里是句子的长度

dp = np.zeros((T,N)) # 初始化dp矩阵,大小是T*N,dp[i][j]: w1...wi, 假设wi的tag是第j个tag
#初始化用来保存当前单词对应词性的最佳值是从前一个单词的哪个词性转移过来的,大小是T*N
ptr = np.zeros((T,N), dtype=int)

#初始化dp矩阵第一列
for j in range(N): # basecase for DP算法
dp[0][j] = log(pi[j]) + log(A[j][x[0]])

#核心算法,三层循环,时间复杂度是T*N*N
for i in range(1,T): # 每个单词
for j in range(N): # 每个词性
# TODO: 以下几行代码可以写成一行(vectorize的操作, 会使得效率变高)
dp[i][j] = -9999999#初始化一个很小的值
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: 把最好的tag sequence 打印出来
best_seq = [0]*T # best_seq = [1,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-1,... 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]])
x = "Social Security number , passport number and details about the services provided for the payment"#注意不能有句号
viterbi(x, pi, A, B)

小结

模型的实质是最简单的HMM模型的求解,还可以对模型进行三个方面的扩展:


  1. 如果只有句子,没有词性标注,要进行无标注的学习,可以用EM算法进行求解;
  2. 模型只考虑了当前单词和当前词性的关系(简化),可以使用CRF模型,考虑前后上下文,前后词性等约束。
  3. 还可以加入LSTM+CRF模型。


推荐阅读
  • Java学习笔记之面向对象编程(OOP)
    本文介绍了Java学习笔记中的面向对象编程(OOP)内容,包括OOP的三大特性(封装、继承、多态)和五大原则(单一职责原则、开放封闭原则、里式替换原则、依赖倒置原则)。通过学习OOP,可以提高代码复用性、拓展性和安全性。 ... [详细]
  • Go Cobra命令行工具入门教程
    本文介绍了Go语言实现的命令行工具Cobra的基本概念、安装方法和入门实践。Cobra被广泛应用于各种项目中,如Kubernetes、Hugo和Github CLI等。通过使用Cobra,我们可以快速创建命令行工具,适用于写测试脚本和各种服务的Admin CLI。文章还通过一个简单的demo演示了Cobra的使用方法。 ... [详细]
  • 本文介绍了禅道作为一款国产开源免费的测试管理工具的特点和功能,并提供了禅道的搭建和调试方法。禅道是一款B/S结构的项目管理工具,可以实现组织管理、后台管理、产品管理、项目管理和测试管理等功能。同时,本文还介绍了其他软件测试相关工具,如功能自动化工具和性能自动化工具,以及白盒测试工具的使用。通过本文的阅读,读者可以了解禅道的基本使用方法和优势,从而更好地进行测试管理工作。 ... [详细]
  • loader资源模块加载器webpack资源模块加载webpack内部(内部loader)默认只会处理javascript文件,也就是说它会把打包过程中所有遇到的 ... [详细]
  • quartus管脚分配后需要保存吗_嵌入式必须会的一些硬件面试题,要试一试吗?你过来呀!...
    1、下面是一些基本的数字电路知识问题,请简要回答之。(1)什么是Setup和Hold时间?答:SetupHoldTime用于测试芯片对输入 ... [详细]
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • CSS3选择器的使用方法详解,提高Web开发效率和精准度
    本文详细介绍了CSS3新增的选择器方法,包括属性选择器的使用。通过CSS3选择器,可以提高Web开发的效率和精准度,使得查找元素更加方便和快捷。同时,本文还对属性选择器的各种用法进行了详细解释,并给出了相应的代码示例。通过学习本文,读者可以更好地掌握CSS3选择器的使用方法,提升自己的Web开发能力。 ... [详细]
  • 本文介绍了Java工具类库Hutool,该工具包封装了对文件、流、加密解密、转码、正则、线程、XML等JDK方法的封装,并提供了各种Util工具类。同时,还介绍了Hutool的组件,包括动态代理、布隆过滤、缓存、定时任务等功能。该工具包可以简化Java代码,提高开发效率。 ... [详细]
  • baresip android编译、运行教程1语音通话
    本文介绍了如何在安卓平台上编译和运行baresip android,包括下载相关的sdk和ndk,修改ndk路径和输出目录,以及创建一个c++的安卓工程并将目录考到cpp下。详细步骤可参考给出的链接和文档。 ... [详细]
  • javascript  – 概述在Firefox上无法正常工作
    我试图提出一些自定义大纲,以达到一些Web可访问性建议.但我不能用Firefox制作.这就是它在Chrome上的外观:而那个图标实际上是一个锚点.在Firefox上,它只概述了整个 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • 利用Visual Basic开发SAP接口程序初探的方法与原理
    本文介绍了利用Visual Basic开发SAP接口程序的方法与原理,以及SAP R/3系统的特点和二次开发平台ABAP的使用。通过程序接口自动读取SAP R/3的数据表或视图,在外部进行处理和利用水晶报表等工具生成符合中国人习惯的报表样式。具体介绍了RFC调用的原理和模型,并强调本文主要不讨论SAP R/3函数的开发,而是针对使用SAP的公司的非ABAP开发人员提供了初步的接口程序开发指导。 ... [详细]
  •   CSS网页布局中不推荐使用的HTML标签,请尽量不要使用这些HTML标签。  Donotusethesehtmlelementsinhtmlpages.  Presentationalelementsshouldnotbeused ... [详细]
  • 篇首语:本文由编程笔记#小编为大家整理,主要介绍了在基于经文主题的神圣古兰经经文检索系统构建我的doc2vec嵌入模型时需要帮助相关的知识,希望对你有一定的参考价值。 ... [详细]
author-avatar
xhl583337984
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有