----以“大屠杀”领域命名实体识别研究为例
作者: Dr. W.J.B. Mattingly
Postdoctoral Fellow at the Smithsonian Institution's Data Science Lab and United States Holocaust Memorial Museum
2021年1月
引用:Mattingly, William. Introduction to Named Entity Recognition, 2020. ner.pythonhumanities.com
本文原本是作为自然语言处理任务之命名实体识别(NER)的教材。NER的目的是从非结构化文本中提取结构化数据,即特定的实体,如人名、地名、日期等。到目前为止,从使用现成的框架到自己开发特定领域的解决方案,还没有一种免费的、广泛的关于NER主题和方法的处理方法。本文使用几个不同的数据集来演示NER的使用方法和功用。这些章节与嵌入相关章节的YouTube视频一起使用。完整的播放列表可以在这里找到:https://www.youtube.com/channel/UC5vr5PwcXiKX_-6NTteAlXw。
致谢
这本NER教材是我在史密森学会数据科学实验室做博士后时与美国大屠杀纪念馆合作编写的。如果没有Rebecca Dikow, Mike Trizna和那些在数据科学实验室的人的帮助,这一切都不可能实现,当我写这一系列笔记时,他们耐心倾听并给予我帮助和建议。我还要感谢USHMM的专家,特别是Michael Haley Goldman, Michael Levy和Robert Ehrenreich。
本文适用于那些有兴趣通过spaCy库训练定制命名实体识别模型的人。特别是对于编码经验有限和没有自然语言处理(NLP)背景的人。本文的学习,需要对Python有基本的了解,然而,对于没有编码经验的人仍然能够通过本文的学习,对自然语言处理、命名实体识别这些领域的常见问题以及这些问题的解决方案有基本的了解。对于那些有兴趣快速了解Python的人,请在PythonHumanities.com上查看我为数字人文主义者设计的学习系列。
本文主要有以下五个目标:
虽然本文是基于大屠杀领域的NER,但其中遇到的问题并非是该领域所独有的。因此,本文可以作为其他领域中遇到类似问题的指南。
主要概念和术语将以粗体标示.
命名实体识别(下面介绍)是自然语言处理(即NLP)的一个分支。自然语言处理是人们使用计算机系统解析人类语言,并从文本中提取重要元数据的过程。NLP的目的之一是执行远程读取(distant reading)。
远程阅读由来已久,一直延续到20世纪后期。通常用于当给定语料库中的文本数量大到使研究人员(或一组研究人员)无法仔细阅读整个语料库的情形。为了理解如此庞大的语料库,研究人员通常会将某些特定任务交给计算机,并容许一定误差。接受这种误差,以换取对该语料库更大、更广泛的理解的能力。远程阅读被用于执行一些重要的任务,比如:
从这些任务中得到的元数据可以用来获得文本的感觉,而无需仔细阅读它们,因此称为“远程阅读”。
NLP与计算语言学的另外两个类似分支——自然语言理解(NLU)和自动语音识别(ASR)协同工作。为了更好地理解这些领域是如何相互关联的,请看下图。
此图在各种NLP教程中经常被使用。它准确地描绘了NLP领域的多样性以及NLU和ASR的密切合作领域。NLP的目标是将文本输入计算机系统,并让它返回某种输出。这通常是通过一系列管道来实现的,这些管道对输入数据执行一系列的操作。
靠前的管道,可能包括一个分词器,其唯一的工作是将文本分解为单个标识(Token)。标识是文本中具有某种语义的单位。它们可以是单词,如“Martha”。但也可以是标点符号,如“,”在关系分句 — “,a senior,”中。同样,缩写“can’t”中的“ 't ”也可以被认为是一个标识,因为英语中的“ 't ”对应单词“not”。
分词器后面一个常见管道是POS标记器,它的工作是识别文本中的词性。这对于计算机理解单个标识如何在句子中发挥作用是至关重要的。我们在不同的语言中标识词性的方式是不一样的。在屈折变化的语言中(如德语)或高度屈折变化的语言中(如拉丁语或古希腊语),单词的结尾包含了很多关于它在句子中作用的信息,如主格单数或与格复数。在低屈折变化的语言中(比如英语),句子中的位置是最重要的。英语是一种名动宾(NVO)语言。让我们考虑一个例句:
The boy took the ball to the store. 男孩把球带到商店去了。
主格(主语)“boy男孩”在句子中位于前面,然后是动词“took带”,然后是宾格(宾语)“ball球”,最后是与格(间接宾语)“store商店”。“The”和“to”也包含重要的信息。" The "出现了两次告诉读者它不是泛指的球,它是特指球;同样的,商店也是一样。这个句号也告诉了我们一些重要的事情:这是一个陈述名,不是疑问句。对于以某一特定语言为母语的人来说,这些词性可能完全不会被注意到。我们凭直觉理解它们。我们中的一些人可能有记忆五级语法解析树的记忆,但在大多数情况下,我们在心理上和语言上都以一种独特的方式与母语一起发展。我们只是使用语言而无需考虑语法。对于那些花时间学习第二语言的人来说,语法是必须的(有时也是祸根)。我们学习其他语言的方式与学习母语的方式不同。对于电脑来说,也是如此。我们需要让计算机理解词性。
命名实体识别通常会在稍后的管道中出现,因为它需要接收标记化的文本,而且在某些语言中,它需要理解单词词性才能有好的表现。当文本在管道中被顺序处理时,它接收到包含有价值信息的跨区域段标签(Span),比如词性(POS)。当文本到达NER管道时,就到了让机器对单个标识(Token)做出一些结构化决策的时候。
实体是文本中的单词,对应于特定类型的数据。它们可以是数字,如基数;时间,如日期;标称,如人名和地名;以及政治,如地缘政治实体(GPE)。简而言之,实体可以是设计人员希望指定为具有相应标签的文本中的任意项目。
命名实体识别(NER)是系统接受非结构化数据(文本)的输入并输出结构化数据的过程,特别是实体的识别。让我们考虑一下这个简短的例子。
大四的玛莎搬到了西班牙,她将在那里打篮球直到2022年6月5日,或者她不能再能打了。
在本例中,我们有几个潜在的实体。首先是“玛莎”。不同的NER模型对于这样的实体有不同的对应标签,但是PERSON或PER被认为是标准惯例。注意这里的标签是大写的。这也是标准的做法。我们还有一个GPE,即地缘政治实体,本例中显而易见是“西班牙”。最后,我们有一个日期实体,“2022年6月5日”。这些是可以从文本中提取的标准标签。但是,如果当前领域有额外的标签,也可以提取这些标签。也许客户机或用户不仅想要提取PERSON、GPE和DATE,而且还想提取SPORT。在这种情况下,“篮球”可以被提取出来,并被赋予SPORT这个标签。
并非所有实体都是单个标识。与文本一样,有时实体是多词标记(MWT)。让我们考虑上面的同一句话,但有稍有不同:
大四学生玛莎·汤普森(Martha Thompson)搬到了西班牙,她将在那里打篮球直到2022年6月5日,或者直到她不能再打了。
本例,玛莎现在有了姓氏“汤普森”。我们可以将玛莎和汤普森作为单独的实体提取出来,或者将二者作为单独的实体提取出来,因为“玛莎·汤普森”是一个单独的个体。因此,一个NER系统应该将“玛莎·汤普森”识别为一个单一的MWT。
随着我们学习本文和视频,我们将学习新的NER概念。现在,我建议你看下面的视频。大部分章节,都有相应的视频课。
为了进行自然语言处理,研究者需首先决定使用什么框架。框架是指研究者进行特定任务所使用软件的术语。对于python,贴切的说法是将框架看作一个库,或者是打包成一组的可用类和函数,以便执行复杂任务。决定使用哪个框架取决于如下因素。
首先,并不是所有的框架都支持所有的语言,也不是所有的框架对同一语言有相同程度的支持。
第二,对于特定任务某些框架的表现要优于其他框架。虽然所有框架都能很好地分词(对英文通常如此),但对某些任务,例如通过词元化(spaCy)和词干化(Stanza)找到单词的根,其方式会有所不同。为此类目的而进行的框架抉择通常存在于计算语言学或远程阅读领域,用于发现词(或词组)如何在文本中以各种形式(动词变位和名词变格)出现的。
第三个需要考虑的因素是框架执行NLP的方式。从本质上讲,有两种执行NLP的方法:基于规则的和基于机器学习的。基于规则的自然语言处理的处理过程是框架通过一组预先确定好的规则来处理特定的任务。例如,为了在文本中查找实体,基于规则的方法将包含一个包括所有实体类型的字典,或者也可能包含正则表达式表示识别匹配实体的模式。
如今大多数框架正在从基于规则的方法向基于机器学习的方法转变。基于机器学习的自然语言处理(NLP),是开发人员利用统计数据教计算机系统(称为模型)根据过去的经验(称为训练)执行任务的过程。我们将在后面的笔记中更多地谈论基于机器学习的NLP,因为本文主题spaCy就是一个基于机器学习的Python库。
spaCy(这个拼写是正确的)库是一个鲁棒性很强的机器学习NLP库,由位于柏林的计算机科学家和计算语言学家团队Explosion AI开发。它支持各种欧洲语言,具有开箱即用的能够解析文本、识别词性和提取实体的统计模型。SpaCy还能够轻松地改进或从头开始训练特定领域文本的自定义模型。
在本文中,我们将详细介绍安装spaCy的步骤,下载一个预先训练好的语言模型,以及执行自然语言处理的基本任务。
对于下载和安装spaCy和语言模型,未在本节讲述。请观看下面的视频并遵循必要的步骤:
https://www.youtube.com/embed/yqruv_QQctI
自然语言处理的一个常见的基本任务是标记化。我们在上一节简要地介绍了标记化,其目的是将文本分解为独立的元素。这种标记化称为分词。然而标记化,还有许多其他形式,如分句。分句与分词完全相同,不同之处在于,我们不是将文本拆分为单个的单词和标点这样的元素,而是将文本拆分为单个的句子。
如果你熟悉Python,你对内置的split()函数也就不陌生,它是通过空格(默认)分割文本,或者通过传递字符串的参数来定义文本的分割位置,例如split(" . ")。一个常见的应用(在没有NLP框架的情况下)是通过简单地使用split函数将文本分割成句子,但这样处理多少有些轻率。请看下例:
text = "Martin J. Thompson is known for his writing skills. He is also good at programming."
#我们以句号来分割句子.
new = text.split(".")
print (new)
[‘Martin J’, ’ Thompson is known for his writing skills’, ’ He is also good at programming’, ‘’]
虽然我们成功地将这两句话分开,但我们在Martin J这儿得到了错误结果。原因可能很明显:在英语中,通常表示句尾和缩写都用相同的标点。原因可以追溯到中世纪早期,当时爱尔兰僧侣开始引入标点符号和空格,以便更好地阅读拉丁语(那是另一个故事)。
然而,让文本更容易阅读的东西,对于我们轻松拆分句子却带来了极大的阻碍。为此,需要寻求另外一种方法。而这便是分句发挥作用的地方。为了了解分句的不同之处,让我们从第一个spaCy的用法开始。
#首先导入spaCy
import spacy
'''
接下来我们需要加载一个NLP模型对象.
使用spacy.load() 函数.
此模型需要一个参数.
我们使用小型英文预训练模型"en_core_web_sm".
'''
nlp = spacy.load("en_core_web_sm")
'''
nlp对象创建后,我们就可以用它来解析文本了.
使用nlp()得到doc对象.
doc包含了文本中的诸多数据.
'''
doc = nlp(text)
#显示doc对象:
print (doc)
结果如下:
Martin J. Thompson is known for his writing skills. He is also good at programming.
'''
虽然这看起来和上面的text字符串没什么不同, 其实却完全不同.
我们用分句器来展示这样不同。
'''
for sent in doc.sents:
print (sent)
Martin J. Thompson is known for his writing skills.
He is also good at programming.
注意,我们已经使用了spaCy分句器来生成所需的输出:将文本正确地分解成句子。这个简单的示例表明了即使对于一个基本的任务,为什么使用一个NLP框架来执行不仅更容易,而且是必要的。为了更详细地解释这个过程,请观看下面的视频:
https://www.youtube.com/embed/ytAyCO-n8tY
NLP的另一个基本任务(也是本文的主要主题),是命名实体识别(NER)。我在前面提到了NER。在这里,我将通过spaCy演示如何执行基本的NER。同样,我们将像上面那样遍历doc对象,但不是遍历doc对象本身,而是docs.ents。对于我们现在的目的,我只想打印出每个实体的文本(实体字符串本身)及其对应的标签(注意标签后面的_)。我将在接下来的两节中更详细地解释这一过程。
for ent in doc.ents:
print (ent.text, ent.label_)
Martin J. Thompson PERSON
如结果所示,一个spaCy的小型统计机器学习模型(en_core_web_sm)已经正确地识别出Martin J. Thompson是一个实体。什么样的实体?一个人(PERSON)。我们将在第以下章节中探讨它是如何做出这一决定的,我们将在其中更深入地探讨机器学习NLP。也可以以下面视频方式了解这一过程,请看下面的视频:
https://www.youtube.com/embed/lxHNsXudkrY
在计算语言学领域,理解词性是至关重要的。SpaCy提供了一种简单的方法来解析文本并识别其词性。下面,我们将遍历文本中的每个标记(单词或标点),并识别其词性。
for token in doc:
print(token.text, token.pos_)
Martin PROPN
J. PROPN
Thompson PROPN
is AUX
known VERB
for ADP
his DET
writing NOUN
skills NOUN
. PUNCT
He PRON
is AUX
also ADV
good ADJ
at ADP
programming NOUN
. PUNCT
在这里,我们可以看到两个至关重要的信息:字符串和相应的词性(pos)。有关pos标签的完整列表,请参阅spaCy文档(Data formats · spaCy API Documentation)。然而,其中大多数应该是能够通过名称体现出来的,即PROPN是专有名词,AUX是辅助动词,ADJ是形容词,等等。关于这个过程的更多信息,请看下面的视频。
https://www.youtube.com/embed/nv0pksknFxY
通常在处理文本时,我们需要抽取名词和名词块。通过spaCy有几种不同的实现方法。提取名词,我们可以利用doc.noun_chunks属性。
for chunk in doc.noun_chunks:
print(chunk.text)
Martin J. Thompson
his writing skills
He
programming
注意,我们得到了一个所有名词和名词块的列表,即“He”和“programming”是名词,“Martin J. Thompson”和“his writing skills”是名词块。更多相关信息,请看下面的视频。
通过给定RegEx预定义模式,我们可以对动词和动词短语做与名词和名词块完全相同的事情。不过这稍许复杂些,需要理解语言模式和文本库。我们将尝试找到一个特定模式的所有实例,即一个辅助动词后跟一个正常动词。
#We import textacy
import textacy
#创建词典列表模式
patterns = [{"POS": "AUX"}, {"POS": "VERB"}]
#利用textacy在doc对象中找到特定模式来创建动词短语
verb_phrases = textacy.extract.matches(doc, patterns=patterns)
#遍历 verb_phrases
for verb_phrase in verb_phrases:
print (verb_phrase)
is known
我们发现了辅助动词后跟规则动词的一个正确的例子:“is known”。更多信息,请查看下面的视频:
https://www.youtube.com/embed/VgGHwIWu-kU
译者:对于中文并不存在词形还原,但为了保证译文的完整性,在些仍保留了这部分内容。
我想在这本节探讨的最后一个项目是词形还原。虽然有些库执行这个概念的方式有所不同,但在大多数NLP框架中,lemmmalization是一个必不可少的组成部分。而像Stanza这样的库可以找到词干,而spaCy则可以找到词元。它们在技术上有点不同,但都试图将所有的单词还原为它们的词根。为了通过spaCy查找词元,我们使用与查找词性相同的过程,即遍历doc对象中的标记。
for token in doc:
print(token.text, token.lemma_)
Martin Martin
J. J.
Thompson Thompson
is be
known know
for for
his -PRON-
writing writing
skills skill
. .
He -PRON-
is be
also also
good good
at at
programming programming
. .
注意,我们看到大多数单词保持不变,但特别要注意的是,“is”被定义为“be”,而“known”变成了“know”。这些是这些动词各自的词元。还要注意对名词的同样影响,比如复数的“skills”被简化为单数形式的“skill”。要了解更多,请看下面的视频。
https://www.youtube.com/embed/YztOLsJkC3A
如前所述,NLP和NER有两种类型:基于规则的和基于机器学习的。本节将讲述基于规则的NER,基于机器学习的NER放到下节。
基于规则的NER,是指NLP工作者创建或利用具有一组预定义的指令或规则的NLP系统,以执行特定的NLP任务的一种方法。对于NER来说,这通常意味着使用所谓的地名辞典。地名辞典是与特定标签对齐的实体的列表或字典。在“人”(PERSON)的例子中,是一个姓和名的列表。如果你正在为某个特定地区开发NER(我们将在后面的章节中介绍),这可能是该地区所有地点的列表。
在21世纪初,有定义良好的地名辞典是执行NER的主要方法。它仍然是一些NLP框架的主要方法,如经典语言工具包(CLTK)的v.0.01,但目前正被机器学习模型所取代。
然而,有时研究者可能没有一个领域域中所有潜在名字的列表。在这些情况下,就应该使用基于模式规则的NER,进行基于规则的NLP。在这个场景中,研究者创建一组特定的模式来查找特定实体,然后假定与该模式相一致的任何东西都是需要查找的特定实体。这可以用于地名,比如德国柏林( Berlin, Germany);英格兰伦敦( London, England);德克萨斯达拉斯(Dallas, Texas)。在此场景中,可以使用字符串模式库,如RegEx(正则表达式)来查找大写单词、逗号和另一个大写单词的实例,并假定这种情况是一个位置。然而,这种方法更常见的是用于查找文本中的日期。虽然有很多方法来表示日期,但模式的数量有限。例如,如果我想表示1月1日,我们可以这样写:
当然可能还有其他潜在的变化,但我要强调的是,表示日期的方法数量有限。因此,通过基于规则模式的方法在文本中查找和提取时间实体通常相当容易。如果日期只以几种方式表示,问题就会变得更加简单。人们可以开发一种模式来找到数字的任何实例,后跟一个大写词或反之亦然,以此作为日期。
3.3 为什么要使用基于规则的NER?
我在前面曾提及,NLP正在从基于规则的方法转向基于机器学习的方法。你可能会想,那为什么还要学习基于规则的NLP和NER。
答案是,在某些情况下,基于规则的解决方案比基于机器学习的解决方案更好。此外,有时两者结合更加合适。理解什么时候使用哪个解决方案也非常重要,因此,对于基于机器学习的NER,基于规则的NER的坚实基础是必不可少的。
一个重要的问题—什么时候应该使用基于规则的NER方法?答案很简单。当表示一个特定实体的方法有限时,因此可以用这样的规则捕获大约95-97%的方法。我说95% -97%并非是行业标准,而是因为这是我的NER模型的目标数值。如果我可以使用基于规则的方法并获得与机器学习模型相同的准确性,我很可能会这么做,因为实现基于规则的方法进行NER,时间通常比训练、验证和测试机器学习模型所花的时间要少。
正如我们将在下面的视频中看到的,当你知道语料库中每个可能的名字时,这是一个特别有用的方法,比如我们将看到哈利波特的例子(仅为展示)。
要牢记:基于规则的方法仅仅是基于规则的。如果一个实体不符合你的规则,那么它将不会被标记为一个实体。这在OCR文档、未检查拼写、未编辑或任何形式的未清理的文本中尤为普遍。
虽然清理文本是NLP适当数据准备的重要组成部分,但有时不可能完全清理文本,有时,使用特定NER框架的人可能不知道如何清理文本。这是基于规则的方法的主要局限性,也是当下研究者使用机器学习方法的主要原因之一。对于全新数据,尽管(在某种程度上)与训练样本存在差异,但机器学习模型可以学习并泛化到全新数据。我们将在下节更详细地探讨这一点。
虽然spaCy主要是作为一个机器学习的NLP库,但它具有使用基于规则的NER方法的能力。这是该库的一个主要优势。因为我们将看到,为了为专业领域开发一个强大的NER系统,基于规则和基于机器学习的方法的结合有时是必不可少的。
尽管spaCy存在几种基于规则的NER方法,但最基本的一种是它的EntityRuler。
让我们回到第1节关于篮球运动员玛莎的例子。在此场景中,我们不仅希望从文本中提取普通实体(PERSON、DATE等),还希望提取一个新的实体SPORT。在后面的章节中,我们将更详细地讨论添加自定义实体。现在,我们将使用这个简单的示例来演示基于规则的NER是如何工作的。
text = "Martha, a senior, moved to Spain where she will be playing basketball until 05 June 2022 or until she can't play any longer."
#Import spacy
import spacy
#创建一个空的spaCy模型将用它来解析英文("en")
nlp = spacy.blank("en")
#创建一个ruler,它将被加到模型上
ruler = nlp.create_pipe("entity_ruler")
#定义一个查找模式并加入ruler
ruler.add_patterns([{"label": "SPORT", "pattern": "basketball"}])
#把ruler加入到模型管道中
nlp.add_pipe(ruler)
#用新的nlp处理文本并得到doc对象
doc = nlp(text)
#遍历所有实体(将得到新实体)
for ent in doc.ents:
print (ent.text, ent.label_)
basketball SPORT
我们看到输出与期望的一样。我们已经提取了篮球(basketball)字符串和它的正确标签SPORT。如果你不完全理解上面的代码是如何工作的,也不要担心。在本文结束后,就知道了。现在,只需理解基于规则的NER是如何工作的。尝试上面的代码,并尝试创建一个模型,可以在下面的文本中识别足球作为一项运动:
text = "Scott enjoys to play soccer."
#复制上面的代码找到 soccer 及其标签 SPORT
完成上述练习后,观看下面关于基于规则的NER的视频。它强化了这些概念,并以哈利波特的第一部作为更大的语料,进行更加深入地探索。
https://www.youtube.com/embed/O_2uq0sdCQo
机器学习是人工智能的一个分支。为了理解人工智能以及现代机器学习与它的前身有何不同,请看我关于这个主题的视频(我的系列视频----Machine Learning for DH. 其中关于机器学习的一个短片)。
https://www.youtube.com/embed/G6cW5JybUPU
机器学习(和深度学习)是人们教计算机系统学会用统计和线性代数(而非规则)执行任务的过程,这样系统就可以从重复的(随机的)经验中学习。如果现在还不明白的话,等你看完本文就明白了。本节必须要包含一些数学知识,但它将被保持在绝对的最低限度。我将只介绍绝对必要的数学和数学概念。
在继续之前,我同样推荐我的关于深度学习的短片(我的系列视频----Neural Networks for DH.)
https://www.youtube.com/embed/G0hvxnb7hHM
机器学习有以下几种不同的类型:监督学习、非监督学习和半监督学习。还有其他形式(如强化学习),但这三种是其基本形式。
监督学习是指我们用已知的数据训练来系统。在NER的情况下,我们使用一系列文本来训练系统,这些文本的实体用相应的标签进行了适当的注释。无监督学习是指当你不知道数据的类别时,向系统提供一系列数据,并允许它自行学习和识别模式。这种方法最常用于主题建模和k-means(这些超出本文的范围)。最后是介于两者之间的半监督学习。
在本文中,我们将使用监督学习,因此,我想从头到尾完整说明这个过程,然后解释它是如何工作的。
如上所述,监督学习是系统从一组已知标签的输入中进行学习的过程。为了进行正确的训练模型,我们将输入数据分为三类:训练数据、验证数据和测试数据。各类数据并没有没有固定的比例。然而,一个好的经验法则是,将所有注释数据的20%的用于测试,然后将剩下的80%以80/20的比例用于(训练/验证)。
前两种数据,训练数据和验证数据,训练模型。它利用训练数据通过预先确定的算法来打磨统计模型。通过预测正确的标签与提供的标签比较,检查其准确性,并做出相应的调整。
所有训练数据的查看与预测完成后,就完成了第一个epoch(数据迭代)。在这个阶段,模型然后根据验证数据测试其准确性。这些数据被排除在训练过程之外,让系统对其整体表现有一个感受。
因为验证数据不在训练过程中,所以它可以用于训练中测试(或验证)其准确性。然后,训练数据被随机化,并epoch次送回系统。同样,没有一个标准来衡量epoch的数值,但是一个好的经验法则是从10开始,看看结果。
一旦模型将这个过程重复到epoch次,它就完成了训练。然后,可以针对测试数据集测试模型的准确性,看看它的性能如何。希望将测试数据与验证数据分开的原因是,尽管一些验证数据没有包含在培训中,但它们渗透到训练过程中。因为测试数据有很好的注释,研究者可以得到模型执行情况的准确感受。
保存了第一个模型后,通常的做法是多次调整模型的参数,以尝试创建更精确的模型。所有的模型都将根据相同的测试数据进行测试。
在这一阶段,根据结果,可能需要获得更多的训练数据,可能需要进行另一个测试,或者研究者可以开始在未见的数据上部署模型并检查结果。未见的数据将是没有注释的数据。
下面的图片很好的描述了这一过程。在图像中,我们看到原始数据输入到算法。
正如标题所示,本节将探索数学领域,特别是统计和线性代数。即使你不是数学迷,也请阅读这一节,因为它包含了机器学习背后的基本原理。为了简单解释机器学习是如何工作的(更重要的是为什么),我将尽量减少数学运算。这将让你更好地了解何时使用机器学习方法来进行NER,以及为什么你可能得不到你想要看到的结果,以及如何改进。
监督学习背后的核心概念是统计模型,这是一个可保存和可应用的系统,给定输入(文本)其输出某种结构化数据(实体的标签)。为了理解什么是模型以及它们是如何工作的,我建议看看下面的短视频:
https://www.youtube.com/embed/I9Nl8QIIP54
通过spaCy,NLP工作者不必沉溺于枯燥的神经网络结构。而是利用由spaCy开发的最先进的神经网络架构,只需几行代码就可以轻松地训练spaCy模型。如果你对训练过程是如何工作的感到好奇,我推荐下面的视频:
https://www.youtube.com/embed/s8yQ4lRrEIY
上节我解释了何时使用基于规则的NER。当你在一个语料库不能列出所有多变的实体,或创建这样的模式过于复杂,相较其准确性不值得时,机器学习就很有用。一个很好的例子就是一个人名字的每一种可能的变化。考虑一下世界上存在的大量人名,以及这些人名的各种组合,它们可以组合成一个独特的实体。即使我们只有1万个姓和1万个名,也需要1亿个模式来匹配所有的变体。世界上姓和名的数量都远不止1万个。
另一种考虑机器学习的情况是,当输入到你的系统的数据不会被完全清理。再次考虑名字的例子。即使你的系统能够处理这些名称的所有1亿种形式,它也会遗漏那些OCR质量较差或具有不同文本编码的名称,因为它是在UTF-8之前创建的文本。
在这些情况下,机器学习是正确的选择,因为机器学习模型不记忆实体。它是学习实体。这意味着,如果一个模型遇到了一个它以前从未见过的实体(即使是错误的),它也有能力用正确的标签泛化和识别这个实体。
所幸正如我们将在本系列中看到的,spaCy不仅使使用机器学习模型变得容易,而且大大降低了训练定制机器学习器模型的复杂性。
#加载库
import spacy
#加载预训练模型
nlp = spacy.load("en_core_web_sm")
#给定文本
text = "Jon Stewart hosted The Daily Show that aired in New York City."
#创建doc
doc = nlp(text)
#从doc中抽取所有实体和相应标签
for ent in doc.ents:
print (ent.text, ent.label_)
Jon Stewart PERSON
The Daily Show WORK_OF_ART
New York City GPE
在上面的例子中,我们看到这小型的英文模型正确地识别了约翰·斯图尔特的身份。它认为《每日秀》是一个有趣的艺术作品,真的是这样的。最后,它正确地将纽约市确定为GPE(地缘政治实体)。现在,让我们引入一些文本损坏问题,以了解模型是如何执行的。
#加载库
import spacy
#加载预训练模型
nlp = spacy.load("en_core_web_sm")
#create a sample text
text = "Jon Stewwasrt hosted The Daily Show that aired in New Yasdfasasdfrk City."
#创建doc
doc = nlp(text)
#从doc中抽取所有实体和相应标签
for ent in doc.ents:
print (ent.text, ent.label_)
Jon Stewwasrt PERSON
The Daily Show WORK_OF_ART
New Yasdfasasdfrk City GPE
请注意,尽管引入了文本损坏,但模型的执行方式完全相同。为什么呢?因为它是从上下文中学习的。它可能以前见过“主持”(hosted)这个词,知道通常是人来主持。它也看到大写的City这个词经常被用来描述GPE。这也就是为什么机器学习能够为不一致的实体或如此数量庞大的实体提供解决方案,但将它们全部合并到一个EntityRuler则是不可能的。
在以上的代码中,我们尝试了一个小的预训练英语模型。据你所学进行一些猜想:尽管引入了文本损坏,但模型为什么会成功或错误地标识出实体?当你这样做时,请记住这是一个小模型。较大的模型性能更好,其原因我们将在本系列的后面讨论。
https://www.youtube.com/embed/2Ny0yATnuxY
Python库spaCy为进行基于规则的NER提供了几种不同的方法。其中一种方法是通过它的EntityRuler。
EntityRuler是一个spaCy工厂,允许创建一组带有相应标签的模式。spaCy中的工厂是一组预加载在spaCy中的类和函数,它们用来执行设置任务。对于EntityRuler,随时可用的工厂允许用户创建EntityRuler,给它一组指令,然后使用该指令来发现和标记实体。
一旦用户创建了EntityRuler并给它一组指令,用户就可以将其作为新管道添加到spaCy管道中。我已经在以前的章节中简要地讲过管道,这里将更加详细地说明对其进行说明。
管件是管道的一个组件。管道的目的是获取输入数据,对输入数据执行某种操作,然后将这些操作输出为新数据或提取的元数据。管件是管道的单个组件。在spaCy中,有几个不同的管件执行不同的任务。分词器,将文本标记为单个词标记;解析器解析文本,NER识别实体并相应地标记它们。所有这些数据都存储在Doc对象中,正如我们在本文的01_02中看到的那样。
记住管道是有顺序的非常重要。这意味着管道中较前的组件会影响后来的组件接收到的内容。有时,这个顺序非常重要,这意味着以后的管件依赖于以前的管件。某些情况下,这个顺序则不是必需的,这意味着以后的管道可以在没有以前的管道的情况下运行。在创建自定义spaCy模型(或任何管道)时,务必记住这一点。
在本文中,我们将密切关注EntityRuler作为一个spaCy模型管道的组件。现成的spaCy模型预装了一个NER模型;然而,他们中并没有一个真正的EntityRuler。为了将一个EntityRuler合并到一个spaCy模型中,它必须被创建为一个新的管件,给出指令,然后添加到模型中。这些工作完成后,用户可以将带有EntityRuler的新模型保存到磁盘上。
spaCy EntityRuler的完整文档可以在这里找到:EntityRuler · spaCy API Documentation
本文为非专业人士摘录了此文档,并提供了一些实际应用中的例子。
在下面的代码中,我们将在spaCy现成的小型英语模型中引入一个新管件。这个EntityRuler的目的是正确地识别波兰的小村庄。
#导入所需库
import spacy
#加载预训练模型
nlp = spacy.load("en_core_web_sm")
#示例文本
text = "Treblinka is a small village in Poland. Treblinka was also an extermination camp."
#创建doc
doc = nlp(text)
#抽取实体
for ent in doc.ents:
print (ent.text, ent.label_)
Treblinka ORG
Poland GPE
Treblinka PERSON
从以上代码的结果可以看出,spaCy的小模型无法正确识别波兰的一个小村庄特雷布林卡(Treblinka)。正如示例文本所示,它在二战期间也是一个灭绝营。在第一句中,spaCy模型将特雷布林卡标记为组织,在第二句中将其标记为人。两者都是错误的。第二句话我会接受ORG,因为spaCy的模型不知道如何对一个灭绝营进行分类,但这些结果表明该模型未能根据数据进行泛化。为什么呢?原因有几个,但我怀疑这个模型从来没有遇到过“特雷布林卡”这个词。
在特定领域的自然语言处理中这是常见的问题。通常情况下,对于特定领域,现成的模型会失败,因为它们没有接受过领域特定文本的训练。然而,我们可以通过spaCy的EntityRuler或训练一个新模型来解决这个问题。我们将在接下来的几节中看到,可以使用spaCy的EntityRuler轻松实现这两个目标。
现在,让我们先纠正这个问题,为模型提供正确识别特雷布林卡的指令。为简单起见,我们将使用spaCy的GPE标签。在以后的章节中,我们将教模型,在后面的上下文中将特雷布林卡识别为集中营。
#导入所需库
import spacy
#加载预训练模型
nlp = spacy.load("en_core_web_sm")
#示例文本
text = "Treblinka is a small village in Poland. Treblinka was also an extermination camp."
#创建EntityRuler
ruler = nlp.create_pipe("entity_ruler")
#实体和模式列表
patterns = [
{"label": "GPE", "pattern": "Treblinka"}
]
#给ruler添加模式
ruler.add_patterns(patterns)
#在"ner"管件之前给模型添加ruler
nlp.add_pipe(ruler, before="ner")
doc = nlp(text)
#抽取实体
for ent in doc.ents:
print (ent.text, ent.label_)
Treblinka GPE
Poland GPE
Treblinka GPE
现在请注意,我们的EntityRuler在“ner”管件之前运行,因此,在NER到达之前预先查找到了实体并标记了它们。因为它在管道中出现得较早,所以它的元数据优先于后面的“ner”管件。另一种选择是将EntityRuler保留在管道的末端,并赋予它覆盖" ner "管件的能力:
#导入所需库
import spacy
#加载预训练模型
nlp = spacy.load("en_core_web_sm")
#示例文本
text = "Treblinka is a small village in Poland. Treblinka was also an extermination camp."
#导入spaCy EntityRuler class
from spacy.pipeline import EntityRuler
#创建EntityRuler,并置为覆盖模式
ruler = EntityRuler(nlp, overwrite_ents=True)
#实体和模式列表
patterns = [
{"label": "GPE", "pattern": "Treblinka"}
]
#给EntityRuler添加模式
ruler.add_patterns(patterns)
#将EntityRuler加入管道
nlp.add_pipe(ruler)
#创建doc
doc = nlp(text)
#抽取实体
for ent in doc.ents:
print (ent.text, ent.label_)
Treblinka GPE
Poland GPE
Treblinka GPE
在某些情况下,标签可能有一组不同类型的变体,这些变体遵循一个或一组不同的模式。电话号码就是一个这样的例子(spaCy文档中)。在美国,电话号码有几种形式。标准的形式化方法是(xxx)-xxx-xxxx,但经常会看到xxx-xxx-xxxx或xxxxxxxxxx。如果机主将相同的号码提供给美国以外的人,那么则变为+1(xxx)-xxx-xxxx。
对于美国这一特定领域,则可以将RegEx公式传递给模式匹配器以获取所有这些实例。
通过将规则传递给模式,spaCy EntityRuler还允许用户引入各种复杂的规则和变体(通过RegEx)。有许多参数可以传递给模式。有关完整列表,请参见: Rule-based matching · spaCy Usage Documentation 。体验其如何工作,我建议使用 spaCy Matcher演示:Rule-based Matcher Explorer · Explosion。
在下面的示例中,我们将使用spaCy文档的中一个示例,其中我们从文本中提取了一个电话号码。当然,同样的任务也可以通过RegEx完成。
#导入所需库
import spacy
#创建一个空白英文模型
nlp = spacy.blank("en")
#示例文本
text = "This is a sample number (555) 555-5555."
#导入spaCy EntityRuler class
from spacy.pipeline import EntityRuler
#创建EntityRuler,并置为覆盖模式
ruler = EntityRuler(nlp, overwrite_ents=True)
#实体和模式列表 (source: https://spacy.io/usage/rule-based-matching)
patterns = [
{"label": "PHONE_NUMBER", "pattern": [{"ORTH": "("}, {"SHAPE": "ddd"}, {"ORTH": ")"}, {"SHAPE": "ddd"},
{"ORTH": "-", "OP": "?"}, {"SHAPE": "dddd"}]}
]
#给EntityRuler添加模式
ruler.add_patterns(patterns)
#将EntityRuler加入管道
nlp.add_pipe(ruler)
#创建doc
doc = nlp(text)
#抽取实体
for ent in doc.ents:
print (ent.text, ent.label_)
(555) 555-5555 PHONE_NUMBER
在spaCy中还有另外两个基于规则的方法:Matcher和PhraseMatcher。虽然这些函数与EntityRuler类似,但它们没有将这些匹配项标识为实体,而是提供了它们自己的惟一标签和id,其超出了Doc对象中实体的范围。
在本节中,尝试开发一个定制EntityRuler来在你自己领域的特定文本中找到一个自定义实体。
以下视频,我们将探索spaCy的EntityRuler的实体用例,在《哈利·波特》第一册中寻找哈利·波特角色。
https://www.youtube.com/embed/wpyCzodvO3A
在本节中,我们将更加仔细地观察训练集,它们是什么,它们为什么重要,以及如何使用spaCy的EntityRuler来自动创建一个良好的训练数据集(太好了!),此数据集需要手动检查。在下一个视频中,我将向你展示如何使用此训练集在spaCy中训练自定义的NER模型。
spaCy的一个优点是,它的可伸缩性非常好(这意味着它可以在小数据和大数据上表现同样良好),它可以很容易地定制和运用高级机器学习方法,而几乎不需要机器学习知识。然而,如本文的第3节所述,理解机器学习的基础知识会很有帮助,因为它将使你了解如何创建一个良好的训练集,以及为什么某些方法可能会失败或陷入困境。事实上,通过简单的操作,你将对在机器学习NER中,某些方法是否有效产生感觉。
在本文的第3节,曾提到用于训练机器学习模型的数据有三种形式:训练数据、验证数据和测试数据。所有这些数据将采用相同的形式。它将是一个列表数据结构,其中每个索引将包含一个文本(一个句子、段落或整个文本)。文本的长度将取决于你希望通过ML-NER实现的目标。文本的大小会影响训练过程。不过,在此我们先忽略这一点。训练数据需要的另一个元素是该文本中实体的列表,包括它们在文本中的开始位置、结束位置和标签。在训练过程中,这些注释将允许卷积神经网络(spaCy机器学习训练过程背后的体系结构)从数据中学习,并能够正确识别正在训练的实体。
SpaCy严格要求你的训练数据以一种特定的形式出现:
TRAIN_DATA = [ (TEXT AS A STRING, {“entities”: [(START, END, LABEL)]}) ]
请注意,TRAIN_DATA是大写的。除了少数例外,python是不大写对象的, TRAIN_DATA即是一个例外。我不知道这个惯例的历史,但是在每本书或教程中,你总会看到TRAIN_DATA是这样的。当然,这是也并非必要,但是在你的代码中尽可能的使用Pythonic总归是一个好作法,这样其他人就可以更容易地阅读你的代码。任何机器学习工作者都希望看到TRAIN_DATA以这种形式呈现。
手工将训练数据转换成这种格式非常困难。研究人员必须计算字符数来指定实体的开始和结束位置。即使使用Python内置的字符串函数来获取起始字符和结束字符,也会遇到另一个问题。spaCy的训练过程读取起始字符和结束字符的方式与使用字符串函数计算字符位置的方式不同。这意味着在训练过程中,spaCy将删除与标记开始和结束位置不一致的标注。这是因为字符串函数对文本分词的方式与spaCy不同。幸运的是,有一些通过EntityRuler内置于spaCy中的方法,能够帮助你完成这一过程。
如果你对手工注释感兴趣,我强烈建议你去试一下Explosion AI的付费软件Prodigy (Prodigy · An annotation tool for AI, Machine Learning & NLP)。我不会因为推销那种产品而得到报酬的。它很昂贵,但是如果你需要做很多注释(对于图像、文本、视频,甚至音频),那么Prodigy就是你的理想选择。它有一个很友好的用户界面,因为它是由spaCy的同一个团队开发的,它可以无缝地融入spaCy工作流程。你可以在这里测试Prodigy演示:https://prodi.gy/demo。
在下面的代码中,我们将通过EntityRuler制作一个spaCy的机器学习训练集。换句话说,我们将使用基于规则的方法自动生成一个基本训练集。这个训练集会不会有错误?很有可能。这就是为什么要查看训练集并手动验证。然而,通过这种方式,可以大幅增进原型设计,以确定想要训练的定制实体是否具有潜在可行性。在机器学习中,对于特定领域的问题很少有固定的解决方案。如果有的话,就不需要专家了。不断尝试和实验通常是机器学习这种项目的代名词,NER机器学习也一样。
因为我们将暂时只使用此模型,因此创建一个空白英文模型。我们不需要其他组件。这个模型只有一个EntityRuler,我们将临时使用它来生成训练集。回想在上一节中,spaCy小型模型无法正确识别Treblinka作为位置。在下面的代码中,我们将从这三个句子中创建一个基本的训练集,这是一个非常小的训练集。在此声明:这些训练数据远不足以训练一个模型。但其可以很好地扩展。
这里的代码与我们之前看到的相同,但示例文本略有不同。注意它的输出:其正确地将Treblinka识别为GPE。
#导入所需库
import spacy
#创建一个空白英文模型
nlp = spacy.blank("en")
#示例文本
text = "Treblinka is a small village in Poland. Wikipedia notes that Treblinka is not large."
#创建EntityRuler
ruler = nlp.create_pipe("entity_ruler")
#实体和模式列表
patterns = [
{"label": "GPE", "pattern": "Treblinka"}
]
ruler.add_patterns(patterns)
nlp.add_pipe(ruler)
doc = nlp(text)
#抽取实体
for ent in doc.ents:
print (ent.text, ent.label_)
Treblinka GPE
Treblinka GPE
现在,我们稍微修改一下这段代码,使其生成一个稍微不同的输出结果,此输出带有文本开头和结尾信息。
#导入所需库
import spacy
#创建一个空白英文模型
nlp = spacy.blank("en")
#示例文本
text = "Treblinka is a small village in Poland. Wikipedia notes that Treblinka is not large."
#创建EntityRuler
ruler = nlp.create_pipe("entity_ruler")
#实体和模式列表
patterns = [
{"label": "GPE", "pattern": "Treblinka"}
]
ruler.add_patterns(patterns)
nlp.add_pipe(ruler)
doc = nlp(text)
#抽取实体
for ent in doc.ents:
print (ent.text, ent.start_char, ent.end_char, ent.label_)
Treblinka 0 9 GPE
Treblinka 61 70 GPE
注意,现在我们的输出分别用0,9和61,70表示每个实体的开始和结束位置。有了这些数据,我们就可以开始生成我们想要的输出。我们先试着把输入的文本分解成句子,然后得到两组不同的训练数据。
#导入所需库
import spacy
#创建一个空白英文模型
nlp = spacy.blank("en")
#示例文本
text = "Treblinka is a small village in Poland. Wikipedia notes that Treblinka is not large."
#创建EntityRuler
ruler = nlp.create_pipe("entity_ruler")
#实体和模式列表
patterns = [
{"label": "GPE", "pattern": "Treblinka"}
]
ruler.add_patterns(patterns)
nlp.add_pipe(ruler)
#遍历句子
for sentence in corpus:
doc = nlp(sentence)
#抽取实体
for ent in doc.ents:
print (ent.text, ent.start_char, ent.end_char, ent.label_)
[‘Treblinka is a small village in Poland.’, ‘Wikipedia notes that Treblinka is not large.’]
Treblinka 0 9 GPE
Treblinka 21 30 GPE
注意,输出和开头和结尾都发生了变化。现在,我们可以再次修改我们的代码,使其成为我们想要的格式:
TRAIN_DATA = [ (TEXT AS A STRING, {“entities”: [(START, END, LABEL)]}) ]
#导入所需库
import spacy
#创建一个空白英文模型
nlp = spacy.blank("en")
#示例文本
text = "Treblinka is a small village in Poland. Wikipedia notes that Treblinka is not large."
#创建EntityRuler
ruler = nlp.create_pipe("entity_ruler")
#实体和模式列表
patterns = [
{"label": "GPE", "pattern": "Treblinka"}
]
ruler.add_patterns(patterns)
nlp.add_pipe(ruler)
TRAIN_DATA = []
#遍历句子
for sentence in corpus:
doc = nlp(sentence)
entities = []
#抽取实体
for ent in doc.ents:
#以要求的格式追加到entities
entities.appen