自然语言处理实战第二版(MEAP)(六)(3)https://developer.aliyun.com/article/1519692
11.7.1 基于模式的关系提取
还记得你如何使用正则表达式来提取字符模式吗?单词模式就像是针对单词而不是字符的正则表达式一样。与字符类不同,你有单词类。例如,不是匹配小写字符,而是可能有一个单词模式决定匹配所有单数名词(“NN” POS 标签)。[14] 一些种子句子被标记了一些正确的关系(事实),这些关系是从这些句子中提取出来的。POS 模式可以用来找到类似的句子,其中主语和宾语词可能会改变,甚至关系词也会改变。
从文本中提取关系的最简单方法是查找所有"主语-动词-宾语"三元组,使用 ROOT 词的"nsubj"和"dobj"标签。但是让我们做一些更复杂的事情。如果我们想从维基百科中提取关于历史人物之间会面的信息怎么办?你可以使用 spaCy 包的两种不同方式来匹配这些模式,在(O(1))(常数时间)内无论你要匹配多少模式:
- 用于任何单词/标记序列模式的 PhraseMatcher[15]
- 用于 POS 标签序列模式的匹配器[16]
让我们从后者开始。
首先,让我们看一个例句并查看每个词的 POS:
spaCy 标记字符串的辅助函数
>>> doc_dataframe(nlp("In 1541 Desoto met the Pascagoula.")) ORTH LEMMA POS TAG DEP 0 In in ADP IN prep 1 1541 1541 NUM CD pobj 2 Desoto desoto PROPN NNP nsubj 3 met meet VERB VBD ROOT 4 the the DET DT det 5 Pascagoula pascagoula PROPN NNP dobj 6 . . PUNCT . punct
现在你可以看到形成良好模式的 POS 或 TAG 特征的序列。如果你正在寻找人与组织之间的“会面”关系,你可能希望允许诸如“PROPN met PROPN”、“PROPN met the PROPN”、“PROPN met with the PROPN”和“PROPN often meets with PROPN”等模式。你可以单独指定每个模式,也可以尝试通过一些*或?操作符捕捉所有这些模式之间的“任意单词”模式:
'PROPN ANYWORD? met ANYWORD? ANYWORD? PROPN'
在 spaCy 中,模式与此伪代码非常相似,但更加强大和灵活。SpaCy 模式非常类似于标记的正则表达式。像正则表达式一样,你必须非常冗长地解释你想在标记序列的每个位置上精确匹配的单词特征。在 spaCy 模式中,你使用列表的字典来捕捉你想要为每个标记或单词匹配的所有词性和其他特征。
示例 spaCy POS 模式
>>> pattern = [ ... {'POS': {'IN': ['NOUN', 'PROPN']}, 'OP': '+'}, ... {'IS_ALPHA': True, 'OP': '*'}, ... {'LEMMA': 'meet'}, ... {'IS_ALPHA': True, 'OP': '*'}, ... {'POS': {'IN': ['NOUN', 'PROPN']}, 'OP': '+'}]
然后,你可以从解析后的句子中提取你需要的标记化标记。
创建一个使用 spaCy 的 POS 模式匹配器
>>> from spacy.matcher import Matcher >>> doc = nlp("In 1541 Desoto met the Pascagoula.") >>> matcher = Matcher(nlp.vocab) >>> matcher.add( ... key='met', ... patterns=[pattern]) >>> matches = matcher(doc) >>> matches [(12280034159272152371, 2, 6)] # #1 >>> start = matches[0][1] >>> stop = matches[0][2] >>> doc[start:stop] # #2 Desoto met the Pascagoula
一个 spaCy 匹配器将列出模式匹配为包含匹配 ID 整数的 3 元组,以及每个匹配的起始和停止标记索引(位置)。因此,你从创建模式的原始句子中提取了一个匹配项,但是关于维基百科的类似句子呢?
使用 POS 模式匹配器
>>> doc = nlp("October 24: Lewis and Clark met their" \ ... "first Mandan Chief, Big White.") >>> m = matcher(doc)[0] >>> m (12280034159272152371, 3, 11) >>> doc[m[1]:m[2]] Lewis and Clark met their first Mandan Chief >>> doc = nlp("On 11 October 1986, Gorbachev and Reagan met at Höfði house") >>> matcher(doc) [] # #1
你需要添加第二个模式以允许动词在主语和宾语名词之后出现。
列表 11.8 将模式组合在一起以处理更多变化
>>> doc = nlp( ... "On 11 October 1986, Gorbachev and Reagan met at Hofoi house" ... ) >>> pattern = [ ... {'POS': {'IN': ['NOUN', 'PROPN']}, 'OP': '+'}, ... {'LEMMA': 'and'}, ... {'POS': {'IN': ['NOUN', 'PROPN']}, 'OP': '+'}, ... {'IS_ALPHA': True, 'OP': '*'}, ... {'LEMMA': 'meet'} ... ] >>> matcher.add('met', None, pattern) # #1 >>> matches = matcher(doc) >>> pd.DataFrame(matches, columns=) [(1433..., 5, 9), (1433..., 5, 11), (1433..., 7, 11), (1433..., 5, 12)] # #2 >>> doc[m[-1][1]:m[-1][2]] # #3 Gorbachev and Reagan met at Hofoi house
现在你有了你的实体和一个关系。你甚至可以构建一个在中间动词(“遇见”)上不那么限制性而在两边的人和团体的名字上更加限制性的模式。这样做可能会使你能够识别出其他暗示一个人或团体遇见另一个人或团体的动词,例如动词“知道”甚至被动短语,如“交谈”或“结识”。然后你可以使用这些新动词为两边的新专有名词添加关系。
但是你可以看到你的种子关系模式与原始含义渐行渐远。这被称为语义漂移。为了确保新句子中找到的新关系真正类似于原始种子(例子)关系,你通常需要限制主语、关系和宾语的词义与种子句子中的词义相似。做到这一点的最佳方式是利用单词含义的某种向量表示。幸运的是,spaCy 不仅使用其 POS 和依赖树信息为解析文档中的单词打标签,还提供了 Word2Vec 单词向量。你可以利用这个向量防止连接动词和两侧的专有名词与你的种子模式的原始含义相去甚远。
使用单词和短语的语义向量表示已经使得自动信息抽取的准确度足以自动构建大型知识库。但是需要人类监督和策划来解决自然语言文本中的大部分歧义。
11.7.2 神经关系抽取
现在你已经看到了基于模式的关系抽取方法,你可以想象研究人员已经尝试使用神经网络做同样的事情了。神经关系抽取任务传统上被分类为两类:封闭和开放。
在封闭关系抽取中,模型仅从给定的关系类型列表中提取关系。这样做的优点是我们可以尽量减少在实体之间得到不真实和奇怪的关系标签的风险,这使我们更有信心在现实生活中使用它们。但是限制是需要人类标记者为每个文本类别的相关标签制定一个列表,你可以想象,这可能会变得繁琐和昂贵。
在开放关系抽取中,模型试图为文本中的命名实体提出其自己的一组可能标签。这适用于处理大型且通常不为人所知的文本,如维基百科文章和新闻条目。
在过去几年里,深度神经网络的实验在三元组抽取方面取得了很强的结果,随后,关于这个主题的大部分研究都采用了神经方法。
不幸的是,与管道的前几个阶段相比,关系提取的现成解决方案并不多。而且,您的关系提取通常会非常有针对性。在大多数情况下,您不会想提取实体之间的所有可能关系,而只会提取与您要执行的任务相关的关系。例如,您可能希望从一组制药文件中提取药物之间的相互作用。
目前用于提取关系的先进模型之一是 LUKE(基于知识的语言理解)。LUKE 使用实体感知注意力 - 这意味着其训练数据包含了每个标记是否是实体的信息。它还经过训练,可以“猜测”基于维基百科数据集中的屏蔽实体(而不仅仅是猜测所有屏蔽的单词,就像 BERT 模型经过训练的那样)。
SpaCy 还包括一些基础设施来创建您自己的关系提取组件,但这需要相当多的工作。我们不会在本书的内容中涵盖这一部分。幸运的是,像 Sofie Van Landeghem 这样的作者已经创建了很好的资源(18),供您根据自己的特定需求进行定制培训关系提取器时参考。
训练您的关系提取模型
当训练您的关系提取器时,您将需要标记正确的标签数据,以便模型学会识别与您任务相关的关系。但是创建和标记大型数据集很困难,因此值得检查一下一些用于基准测试和微调最先进模型的现有数据集是否已经包含了您需要的数据。
DocRED 和斯坦福 TACRED 一起是关系提取方法的事实标准基准数据集和模型,因为它们的大小和知识图的一般性。
斯坦福文本分析会议关系提取数据集(TACRED)包含超过 100,000 个示例自然语言段落,配对其相应的关系和实体。它涵盖了 41 种关系类型。在过去几年里,研究人员通过诸如 Re-TACRED 和 DocRED 等数据集改进了 TACRED 的数据质量,并减少了关系类别中的歧义。
文档关系提取数据集(DocRED)扩展了可以用于关系提取的自然语言文本的广度,因为它包括需要解析多个句子的自然语言文本的关系。用于训练 DocRED 的训练和验证数据集目前(2023 年)是最大的人工注释数据集,用于文档级关系提取。DocRED 中的大部分人工注释知识图数据包含在 Wikidata 知识库中。相应的自然语言文本示例可以在维基百科的存档版本中找到。
现在你对如何将非结构化文本转化为一系列事实有了更好的了解。现在是我们流程的最后阶段了——建立知识数据库。
11.8 建立你的知识库
所以,你从文本中提取出了关系。你可以把它们都放到一个大表格中;但是,我们仍然在谈论知识图谱。是什么让这种特定的数据结构方式如此强大呢?
让我们回到我们在上一章中遇到的斯坦尼斯拉夫·彼得罗夫。如果我们想回答类似“斯坦尼斯拉夫·彼得罗夫的军衔是什么?”这样的问题,一个单一的关系三元组‘斯坦尼斯拉夫·彼得罗夫’、‘是-一个’、‘中校’是不够的——因为你的问答机器还需要知道“中校”是一个军衔。然而,如果你将你的知识组织成图形,回答这个问题就成为可能。看一下图 11.4 了解它是如何发生的。
图 11.4 斯坦尼斯拉夫知识图谱
此知识图谱中的红色边和节点代表了一个不能直接从关于斯坦尼斯拉夫的陈述中提取出的事实。但是,“中校”是一个军衔的事实可以从一个成员是军事组织成员的人的头衔是一个军衔的事实推断出来。从知识图谱中推导事实的这种逻辑操作称为知识图谱推理。它也可以称为查询知识库,类似于查询关系数据库。一个名为知识库问答的领域专注于找到更有效地回答这类问题(它们被称为“多跳问题”)的方法。
对于关于斯坦尼斯拉夫军衔的特定推理或查询,你的知识图谱必须已经包含有关军队和军衔的事实。如果知识库中还有关于人的头衔以及人与职业(工作)的关系的事实,甚至可能会有所帮助。也许现在你能明白,一组知识如何帮助机器比没有这些知识时更多地理解一个陈述。没有这组知识,一个简单陈述中的许多事实将会“超出”你的聊天机器人的理解范围。你甚至可以说,对职业等级的问题对于一个只知道如何根据随机分配的主题对文档进行分类的机器人来说是“超出了薪酬水平”的。(如果你忘记了随机主题分配的工作原理,请参阅第四章。)
这可能不太明显,但这是一个大问题。如果你曾经与一个不懂得"哪个方向是上"的聊天机器人交互过,字面上而言,你会明白。在人工智能研究中最具挑战性的挑战之一是编译和高效查询常识知识的知识图谱。我们在日常对话中理所当然地运用常识知识。
人类在获得语言技能之前就开始获取大部分常识知识。我们的童年并不是在写关于白天从光开始,夜晚通常在日落后开始睡觉的文章。我们也不会编辑维基百科文章,说明空腹应该只填满食物而不是泥土或石头。这使得机器很难找到一个常识知识语料库来阅读和学习。不存在供你的机器人进行信息提取的常识知识维基百科文章。而且其中一些知识是本能,已经硬编码到我们的 DNA 中。^([19])
事物和人之间存在各种各样的事实关系,如 “kind-of”、“is-used-for”、“has-a”、“is-famous-for”、“was-born” 和 “has-profession”。卡内基梅隆大学的永不停止的语言学习机器人 NELL 几乎完全专注于提取关于 'kind-of'
关系的信息。
大多数知识库会规范化定义这些关系的字符串,这样 “kind of” 和 “type of” 就会被分配一个规范化的字符串或 ID 来表示该特定关系。而一些知识库还会对表示知识库中对象的名词进行规范化,使用我们之前描述的指代消解。因此,二元组 “Stanislav Petrov” 可能会被分配一个特定的 ID。 “Stanislav Petrov” 的同义词,如 “S. Petrov” 和 “Lt Col Petrov”,如果 NLP 流水线怀疑它们指的是同一个人,则也会被分配给相同的 ID。
11.8.1 一个庞大的知识图谱
如果你曾经听说过"思维导图",它们可以很好地展示知识图谱是什么:你头脑中概念之间的连接。为了给你一个更具体的概念模型,你可能想探索网络上最古老的公共知识图谱:NELL(永不停止的语言学习)图,由我们在上一节中遇到的机器人创建。
NLPiA2 Python 包有一些实用工具,可以让 NELL 知识图谱稍微容易理解一些。在本章后面,你将看到关于这些工具如何工作的详细信息,以便你可以美化你正在处理的任何知识图谱。
>>> import pandas as pd >>> pd.options.display.max_colwidth = 20 >>> from nlpia2.nell import read_nell_tsv, simplify_names >>> df = read_nell_tsv(nrows=1000) >>> df[df.columns[:4]].head() entity relation value iteration 0 concept:biotechc... generalizations concept:biotechc... 1103 1 concept:company:... concept:companyceo concept:ceo:lesl... 1115 2 concept:company:... generalizations concept:retailstore 1097 3 concept:company:... generalizations concept:company 1104 4 concept:biotechc... generalizations concept:biotechc... 1095
实体名称在层次结构中非常精确和明确定义,就像文件的路径或 Python 中的命名空间变量名一样。所有实体和值名称都以“概念:”开头,因此你可以从你的名称字符串中去掉这个来使数据更容易处理。为了进一步简化事情,你可以消除命名空间层次结构,只关注层次结构中的最后一个名称。
>>> pd.options.display.max_colwidth = 40 >>> df['entity'].str.split(':').str[1:].str.join(':') 0 biotechcompany:aspect_medical_systems 1 company:limited_brands 2 company:limited_brands 3 company:limited_brands 4 biotechcompany:calavo_growers ... >>> df['entity'].str.split(':').str[-1] 0 aspect_medical_systems 1 limited_brands 2 limited_brands 3 limited_brands 4 calavo_growers ...
nlpia2.nell
模块进一步简化了事物的名称。这使得在网络图中浏览知识图谱变得更加容易。否则,实体的名称可能会填满绘图的宽度,并相互挤出。
>>> df = simplify_names(df) # #1 >>> df[df.columns[[0, 1, 2, 4]]].head() entity relation value prob 0 aspect_medical_systems is_a biotechcompany 0.924 1 limited_brands ceo leslie_wexner 0.938 2 limited_brands is_a retailstore 0.990 3 limited_brands is_a company 1.000 4 calavo_growers is_a biotechcompany 0.983
NELL 从 Twitter 上抓取文本,因此事实的拼写和措辞可能会变化很大。在 NELL 中,实体、关系和对象的名称已经通过将它们转换为小写并删除所有标点符号(如撇号和连字符)进行了标准化。只有专有名词允许保留它们的空格,以帮助区分包含空格的名称和被拼接在一起的名称。然而,在 NELL 中,就像在 Word2vec 标记标识符中一样,专有名词是用下划线(“_”)字符连接的。
自然语言处理实战第二版(MEAP)(六)(5)https://developer.aliyun.com/article/1519700