全文共6382字,預計學習時長20分鐘
概覽
知識圖譜是數據科學中最吸引人的概念之一學習如何使用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學習與發展的乾貨
如轉載,請後臺留言,遵守轉載規範