资讯动态

深度学习时代的依存句法分析:核心概念与工程实践

发布时间:2026/10/17 2:51:42 来源:尧图企业网站定制
这几周一直在啃依存句法分析顺手把CS224N 2019年 Lecture 5 的内容重新过了一遍。这门课在NLP圈子里几乎是入门必刷而第五讲正好是“Linguistic Structure: Dependency Parsing”属于那种从传统方法过渡到神经网络方法的关键节点。我第一次看的时候觉得只是个句法工具等到真去做关系抽取、语义解析、甚至是生成任务里对齐结构信息时才意识到这一讲的内容其实卡在语言学知识和深度学习模型的中间地带理解不透后面很多结构化的任务都会踩坑。本期笔记我会沿着课程本身的脉络把“为什么要用依存句法”“依存树到底怎么定义”“传统解析器怎么做”“神经网络怎么改进”这四个核心问题重新梳理一遍同时补上一些我在复现论文、调模型和整理数据集时实际踩过的坑。无论你是刚入门NLP的学生还是在做信息抽取、机器翻译或者问答系统的工程师这篇笔记都会比单纯看PPT多一层实操视角。1. 语言学结构从“组块”到“依存”的路线切换1.1 两棵树的较量短语结构树与依存树NLP里的句法分析主要有两条路线短语结构语法Phrase Structure Grammar和依存语法Dependency Grammar。课程一开始对比了这两棵“树”的区别这也是理解后续所有内容的基石。短语结构树的核心思路是把句子拆成语块语块再逐层嵌套。以“The cat sat on the mat”为例它先分成名词短语NP和动词短语VPVP再往下拆出动词和介词短语PP。这种表示方式强调的是“组成性”——句子由哪些成分组成每个成分内部又是什么结构。早年很多基于上下文无关语法CFG的解析器都是这个思路形式化漂亮、递归性强但弊端也很明显语块的边界往往和实际语义角色对不齐而且不同语言里语块的排列规则差异很大跨语言迁移比较痛苦。依存树走的是另一条路。它不关心“哪个词组在一起”而是直接刻画词与词之间的“修饰/支配关系”。同样是上面那句话依存树会告诉你cat是主语依赖核心动词saton引导的介词短语修饰satmat是on的宾语。整棵树的节点就是词本身边带着关系标签没有“中间层”的语块节点。直觉上更接近人对句子“谁修饰谁”的理解信息抽取任务里做实体关系抽取时这种表示也天然更匹配主语和谓语的关系、谓语和宾语的关系直接就挂在一条依存弧上。课程里专门用了很多例子来说明两棵树的差异我个人学习时的体会是短语结构树是“结构优先”的表示适合做句法生成和形式语义依存树是“关系优先”的表示适合做语义理解和下游任务的特征提取。2019年前后NLP的实用系统基本已经全面倒向依存句法因为深度学习模型吃的是局部上下文而依存关系恰好能表示词与词之间的“长距离依赖”——这是短语结构树不太容易直接给到模型的东西。1.2 为什么依存句法在深度学习时代成了主流很多人会问既然短语结构语法历史悠久、形式化也很完备为什么深度学习起来之后反而是依存句法更吃香课程里其实也隐含了答案我从工程角度再补充几条自己的观察。首先依存树的标注成本更低、跨语言更稳定。短语结构树的标注依赖大量语言特定的短语结构规则标注员需要接受大量训练而且每个语言的规则还不一样。依存关系则更接近“谓词-论元”这种语义角色不同语言之间的差异相对小尤其现在普遍用的Universal DependenciesUD框架把几十种语言的依存关系标签统一成一套体系直接催生了一批跨语言解析器。这一点对工业界的吸引力是巨大的——做多语言产品的时候一套模型架构吃所有语言光维护成本就省一大截。其次依存关系对下游任务友好。做关系抽取时两个实体的最短依存路径shortest dependency path直接比整棵句法树更聚焦特征更“薄”更准做机器翻译时调换语序的约束也更多依赖于词间的改写关系而不是整个句子的短语结构。神经网络的self-attention其实某种程度上也在隐式学习“词和词是否应该建立联系”但它没有明确的语法约束所以很多研究者会把依存树作为监督信号引入模型让attention更“有谱”。最后是实用性。当时主流的评测基准比如PTB上的依存解析评测已经沉淀得很成熟基于转移transition-based和基于图graph-based的方法都有了非常稳定的实现再加上后来出现的高性能神经解析器工程上可以直接拿来用。换句话说它不只是“理论好看”而是“真的能跑、能上生产”。当时我们做某个关系抽取系统时接入现成的依存解析器比重新搭一套短语结构标注流程要快好几周这种差距在项目节奏紧张时非常致命。2. 依存句法核心概念与数据认知2.1 依存弧、依存关系与投影性规定动作不能含糊这一讲前半部分基本都在定义依存句法的基本概念虽然听起来基础但里面有几个细节特别容易搞混我逐个说一下。核心定义包含三个方面依存树dependency tree、依存弧dependency arc和关系标签dependency relation。一棵合法的依存树需要满足几个条件必须有且只有一个根节点root除根节点外每个词有且仅有一个父节点head从根出发能到达所有节点连通性。这三个条件保证了依存结构构成一棵真正的树而不是森林或图。但真正容易出问题的是“投影性”projectivity这个概念。投影性的通俗理解是如果把一棵依存树的节点按句子顺序排列所有依存弧都不能交叉。也就是你从弧的两端往下画两条竖线弧中间区域包含的所有节点都得是这条弧的“子孙”或者弧本身的两个端点。一旦出现交叉弧就是非投影结构。非投影结构在真实语料里并不少见德语、荷兰语、捷克语这些屈折语里尤其多因为语序相对自由词和它依赖的head常常隔着别的成分。比如德语里“Er sagt, dass er das Buch gelesen hat”这种从句结构直接翻译成中文是“他说他读过那本书”这里的“gelesen”读过和“hat”有/助动词构成复合谓语但两者被其他成分隔开如果按严格的人际顺序连线就很容易交叉。投影性之所以重要是因为基于转移的解析器天然只处理投影结构遇到非投影结构就无解而基于图的解析器虽然可以处理但早期求最大生成树的算法也隐含一些约束。也就是说“这棵树合法吗”和“这棵树可投影吗”是两个不同的问题看论文时一定要分清。真正动手时我建议初学者先把Universal Dependencies的标签体系过一遍名词主语nsubj、宾语obj、修饰语amod、advmod、介词关系case、并列关系conj这些高频标签要烂熟于心。它们不是随便选的标签几乎每个都对应一个语义角色或者语法功能对下游特征提取有直接影响。我当时犯过一个低级错误——把所有带“subj”后缀的标签都当成同一类来处理结果在做细粒度关系抽取时精度掉了好几个点教训相当深刻。2.2 训练数据与评测基准没有高质量数据一切模型都是空中楼阁讲到依存句法分析就绕不开数据。课程大量引用了Universal Dependencies项目这个项目做了件非常有价值的事把全世界几十种语言的树库统一成一套标注规范和标签体系。我在学习时专门去翻了UD官网发现它有在线浏览器可以直接查某个句子每种语言的依存标注这种“同一句话在不同语言里怎么看”的体验比看任何论文示例都直观。除了UD英语领域最经典的基准是Penn TreebankPTB转换出来的依存树库Stanford Dependencies转换格式。PTB本身是短语结构标注用一套规则转成依存树所以会有一些标注体系转换的噪音。做研究和工程时要注意同一句英文用Stanford Dependencies、UD、或者某些老版的LDC标注依存树的形态可能差不少这种细节会影响你对解析器输出质量的判断。评测指标方面最主要的两个是UASUnlabeled Attachment Score和LASLabeled Attachment Score。UAS只判断每个词找到的head是否正确LAS则要求head和关系标签都正确。以我的经验LAS通常比UAS低3到5个点这很正常因为关系标签分类本身就是一个不小的挑战。很多论文报告结果时会同时给这两个数你用别人代码的时候别只盯着UAS哪怕只是微调了标签分类器LAS的变化也能反映问题。当时我在复现某模型时UAS差一点就到标准线但LAS总差一个多点最后发现是某个小类标签如obl、advcl数据太少、分类头欠拟合属于典型的标签不平衡问题。3. 依存句法分析的实现路线从传统算法到神经网络3.1 传统解析器的两个流派转移系统与最大生成树2019年课程讲到依存解析器设计时重点放在了两个传统框架上现在虽然很多论文直接用神经解析器做端到端但理解这两个框架仍然是理解现代模型的基础。第一个是**基于转移transition-based**的解析器。核心思路是边扫描句子边构建依存树通过一系列“转移动作”逐步把输入词流转成一棵树。我在学习时认为转移系统最友好的入门是Arc-Standard算法。系统维护两个栈和一个队列初始时队列装着所有词栈只有一个根每一步系统可以执行三种动作之一——“移进Shift”把队列第一个词压入栈、“左归约Left-Arc”让栈顶第二个词成为栈顶词的head并弹出第二个词、“右归约Right-Arc”让栈顶词成为栈顶第二个词的head并弹出栈顶词。整个过程直到队列清空、栈只剩根才结束。这个系统代码逻辑很直观但搜索空间很大传统解法依赖分类器比如支持向量机、最大熵模型对“当前配置下最优动作”做预测特征工程复杂。它天然只能构建投影树因为栈式结构的左右约束非常严格遇到交叉弧会直接失败。好处是速度极快、解码复杂度接近线性非常适合工业界大规模推理。第二个是**基于图graph-based**的解析器。核心思路是把依存解析建模成一个有向图上的优化问题句子每个词之间都假设可以连边然后用打分函数给每条可能的依存弧打分最后求一个最大生成树maximum spanning tree并且用算法保证树结构合法。经典的算法是Eisner算法利用CYK风格的动态规划在O(n³)时间里找到最优树。基于图的方法天生可以处理非投影结构而且全局最优性更好在较长句子上准确率通常更稳。传统版本的最大问题在于特征工程——每条弧的特征都是手工拼出来的词形、词性、距离、标点信息等特征稀疏且表达能有限。我在学习时画出过两类方法的对比表思路大致如下对比维度基于转移基于图解码复杂度近似线性通常O(n³)投影性约束只能处理投影树可以处理非投影树特征强度依赖局部上下文特征依赖弧级特征组合全局最优性贪心或束搜索动态规划保证全局最优典型代表Arc-Standard、Arc-EagerEisner算法、最大生成树3.2 神经网络如何重塑解析器从BiLSTM到注意力机制2019年这版课程最大的看点还是把神经网络方法与传统方法做了深度融合。我当时看完最大的感受是与其说神经网络淘汰了传统框架不如说它把传统框架里的“特征工程”环节全部端到端替换了解码算法本身还保留着。以Kiperwasser and Goldberg在2016年前后的工作为代表基于转移的神经解析器把“当前配置的状态”用一个BiLSTM编码器统一表示。具体来说他们给每个词的双向LSTM输出作为词的动态表示而不是只靠词向量拼接。栈和队列的状态用一个表示整个配置的向量来概括比如把栈顶若干节点对应的BiLSTM输出拼接起来再加上一些标点特征之类的手工特征这部分也还是保留了一点点。这个向量送到MLP里预测下一步动作。效果对比传统特征版提升非常明显核心原因是BiLSTM把词的上下文信息都融进了表示分类器能见到的信息边界大大扩展不再依赖手工拼出的稀疏特征。另一个更关键的创新是Dozat and Manning在ICLR 2017提出的Deep Biaffine Attention解析器。这个模型是基于图方法的神经版。它同样用BiLSTM获得每个词的表示然后把每个词当作“候选head”和“候选dependent”分别投影成两套向量再用一个双线性biaffine打分函数计算弧存在的分数和关系标签的分数。这里“biaffine”比简单的点积或者线性变换多了一个可学习的双线性矩阵表达能力更强。训练时采用两个任务联合学习一个任务判断弧是否合法head选择另一个预测该弧的关系标签。推理时还是用Eisner算法保证输出是合法的树。这个模型在当时的PTB和UD基准上屠榜速度也快利用GPU批量计算所有弧分数。课程里特别强调了一个细节Biaffine模型通过一个额外的“根节点打分”机制来决定哪个词做根而传统的Eisner算法只会选出最大权重的边集不一定会恰好选出一个根。这个细节很多人复现时会忽略导致预测的树根节点不合法。更近一些的Tree-LSTM和基于GCN的方法比如在SyntaxGCN等模型里开始尝试在图结构上做编码既然依存树给出了词的邻接关系那就用图神经网络编码每个词的局部子结构再把编码结果喂给下游。课程没有展开太多但ETS方向的不少任务语义角色标注、关系抽取从那之后都开始搭“依存树图网络”的架构很多代码库至今还在维护值得留意。4. 实操训练一个依存解析器的关键环节4.1 数据预处理与词嵌入别在入口处翻车我以PyTorch环境为例把实操中容易踩坑的几个点一圈一圈拆开讲。第一步是数据预处理。现在主流做法是直接用CoNLL-U格式UD数据的标准格式每行一个词用制表符分隔十个字段ID、词形、词干、词性标签、依存关系标签等。训练解析器之前要做几个关键预处理动作把句子切分成词如果有现成的分词器直接用UD官方推荐的英文用PTB分词风格中文需要额外注意分词和依存标签的对齐建立词表、词性标签集合、依存关系标签集合注意保留所有训练集的标签类型图方法中要额外构造“每个词所有可能的候选head”矩阵一般就是除了自己以外的所有词加上根节点。词嵌入这块课程里的默认做法是预训练词向量GloVe或者fastText 字符级CNN/BiLSTM提取词形特征。词性标签也作为额外特征直接拼接。比较新的做法是把位置编码和依存距离也编码进表示。我在复现时发现词向量是否参与联合训练对UAS的影响很大——固定词向量会在OOV未登录词和低频词上明显拉胯而联合训练又有过拟合风险。一个折中方案是预训练向量初始化微调同时加一个高概率dropout0.5左右效果普遍稳。4.2 模型搭建与训练技巧从Biaffine到GCN的踩坑笔记实战中绝大多数人复现的第一个模型就是Deep Biaffine Attention Parser。它的结构很清晰输入端是词表示词向量词性向量字符向量拼接经过一个双向LSTM编码器每个词得到两个方向的hidden state拼接。然后分两条路一条计算弧分数一条计算标签分数。弧分数部分用两个独立的前馈网络MLP分别从词表示中提取“作为head的表示”和“作为dependent的表示”得到h_head和h_dep然后打分函数是score h_head^T * U * h_dep W_head * h_head W_dep * h_dep b这里U是双线性权重矩阵W_head和W_dep是线性偏置。这个公式在Dozat and Manning的论文里有明确描述实操时要注意三点一是双线性矩阵的维度要适中一般取500维左右即可二是评分时不要忘了加上一维“根节点”的表示否则根不合法三是打分后做MST解码时注意处理的图是稠密有向图Eisner算法实现要正确区分内、外、完成、进行四类状态转移。训练方面有几个常用的trick使用Adam优化器学习率初始1e-3配合指数学习率衰减dropout加在输入表示和LSTM输出上概率0.33左右如果训练数据量不大比如只有几千句可以用早停模型平均来避免过拟合标签分类头因为类别数多且分布不均单用交叉熵有时会让某些稀有标签永远预测不出来可以尝试对高频标签做一点点过采样或者用带类别权重的损失函数。至于基于转移的神经解析器复现相对简单一些但attention-toy这类库很少把Arc-Standard的配置表示写成纯向量所以很多人的第一步是卡在读代码上。我的建议是先画配置状态流程图搞清楚栈和队在每一步怎么变化再对照代码走一遍。可能的话跑一个小语料几百句调试输出每一步动作序列自己手动推一遍看是否符合预期这个功夫省不了。4.3 评估与调试UAS/LAS之外的那些隐藏指标评测阶段的标准流程是预测完所有句子的依存关系后用官方CoNLL-U评估脚本计算UAS/LAS。但只看这两个数远远不够。我的一些实际经验如下首先要按句长拆开看效果。长句子超过40词的UAS会明显下降原因是BiLSTM对长程依赖的编码损耗和MST解码错误的累积。如果你要处理的是长文档抽取任务建议专门调一个长句测试集来评估别被平均分迷惑。其次要查看根节点预测的准确率。根节点错了整棵树基本废掉LAS会掉很多。很多解析器在“谁做根”上做得不够好尤其是口语、省略主语或复杂从句场景。一个粗暴的兜底方案是如果模型预测的根节点明显不符合预期比如根节点属于标点符号或虚词强制改成动词或名词这个规则在大多数情况下能拉回零点几个点。第三官方评测脚本用的是“完全匹配”判定即整棵树的UAS/LAS但工程里我们往往更关心关键依存关系nsubj、obj、amod的抽取准确性。我自己会额外跑一个分标签的P/R/F1统计这类信息对下游任务调优帮助很大。比如实际跑关系抽取时“nsubj”和“obj”判错两个点就可能造成候选三元组错一大片远比整体UAS下降2个点影响大。5. 常见问题与排查技巧实录5.1 非投影结构字典集里没有“交叉”这个词我最早用基于转移的解析器时遇到德语和捷克语语料上离谱的UAS下降排查很久才意识到是非投影结构搞的鬼。Arc-Standard不支持交叉弧一旦真语料里出现非投影树模型的预测就只能在投影树里“硬凑”一个近似解。解决办法有几条路线换基于图的解析器或者用伪投影化pseudo-projective技术把非投影树先“投影化”再训练解码后再把非投影边恢复更现代的做法是直接用允许交叉边的图解析器变体但Eisner算法的约束比较强很多工作选择在解码时直接允许交叉边配合可学习的弧打分来控制质量。课程里提得不多但工程上这是个常见的“隐藏坑”至少要知道数据里投影树比例大概多少再决定选哪条线。5.2 根节点与孤岛问题MST解码的“常识”之地我在一次复现中遇到过一种很有意思的现象模型把一整句话的依存树预测成了两个不相连的“小岛”加起来没有根。后来查到原因是MST解码允许每个子树的根是一堆平均分布的小根不是唯一的。解决办法有两种一是强制根只有一个在解码时加一个约束二是训练时根节点的打分信号要加强。有一个常见的教学实现会直接用给根节点连续加多条高权边的方式迫使MST选中同一个根。实际中也可以用post-processing解码完成后如果检测到多个根或没有根选择概率最高的动词/名词做根然后合并森林。老实说这类规则在标准基准上能稳定提升0.5~1个UAS点不算多但在生产系统里能避免很多“下游崩溃”问题——比如关系抽取的空指代、事件抽取的触发词缺失等等。5.3 资源与工具初学者怎么搭出可运行的实验环境最后聊一下资源。课程里用了不少现成的解析器其中Stanza斯坦福的Python工具包和spaCy的英文/中文模型现在依然好用。初学者如果只是想先看效果直接装stanza然后下载UD模型就可以了底层基本把Biaffine解析器封装好了。想要自己手写模型建议先找一个开源实现比如PyTorch版的Biaffine Parser在上面改数据预处理和特征部分别从零开始写MST太浪费时间了。另外平时看论文时可以留意Treebank树库的选取英文PTB转化版、UD_English-EWT、中文UD_GSDSimp是几个常见benchmark实验报告里对比的是哪个要搞清楚。模型切分、词表范围、预训练词向量是否包含验证集词汇这些细节对数字影响很大跨论文复现时容易对不上先确认这些前提再判断好坏。还有个建议——多去看错误案例。当时我跑出一棵树把某个介词短语挂在了名词下面而不是动词下面导致关系抽取完全错了。这种错误如果不看原始依存树是发现不了的。用可视化工具比如Displacy或者Udapi的网页版把预测树画出来肉眼扫几十个句子错误模式很快就会浮现到底是标点依存乱挂、还是长从句漏连、还是并列结构的连接词处理不好。模型改进方向往往就从这几十个句子里冒出来。6. 课程之外我自己的一点后续体会这一讲的内容从语言学到传统算法再到神经网络跨度很大但核心逻辑线性清晰先给出语法表示然后定义解析问题最后在模型层面用特征学习和结构预测解决它。如果吃透了Lecture 5再往后看待语义角色标注、共指消解、乃至基于句法增强的预训练模型都会觉得它们对“结构”的依赖其实是一脉相承的。我个人的实操体验是不要急着上最新的大模型解法。花一个周末手动实现一遍Biaffine解析器哪怕只跑几百句的玩具数据你对BiLSTM编码、双线性打分和MST解码的理解会远超直接调库刷榜。除此之外一定多看一眼不同语言的UD树库。中文、英文、日语的依存结构差异极大在中文上调试出的规则换到日文可能完全失效数据分布和标签体系的影响往往被很多人忽略。最后再分享一个小技巧如果你在下游任务里需要用依存信息不要直接把整棵解析树灌进模型而是先统计你要的关系在依存树上通常落在哪些标签上、距离多远。很多情况下只用“最短依存路径上的词和标签”就足够完成任务模型更简单训练更快效果反而更稳。这个技巧我第一次用是在一个关系抽取Demo上把候选实体对之间的依存路径特征接进分类器整体F1提升了差不多两个点。比起硬套复杂模型这算是这一讲知识最划算的落地方式之一。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑