目录
依存句法分析器综述
谈起依存句法分析,主流的统计句法分析一般分为两大流派——生成式和判决式。
生成式句法分析
生成式就是生成一系列句法树,从里面挑选出概率最大的那一棵作为输出。在具体实现的时候,可以选择最大熵等模型去计算单条依存边的概率,利用最大生成树算法来挑选最佳句法树,比如《最大熵依存句法分析器的实现》。
其优点是效果好,但开销大。训练的时候常常要用一份巨大的特征模板,得到的模型中含有大量复杂的特征函数。在解码的时候,这些特征函数的储存和运算成本很高。由于是全局最优,所以可以取得较高的准确率,还可以很方便地处理非投射的句法树。不过也由于搜索的全局性和特征函数的复杂度,模型常常会过拟合,在训练集和测试集上的准确率差别很大。
判决式句法分析
判决式一般是基于动作(或称转移)和一个分类器实现的,仿照人类从左到右的阅读顺序,判决式句法分析器不断地读入单词,根据该单词和已构建的句法子树等信息建立分类模型,分类模型输出当前状态下的最佳动作,然后判决式分析器根据最佳动作“拼装”句法树。
动作体系简介
一般有两大体系,分别处理投射和非投射两种语言现象。
投射
刘群老师在计算语言学讲义中说,大多数语言,包括汉语和英语,满足投射性。
所谓投射性是指:如果词p依存于词q,那么p和q之间的任意词r就不能依存到p和q所构成的跨度之外(用白话说,就是任意构成依存的两个单词构成一个笼子,把它们之间的所有单词囚禁在这个笼子里,只可内部交配,不可与外族通婚)。比如:
再比如:
处理投射现象的动作体系有Arc-eager和Arc-standard (Nirve, 2004),后者就是本依存句法分析器采用的动作体系,后面会详细阐述。
非投射
非投射就没有上述限制了,这会导致依存边有交叉,怎么都理不顺:
事实上,不同于刘群老师的观点,Saarbrücken说道,25% or more of the sentences in some languages are non-projective。对于汉语,王跃龙还专门写了《汉语交叉依存类非投射性现象.pdf》进行分门别类地详细研究,比如:
处理非投射的动作体系有Arc standard + swap (Nirve, ACL 2009)。
本Parser无法处理非投射现象,也就是说,即使一个句子本身是非投射的,但分析出来的依存句法树依然是投射的。
Arc-standard详解
在arc-standard system中,一次分析任务由一个栈s(题外话,有些人包括维基认为栈也叫堆栈,我强烈反对,堆是heap,栈是stack,heap和stack才混称堆栈),一个队列b,一系列依存弧A构成。如果定义一个句子为单词构成的序列
,那么——
栈
栈s是用来储存系统已经处理过的句法子树的根节点的,初始状态下。另外,定义从栈顶数起的第i个元素为si。那么栈顶元素就是s1,s1的下一个元素就是s2:
在一些中文论文中习惯使用焦点词这个表述,我觉得该表述更形象,如果我们将栈横着放,亦即让先入栈的元素在左边,后入栈的元素在右边:
则称s2为左焦点词,s1为右焦点词。接下来的动作都是围绕着这两个焦点词展开的。
队列
在《A Fast and Accurate Dependency Parser using Neural Networks.pdf》中,使用的是buffer这个单词,与其翻译为缓冲区,我认为这个概念更像一个顺序队列,而不是随机访问的缓冲区。队列模拟的是人眼的阅读顺序,人眼从左到右阅读,系统也从左往右读入单词,未读入的单词就构成一个队列,队列的出口在左边。初始状态下队列就是整个句子,且顺序不变:b=
。
依存弧
一条依存弧(原文dependency arcs,我认为翻译成变换也许更好)有两个信息:动作类型+依存关系名称l。l视依存句法语料库中使用了哪些依存关系label而定,在arc-standard系统中,一共有如下三种动作:
LEFT-ARC(l):添加一条s1 -> s2的依存边,名称为l,并且将s2从栈中删除。前提条件:。亦即建立右焦点词依存于左焦点词的依存关系,例如:
RIGHT-ARC(l):添加一条s2 -> s1的依存边,名称为l,并且将s1从栈中删除。前提条件:。亦即建立左焦点词依存于右焦点词的依存关系,例如:
SHIFT:将b1出队,压入栈。亦即不建立依存关系,只转移句法分析的焦点,即新的左焦点词是原来的右焦点词,依此类推。例如:
可见当存在种依存关系时,系统一共有
种变换。
基于神经网络的句法分析器
神经网络模型
初等神经网络背景知识请参考《反向传播神经网络极简入门》,这并不是本文的重点。
《反向传播神经网络极简入门》中介绍的是二分类模型,然而arc-standard转移系统中,一共有种变换,这时候就得将输出层由一个节点换成多个节点。
或者由一个节点换成一个softmax层(这也是Chen and Manning (2014)中的标准做法)。单一输出节点和softmax层的区别如图:
VS
假设训练集由m个样本组成:,每个样本的特征维度都是n+1,其中
对应截距项。
在二分类神经网络模型中,对隐藏层的输出做了一次逻辑斯蒂回归:
这个假设函数对应y=0的概率,用1减掉这个得到y=1的概率,二分类就是这么简单。
而在多分类神经网络模型中,做的是softmax回归:
其中,输出是一个k维的向量,k是所有类别的数量。是模型的参数,在实现的时候,一般用一个
的矩阵储存:
而则是用于概率分布的归一化。
不过,在LTP的实现中,并没有使用softmax层,而是直接用了隐藏层的输出:
这使得该神经网络模型的输出不再是概率,而是表示概率大小的一个“分值”,所以代码中的方法名为:
/** * 给每个类别打分 * @param attributes 属性 * @param retval 返回各个类别的得分 */ void score(final List<Integer> attributes, List<Double> retval)
特征提取
不同于传统句法分析器的人工编写特征模板的作坊式风格,Chen and Manning将一些零散的原始特征直接作为输入传入神经网络模型中。至于它们是如何组合的,不再由人工编写的特征模板决定,而是由神经网络模型的隐藏层自动提取。
如上图所示,Chen and Manning使用了词、词性、依存关系名称等作为了原始特征(这里还有他们的独创做法,且听下文分解)。
具体是哪些词和词性以及依存关系呢?对转移系统的当前状态(亦即上下文)来讲,有如下值得注意的词语:
/** * 上下文 * @author hankcs */ public class Context { int S0, S1, S2, N0, N1, N2; // 栈和队列的第i个元素 int S0L, S0R, S0L2, S0R2, S0LL, S0RR; // 该元素最左右的子节点、倒数第2个最左右的子节点 int S1L, S1R, S1L2, S1R2, S1LL, S1RR; }
NeuralNetworkParser#get_basic_features这个方法将它们的词和词性作为原始特征添加到了待分析的实例中。
词向量、词性向量、依存向量
词向量就是众所周知的word2vec训练出的向量,英文术语是embedding,我觉得直译成“词嵌入”有些过硬,还是通俗的词向量比较容易理解。
比较先进的句法分析器已经开始用词向量代替词本身来作为特征了,但Chen and Manning还有更绝的,他们把词性和依存关系也向量化了(To our best knowledge, this is the first attempt to introduce POS tag and arc label embeddings instead of discrete representations)。Chen and Manning说,既然词和词之间有相似的关系,那么词性、依存相互之间都应该有相似的关系。这些有相似度的向量导入模型后,对性能有所提升。
这些向量被存在一个矩阵中:
/** * 向量化(词向量、词性向量、依存关系向量) */ Matrix E;
加载完模型后确定向量的维度
embedding_size = E.rows();
以后每个特征(词、词性、依存关系)都直接去这个矩阵里取出一列作为列向量就完成了到向量的转化:
E.col(aid)
不过,在此之前,还有一个性能提升的措施。
词聚类
词聚类也是一种特征的抽象化,相似的词在句法树中的功能也应该是相似的。LTP的文档中指出利用了Guo et. al, (2015)中提出的词聚类特征,然而我并没有找到这篇论文。如果你有,恳请告诉我。
接下来我将通过代码反推词聚类大概的作用。
首先,聚类的第一个问题就是聚成几个类。在代码中,一共聚类了3次,每次的类的数目都不同,从少到多依次是:
/** * 将词映射到词聚类中的某个类 */ Map<Integer, Integer> form_to_cluster4; Map<Integer, Integer> form_to_cluster6; Map<Integer, Integer> form_to_cluster;
另外,还保留了每个聚类中每个类别的String->ID映射:
/** * 少量类目数的聚类 */ Alphabet cluster4_types_alphabet; /** * 中等类目数的聚类 */ Alphabet cluster6_types_alphabet; /** * 大量类目数的聚类 */ Alphabet cluster_types_alphabet;
接下来就会将词映射到类的ID:
/** * 获取词聚类特征 * @param data 输入数据 * @param cluster4 * @param cluster6 * @param cluster */ void get_cluster_from_dependency(final Dependency data, List<Integer> cluster4, List<Integer> cluster6, List<Integer> cluster) { if (use_cluster) { int L = data.forms.size(); for (int i = 0; i < L; ++i) { int form = data.forms.get(i); cluster4.add(i == 0 ? cluster4_types_alphabet.idOf(SpecialOption.ROOT) : form_to_cluster4.get(form)); cluster6.add(i == 0 ? cluster6_types_alphabet.idOf(SpecialOption.ROOT) : form_to_cluster6.get(form)); cluster.add(i == 0 ? cluster_types_alphabet.idOf(SpecialOption.ROOT) : form_to_cluster.get(form)); } } }
上文的特征提取中的词,其实都指的是词的聚类的ID,这就是上文提到的性能提升的措施。
距离和配价特征
Zhang and Nivre (2011)在中指出,两个焦点词的距离和配价(Valency,指的是一个词有几个子节点)都有助于分类决策。在实现中,分别对应:
/** * 获取距离特征 * @param ctx 当前特征 * @param features 输出特征 */ void get_distance_features(final Context ctx, List<Integer> features)
和
/** * 获取(S0和S1的)配价特征 * @param ctx 上下文 * @param nr_left_children 左孩子数量列表 * @param nr_right_children 右孩子数量列表 * @param features 输出特征 */ void get_valency_features(final Context ctx, final List<Integer> nr_left_children, final List<Integer> nr_right_children, List<Integer> features)
立方激活函数
如同《反向传播神经网络极简入门》中介绍的一样,常用的激活函数有tanh和Sigmoid这两种。不过在本句法分析器中,使用的是立方激活函数:
这个函数有什么好处呢?它可以将输入特征充分地组合起来,因为特征来自词、词性、依存这三个大类,假设分别为,那么
立方展开后将ijk遍历相乘,组合在一起了。据Chen and Manning试验,该立方激活函数在性能上优于传统的激活函数:
在代码中,对应:
Matrix output = W2.times(new Matrix (hidden_layer.cube())); // 立方激活函数
请问这个神经网路模型的训练代码在哪里。我找了好久没有找到。
图片显示不了
切换到外网IP就可以了 应该是我的网络的问题
您好 我看代码 好像没有神经网络的训练部分吧?请问训练是如何完成的?
麻烦问一下,最后你找到训练部分了吗?
博主,请问
# 依存句法分析
print(HanLP.parseDependency("徐先生还具体帮助他确定了把画雄鹰、松鼠和麻雀作为主攻目标。"))
Traceback (most recent call last):
File "C:/Users/admin/PycharmProjects/text1/test6_jufa1.py", line 32, in <module>
print(HanLP.parseDependency("徐先生还具体帮助他确定了把画雄鹰、松鼠和麻雀作为主攻目标。"))
jpype._jexception.ExceptionInInitializerErrorPyRaisable: java.lang.ExceptionInInitializerError
依存句法为什么会报这样的错误?
博主您好,我想试下的s-LSTM方法,请问能否给下语料呢?多谢!
请参考文末Reference
您好,想在NLP中使用神经网络文本特征的输入格式应该怎么定义?
请详细描述问题。
想用DBN 从中文文本挖掘信息,有标注好的语料,如何把这些标注好的语料作为特征放到DBN中训练
请参考对方的文档。
博主好,最近在用你的库,其中“再说”跟“再讲”的词性一个是C,一个是V,这很奇怪,像这种情况,我们该怎么把“再说”的词性改过来?
请参考https://github.com/hankcs/HanLP#修改方法
“修改了后者之后必须同步删除前者的缓存”,删除缓存是什么意思啊?有一个文件还是有这样的接口可调用?
请通读文档至少一次!
譬如这样的句子:在说什么&在讲什么,再说一遍&再讲一遍,句子结构、词性应该是一样的,为什么分析的结果会不一样?
词不一样,数据的稀疏性。
博主您好,看过您的文章受益匪浅。我使用LTP时发现,LTP的“语义依存分析”似乎比“依存句法分析”更加适合应用于语义理解,请问您有“语义依存分析”的源码或者知道实现方法吗
你好,这是学术界新提出来的方法,最近还有一场赛事。在论文都没有公开发表的时候,没人知道详情。
嗯,我会继续关注之,谢了
大神我也在关注这个,有最新消息分享一下,还有我发现你能找到好多论文的材料,请问你平时都是去哪里找的,阁下有空的话回答下我的问题。