导语
审视可行的策略,以减轻紧张、增进自信并培养健康的关系。探索如何借助持续学习、正念、目标设定以及使用 Python 中的知识图谱技术来助力实现你的目标。无论你的追求是更高的事业目标、职业成功还是情绪稳定,这本宝贵的书籍都将为你提供应对挑战所需的工具。准备好迎接变革,认识到你的全部潜能,并塑造出你真正向往的生活。这是启程踏上改变之旅的起点。
什么是知识图谱
事先说明一点:在本文中,我们会经常讨论阐述”图“(一词),但(记住这)指的不是(什么)柱状图或饼状图。此外,我们将深入探讨诸如人员、地点和组织之类的相互关联实体,以构建一个知识图谱 Python。这种方法涉及知识图谱机器学习技术和 Python。
我们可以将”图“定义为节点和边的集合。
来看下面的图:
这里的节点A和节点B是两个不同的实体。这些节点由一条表示两个节点之间关系的边连接。到这里,这就是我们可以构建的最小的Python知识图谱——它也被称为三元组。
知识图谱的形状和大小各不相同,例如,截至2019年10月,Wikidata的知识图谱有59910568个节点。
如何在图中表示知识(信息)?
在我们开始构建知识图谱 Python 之前,重要的是要理解信息或知识是如何嵌入到这些图中的。
一个节点或实体也可以有多个关系。(例如)普京(Putin)不仅是俄罗斯总统,他还为苏联的安全机构克格勃(KGB)工作。但(话说回来)我们如何将关于普京的新信息纳入上述知识图谱Python中?
其实很简单,只需要为新实体 KGB 添加一个节点:
(此外)新的关联关系不仅可以出现在知识图谱中的第一个节点中,还可以在任何节点中出现,如下所示:
俄罗斯是亚太经济合作组织(APEC)的成员国。
找出实体和它们之间的关系对我们来说并不困难。但是,(像上面那样)徒手去构建知识图谱是不行的,没有人会花费大量时间去浏览成千上万份文件去找出所有的实体以及它们之间的关系!
这也是为什么机器更适合执行这项任务,因为对它们来说,阅读数百甚至数千份文档易如反掌。但是,另一个困难点是 —— 机器不理解自然语言。这也就是自然语言处理(NLP)的由来。。
为了从文本中构建知识图谱Python,重要的是要让我们的机器理解自然语言。这可以通过使用NLP技术来实现,如句子分割、依存分析、词性标注和实体识别。下面我们会更详细地讨论这些内容。
句子分割
构建知识图谱 Python 的第一步是将文本文档或文章分割为句子。然后,
“Indian tennis player Sumit Nagal moved up six places from 135 to a career-best 129 in the latest men’s singles ranking. The 22-year-old recently won the ATP Challenger tournament. He made his Grand Slam debut against Federer in the 2019 US Open. Nagal won the first set.”
“印度网球选手苏米特·纳加尔在最新的男子单打排名中从第135位上升了6位,达到职业生涯最佳的第129位。这位22岁的选手最近赢得了ATP挑战者锦标赛。他在2019年美国公开赛上首次与费德勒对阵,纳加尔赢得了第一盘比赛。”
让我们把上面的段落分成句子:
- Indian tennis player Sumit Nagal moved up six places from 135 to a career-best 129 in the latest men’s singles ranking 印度网球选手苏米特·纳加尔在最新的男子单打排名中上升了6位,从第135位上升到职业生涯最佳的第129位
- The 22-year-old recently won the ATP Challenger tournament 这位22岁的球员最近赢得了ATP挑战赛
- He made his Grand Slam debut against Federer in the 2019 US Open 他在2019年美网公开赛上首次在大满贯对阵费德勒。
- Nagal won the first set 纳加尔赢得了第一盘。
在上面的四个句子中,我们将第二个和第四个句子列入候选名单,因为它们都包含一个主语和一个宾语,在第二个句子中,“22-year-old”是主语,而“ATP Challenger tournament”是宾语,在第四个句子中,主语是“Nagal”,而“first set”是宾语:
困难在于机器(如何)理解文本,特别是在多词宾语和主语的情况下。例如,提取上面两个句子中的宾语就有点棘手。你能想到任何解决这个问题的方法吗?
实体提取
从句子中提取单个词实体并不是一项艰巨的任务。我们可以轻松地借助词性标记(POS)来完成这项任务。名词和专有名词将是我们的实体。
然而,当一个实体跨越多个单词时,仅有 POS 标记是不够的。我们需要解析句子的依赖树。可以在下面的文章中阅读更多关于依赖解析的信息。
让我们使用流行的 spaCy
库来实现获取其中一个列出句子的依赖标记这个任务:
Python代码:
import spacy
nlp = spacy.load('en_core_web_sm')
doc = nlp("The 22-year-old recently won ATP Challenger tournament.")
for tok in doc:
print(tok.text, "...", tok.dep_)
输出:
The ... det22-year ... amod- ... punctold ... nsubjrecently ... advmodwon ... ROOTATP ... compoundChallenger ... compoundtournament ... dobj. ... punct
依赖分析器显示这个句子的主语是“old”,但这不是我们想要的实体,我们想要提取的是“22-year-old”。
“22-year”的依赖标记是“amod”,意味着它是“old”的修饰语。因此,我们可以定义一个规则来提取这样的实体。
规则应该是这样的:提取主语/宾语及其修饰符,并提取它们之间的标点符号。
不过再看看句子中的宾语(dobj),是“tournament”,而不是“ATP Challenger tournament”。在这,我们没有修饰词,而是复合词。
复合词是那些共同构成一个新术语并具有不同含义的词。因此,我们可以将上述规则更新为:提取主语/宾语及其修饰词、复合词,并提取它们之间的标点符号。
简而言之,我们将使用依赖解析来提取实体。
关系提取
实体提取只是完成了工作的一半。为了构建知识图谱,我们还需要边来连接节点(实体)。这些边是一对节点之间的关系。
回到上一节的例子,我们列出了几个句子来构建知识图谱 Python:
你能猜出这两个句子中主语和宾语的关系吗?这两个句子有相同的关系 —— “won”。让我们看看如何提取这些关系。我们将再次使用依赖分析:
doc = nlp("Nagal won the first set.")
for tok in doc:
print(tok.text, "...", tok.dep_)
输出:
Nagal ... nsubjwon ... ROOTthe ... detfirst ... amodset ... dobj. ... punct
要提取关系,我们必须找到句子的根(即句子的动词),因此,从这个句子中提取的关系是“won”。
最后,这两个句子的知识图谱将是这样的:
从文本数据构建知识图谱
那么到这要开始写代码了!让我们打开 Jupyter Notebooks(或者任何你喜欢的 IDE,如Pycharm)。
我们将使用与电影相关的一组文本去从头开始构建知识图谱。我已经从500多篇文章中提取了大约4300个句子。这些句子中的每个句子都包含两个实体——一个主语和一个宾语。你可以从这里下载这些句子。
导入库
import re
import pandas as pd
import bs4
import requests
import spacy
from spacy import displacy
nlp = spacy.load('en_core_web_sm')
from spacy.matcher import Matcher
from spacy.tokens import Span
import networkx as nx
import matplotlib.pyplot as plt
from tqdm import tqdm
pd.set_option('display.max_colwidth', 200)
%matplotlib inline
读取数据
读取包含电影文本句子的CSV文件:
#导入文件
candidate_sentences = pd.read_csv("wiki_sentences_v2.csv")
candidate_sentences.shape
输出:(4318,1)
让我们来看看几个例句:candidate_sentences['sentence'].sample(5)
输出:
让我们看看其中一个句子的主语和宾语。理想情况下,句子中应该有一个主语和一个宾语:
doc = nlp("the drawdown process is governed by astm standard d823")
for tok in doc:
print(tok.text, "...", tok.dep_)
输出:
完美!只有一个主语(‘process’)和一个宾语(‘standard’)。你也可以用这个方式看看其他句子。
实体对抽取
构建知识图谱,最重要的是节点和它们之间的边。这些节点就是上面句子中的实体。而边是连接这些实体之间的关系。下面我们将以无监督的方式提取这些元素,即使用句子的语法。
主要思想是遍历一个句子,并在遇到主语和宾语时提取它们。然而,有个问题是:一个实体可能包括多个单词,例如“red wine”,而依赖分析器只能将单个单词标记为主语或宾语。
因此,创建一个函数来从句子中提取主语和宾语(实体),并解决上面的问题。为了方便看,将代码分成多个代码块:
def get_entities(sent):
## 块1
ent1 = ""
ent2 = ""
prv_tok_dep = "" # 句子中前一个标记的依赖标记
prv_tok_text = "" # 句子中前一个标记
prefix = ""
modifier = ""
#############################################################
for tok in nlp(sent):
## 块2
# 如果标记是一个标点符号,那么继续移至下一个标记。
if tok.dep_ != "punct":
#检查标记是否为复合词。
if tok.dep_ == "compound":
prefix = tok.text
# 如果前一个单词也是“复合词”,则将当前单词添加到它后面。
if prv_tok_dep == "compound":
prefix = prv_tok_text + " "+ tok.text
# 检查:标记是否为修饰语。
if tok.dep_.endswith("mod") == True:
modifier = tok.text
# 如果前一个单词也是“复合词”,则将当前单词添加到其中。
if prv_tok_dep == "compound":
modifier = prv_tok_text + " "+ tok.text
## 块3
if tok.dep_.find("subj") == True:
ent1 = modifier +" "+ prefix + " "+ tok.text
prefix = ""
modifier = ""
prv_tok_dep = ""
prv_tok_text = ""
## 块4
if tok.dep_.find("obj") == True:
ent2 = modifier +" "+ prefix +" "+ tok.text
## 块5
# 更新变量。
prv_tok_dep = tok.dep_
prv_tok_text = tok.text
#############################################################
return [ent1.strip(), ent2.strip()]
让我来解释一下上面函数中的代码块:
块1
我在这个块中定义了一些空变量。prv_tok_dep
和prv_tok_text
将分别保存句子中前一个单词的依赖项标记和前一个单词本身。prefix
和modifier
将保存与主语或宾语相关联的文本。
块2
接下来,我们将循环句子中的标记。我们将首先检查标记是否是标点符号。如果是,则忽略它并继续到下一个标记。如果标记是复合词的一部分(依赖标记=”compound”),我们将它保存在前缀变量中。复合词是多个单词的组合,连接形成一个具有新含义的单词(例如 “Football Stadium”, “animal lover”)。
当我们在句子中遇到主语或宾语时,我们会给它加上这个前缀。 我们对修饰词也会做同样的事情,比如“nice shirt”,“big house”等。
块3
如果标记是主语,那么它将被当作为 ent1 变量中的第一个实体,而诸如前缀、修饰符、prv_tok_dep 和 prv_tok_text 等变量将被重置。
块4
在这,如果标记是宾语,那么它将被当作为 ent2 变量的第二个实体,而变量如前缀、修饰符、prv_tok_dep 和 prv_tok_text 也将再次被重置。
块5
一旦我们获取了句子中的主语和宾语,我们将更新前面的标记及其依赖标记。
让我们用一个句子来测试这个函数:get_entities("the film had 200 patents")
输出: [‘film’, ‘200 patents’]
很好,它似乎和我们想的一样。在上面的句子中,“film”是主语,“200 patents”是宾语。
现在我们可以使用这个函数来提取数据中所有句子的实体对:
entity_pairs = []
for i in tqdm(candidate_sentences["sentence"]):
entity_pairs.append(get_entities(i))
entity_pairs 列表包含了 Wikipedia 中所有的主-客对,让我们来看看其中的几个:entity_pairs[10:20] entity_pairs [20]
输出:
依上面所见,这些实体对中有一些代词,如“we”、“it”、“she”等。我们希望使用专有名词或名词来代替。因此也许我们可以进一步改进 get_entities()
函数来过滤掉代词。但现在,我们先不变继续进行关系提取。
关系/谓词提取
这是本文非常有趣的一个部分。我们的假设是谓词是句子中的主要动词。例如,在句子“Sixty Hollywood musicals were released in 1929”中,动词是“released in”,而这就是我们从这个句子中获取的,将要用作三元组的谓语部分。
下面的函数能够从句子中获取这样的谓词,这里我使用了spaCy
的基于规则的匹配:
def get_relation(sent):
doc = nlp(sent)
# 匹配器类对象
matcher = Matcher(nlp.vocab)
# 定义匹配规则
pattern = [{'DEP':'ROOT'},
{'DEP':'prep','OP':"?"},
{'DEP':'agent','OP':"?"},
{'POS':'ADJ','OP':"?"}]
matcher.add("matching_1", None, pattern)
matches = matcher(doc)
k = len(matches) - 1
span = doc[matches[k][1]:matches[k][2]]
return(span.text)
函数中定义的模式匹配尝试查找句子中的根词(ROOT)或主要动词。一旦确定了 根词,模式匹配就会检查它是否后跟一个介词 (‘prep’) 或代词。如果是,则将其添加到根词中。
下面展示一下这个函数:
get_relation("John completed the task")
输出:completed
同理,我们从下载的句子中提取关系:
relations = [get_relation(i) for i in tqdm(candidate_sentences['sentence'])]
来看看刚刚提取的最频繁的关系或谓词:
pd.Series(relations).value_counts()[:50]
输出:
结果显示,“A是B”和“A曾是B”是最常见的关系,但还有不少关系与整个主题——“电影的生态系统”(“the ecosystem around movies”.)更相关,比如“由作曲”、“发行于”、“制作”、“编写”(“composed by”, “released in”, “produced”, “written by”)等。
建立知识图谱
最后,我们将从提取的实体(主-客体对)和谓词(实体之间的关系)创建一个知识图谱。
首先,让我们创建一个实体和谓词的数据框:
# 提取主体(主语)
source = [i[0] for i in entity_pairs]
# 提取客体(宾语)
target = [i[1] for i in entity_pairs]
kg_df = pd.DataFrame({'source':source, 'target':target, 'edge':relations})
接下来,我们将使用 networkx
库从这个数据框创建一个网络,节点将代表实体,节点之间的边或连接将代表节点之间的关系。
它将是一个有向图,换句话说,任何连接节点对之间的关系并不是双向的,它只是从一个节点到另一个节点,例如,“John eats pasta”:
#依据数据创建一个有向图。
G=nx.from_pandas_edgelist(kg_df, "source", "target", edge_attr=True, create_using=nx.MultiDiGraph())
画出网络图:
plt.figure(figsize=(12,12))
pos = nx.spring_layout(G)
nx.draw(G, with_labels=True, node_color='skyblue', edge_cmap=plt.cm.Blues, pos = pos)
plt.show()
输出:
显然,这并不是我们想要的(尽管看起来很牛逼)。
我们创建了一个包含所有关系的图,很难想象一个包含这么多关系或谓词的图。所以,这里建议只使用几个重要的关系来可视化图表。下面一次只讨论一个关系。我们从“composed by”关系开始:
G=nx.from_pandas_edgelist(kg_df[kg_df['edge']=="composed by"], "source", "target",
edge_attr=True, create_using=nx.MultiDiGraph())
plt.figure(figsize=(12,12))
pos = nx.spring_layout(G, k = 0.5) # k:调节节点之间的距离。
nx.draw(G, with_labels=True, node_color='skyblue', node_size=1500, edge_cmap=plt.cm.Blues, pos = pos)
plt.show()
这个图就清晰多了。这里箭头指向作曲家。例如,A.R. Rahman,他是一位著名的音乐作曲家,在上图中,他与“配乐”、“电影配乐”和“音乐”等实体相关。
再看看其他一些关系。因为创作在任何电影中都是一个重要谓词,所以将“written by”的关系可视化:
G=nx.from_pandas_edgelist(kg_df[kg_df['edge']=="written by"], "source", "target",
edge_attr=True, create_using=nx.MultiDiGraph())
plt.figure(figsize=(12,12))
pos = nx.spring_layout(G, k = 0.5)
nx.draw(G, with_labels=True, node_color='skyblue', node_size=1500, edge_cmap=plt.cm.Blues, pos = pos)
plt.show()
输出:
这个知识图谱给我们带来了一些不一样的信息,像Javed Akhtar、Krishna Chaitanya和Jaideep Sahni这样的人都是著名的歌词作者,这个图完美地提取到了这种关系。
让我们看一下另一个重要的谓词的知识图谱,即“released in”:
G=nx.from_pandas_edgelist(kg_df[kg_df['edge']=="released in"], "source", "target",
edge_attr=True, create_using=nx.MultiDiGraph())
plt.figure(figsize=(12,12))
pos = nx.spring_layout(G, k = 0.5)
nx.draw(G, with_labels=True, node_color='skyblue', node_size=1500, edge_cmap=plt.cm.Blues, pos = pos)
plt.show()
输出:
我可以在这个图中看到很多有意思的信息。例如这个关系——“在20世纪80年代发行的几部动作恐怖电影”和“在4844个屏幕上pk播放”。因此,我们可以从文本中挖掘出这样神奇的事情。
结论
在本文中,我们讨论了如何从文本中提取信息并制作知识图谱。尽管我们只使用了包含两个事物的句子,但我们制作的图仍然包含了许多有用的信息。在机器学习中,我们可以使用知识图谱做更多的事情!尝试探索更多内容以找到答案。
在下面的评论中分享你的想法!
暂无评论内容