文章目录
从头开始训练一个依存分析器
依存句法通过分析语言单位内成分之前的依存关系解释其句法结构,主张句子中核心动词是支配其他成分的中心成分。而它本身却不受其他任何成分的支配,所有受支配成分都以某种关系从属于支配者。
重要概念
- 依存句法认为“谓语”中的动词是一个句子的中心,其他成分与动词直接或间接地产生联系。
- 依存句法理论中,“依存”指词与词之间支配与被支配的关系,这种关系不是对等的,这种关系具有方向。确切的说,处于支配地位的成分称之为支配者,而处于被支配地位的成分称之为从属者。
- 依存语法本身没有规定要对依存关系进行分类,但为了丰富依存结构传达的句法信息,在实际应用中,一般会给依存树的边加上不同的标记。
- 依存语法存在一个共同的基本假设:句法结构本质上包含词和词之间的依存(修饰)关系。一个依存关系连接两个词,分别是核心词和依存词。依存关系可以细分为不同的类型,表示两个词之间的具体句法关系。
关系标签
标签表示从属的语法功能,名词性的标签是:
- root:中心词,通常是动词
- nsubj:名词性主语(nominal subject)
- dobj:直接宾语(direct object)
- prep:介词
- pobj:介词宾语
- cc:连词
其他常用的标签:
- compound:复合词
- advmod:状语
- det:限定词
- amod:形容词修饰语-
标注关系
中文依存分析关系如下:
应用场景
- 帮助进行NER标注:名词短语通常被标记为一个完整的实体,这些短语经过语法解析后就可以被准确识别。
- 机器翻译:通过构建完整的语法树,可以正确的建立词语之间的关系避免翻译歧义。
- 通过重新定义词语之间的联系,识别词语之间的关系,例如:人物亲戚关系。
接下来开始 spaCy 训练依存分析器。
注:本文使用 spaCy 3.0 代码实现。
一、自定义模型
1、导入所需要的包与模块
from __future__ import unicode_literals, print_function
import plac
import random
import spacy
from pathlib import Path
from spacy.training import Example
2、导入训练样本
TRAIN_DATA = [
("房祖名 是 成龙 的 儿子", {
'heads': [2, 4, 2, 2, 0],
'deps': ['SON', '-', 'ROOT', '-', 'ATT']
}),
("张若昀 的 妻子 是 唐艺昕", {
'heads': [0, 0, 4, 2, 0],
'deps': ['ROOT', '-', 'ATT', '-', 'HUS']
}),
("陈凯歌 是 陈赫 的 舅舅", {
'heads': [2, 4, 2, 2, 0],
'deps': ['UNCLE', '-', 'ROOT', '-', 'ATT']
}),
("焦曼婷 是 焦恩俊 的 女儿", {
'heads': [2, 4, 2, 2, 0],
'deps': ['DAU', '-', 'ROOT', '-', 'ATT']
})
]
数据可以自己再添加,越多越好,其中heads
为根据下标的依存匹配(即从属词与支配词的划分)。
二、训练模型
1、模型参数的注解(语种、输出目录以及训练迭代次数)
@plac.annotations(
lang=("ISO Code of language to use", "option", "l", str),
output_dir=("Optional output directory", "option", "o", Path),
n_iter=("Number of training iterations", "option", "n", int))
2、对现有的模型进行优化
if model is not None:
nlp = spacy.load(model) # 加载存在的模型
print("Loaded model '%s'" % model)
else:
nlp = spacy.blank('en') # 创建空白模型
print("Created blank 'en' model")
3、创建内置管道组件
使用 add_pipeline
函数创建流水线
if 'parser' in nlp.pipe_names:
nlp.remove_pipe('parser')
parser = nlp.add_pipe('parser', first=True)
4、添加train data的标签
for text, annotations in TRAIN_DATA:
for dep in annotations.get('deps', []):
parser.add_label(dep)
5、构建模型
训练过程本身很简单,nlp.update()
方法为我们抽象了所有内容,由 spaCy 处理实际的机器学习和训练过程。
# 禁用流水线中所有其他组件,以便只训练/更新NER标注器
other_pipes = [pipe for pipe in nlp.pipe_names if pipe != 'parser']
with nlp.disable_pipes(*other_pipes): # 仅训练我们标注的标签,假如没有则会对所有的标签训练,
for itn in range(n_iter):
random.shuffle(TRAIN_DATA) # 训练数据每次迭代打乱顺序
losses = {} # 定义损失函数
for batch in spacy.util.minibatch(TRAIN_DATA, size=2):
for text, annotations in batch:
#建立一个案例
doc = nlp.make_doc(text)
example = Example.from_dict(doc, annotations)
nlp.update([example], losses=losses, drop=0.3)
print(losses)
6、模型保存
test_model(nlp)
if output_dir is not None:
output_dir = Path(output_dir)
if not output_dir.exists():
output_dir.mkdir()
nlp.to_disk(output_dir)
print("Saved model to", output_dir)
print("Loading from", output_dir)
nlp2 = spacy.load(output_dir)
test_model(nlp2)
三、模型测试
def test_model(nlp):
texts = ["小红 是 小绿 的 妻子",
"房祖名 和 成龙 的 关系 是 父子",
"张晓龙 和 张佳宁 的 舅舅"]
docs = nlp.pipe(texts)
for doc in docs:
print(doc.text)
print([(t.text, t.dep_, t.head.text) for t in doc if t.dep_ != '-'])
if __name__ == '__main__':
plac.call(main)
模型的效果如下
小红 是 小绿 的 妻子
[('小红', 'FAT', '小绿'), ('是', 'dep', '小红'), ('小绿', 'ROOT', '小绿'), ('妻子', 'dep', '小绿')]
房祖名 和 成龙 的 关系 是 父子
[('房祖名', 'SON', '成龙'), ('和', 'dep', '房祖名'), ('成龙', 'ROOT', '成龙'), ('关系', 'ATT', '父子'), ('父子', 'dep', '成龙')]
张晓龙 和 张佳宁 的 舅舅
[('张晓龙', 'UNCLE', '张佳宁'), ('和', 'dep', '张晓龙'), ('张佳宁', 'ROOT', '张佳宁'), ('舅舅', 'dep', '张佳宁')]
可以看到效果还是可以的,只有第一条没有匹配成功。
不足的地方
本文模型用的是英文模型,可以看到本文将中文句子事先用空格隔开,以到达成功训练,后续待改进。
参考
1、【法】巴格夫·斯里尼瓦萨-德西坎.《自然语言处理与计算语言学》.人民邮电出版社
2、https://zhuanlan.zhihu.com/p/51186364
3、https://blog.csdn.net/sinat_33741547/article/details/79258045