跟着其他博客走的步骤,最后做的不一定对,随便写写,表喷-_-
虽然也没完全弄懂。。
大体步骤是已知A->AB或A->b的概率,对输入的字符串,建立多棵树,最大概率的树即为该字符串的句法树。
首先根据https://blog.csdn.net/bbbeoy/article/details/79649690
里已经推导出的各个产生式的概率
(新注:根据后来的不断实践,图表中的P和IN是同一个)
建立一个二维字典(和ll1和lr1同理)
引用博客给出的算法:
以下用pi表示Π
算法框架:
输入 长度为n的语句单词个数 和 PCFG文法(N:非终结字符集,大埃普西隆:终结字符集 R:产生式规则 S:开始符号)
首先 初始化 对于所有的i取值为1-n 如果存在X->xi(第i个单词)
那么 pi(i,i,X)就为该产生式对应的概率,其他为0,pi(i,j,X)表示单词序列下标i到j的单词段,以X为根的子树的概率,初始化也就是把X->单词的所有概率写入
采用n*n二维数组嵌套一个字典结构,初始化所有值为空字典{}、
执行算法:(根据实际储存对原算法进行修改)
第一层遍历L取值为0到n-2:
第二层遍历i取值为0到n-L-2:
令j为i+L
第三层遍历对于所有的非终结字符X:
初始化最大值为0,取最大值时的参数为-1
第四层遍历s取值为i到j-1:
如果 (X->YZ)的概率乘pi(i,s,Y)乘pi(s+1,j,Z)大于之前的最大值,则更新最大值和最大值参数为当前值
第四层遍历完:把最大值写入pi(i,j,X) 参数写入bp(i,j,X)
[特别像Floyd算法]
输入句子:the man saw the dog with the telescope
实现代码:
# -*- coding: utf-8 -*-
"""
Created on Thu Jan 3 19:58:23 2019
@author: 71405
"""
"""
对于字符串中的字符xi和任意非终结字符N
初始化
pi(i,i,N)=N->xi的概率 如果不满足产生式则为0
开始
for l=1:n-1
for i=1:n-l
另 j=i+l
对于所有终结符X计算:
pi(i,j,X)=max(q(X->YZ)*pi(i,s,Y)*pi(s+1,j,Z)) 满足X->YZ且 s取值为i到j-1
和bp(i,j,X)=argmax(q(X->YZ)*pi(i,s,Y)*pi(s+1,j,Z))
"""
from collections import defaultdict
import re
def addtwodimdict(thedict, key_a, key_b, val):
if key_a in thedict:
thedict[key_a].update({key_b: val})
else:
thedict.update({key_a:{key_b: val}})
T=['saw','dog','man','telescope','the','with','in']
I=['SS','VP','NP','PP','VT','VI','NN','DT','IN','PX']
p=dict()
addtwodimdict(p, 'SS', ('NP','VP'), 1.0)
addtwodimdict(p, 'VP', 'VI', 0.4)
addtwodimdict(p, 'VP', ('VT','NP'), 0.4)
addtwodimdict(p, 'VP', ('VP','PP'), 0.2)
addtwodimdict(p, 'NP', ('DT','NN'), 0.3)
addtwodimdict(p, 'NP', ('NP','PP'), 0.7)
addtwodimdict(p, 'PP', ('IN','NP'), 1.0)
addtwodimdict(p, 'VI', 'sleep', 1.0)
addtwodimdict(p, 'VT', 'saw', 1.0)
addtwodimdict(p, 'NN', 'man', 0.7)
addtwodimdict(p, 'NN', 'dog', 0.2)
addtwodimdict(p, 'NN', 'telescope', 0.1)
addtwodimdict(p, 'DT', 'the', 1.0)
addtwodimdict(p, 'IN', 'with', 0.5)
addtwodimdict(p, 'IN', 'in', 0.5)
sen="the man saw the dog with the telescope"
ss=re.split(r'[\s]',sen)
R1=open('gram3.txt').readlines()# 非终->非终
R2=open('gram4.txt').readlines()#非终->终
i=0
while i'符号
R1[i]=R1[i][0:2]+R1[i][4:len(R1[i])-1]
i+=1
i=0
while i'符号
R2[i]=R2[i][0:2]+R2[i][4:len(R2[i])-1]
i+=1
"""
pi设置为二维数组内嵌一元字典
bp同理
"""
N=len(ss)
#初始化
pi=[ [{} for i in range(N)] for i in range(N)]
bp=[ [{} for i in range(N)] for i in range(N)]
for i in range(N):
for x in I:
if x+ss[i] in R2:
pi[i][i][x]=p[x][ss[i]]
else:
pi[i][i][x]=0
l=0
while l<=N-2:
i=0
while i<=N-l-2:
j=i+l+1
for X in I:
s=i
maxi=0
args=-1
while s<=j-1:
for TAB in R1:
if X==TAB[0:2] and len(TAB)==6:
Y=TAB[2:4]
Z=TAB[4:]
maxp=p[X][(Y,Z)]*pi[i][s][Y]*pi[s+1][j][Z]
if maxp>maxi:
maxi=maxp
args=s
s+=1
pi[i][j][X]=maxi
bp[i][j][X]=args
i+=1
l+=1
print('以第一个字符a开始第二个b为结束的段落以X为根的句法树的概率:')
print("a b X P")
count=0
q=[ [0 for i in range(3)] for i in range(64)]
root=[]
i=0
while imaxi:
maxi=maxq
i+=1
return maxi
dp(0,7)
"""
把pi打印了一下:
以第一个字符a开始第二个b为结束的段落以X为根的句法树的概率:
a b X P
0 0 DT 1.0
0 1 NP 0.21
0 4 SS 0.00504
0 7 SS 5.2919999999999995e-05
1 1 NN 0.7
2 2 VT 1.0
2 4 VP 0.024
2 7 VP 0.000252
3 3 DT 1.0
3 4 NP 0.06
3 7 NP 0.0006299999999999999
4 4 NN 0.2
5 5 IN 0.5
5 7 PP 0.015
6 6 DT 1.0
6 7 NP 0.03
7 7 NN 0.1
由第五行得最大概率树的最大概率为5.29*10的负5次方
然后在根据pi和bp进行从下往上建树即可
由于时间关系先不程序实现列树了
有缘补上(一定)
【注:1.
本次编程出现一个错误 :
i=0
j=0
while i while j 发现第一个循环只执行i=0一次,原来才发现j=0应该放在while i 内(连续出现两次) 2.读取的gram3和gram4分别为图一左表和右表的产生式(为了方便录入NN等字符) 3.由于刚开始对图一P和NN的不可区分性,导致最后运行结果出现了0-4以SS为根的结果,后来思索了一天也没想出来怎么继续做(以为得出来的都是子树然后……) 刚开始用动态规划做(代码在最后注释,留下就当复习算法了),后来想想不太对,没有考虑规则R的问题………………………………直到最后手动建表发现IN没有其他非终结符能产生!然后查了另外一个图(未附)得出结论:P为IN 于是把gramm3和p表中的P更换为IN 跑出了正确的结果 (太尼玛坑了) 】