龙空技术网

知识图谱——用Python代码从文本中挖掘信息的强大数据科学技术

读芯术 35396

前言:

现时兄弟们对“python文本挖掘”大致比较着重,小伙伴们都需要了解一些“python文本挖掘”的相关知识。那么小编在网摘上收集了一些对于“python文本挖掘””的相关资讯,希望你们能喜欢,同学们一起来了解一下吧!

全文共6382字,预计学习时长20分钟

图源:Unsplash摄影:Clem Onojeghuo

概览

知识图谱是数据科学中最吸引人的概念之一学习如何使用Wikipedia页面中的文本构建知识图谱在Python中使用流行的spaCy库在Python中构建知识图谱

引言

不用过多介绍,大家都知道梅西。即使是那些不关注足球的人,也听说过这位最伟大球员在辉煌。下面是他的维基百科页面:

这个页面包含了很多信息!里面不仅有文本、大量的超链接,甚至还有音频片段。整个网页上有很多相关的和可能会有用的信息,将它们应用到实际生活中可能性是无穷的。

然而,还有一个小问题。这不是给机器提供数据的理想来源,至少不是以现在的这种形式。

是否能找到一种方法,使这些文本数据变为机器可读?能否将这些文本数据转录成既让机器可以使用,也能让我们可以轻松解释的东西?

答案是肯定的。我们可以借助知识图谱(KG),这是数据科学最吸引人的概念之一。我已经被知识图谱的巨大潜力和实际应用震惊了,我相信你也会和我一样。

在本文中,你将了解什么是知识图谱,以及它们的作用,然后我们将基于从维基百科中提取的数据,来构建知识图谱以用于深入研究代码。

目录

1. 什么是知识图谱?

2. 如何在图谱中表示知识?

○ 句子分割

○ 实体抽取

○ 关系抽取

3. 依靠文本数据构建知识图谱

什么是知识图谱?

先明确一个概念:在本文中经常出现的术语“图谱”,并不是指柱状图、饼状图或线状图,而是相互关联的实体,它们可以是人、地点、组织,甚至是一个事件。

不妨说,图谱是节点和边*的组合。

看看下面的数据:

*边(Edge)是节点间的连线,用于表示节点间关系。

这里的节点a和节点b是两个不同的实体,节点通过边连接。如图是我们可以构建的最小的知识图谱——它也被称为三元组(实体-关系-实体)。

知识图谱有多种形状和大小。例如,截至2019年10月,维基数据(Wikidata)的知识图谱有59,910,568个节点。

如何在图谱中表示知识?

在开始构建知识图谱前,我们需要了解信息或知识是如何嵌入到这些图谱中的。

举例来解释:如果节点a=普京,节点b=俄罗斯,那么边很可能是“俄罗斯总统”:

一个节点或实体也可以有不止一个关系。普京不仅是俄罗斯总统,他还曾为苏联安全机构克格勃工作。但是如何把这些关于普京的新信息,整合到上面的知识图谱中呢?

其实很简单。只需为新实体“克格勃”再添加一个节点:

新的关系不仅可以添加在第一个节点,而且可以出现在知识图谱中的任何节点,如下所示:

俄罗斯是亚太经济合作组织(APEC)的成员国

识别实体和他们的相互关系并不是一项困难的任务。但是,手动构建知识图谱是难以处理大量信息的。没有人会浏览成千上万的文档,然后提取出所有的实体和它们之间的关系。

因此,机器无疑是更好的选择,浏览成百上千的文件对它们来说简直小菜一碟。但是还有另外一个挑战——机器不懂自然语言。这就轮到自然语言处理 (Natural Language Processing,简称NLP) 技术出场了。

想要从文本中构建知识图谱,让机器能理解自然语言就至关重要。这可以通过使用自然语言处理(NLP)技术来实现,如句子分割、依存句法分析、词性标注和实体识别。下文将更详细地对它们进行探讨。

句子分割

构建知识图谱的第一步是将文本或文章分割成句子。然后,列出那些只有一个主语和宾语的句子。下面是示例文本:

“在最新的男子单打排名中,印度网球选手苏米特•纳加尔(Sumit Nagal)从135名上升了6个名次,达到职业生涯中的最好成绩129名。这位22岁的选手最近赢得了ATP挑战锦标赛。2019年美国网球公开赛中他首次亮相时,就在对阵费德勒的比赛中赢得了大满贯。纳加尔赢得了第一组比赛。(Indian tennis player Sumit Nagal moved up six places from 135to a career-best 129 in the latest men’s singles ranking. The 22-year-oldrecently won the ATP Challenger tournament. He madehis Grand Slam debut against Federer in the 2019 US Open. Nagal won the firstset.)”

我们把上面的段落分成几个句子:

1. 在最新的男子单打排名中,印度网球选手苏米特•纳加尔(SumitNagal)从135名上升了6个名次,达到职业生涯中的最好成绩129名。(Indian tennis player Sumit Nagal moved up six places from135 to a career-best 129 in the latest men’s singles ranking.)

2. 这位22岁的选手最近赢得了ATP挑战锦标赛。(The 22-year-old recently won the ATP Challengertournament))

3. 2019年美国网球公开赛中他首次亮相时,就在对阵费德勒比赛中赢得了大满贯。(Hemade his Grand Slam debut against Federer in the 2019 US Open.)

4. 纳加尔赢得了第一组比赛。(Nagalwon the first set.)

在这四个句子中,我们将选出第二个和第四个句子,因为每个句子都包含一个主语和一个宾语。第二句中,“22岁的选手(22-year-old)”是主语,宾语是“ATP挑战赛(ATP Challenger tournament)”。第四句中,主语是“纳加尔(Nagal)”,“第一组比赛(first set)”是宾语:

让机器理解文本是最大的挑战,特别是在主语和宾语不止一个的情况下。以上面两个句子为例,提取句子中的宾语有点棘手。你能想到解决这个问题的方法吗?

实体抽取

从句子中提取一个词实体并不是艰巨的任务。我们可以借助词性(POS)标签轻松做到这一点。名词和专有名词可以是我们的实体。

然而,当一个实体包括多个单词时,仅仅借助词性标记是不够的。我们需要解析句子间的复杂关系。先来获取其中一个句子的依存标记。使用流行的spaCy库来完成这个任务:

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… det

22-year … amod

– … punct

old … nsubj

recently … advmod

won … ROOT

ATP … compound

Challenger … compound

tournament … dobj

. … punct

根据依存关系解析器,这个句子中的主语(nsubj)是“old”。这不是我们想要的实体。我们想要的是“22岁的选手(22-year-old)”。

“22岁的(22-year)”的依存关系标签是amod,这意味着它是(old)”的修饰语。因此,应该定义一个规则来提取这样的实体。

可以是这样的规则:提取主语/宾语及其修饰语,并提取它们之间的标点。

但是再看看句子中的宾语(dobj)。它只是“锦标赛(tournament)”而不是“ATP挑战锦标赛(ATPChallenger tournament)”。在这句里,没有修饰词,只有复合词。

复合词是指不同词组合起来所形成的新术语,它们有着和源词不同的意思。因此,我们可以更新上面的规则----提取主语/宾语及其修饰语、复合词,并提取它们之间的标点。

简而言之,我们使用依存句法分析来提取实体。

提取关系

实体提取仅仅是一半的工作。想要建立知识图谱,需要边来连接各个节点(实体)。这些边表示节点之间的关系。

回到上一节中的例子,选出几句话来构建一个知识图谱:

你能猜出这两个句子中主语和宾语的关系吗?

两个句子有相同的关系——“赢得(won)”。看看该如何提取这些关系。这里将再次使用依存项解析:

doc = nlp("Nagal won the first set.")

for tok in doc:

print(tok.text, "...", tok.dep_)

输出:

Nagal… nsubj

won … ROOT

the … det

first … amod

set … dobj

. … punct

为了提取关系,必须找到句子的根(root),它也是句子里的动词。因此,在这个句子里找到的关系就是“赢得(won)”。

最后,这两个句子的知识图谱是这样的:

从文本数据构建知识图谱

是时候进行代码操作了!打开Jupyter Notebooks(或者任何你喜欢的集成开发环境-IDE)。

使用一组从维基百科中找到的电影中的文本,从头开始构建一个知识图谱。我已经从500多篇维基百科文章中摘录了大约4300句话。每个句子都包含两个实体——一个主语和一个宾语。你可以点击这里下载这些句子。

推荐使用Google Colab,能加快计算的运行速度。

导入库

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文件:

# import wikipedia sentences

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)”,依存关系解析器只会将单个单词标记为主语或宾语。

因此,下面创建了一个函数来从一个句子中提取主语和宾语(也就是实体),同时解决了上面提到的挑战。方便起见,将代码分成了多个板块:

defget_entities(sent):

## chunk 1

ent1 =""

ent2 =""

prv_tok_dep ="" # dependency tag of previous token in the sentence

prv_tok_text ="" # previous token in the sentence

prefix =""

modifier =""

#############################################################

for tok in nlp(sent):

## chunk 2

# if token is a punctuation mark then move on to the next token

if tok.dep_ !="punct":

# check: token is a compound word or not

if tok.dep_ =="compound":

prefix = tok.text

# if the previous word was also a 'compound' then add the current word to it

if prv_tok_dep =="compound":

prefix = prv_tok_text +""+ tok.text

# check: token is a modifier or not

if tok.dep_.endswith("mod") ==True:

modifier = tok.text

# if the previous word was also a 'compound' then add the current word to it

if prv_tok_dep =="compound":

modifier = prv_tok_text +""+ tok.text

## chunk 3

if tok.dep_.find("subj") ==True:

ent1 = modifier +""+ prefix +""+ tok.text

prefix =""

modifier =""

prv_tok_dep =""

prv_tok_text =""

## chunk 4

if tok.dep_.find("obj") ==True:

ent2 = modifier +""+ prefix +""+ tok.text

## chunk 5

# update variables

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

接下来,循环浏览句子中的所有标记。首先要检查标记是不是标点符号。如果是,那么我们将忽略它看下一标记(token)。如果标记是复合词(依存标记=“compound”)中的一部分,将把它保存在“前缀”变量中。一个复合词是多个词的组合,它们联系在一起形成一个有新意义的词,例如“足球场”(“FootballStadium”),“动物爱人”(“animallover”)。

当我们在句子中遇到主语或宾语时,会给它加上这个前缀。修饰语同样,比如“漂亮的衬衫”、“大房子”等等。

板块3

在这里,如果标记是主语的话,它将被捕获为ent1变量中的第一个实体。诸如prefix, modifier, prv_tok_dep,和 prv_tok_text 这些变量将被重置。

板块4

在这里,如果标记是宾语,那么它将被捕获为ent2变量中的第二个实体。诸如prefix, modifier, prv_tok_dep,和 prv_tok_text 这些变量将再次被重置。

板块5

一旦在句子中捕获了主语和宾语,我们将更新前面的标记及其依存标记。

在一个句子上测试一下这个函数:

get_entities("the film had 200 patents")

输出: [‘film’,‘200 patents’]

很好,看起来一切都和预想的一样。在上面的句子中,“电影(film)”是主语,“200项专利(200 patents)”是宾语。

现在可以使用这个函数,提取数据中所有句子的实体对:

entity_pairs = []

for i in tqdm(candidate_sentences["sentence"]):

entity_pairs.append(get_entities(i))


这个实体对名单包含维基百科句子中的所有主语-宾语对。来看看其中的几个:

entity_pairs[10:20]

输出:

在这些实体对中有一些代词,如“我们(we)”、“它(it)”、“她(she)”等等。我们希望用专有名词或名词来代替它们。也许可以进一步改进get_entities()函数来过滤代词。现在,让我们暂时保持现状,进入关系抽取部分。

关系/谓语提取

这将是本文非常有趣的一部分。假设是——谓语实际上是句子中的主要动词。

例如,在“1929年共有六十部好莱坞音乐剧被发行”这个句子中,动词是“被发行(released in)”,它也是本句得到的三重结构的谓语。

下面的函数能够从句子中捕获到这样的谓语。这里使用了spaCy库基于规则的匹配功能:

defget_relation(sent):

doc = nlp(sent)

# Matcher class object

matcher = Matcher(nlp.vocab)

#define the pattern

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_entities("John completed the task")

输出: “completed

用同样的方法找出所有维基百科句子中的关系:

relations = [get_relation(i) for i in tqdm(candidate_sentences['sentence'])]

来看一看刚刚提取的最常见的关系或谓词:

pd.Series(relations).value_counts()[:50]

输出:

结果表明,“A现在是(is)B”和“A曾是(was)B”是最常见的关系。然而,有很多的关系与整个主题,即“围绕电影的生态系统”联系更紧密。其中一些例子是“由”、“发布”、“制作”、“编写”等等。

构建知识图谱

最后,从提取的实体(主语-宾语对)和谓语(实体之间的关系)构建知识图。

创建一个实体和谓语的数据框架:

# extract subject

source = [i[0] for i in entity_pairs]

# extract object

target = [i[1] for i in entity_pairs]

kg_df = pd.DataFrame({'source':source, 'target':target, 'edge':relations})


接下来,使用networkx库从这个数据框架创建一张网。节点表示实体,节点之间的边或连接表示节点之间的关系。

它将是一个有向图谱。换句话说,任何连接的节点对之间的关系都不是双向的,它只是从一个节点到另一个节点。例如,“约翰吃意大利面”:

# create a directed-graph from a dataframe

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()

输出:

好吧,这并不是我们期待的结果,但看起来还是相当壮观的!

但已经构建了一个图谱,显示了所有的关系。很难想象将这么多关系或谓语可视化成一个图谱。

因此,建议只把一些重要的关系做成可视化图谱,我一次处理一个关系。从“由”组成的关系开始:

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 regulates the distance between nodes

nx.draw(G, with_labels=True, node_color='skyblue', node_size=1500, edge_cmap=plt.cm.Blues, pos= pos)

plt.show()


输出:

这是一个更清晰的图谱。这里箭头指向作曲家。例如,A.R.拉赫曼是一位著名的音乐作曲家,在上面的图谱中,他有诸如“电影原声带”、“电影配乐”和“音乐”这样的实体。

再看更多的关系。

因为编写在任何一部电影中都占重要角色,所以我想把“编写”关系的图谱可视化:

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都是著名的作词家,这张图谱很好地捕捉到了这种关系。

看看另一个重要谓语的知识图谱,即“释放(发布于)”:

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块屏幕上放映的格斗电影”。这些都是事实,这张图谱告诉我们,确实可以从文本中挖掘出这些事实。真是太神奇了!

结语

在本文中,我们学习了如何以三元组的形式,从给定文本中提取信息,并借此构建知识图谱。

然而,我们只使用恰好有两个实体的句子。即使这样,也能够建立信息量很大的知识图谱,所以想象一下它的潜力!

我鼓励大家探索这个领域的信息抽取,学习更复杂关系的提取。如果你有任何疑问或想要分享你的想法,请随时留言。

留言点赞关注

我们一起分享AI学习与发展的干货

如转载,请后台留言,遵守转载规范

标签: #python文本挖掘