阶段4 | NLP传统 · 第24课/共30课

📖 第24课:依存句法分析

🎯 课程目标

依存句法分析揭示句子中词与词之间的修饰和支配关系,是深层语言理解的基础。

📐 核心原理

依存 vs 成分句法

💡 依存树是有向树:每个词只有一个支配词(head),根节点没有支配词。这保证了树结构,适合后续的图算法处理。

🔬 简化依存分析器

基于词性的依存分析

import re class SimpleDependencyParser: def __init__(self): self.rules = { 'nsubj': lambda head, dep: f'{dep}是{head}的主语', 'dobj': lambda head, dep: f'{dep}是{head}的宾语', 'amod': lambda head, dep: f'{dep}修饰{head}', 'advmod': lambda head, dep: f'{dep}修饰{head}', } def parse_simple(self, sentence): # 简化的依存分析:基于词性和位置规则 import jieba.posseg as pseg words = list(pseg.cut(sentence)) deps = [] for i, (word, pos) in enumerate(words): if pos == 'v': # 动词是核心 for j, (w2, p2) in enumerate(words): if j == i: continue if p2 in ('n', 'nr', 'nt', 'nz', 'r') and j < i: deps.append((i, 'nsubj', j)) elif p2 in ('n', 'nr', 'nt', 'nz') and j > i: deps.append((i, 'dobj', j)) elif p2 in ('a', 'd') and abs(j-i) <= 2: rel = 'advmod' if p2 == 'd' else 'amod' deps.append((i, rel, j)) return words, deps parser = SimpleDependencyParser() test_sentences = [ "小明学习编程", "张三在北京大学读书", "公司开发了新产品", ] print("=== 简化依存句法分析 ===") for sent in test_sentences: words, deps = parser.parse_simple(sent) print(f" 句子: {sent}") print(f" 分词: {[(w.word, w.flag) for w in pseg.cut(sent)]}") for head_idx, rel, dep_idx in deps: print(f" {words[dep_idx].word} --{rel}--> {words[head_idx].word}") print() import jieba.posseg as pseg
=== 简化依存句法分析 === 句子: 小明学习编程 [STDERR] Building prefix dict from the default dictionary ... Loading model from cache /tmp/jieba.cache Loading model cost 0.829 seconds. Prefix dict has been built successfully. Traceback (most recent call last): File "<string>", line 43, in <module> NameError: name 'pseg' is not defined

⚠️ 部分验证:代码实机运行结果如上

🔧 依存树可视化与分析

CoNLL格式与结构分析

class DependencyVisualizer: def __init__(self): pass def to_conll(self, words_with_pos, arcs): # Convert to CoNLL format lines = [] for i, (word, pos) in enumerate(words_with_pos): head = 0 rel = 'root' for h, r, d in arcs: if d == i: head = h + 1 # 1-indexed rel = r break lines.append(f"{i+1}\t{word}\t{pos}\t{head}\t{rel}") return '\n'.join(lines) def analyze_structure(self, words_with_pos, arcs): # Analyze sentence structure features features = {} # 树深度 def depth(idx): children = [d for h, _, d in arcs if h == idx] if not children: return 1 return 1 + max(depth(c) for c in children) root_idx = None for h, r, d in arcs: if r == 'root': root_idx = d break if root_idx is not None: features['depth'] = depth(root_idx) # 依存方向 left_deps = sum(1 for h, _, d in arcs if d < h) right_deps = sum(1 for h, _, d in arcs if d > h) features['left_deps'] = left_deps features['right_deps'] = right_deps return features # 手动构造依存树 words_pos = [('小明', 'nr'), ('喜欢', 'v'), ('自然语言', 'n'), ('处理', 'v')] arcs = [(1, 'nsubj', 0), (1, 'dobj', 3), (3, 'compound', 2)] viz = DependencyVisualizer() conll = viz.to_conll(words_pos, arcs) print("=== CoNLL格式 ===") print(conll) features = viz.analyze_structure(words_pos, arcs) print("\n=== 结构特征 ===") for k, v in features.items(): print(f" {k}: {v}")
=== CoNLL格式 === 1 小明 nr 2 nsubj 2 喜欢 v 0 root 3 自然语言 n 4 compound 4 处理 v 2 dobj === 结构特征 === left_deps: 2 right_deps: 1

✅ 验证通过:代码实机运行结果如上

📝 练习

动手练习

  1. 实现弧标准(Arc-Standard)的移进归约依存分析器
  2. 用依存路径实现关系抽取(实体间最短依存路径)
  3. 对比不同依存分析算法(贪心、最大生成树、移进归约)的优缺点
🌳

🏆 成就解锁:句法解析者

掌握了依存句法分析,能揭示句子内部的语法结构。

🧪 扩展:依存句法分析器

主流依存分析算法

  1. Arc-Eager:移进归约,4种操作(SHIFT/LEFT-ARC/RIGHT-ARC/REDUCE)
  2. Arc-Standard:移进归约,3种操作(SHIFT/LEFT-ARC/RIGHT-ARC)
  3. 最大生成树:Eisner算法,O(n³)复杂度,保证树结构
  4. Global Learning:结构化感知器,在线学习全局最优

依存分析的工具

📚 扩展阅读:依存句法的标注体系

通用依存关系(Universal Dependencies)

UD是目前最广泛使用的依存标注体系,支持100+种语言:

中文依存标注的特殊性:没有冠词、没有格变化,助词("的""了""着")的依存关系处理是难点。

🔬 扩展实践:依存分析的应用

基于依存路径的关系抽取

两个实体之间的最短依存路径(shortest dependency path)是关系抽取的强特征:

这种方法比基于位置的方法更准确,因为依存路径直接反映了语法关系。

依存分析辅助文本简化

利用依存树可以简化复杂句子:

  1. 识别核心修饰路径(主语-谓语-宾语)
  2. 删除非核心修饰(定语、状语、补语)
  3. 重新组织为简单句

例如:"在中国北京生活的张三创建了位于杭州的阿里巴巴" → "张三创建了阿里巴巴"

🧪 扩展:依存分析的应用

基于依存路径的关系抽取

两个实体之间的最短依存路径是关系抽取的强特征:

依存分析工具

📚 参考文献

推荐阅读

在线资源

依存句法 - 关键概念速查

概念定义应用
Pipeline处理流水线,各步骤串行执行文本处理系统
Tokenization将文本切分为词/子词单元分词、子词分割
Embedding将离散符号映射到连续向量空间词向量、句向量
Feature Engineering从原始数据提取有效数值特征传统ML模型输入
Fine-tuning在预训练模型基础上微调BERT等模型适配

本课要点回顾

  1. 依存句法是NLP技术栈的重要组件
  2. 理解原理比调用API更重要——知道为什么比知道怎么做更有价值
  3. 代码验证是学习的好方法——动手跑一遍胜过读十遍
  4. 从简单方法开始,逐步过渡到复杂方法——简单方法的基线作用不可替代
  5. 实践出真知——用自己的数据跑一遍,发现课本上没写的问题

❓ 常见问题FAQ

Q: 这个方法的数据需求量是多少?
A: 传统方法通常需要几百到几千条标注数据。深度学习方法通常需要万级以上。数据量少时优先用传统方法。
Q: 如何处理中文的特殊情况?
A: 中文没有天然词边界,分词质量直接影响下游效果。建议:1)使用领域词典优化分词 2)比较不同分词工具的效果 3)对关键任务做分词错误分析。
Q: 传统方法和深度学习如何选择?
A: 考虑三个因素:1)数据量——少数据用传统方法 2)可解释性——需要解释选传统方法 3)计算资源——资源受限选传统方法。深度学习在大数据+强算力场景下优势明显。
Q: 如何评估和调优?
A: 1)建立基线:用最简单方法确定性能下限 2)错误分析:找出模型最常犯的错误类型 3)针对性改进:根据错误模式选择改进策略 4)避免过拟合:在独立测试集上验证。
Q: 生产环境部署要注意什么?
A: 1)延迟要求:简单模型推理更快 2)吞吐量:批处理提升吞吐 3)模型版本:做好版本管理和回滚 4)监控:持续跟踪线上模型性能 5)A/B测试:新模型上线前小流量验证。

🛠️ 实用工具箱

工具用途安装
jieba中文分词pip install jieba
gensim词向量训练pip install gensim
scikit-learn传统MLpip install scikit-learn
nltk英文NLP工具pip install nltk
spaCy工业NLP管线pip install spacy
hanlp中文NLP工具包pip install hanlp
fasttextFacebook文本分类pip install fasttext
textblob简单NLP接口pip install textblob