Simple and Accurate Dependency Parsing Using Bidirectional LSTM Feature Representations
2017-01-08 14:08
381 查看
对于句子s,将其分词为n个词wi,每个词wi对应取wi的词性为ti,取向量e(wi),和词性向量e(ti),将词向量和词性向量串联得到新的向量xi=concat(e(wi),e(ti))。
BILSTM 向量vi=BILSTM(x_1:n,i)=concat(RNN_F(x_1:i),RNN_R(x_n:i))
将一小部分的BILSTM 向量vi串联,从而得到特征向量F,实际的特征向量F依赖于句法分析,我们将在讨论句法分析的时候分析特征向量。得到特征向量后,我们就可以将特征向量输入常规的句法分析网络,例如可以通过一个非线性函数,例如一个MLP网络,得到每个句法标记的得分:
除了使用基于BILSTM的特征向量外,我们利用标准的句法分析技术,联合训练BILSTM模型和句法分析模型,这使得所学习到的特征适合于句法分析任务。
考虑串联两个BILSTM 向量vi,vj,x=concat(vi,vj),将x输入MLP得到输出得分y。
Transition-based parser:
本文所使用的句法分析网络为transition-based parser,算法如下:
给定一个句子s,初始化句法分析网络的配字c(步骤2),之后,一个特征函数fi(c),将配置c表示为特征向量v_c,将特征向量输入到socoring 函数,将scores,c,trainsition组合成对,socre函数得到可能的转移t,旋转得分最高的转移t’,将转移t’应于语配置网络,再有配置网络输出得到句法分析树(步骤6).
转移系统t与配置系统不同,转移系统可以决定句法分析,特征函数和得分函数。
本分采用的转移系统为Arc-Hybrid Transition System:
在Arc-Hybrid Transition System系统中,配置c=(sigma,belta,T),有stack sigma,buffer belta, 集合T组成dependency arcs。Stack 和buffer保存有句子元素的索引,给定句子s,句子分词为wi,词性标注为ti,系统初始化为空的stack,空的arc set,且belta=1,...,n,ROOT,这里ROOT为root的索引。任何配置c包含有一个空的stack和一个buffer,这个buffer只包含有ROOT,则这个配置c为terminal,并且此时可以得到parse tree为arc set Tc。Arc-Hybrid Transition System允许3个可能的转移,分别为shift,left_l,right_l,定义如下:
Shift移动buffer(b0)的第一个元素到stack。Left_l移除stack的最顶端的元素,并用bo 和标记l代替连接它,right_l从stack移除s0,并将so连接stack(s1)的下一个元素,添加arc(s1,s0,l)。
Scoring function:
Simple Feature Function:
我们的特征函数为串联的stack的顶端的3个BILSTM向量和buffer的第一个元素。例如对于配置c=(...|s2|s1|s0, b0|...,T),提取配置c的特征向量:
该特征函数考虑了s0,s1,s2,b0的BLSTM表示,其可以受转移系统的影响得分。
Details of the Training Algorithm:
句法分析配置c的损失函数定义为:
A为可能的转移集合,G为正确的转移集合.函数的目的是使得正确分类的得分与错误分类的得分的差距最大,网络结构可见下如:
代码解析:
网络结构为,embedding+bilstm+MLP
Emdeding层代码如下:
mbedding层就是对于输入已经分词、词性标注的句子s,对于s的每个词wi,对于词性pi,查找wi,pi对应的向量表示,并将其串联为输入向量input_bilstm,将vi输入bilstm得到输出向量ivec。
训练阶段:
我们分配两个数组,stack,buf,首先初始化stack为空,并将句子s中所有的词放入buf,
将取b0=buf[0],s0=stack[-1],s1=stack[-2],s2=stack[-3],将其对应的bilstm向量串联得到MLP的输入向量input_mlp,将输入向量输入mlp1,mlp2网络得到routput,output,mlp1网络输出维度为2*size(relation)+1,即用于预测relation,mlp2输出维度为3,用于只是stack,buf中元素的输入或移除(具体原理不明白??),此部分代码为:
routput为预测relation的交叉损失熵,routput.value为预测relation的概率。
得到预测值之后 ,我们便可以求解总的损失函数:
BILSTM 向量vi=BILSTM(x_1:n,i)=concat(RNN_F(x_1:i),RNN_R(x_n:i))
将一小部分的BILSTM 向量vi串联,从而得到特征向量F,实际的特征向量F依赖于句法分析,我们将在讨论句法分析的时候分析特征向量。得到特征向量后,我们就可以将特征向量输入常规的句法分析网络,例如可以通过一个非线性函数,例如一个MLP网络,得到每个句法标记的得分:
除了使用基于BILSTM的特征向量外,我们利用标准的句法分析技术,联合训练BILSTM模型和句法分析模型,这使得所学习到的特征适合于句法分析任务。
考虑串联两个BILSTM 向量vi,vj,x=concat(vi,vj),将x输入MLP得到输出得分y。
Transition-based parser:
本文所使用的句法分析网络为transition-based parser,算法如下:
给定一个句子s,初始化句法分析网络的配字c(步骤2),之后,一个特征函数fi(c),将配置c表示为特征向量v_c,将特征向量输入到socoring 函数,将scores,c,trainsition组合成对,socre函数得到可能的转移t,旋转得分最高的转移t’,将转移t’应于语配置网络,再有配置网络输出得到句法分析树(步骤6).
转移系统t与配置系统不同,转移系统可以决定句法分析,特征函数和得分函数。
本分采用的转移系统为Arc-Hybrid Transition System:
在Arc-Hybrid Transition System系统中,配置c=(sigma,belta,T),有stack sigma,buffer belta, 集合T组成dependency arcs。Stack 和buffer保存有句子元素的索引,给定句子s,句子分词为wi,词性标注为ti,系统初始化为空的stack,空的arc set,且belta=1,...,n,ROOT,这里ROOT为root的索引。任何配置c包含有一个空的stack和一个buffer,这个buffer只包含有ROOT,则这个配置c为terminal,并且此时可以得到parse tree为arc set Tc。Arc-Hybrid Transition System允许3个可能的转移,分别为shift,left_l,right_l,定义如下:
Shift移动buffer(b0)的第一个元素到stack。Left_l移除stack的最顶端的元素,并用bo 和标记l代替连接它,right_l从stack移除s0,并将so连接stack(s1)的下一个元素,添加arc(s1,s0,l)。
Scoring function:
Simple Feature Function:
我们的特征函数为串联的stack的顶端的3个BILSTM向量和buffer的第一个元素。例如对于配置c=(...|s2|s1|s0, b0|...,T),提取配置c的特征向量:
该特征函数考虑了s0,s1,s2,b0的BLSTM表示,其可以受转移系统的影响得分。
Details of the Training Algorithm:
句法分析配置c的损失函数定义为:
A为可能的转移集合,G为正确的转移集合.函数的目的是使得正确分类的得分与错误分类的得分的差距最大,网络结构可见下如:
代码解析:
网络结构为,embedding+bilstm+MLP
Emdeding层代码如下:
ef getWordEmbeddings(self, sentence, train): for root in sentence: c = float(self.wordsCount.get(root.norm, 0))#get the word index,root.norm is the word dropFlag = not train or (random.random() < (c/(0.25+c))) root.wordvec = lookup(self.model["word-lookup"], int(self.vocab.get(root.norm, 0)) if dropFlag else 0)#得到字的向量表示 root.posvec = lookup(self.model["pos-lookup"], int(self.pos[root.pos])) if self.pdims > 0 else None#得到词性的向量表示 if self.external_embedding is not None:#是否添加附加向量 if not dropFlag and random.random() < 0.5: root.evec = lookup(self.model["extrn-lookup"], 0) elif root.form in self.external_embedding: root.evec = lookup(self.model["extrn-lookup"], self.extrnd[root.form], update = True) elif root.norm in self.external_embedding: root.evec = lookup(self.model["extrn-lookup"], self.extrnd[root.norm], update = True) else: root.evec = lookup(self.model["extrn-lookup"], 0) else: root.evec = None root.ivec = concatenate(filter(None, [root.wordvec, root.posvec, root.evec]))#串串联字向量、词性向量作为输入向量 if self.blstmFlag: forward = self.surfaceBuilders[0].initial_state()#前向lstm backward = self.surfaceBuilders[1].initial_state()#逆向lstm for froot, rroot in zip(sentence, reversed(sentence)): forward = forward.add_input( froot.ivec ) backward = backward.add_input( rroot.ivec ) froot.fvec = forward.output()#前向lstm输出 rroot.bvec = backward.output()#逆向lstm输出 for root in sentence: root.vec = concatenate( [root.fvec, root.bvec] )#bilstm输出 if self.bibiFlag: bforward = self.bsurfaceBuilders[0].initial_state() bbackward = self.bsurfaceBuilders[1].initial_state() for froot, rroot in zip(sentence, reversed(sentence)): bforward = bforward.add_input( froot.vec ) bbackward = bbackward.add_input( rroot.vec ) froot.bfvec = bforward.output() rroot.bbvec = bbackward.output() for root in sentence: root.vec = concatenate( [root.bfvec, root.bbvec] ) else: for root in sentence: root.ivec = (self.word2lstm * root.ivec) + self.word2lstmbias root.vec = tanh( root.ivec )
mbedding层就是对于输入已经分词、词性标注的句子s,对于s的每个词wi,对于词性pi,查找wi,pi对应的向量表示,并将其串联为输入向量input_bilstm,将vi输入bilstm得到输出向量ivec。
训练阶段:
我们分配两个数组,stack,buf,首先初始化stack为空,并将句子s中所有的词放入buf,
stack = ParseForest([]) buf = ParseForest(sentence)
将取b0=buf[0],s0=stack[-1],s1=stack[-2],s2=stack[-3],将其对应的bilstm向量串联得到MLP的输入向量input_mlp,将输入向量输入mlp1,mlp2网络得到routput,output,mlp1网络输出维度为2*size(relation)+1,即用于预测relation,mlp2输出维度为3,用于只是stack,buf中元素的输入或移除(具体原理不明白??),此部分代码为:
def __evaluate(self, stack, buf, train): topStack = [ stack.roots[-i-1].lstms if len(stack) > i else [self.empty] for i in xrange(self.k) ]#去s0,s1,s2对应的bilstm输出特征向量 topBuffer = [ buf.roots[i].lstms if len(buf) > i else [self.empty] for i in xrange(1) ]#取bo对应的bilstm输出特征向量 input = concatenate(list(chain(*(topStack + topBuffer))))#特征向量串联 #将特征向量输入MLP if self.hidden2_units > 0: routput = (self.routLayer * self.activation(self.rhid2Bias + self.rhid2Layer * self.activation(self.rhidLayer * input + self.rhidBias)) + self.routBias) else: routput = (self.routLayer * self.activation(self.rhidLayer * input + self.rhidBias) + self.routBias) if self.hidden2_units > 0: output = (self.outLayer * self.activation(self.hid2Bias + self.hid2Layer * self.activation(self.hidLayer * input + self.hidBias)) + self.outBias) else: output = (self.outLayer * self.activation(self.hidLayer * input + self.hidBias) + self.outBias) scrs, uscrs = routput.value(), output.value()#.value为relation(141),r(3)的概率,routput,output为对应的交叉损失熵 uscrs0 = uscrs[0] uscrs1 = uscrs[1] uscrs2 = uscrs[2] if train:#训练阶段 output0 = output[0] output1 = output[1] output2 = output[2] #ret[0]=[rel(依存关系),0,奇数下标依存关系对应的概率+uscrs[0],依存个关系对于的估计网络输出+output[1]] ret = [ [ (rel, 0, scrs[1 + j * 2] + uscrs1, routput[1 + j * 2 ] + output1) for j, rel in enumerate(self.irels) ] if len(stack) > 0 and len(buf) > 0 else [], [ (rel, 1, scrs[2 + j * 2] + uscrs2, routput[2 + j * 2 ] + output2) for j, rel in enumerate(self.irels) ] if len(stack) > 1 else [], [ (None, 2, scrs[0] + uscrs0, routput[0] + output0) ] if len(buf) > 0 else [] ] else:#预测阶段 s1,r1 = max(zip(scrs[1::2],self.irels))#取奇数下标索引,求其最大值s1对应的索引值为当前依存关系r1 s2,r2 = max(zip(scrs[2::2],self.irels))#去偶数下标索引,求其最大值s2对应的索引值为当前依存关系r2 s1 += uscrs1 s2 += uscrs2 ret = [ [ (r1, 0, s1) ] if len(stack) > 0 and len(buf) > 0 else [], [ (r2, 1, s2) ] if len(stack) > 1 else [], [ (None, 2, scrs[0] + uscrs0) ] if len(buf) > 0 else [] ] return ret
routput为预测relation的交叉损失熵,routput.value为预测relation的概率。
得到预测值之后 ,我们便可以求解总的损失函数:
for iSentence, sentence in enumerate(shuffledData): if iSentence % 100 == 0 and iSentence != 0: print 'Processing sentence number:', iSentence, 'Loss:', eloss / etotal, 'Errors:', (float(eerrors)) / etotal, 'Labeled Errors:', (float(lerrors) / etotal) , 'Time', time.time()-start start = time.time() eerrors = 0 eloss = 0.0 etotal = 0 lerrors = 0 ltotal = 0 sentence = sentence[1:] + [sentence[0]]#将句子中最后一个词放到数组最后 self.getWordEmbeddings(sentence, True)#得到每个词的输入向量表示 stack = ParseForest([])#初始化stack为空 buf = ParseForest(sentence)#将sentence放入buf for root in sentence: root.lstms = [root.vec for _ in xrange(self.nnvecs)]#词的lstm输入为self.nnvecs个输入向量串联 hoffset = 1 if self.headFlag else 0 while len(buf) > 0 or len(stack) > 1 : scores = self.__evaluate(stack, buf, True)#MLP对数如特征估计得分 scores.append([(None, 3, ninf, None)]) alpha = stack.roots[:-2] if len(stack) > 2 else [] s1 = [stack.roots[-2]] if len(stack) > 1 else [] s0 = [stack.roots[-1]] if len(stack) > 0 else [] b = [buf.roots[0]] if len(buf) > 0 else [] beta = buf.roots[1:] if len(buf) > 1 else [] left_cost = ( len([h for h in s1 + beta if h.id == s0[0].parent_id]) + len([d for d in b + beta if d.parent_id == s0[0].id]) ) if len(scores[0]) > 0 else 1 right_cost = ( len([h for h in b + beta if h.id == s0[0].parent_id]) + len([d for d in b + beta if d.parent_id == s0[0].id]) ) if len(scores[1]) > 0 else 1 shift_cost = ( len([h for h in s1 + alpha if h.id == b[0].parent_id]) + len([d for d in s0 + s1 + alpha if d.parent_id == b[0].id]) ) if len(scores[2]) > 0 else 1 costs = (left_cost, right_cost, shift_cost, 1) bestValid = max(( s for s in chain(*scores) if costs[s[1]] == 0 and ( s[1] == 2 or s[0] == stack.roots[-1].relation ) ), key=itemgetter(2)) bestWrong = max(( s for s in chain(*scores) if costs[s[1]] != 0 or ( s[1] != 2 and s[0] != stack.roots[-1].relation ) ), key=itemgetter(2)) best = bestValid if ( (not self.oracle) or (bestValid[2] - bestWrong[2] > 1.0) or (bestValid[2] > bestWrong[2] and random.random() > 0.1) ) else bestWrong if best[1] == 2:#未得到relation,将b0移入stack stack.roots.append(buf.roots[0]) del buf.roots[0] elif best[1] == 0: child = stack.roots.pop() parent = buf.roots[0]#父结点为b0 child.pred_parent_id = parent.id child.pred_relation = best[0] bestOp = 0 if self.rlMostFlag: parent.lstms[bestOp + hoffset] = child.lstms[bestOp + hoffset] if self.rlFlag: parent.lstms[bestOp + hoffset] = child.vec elif best[1] == 1: child = stack.roots.pop() parent = stack.roots[1]#父结点为s0 child.pred_parent_id = parent.id child.pred_relation = best[0] bestOp = 1 if self.rlMostFlag: parent.lstms[bestOp + hoffset] = child.lstms[bestOp + hoffset] if self.rlFlag: parent.lstms[bestOp + hoffset] = child.vec if bestValid[2] < bestWrong[2] + 1.0: loss = bestWrong[3] - bestValid[3]#损失函数 mloss += 1.0 + bestWrong[2] - bestValid[2] eloss += 1.0 + bestWrong[2] - bestValid[2] errs.append(loss) if best[1] != 2 and (child.pred_parent_id != child.parent_id or child.pred_relation != child.relation): lerrors += 1#id或者relation估计不准确,则错误加1 if child.pred_parent_id != child.parent_id:#如果估计id不准确,则错误加1 errors += 1 eerrors += 1 etotal += 1 if len(errs) > 50: # or True: #eerrs = ((esum(errs)) * (1.0/(float(len(errs))))) eerrs = esum(errs) scalar_loss = eerrs.scalar_value() eerrs.backward() self.trainer.update() errs = [] lerrs = [] renew_cg() self.Init() if len(errs) > 0: eerrs = (esum(errs)) # * (1.0/(float(len(errs)))) eerrs.scalar_value() eerrs.backward()#根据损失函数求梯度 self.trainer.update()#参数更新 errs = [] lerrs = [] renew_cg() self.trainer.update_epoch() print "Loss: ", mloss/iSentence
相关文章推荐
- jsTree ajax 获取json数据加载树
- JAVA WEB从入门到精通day05 javascript学习(3)
- 把JavaScript插入到HTML中的注意事项-学习笔记
- nodejs的require语句,区别于requirejs
- 读书笔记:《HTML5开发手册》--figure、time、details、mark
- jsp <%! %> 与 <% %> 区别
- Reactor模式详解
- Codeforces Round #390 (Div. 2)-DFedor and coupons(优先队列)
- Jquery相关总结
- rxjs5.X系列 —— Combination/Multicasting系列 api 笔记
- 怎么让Eclipse对html和js代码自动提示
- js中load与onload的区别
- TaoBeier 的 Vim 配置,支持 Python、Javascript、Golang 等
- HTML中空格的应用
- 前端性能优化
- JavaScript "===" 与 "==" 的区别
- 自定义文件上传的按钮的样式css+js
- JSP三大指令
- 从输入URL到页面加载,发生了什么
- JavaScript与python的yield