title | description | prev | next | type | id |
---|---|---|---|---|---|
第四章:训练神经网络模型 |
本章中,我们要学习更新spaCy的统计模型使其能够为特定的使用场景做出定制化。一个例子是我们想要在网络上的评论中抽取一种新的实体。我们将会学到如何从头训练自己的模型,了解模型训练的基本工作原理,以及一些技巧使得我们自己的定制化自然语言处理项目能够更加成功。 |
/chapter3 |
chapter |
4 |
要训练一个模型,我们通常需要训练数据 和 用来评估模型的开发数据。这个评估数据是用来做什么的?
训练过程中,模型只能由训练数据来进行更新。开发数据只是用来将模型在未见过的数据上预测的结果与真实标注做对比, 来评估模型表现的。准确度分数由此计算而来。
开发数据只是用来将模型在未见过的数据上预测的结果与真实标注做对比, 来评估模型表现的。准确度分数由此计算而来。
开发数据只是用来将模型在未见过的数据上预测的结果与真实标注做对比, 来评估模型表现的。准确度分数由此计算而来。
spaCy的基于规则的Matcher
可以很好地被用来快速创建一些命名实体模型的训练数据。变量
TEXTS
中存储着句子的列表,我们可以将其打印出来做检查。我们想要找到所有对应不同iPhone
型号的文本,所以我们可以创建一些训练数据来教会模型把它们识别为电子产品"GADGET"
。
- 编写一个模板,含有两个词符且它们的小写形式可以匹配到
"iphone"
和"x"
。 - 编写一个模板,含有两个词符,第一个词符的小写形式匹配到
"iphone"
,第二个词符用"?"
运算符匹配到一个数字。
- 要匹配一个小写形式的词符,我们可以用
"LOWER"
属性,比如{"LOWER": "apple"}
。 - 要寻找一个数字词符,我们可以用
"IS_DIGIT"
标签,比如{"IS_DIGIT": True}
。
在为我们的语料创建数据之后,我们需要将其存放在一个后缀为.spacy
的文件中。可以参见上一个例子中的代码。
- 使用
docs
的列表初始化DocBin
。 - 将
DocBin
存储到一个名为train.spacy
的文件中。
- 我们可以把一个含有多个文档的列表传入关键字参数
docs
中来初始化DocBin
。 DocBin
的to_disk
方法需要一个参数:二进制文件存储的路径。 要确保文件的后缀名是.spacy
.
config.cfg
文件是使用spaCy训练流程的“唯一真理来源”。下列关于配置的说法哪个是 错误 的?
配置文件含有训练流程的所有设定,包括超参数。
配置文件含有 所有 设定,也没有隐藏的默认值,所以可以帮助我们的训练实验更加容易复现。 其他人可以轻松通过相同设定重新跑通我们的实验。
配置文件含有和训练与流程相关的所有设定,但并不能为流程打包。
要创建可安装的Python包,我们可以使用spacy package
命令。
配置文件中的'[components]'包含了所有流程组件和各自的设定,包括所使用的模型实现。
init config
命令 自动生成一个使用默认设定的训练配置文件。
我们想要训练一个命名实体识别器,所以我们要生成一个含有一个流程组件ner
的配置文件。
因为我们在本课程中是在Jupyter环境中运行命令,所以加上前缀!
。
如果是在本地终端中运行则不需要加这个前缀。
- 使用spaCy的
init config
命令来自动生成一个中文流程的配置。 - 将配置保存到文件
config.cfg
中。 - 使用
--pipeline
参数指明一个流程组件ner
。
--lang
参数定义了语言类,比如zh
指中文。
我们来看看spaCy刚刚生成的配置文件! 我们可以运行下面的命令将配置打印到屏幕上。
让我们用前面练习中生成的配置文件和训练语料来训练一个命名实体识别器!
使用train
命令来调取训练配置文件来训练一个模型。
一个名为config_gadget.cfg
的文件已经在exercise/zh
中了,
同时还有一个名为train_gadget.spacy
的文件包含了一些训练数据,dec_gadget.spacy
文件包含了测试数据。
因为我们在本课程中是在Jupyter环境中运行命令,所以加上前缀!
。
如果是在本地终端中运行则不需要加这个前缀。
- 在文件
exercises/zh/config_gadget.cfg
上面运行train
命令。 - 将训练好的流程保存在
output
文件夹中。 - 传入路径
exercises/zh/train_gadget.spacy
和exercises/zh/dev_gadget.spacy
- 命令
spacy train
的第一个参数是配置文件的路径。
让我们来看看模型在未出现过的新数据上表现如何!为了节省时间,我们已经在一些文本上面
训练好了一个带有标签"GADGET"
的流程。这里是一些结果:
文本 | 实体 |
---|---|
苹果已经开始让iPhone 8和iPhone X变得越来越慢了,怎么办 | (iPhone 8, iPhone X) |
我终于明白iPhone X的“刘海”是干嘛的了 | (iPhone X,) |
关于Samsung Galaxy S9你需要了解的一切 | (Samsung Galaxy,) |
想要比较不同的iPad型号?这里是2020年所有的产品线对比。 | (iPad,) |
iPhone 8和iPhone 8 Plus是苹果公司设计、研发和销售的智能手机 | (iPhone 8, iPhone 8) |
那个型号ipad是最便宜的,尤其是ipad pro里面的?? | (ipad, ipad) |
Samsung Galaxy是三星电子公司设计、生产并退出市场的一系列移动计算设备 | (Samsung Galaxy,) |
在模型从文本中抽取的所有实体中,有多少实体模型的判断是正确的? 注意如果实体的跨度span不完整的话也被认为是一个错误! 小技巧:数一数模型 应该 抽取出的实体数目,然后数一数模型 实际中 抽取正确的实体数目, 把后者除以前者也就是全部正确实体的数目就可以得到准确率。
试着数数模型正确抽取的实体数目,然后除以模型 _应该_ 抽取出的全部正确实体的数目。试着数数模型正确抽取的实体数目,然后除以模型 应该 抽取出的全部正确实体的数目。
在我们的测试数据上模型的准确率是70%。
试着数数模型正确抽取的实体数目,然后除以模型 应该 抽取出的全部正确实体的数目。
这是一段摘抄,来自于一个训练集试图在旅行者的评论中标注实体类型
TOURIST_DESTINATION
(游客目的地)。
doc1 = nlp("我去年去了西安,那里的城墙很壮观!")
doc1.ents = [Span(doc1, 5, 7, label="TOURIST_DESTINATION")]
doc2 = nlp("人一辈子一定要去一趟巴黎,但那里的埃菲尔铁塔有点无聊。")
doc2.ents = [Span(doc2, 10, 12, label="TOURIST_DESTINATION")]
doc3 = nlp("深圳也有个巴黎的埃菲尔铁塔,哈哈哈")
doc3.ents = []
doc4 = nlp("北京很适合暑假去:长城、故宫,还有各种好吃的小吃!")
doc4.ents = [Span(doc4, 0, 2, label="TOURIST_DESTINATION")]
为什么这段数据和标注方法有问题?
一个更好的方法应该是只标注"GPE"
(地理政治实体)或者是"LOCATION"
(位置实体),然后用基于规则的系统
来判断这个实体在语境中是不是游客目的地,比如我们可以在知识库中寻找该实体类别或者在旅行百科
中查询这些实体。
虽然Paris确实有可能是一个游客目的地,但这恰恰表明了这种标注的方法有多么主观, 以及决定标签是否符合这个实体有多么困难。结果就是我们的实体识别器也很难学习到这种区别。
就算是不常见的或者拼写错误的词也是可以被标注为实体的。实际上基于语境来判断拼写错误的文本 的类别恰恰是统计实体识别模型的一大优势。
- 重写
doc.ents
使其跨度span的标签为"GPE"
(城市、州省、国家)而非"TOURIST_DESTINATION"
。 - 别忘了添加那些数据中本来未被标注为
"GPE"
的实体的跨度span。
- 对于那些已经标注过的span,我们只需要将其标签名从
"TOURIST_DESTINATION"
换为"GPE"
。 - 有一段文本包含了城市和州省实体但还没有被标注。要加入实体的跨度span,我们需要数一数词符token
来找出实体的span是从哪里开始和从哪里结束。注意最后一个词符的索引是 不包含的!
然后把新的
Span
加入到doc.ents
中。 - 注意分词!如果不确定可以把
Doc
中的词符打印出来。
这里是某个数据集的一个样品,我们创建它来训练一个新的实体种类"WEBSITE"
。
原始的数据集包含了几千个句子。这个练习中我们要手动做标注。实际工作中我们
很可能想要用一些标注工具来自动化这一步,比如Brat,
一个很流行的开源方案,或者Prodigy,我们自己开发的整合
了spaCy的标注工具。
- 完成数据中所有
"WEBSITE"
实体的位置参数。
- 要注意终止词符的span是不包含的。
所以如果一个实体从位置2开始而从位置3结束,那么start就是
2
,而end是4
.
我们已经用刚才标注好的数据和其它几千个类似的例子训练了一个模型。训练完成之后,这个模型
对"WEBSITE"
的抽取表现很好,但却识别不了"PERSON"
了。这是怎么回事?
让模型学习到不同类别是完全可能的。比如spaCy的预训练英文模型就可以识别人名、组织名或者 百分数。
如果"PERSON"
实体在训练数据中出现但并未被标注,模型就会学到这些实体不应该被抽取出来。
类似的如果一个已有的实体类别没有出现在训练数据中,模型就会 "忘记" 它而停止抽取。
超参数确实对模型准确度有影响,但不是这里的问题所在。
- 更新训练数据,加入对
"PERSON"
实体"李子柒"和"马云"的标注。
- 要添加更多的实体,给
doc.ents
增加一个Span
就行。 - 要注意终止词符的span是不包含的。
所以如果一个实体从位置2开始而从位置3结束,那么start就是
2
,而end是4
.