本文剖析了一个基于神经网络分类模型和arc-standard转移动作的判决式汉语依存句法分析器,其Java实现由我移植自LTP的C++代码,并添加了详细的注释,将内部数据结构由哈希表替换为高速的DoubleArrayTrie,分词和词性标注替换为HanLP原生的分词器,并深度集成到了HanLP中开源;现在还可以在线句法分析并可视化。本文将结合相应的论文,以图文形式记录代码和注释所不能覆盖的细节,以贯彻HanLP项目向生产环境普及NLP技术的初衷。
本文按照由浅到深,再由深到浅的方针编排。第一页介绍了调用接口,给用户或读者简要介绍了一下这是什么、怎么使用,给大家一个具体的印象;第二页整理了本神经网络句法分析器涉及到的众多论文,尝试深度剖析其原理;第三页从错综繁复的理论中走出来,展示了工程上的实现细节,以供借鉴改进。
本文中“代码”“实现”如不注明,都特指HanLP中移植的LTP Parser的Java代码。必须注意的是,虽然HanLP是一个商业友好的开源项目,但LTP是商用收费的。所以本Java实现严格遵守LTP的Licence,仅供科研人员和NLP爱好者研究学习用,商用必须向LTP付费,详见文末的版权声明一章。
水平有限,加上成文仓促,文中错误敬请指正。
调用方法
依存句法分析器类图
HanLP中有很多句法分析器,其中大部分都很朴素,目前的设计如下:
最左边的就是本文将要剖析的神经网络依存句法分析器,也许再加一个父类TransitionBasedDependencyParser会更好。
接口
/** * 依存句法分析器接口 * * @author hankcs */ public interface IDependencyParser { /** * 分析句子的依存句法 * * @param termList 句子,可以是任何具有词性标注功能的分词器的分词结果 * @return CoNLL格式的依存句法树 */ CoNLLSentence parse(List<Term> termList); /** * 分析句子的依存句法 * * @param sentence 句子 * @return CoNLL格式的依存句法树 */ CoNLLSentence parse(String sentence); /** * 获取Parser使用的分词器 * * @return */ Segment getSegment(); /** * 设置Parser使用的分词器 * * @param segment */ IDependencyParser setSegment(Segment segment); /** * 获取依存关系映射表 * * @return */ Map<String, String> getDeprelTranslator(); /** * 设置依存关系映射表 * * @param deprelTranslator */ IDependencyParser setDeprelTranslator(Map<String, String> deprelTranslator); /** * 依存关系自动转换开关 * @param enable */ IDependencyParser enableDeprelTranslator(boolean enable); }
一个Demo
/** * 依存句法分析(神经网络句法模型需要-Xms1g -Xmx1g -Xmn512m) * @author hankcs */ public class DemoDependencyParser { public static void main(String[] args) { CoNLLSentence sentence = HanLP.parseDependency("徐先生还具体帮助他确定了把画雄鹰、松鼠和麻雀作为主攻目标。"); System.out.println(sentence); // 可以方便地遍历它 for (CoNLLWord word : sentence) { System.out.printf("%s --(%s)--> %s\n", word.LEMMA, word.DEPREL, word.HEAD.LEMMA); } // 也可以直接拿到数组,任意顺序或逆序遍历 CoNLLWord[] wordArray = sentence.getWordArray(); for (int i = wordArray.length - 1; i >= 0; i--) { CoNLLWord word = wordArray[i]; System.out.printf("%s --(%s)--> %s\n", word.LEMMA, word.DEPREL, word.HEAD.LEMMA); } // 还可以直接遍历子树,从某棵子树的某个节点一路遍历到虚根 CoNLLWord head = wordArray[12]; while ((head = head.HEAD) != null) { if (head == CoNLLWord.ROOT) System.out.println(head.LEMMA); else System.out.printf("%s --(%s)--> ", head.LEMMA, head.DEPREL); } } }
输出
1 徐先生 徐先生 nh nr _ 4 主谓关系 _ _ 2 还 还 d d _ 4 状中结构 _ _ 3 具体 具体 a a _ 4 状中结构 _ _ 4 帮助 帮助 v v _ 0 核心关系 _ _ 5 他 他 r rr _ 4 兼语 _ _ 6 确定 确定 v v _ 4 动宾关系 _ _ 7 了 了 u ule _ 6 右附加关系 _ _ 8 把 把 p pba _ 15 状中结构 _ _ 9 画 画 v v _ 8 介宾关系 _ _ 10 雄鹰 雄鹰 n n _ 9 动宾关系 _ _ 11 、 、 wp w _ 12 标点符号 _ _ 12 松鼠 松鼠 n n _ 10 并列关系 _ _ 13 和 和 c cc _ 14 左附加关系 _ _ 14 麻雀 麻雀 n n _ 10 并列关系 _ _ 15 作为 作为 p p _ 6 动宾关系 _ _ 16 主攻 主攻 v v _ 17 定中关系 _ _ 17 目标 目标 n n _ 15 动宾关系 _ _ 18 。 。 wp w _ 4 标点符号 _ _ 徐先生 --(主谓关系)--> 帮助 还 --(状中结构)--> 帮助 具体 --(状中结构)--> 帮助 帮助 --(核心关系)--> ##核心## 他 --(兼语)--> 帮助 确定 --(动宾关系)--> 帮助 了 --(右附加关系)--> 确定 把 --(状中结构)--> 作为 画 --(介宾关系)--> 把 雄鹰 --(动宾关系)--> 画 、 --(标点符号)--> 松鼠 松鼠 --(并列关系)--> 雄鹰 和 --(左附加关系)--> 麻雀 麻雀 --(并列关系)--> 雄鹰 作为 --(动宾关系)--> 确定 主攻 --(定中关系)--> 目标 目标 --(动宾关系)--> 作为 。 --(标点符号)--> 帮助 。 --(标点符号)--> 帮助 目标 --(动宾关系)--> 作为 主攻 --(定中关系)--> 目标 作为 --(动宾关系)--> 确定 麻雀 --(并列关系)--> 雄鹰 和 --(左附加关系)--> 麻雀 松鼠 --(并列关系)--> 雄鹰 、 --(标点符号)--> 松鼠 雄鹰 --(动宾关系)--> 画 画 --(介宾关系)--> 把 把 --(状中结构)--> 作为 了 --(右附加关系)--> 确定 确定 --(动宾关系)--> 帮助 他 --(兼语)--> 帮助 帮助 --(核心关系)--> ##核心## 具体 --(状中结构)--> 帮助 还 --(状中结构)--> 帮助 徐先生 --(主谓关系)--> 帮助 麻雀 --(并列关系)--> 雄鹰 --(动宾关系)--> 画 --(介宾关系)--> 把 --(状中结构)--> 作为 --(动宾关系)--> 确定 --(动宾关系)--> 帮助 --(核心关系)--> ##核心##
可视化
可以使用DependencyViewer进行可视化:
使用英文依存标签
由于训练的时候使用的是Chinese Dependency Treebank 1.0,所以原始的标签是英文的,在Parser中,被按照下表进行了转换:
Tag | 关系 | Description | Example |
---|---|---|---|
SBV | 主谓关系 | subject-verb | 我送她一束花 (我 <– 送) |
VOB | 动宾关系 | 直接宾语,verb-object | 我送她一束花 (送 –> 花) |
IOB | 间宾关系 | 间接宾语,indirect-object | 我送她一束花 (送 –> 她) |
FOB | 前置宾语 | 前置宾语,fronting-object | 他什么书都读 (书 <– 读) |
DBL | 兼语 | double | 他请我吃饭 (请 –> 我) |
ATT | 定中关系 | attribute | 红苹果 (红 <– 苹果) |
ADV | 状中结构 | adverbial | 非常美丽 (非常 <– 美丽) |
CMP | 动补结构 | complement | 做完了作业 (做 –> 完) |
COO | 并列关系 | coordinate | 大山和大海 (大山 –> 大海) |
POB | 介宾关系 | preposition-object | 在贸易区内 (在 –> 内) |
LAD | 左附加关系 | left adjunct | 大山和大海 (和 <– 大海) |
RAD | 右附加关系 | right adjunct | 孩子们 (孩子 –> 们) |
IS | 独立结构 | independent structure | 两个单句在结构上彼此独立 |
WP | 标点符号 | punctuation | 标点符号 |
HED | 核心关系 | head | 指整个句子的核心 |
如果你更喜欢英文标签,还可以关闭中文依存自动转换:
IDependencyParser parser = new NeuralNetworkDependencyParser().enableDeprelTranslator(false); System.out.println(parser.parse("徐先生还具体帮助他确定了把画雄鹰、松鼠和麻雀作为主攻目标。"));
输出:
1 徐先生 徐先生 nh nr _ 4 SBV _ _ 2 还 还 d d _ 4 ADV _ _ 3 具体 具体 a a _ 4 ADV _ _ 4 帮助 帮助 v v _ 0 HED _ _ 5 他 他 r rr _ 4 DBL _ _ 6 确定 确定 v v _ 4 VOB _ _ 7 了 了 u ule _ 6 RAD _ _ 8 把 把 p pba _ 15 ADV _ _ 9 画 画 v v _ 8 POB _ _ 10 雄鹰 雄鹰 n n _ 9 VOB _ _ 11 、 、 wp w _ 12 WP _ _ 12 松鼠 松鼠 n n _ 10 COO _ _ 13 和 和 c cc _ 14 LAD _ _ 14 麻雀 麻雀 n n _ 10 COO _ _ 15 作为 作为 p p _ 6 VOB _ _ 16 主攻 主攻 v v _ 17 ATT _ _ 17 目标 目标 n n _ 15 VOB _ _ 18 。 。 wp w _ 4 WP _ _
可视化
与LTP对比
可以发现对这个句子,除了分词和词性标注的不同,其他都是一致的。至于箭头的方向,那是可视化工具决定的,我个人认为应该指向head更好。
请问这个神经网路模型的训练代码在哪里。我找了好久没有找到。
图片显示不了
切换到外网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的“语义依存分析”似乎比“依存句法分析”更加适合应用于语义理解,请问您有“语义依存分析”的源码或者知道实现方法吗
你好,这是学术界新提出来的方法,最近还有一场赛事。在论文都没有公开发表的时候,没人知道详情。
嗯,我会继续关注之,谢了
大神我也在关注这个,有最新消息分享一下,还有我发现你能找到好多论文的材料,请问你平时都是去哪里找的,阁下有空的话回答下我的问题。