自然语言处理实战第二版(MEAP)(六)(4)https://developer.aliyun.com/article/1519696
实体和关系名称类似于 Python 中的变量名称。你希望能够像数据库中的字段名称那样查询它们,因此它们不应该有歧义的拼写。原始的 NELL 数据集每行包含一个三元组(事实)。三元组可以被读作简洁、明确定义的句子。知识三元组描述了世界上的一个单独事实。它们给出了关于世界中一个实体(对象)的一条信息。
至少,知识三元组由实体、关系和值组成。知识三元组的第一个元素给出了关于事实的实体名称。第二列“关系”包含与世界中某些其他性质(形容词)或对象(名词)的关系,称为它的值。关系通常是以“是”或“有”等词开始或暗示的动词短语。第三列“值”包含该关系的某个质量的标识符。该“值”是关系的对象,并且与三元组的主语(“实体”)一样是一个命名实体。
因为 NELL 众包了知识库的管理,所以你还有一个概率或置信度值,可以用来推断冲突信息。而且 NELL 还有关于事实的 9 个更多信息列。它列出了用于引用特定实体、关系或值的所有替代短语。NELL 还识别了创建该事实的迭代(遍历 Twitter)。最后一列提供了数据的来源 - 创建事实的所有文本列表。
NELL 包含有关 800 多种唯一关系和超过 200 万个实体的事实。因为 Twitter 主要涉及人物、地点和企业,所以它是一个很好的知识库,可用于增强常识知识库。它对于对名人或企业以及经常是错误信息宣传目标的地点进行事实核查也可能很有用。甚至有一个 “latitudelongitude” 关系,您可以使用它来验证与事物位置相关的任何事实。
>>> islatlon = df['relation'] == 'latlon' >>> df[islatlon].head() entity relation value 241 cheveron latlon 40.4459,-79.9577 528 licancabur latlon -22.83333,-67.88333 1817 tacl latlon 13.53333,37.48333 2967 okmok latlon 53.448195,-168.15472 2975 redoubt_volcano latlon 60.48528,-152.74306
现在你已经学会了如何将事实组织成知识图谱。但是当我们需要使用这些知识时 - 例如,用于回答问题时,我们该怎么办?这将是本章最后一节要处理的内容。
11.9 在知识图谱中查找答案
现在我们的事实都组织在一个图形数据库中,我们如何检索那些知识呢?与任何数据库一样,图形数据库有特殊的查询语言来从中提取信息。就像 SQL 及其不同的方言用于查询关系数据库一样,一系列语言,如 SPARQL(SPARQL 协议和 RDF 查询语言),Cypher 和 AQL 存在用于查询图数据库。在本书中,我们将专注于 SPARQL,因为它已被开源社区采用为标准。其他语言,如 Cypher 或 AQL,用于查询特定的图知识库,如 Neo4j 和 ArangoDB。
我们将使用一个比 NELL 更大的知识图谱作为我们的知识基础:Wikidata,维基百科的知识数据库版本。它包含超过 1 亿个数据项(实体和关系),由志愿编辑和机器人维护,就像所有其他维基媒体项目一样。
在 Wikidata 中,实体之间的关系被称为 属性。Wikidata 系统中有超过 11,000 个属性,每个属性都有其 “P-id”,这是一个用于在查询中表示该属性的唯一标识符。同样,每个实体都有其自己独特的 “Q-id”。您可以通过使用 Wikidata 的 REST API 轻松检索任何维基百科文章的 Q-id:
>>> def get_wikidata_qid(wikiarticle, wikisite="enwiki"): ... WIKIDATA_URL='https://www.wikidata.org/w/api.php' ... resp = requests.get(WIKIDATA_URL, timeout=5, params={ ... 'action': 'wbgetentities', ... 'titles': wikiarticle, ... 'sites': wikisite, ... 'props': '', ... 'format': 'json' ... }).json() ... return list(resp['entities'])[0] >>> tg_qid = get_wikidata_qid('Timnit Gebru') >>> tg_qid 'Q59753117'
您可以通过前往 (www.wikidata.org/entity/Q59753117
) 确认您的发现,并在那里找到该实体的更多属性,将其链接到不同的实体。正如您所看到的,这是一个简单的 “GET” 查询,只有在我们已经有了实体名称并且想要找到 Q-id(或反之)时才有效。对于更复杂的查询,我们将需要使用 SPARQL。那么我们来写你的第一个查询吧!
假设你想知道谁是 Timnit Gebru 的关于 Stochastic Parrots 的著名论文的合著者。如果你不记得论文的名称确切,你实际上可以通过一个简单的查询找到它。为此,你需要一些属性和实体 ID - 为简单起见,我们只在代码中列出它们。
>>> NOTABLE_WORK_PID = 'P800' # #1 >>> INSTANCE_OF_PID = 'P31' # #2 >>> SCH_ARTICLE_QID= 'Q13442814' # #3 >>> query = f""" ... SELECT ?article WHERE {{ ... wd:{tg_qid} wdt:{NOTABLE_WORK_PID} ?article. ... ?article wdt:{INSTANCE_OF_PID} wd:Q13442814. ... ... SERVICE wikibase:label {{ bd:serviceParam ... wikibase:language "en". }} ... }} ... """
重要提示
不要忘记在 f-strings 中双重转义大括号!而且你不能在 f-strings 中使用反斜杠作为转义字符。 错误:f"{“,而应该是双大括号。 正确:f”{{"
如果你熟悉jinja2
包,请注意混合使用 Python f-strings 来填充 jinja2 模板时,你需要四个花括号来创建一个文字双花括号。
乍一看,这个查询看起来有些神秘,它的意思是“找到一个实体 A,使得 Timnit Gebru 有 A 作为知名作品,并且 A 是学术文章的一个实例”。你可以看到每个关系条件在 SPARQL 中是如何编码的,操作数 wd:
在实体 Q-id 前面,操作数 wdt:
在属性 P-id 前面。每个关系约束都有一个“实体有-属性-实体”的形式。
现在让我们使用 WIKIDATA 的 SPARQL API 来检索我们查询的结果。为此,我们将使用一个专门的SPARQLWrapper
包,它将简化我们的查询过程。首先,让我们设置我们的包装器:
>>> from SPARQLWrapper import SPARQLWrapper, JSON >>> >>> endpoint_url = "https://query.wikidata.org/sparql" >>> sparql = SPARQLWrapper(endpoint_url) >>> sparql.setReturnFormat(JSON) # #1
一旦设置好,你就可以执行你的查询并检查响应:
>>> sparql.setQuery(query) >>> result = sparql.queryAndConvert() >>> result {'head': {'vars': ['article', 'articleLabel']}, 'results': {'bindings': [{'article': {'type': 'uri', 'value': 'http://www.wikidata.org/entity/Q105943036'}, 'articleLabel': {'xml:lang': 'en', 'type': 'literal', 'value': 'On the Dangers of Stochastic Parrots: Can Language Models Be Too Big?🦜'}}]}}
看起来没问题!现在你已经得到了文章的 Q-id - 你可以通过使用文章的 ‘author’ 属性来检索它的作者:
>>> import re >>> uri = result['results']['bindings'][0]['article']['value'] >>> match_id = re.search(r'entity/(Q\d+)', uri) >>> article_qid = match_id.group(1) >>> AUTHOR_PID = 'P50' >>> >>> query = f""" ... SELECT ?author ?authorLabel WHERE {{ ... wd:{article_qid} wdt:{AUTHOR_PID} ?author. ... SERVICE wikibase:label {{ bd:serviceParam wikibase:language "en". }} ... }} ... """ >>> sparql.setQuery(query) >>> result = sparql.queryAndConvert()['results']['bindings'] >>> authors = [record['authorLabel']['value'] for record in result] >>> authors ['Timnit Gebru', 'Margaret Mitchell', 'Emily M. Bender']
现在你有了你问题的答案!
我们可以通过将查询嵌套在彼此内部来完成相同的结果,而不是执行两个查询,就像这样:
>>> query = """ ... SELECT ?author ?authorLabel WHERE { ... { ... SELECT ?article WHERE { ... wd:Q59753117 wdt:P800 ?article. ... ?article wdt:P31 wd:Q13442814. ... } ... } ... ?article wdt:P50 ?author. ... SERVICE wikibase:label { ... bd:serviceParam wikibase:language "en". ... } ... } ... """
SPARQL 是一种功能齐全的语言,其功能不仅限于简单的查询。Wikidata 本身对 SPARQL 有一个相当不错的手册。你挖掘 Wikidata 使用 SPARQL 的深度越深,你将在你的 NLP 应用中找到越多的用途。这是你可以自动评估你的 NLP 流水线对用户断言的事实的质量和正确性的唯一方式之一。
11.9.1 从问题到查询
因此,你成功在知识数据库中找到了一个相当复杂的问题的答案。如果你的数据库是关系型的,或者你只有非结构化的文本,那几乎是不可能做到的。
然而,寻找答案花费了我们相当多的工作和两个 SPARQL 查询。如何将自然语言问题转化为像 SPARQL 这样的结构化语言的查询?
你以前已经做过这种转换,在第九章的时候。将人类语言翻译成机器语言比在人类语言之间进行翻译要困难一些,但对于机器来说,这仍然是同一个基本问题。现在你知道了转换器擅长将一种语言转换成另一种语言。作为庞大的转换器,LLMs 尤其擅长此类操作。Sachin Charma 创建了一个很好的示例,使用另一个图数据库 ArangoDB 构建知识图谱。他使用 OpenAI 的模型来使数据库上的自然语言问答成为可能。
11.10 自我测试
- 给出一个问题的例子,这个问题比使用关系数据库更容易回答。
- 使用
networkx
的有向图将其转换为一个 Pandas DataFrame 的边列表,其中包含两列source_node
和target_node
。对于单个源节点,检索所有目标节点 ID 需要多长时间?对于这些新的源节点的所有目标节点呢?如何通过索引加速 Pandas 图查询? - 创建一个 Spacy Matcher,可以从关于 Timnit Gebru 的维基百科文章中提取更多的工作地点。您能够检索到多少个?
- 图数据库能做到的事情有关系数据库不能做到的吗?关系数据库能做到图数据库不能做到的事情吗?
- 使用大型语言模型从自然语言生成 SPARQL wikidata 查询。在没有编辑代码的情况下,它是否正确工作?对于需要在您的知识图谱中进行五次关系(边)遍历的查询,它是否有效?
- 使用
nlpia2.text_processing
中的extractors.py
和heatmaps.py
为从您自己的长文档(可能是一系列关于自然语言处理的 Mastodon 微博帖子)中提取的句子创建 BERT 相似度热图。编辑heatmaps.py
代码以改进它,以便您可以专注于非常相似的行。提示:您可以使用非线性函数来缩放余弦相似度值,并使用阈值将相似度值重置为零。
11.11 总结
- 知识图谱可以用来存储实体之间的关系。
- 您可以使用基于规则的方法(如正则表达式)或基于神经网络的方法来隔离和提取非结构化文本中的信息。
- 词性标注和依赖分析允许您提取句子中提到的实体之间的关系。
- 像 SPARQL 这样的语言可以帮助您在知识图谱中找到所需的信息。
[1] Wikipedia 上的“Symbolic AI”文章(en.wikipedia.org/wiki/Symbolic_artificial_intelligence
)
[2] 参见名为“自然语言处理:TM-Town”的网页(www.tm-town.com/natural-language-processing#golden_rules
)。
[3] “PyPi 上的 Rstr 包”(pypi.org/project/rstr/
)。
[4] 参见名为“在 DuckDuckGo 上搜索 Python 句子分段”的网页(duckduckgo.com/?q=Python+sentence+segment&t=canonical&ia=qa
)。
[5] GitLab 上的手稿源代码(gitlab.com/tangibleai/nlpia2/-/tree/main/src/nlpia2/data/manuscript/adoc
)
[6] 单层神经网络或感知器中的每个神经元,在数学上等同于逻辑回归。
[7] 参见名为“Facts & Figures:spaCy 使用文档”的网页(spacy.io/usage/facts-figures
)。
[8] 参见名为“nltk.tokenize 包 — NLTK 3.3 文档”的网页(www.nltk.org/api/nltk.tokenize.html#module-nltk.tokenize.punkt
)。
[9] SpaCy 是迄今为止我们发现的最准确、最高效的 NLP 解析器,并由欧洲的一个出色的、超级合作的 NLP 工程师团队在 Explosion.ai 定期维护和更新(explosion.ai/about
)。
[10] GitLab 上 nlpia2 包中的heatmaps.py
模块(gitlab.com/tangibleai/nlpia2/-/blob/main/src/nlpia2/heatmaps.py
)。
[11] GitLab 上 nlpia2 包中的extractors.extract_lines()
函数(gitlab.com/tangibleai/nlpia2/-/blob/main/src/nlpia2/text_processing/extractors.py#L69
)。
[12] 官方 AsciiDoc 解析器是 Ruby。根据文档,目前还没有 Python 包(gitlab.eclipse.org/eclipse-wg/asciidoc-wg/asciidoc.org/-/blob/main/awesome-asciidoc.adoc#convert
)。
[13] 斯坦福大学 AI 指数上有关 AI 研究的统计数据(AIIndex.org
)。
[14] spaCy 使用“OntoNotes 5”词性标签:(spacy.io/api/annotation#pos-tagging
)。
[15] 查看名为“代码示例:spaCy 使用文档”的网页(spacy.io/usage/examples#phrase-matcher
)。
[16] 查看名为“匹配器:spaCy API 文档”的网页(spacy.io/api/matcher
)。
[17] 这是一项积极研究的课题:nlp.stanford.edu/pubs/structuredVS.pdf
。
[18] “实现用于关系抽取的自定义可训练组件”:(explosion.ai/blog/relation-extraction
)。
[19] 有一些硬编码的常识知识库供您使用。Google Scholar 是您在知识图谱搜索中的好朋友。
[20] Wikidata SPARQL 教程:(www.wikidata.org/wiki/Wikidata:SPARQL_tutorial
)。
[21] 如何使用 ChatGPT 和 ArangoDB 构建知识图谱增强的聊天机器人(archive.today/fJB7H
)。