你知道《圣經》中的主要角色有哪些嗎?三種NLP工具將告訴你答案!
提到數據科學,我們想到的都是數字的統(tǒng)計分析,但如今需要對很多非結構文本進行量化分析。本文將以《圣經》為例,用 spaCy Python 庫把三個最常見的 NLP 工具(理解詞性標注、依存分析、實體命名識別)結合起來分析文本,以找出《圣經》中的主要人物及其動作。
引言
在思考數據科學的時候,我們常常想起數字的統(tǒng)計分析。但是,各種組織機構越來越頻繁地生成大量可以被量化分析的非結構文本。一些例子如社交網絡評論、產品評價、電子郵件以及面試記錄。
就文本分析而言,數據科學家們通常使用自然語言處理(NLP)。我們將在這篇博客中涵蓋 3 個常見的 NLP 任務,并且研究如何將它結合起來分析文本。這 3 個任務分別是:
- 詞性標注——這個詞是什么類型?
- 依存分析——該詞和句子中的其他詞是什么關系?
- 命名實體識別——這是一個專有名詞嗎?
我們將使用 spaCy Python 庫把這三個工具結合起來,以發(fā)現誰是《圣經》中的主要角色以及他們都干了什么。我們可以從那里發(fā)現是否可以對這種結構化數據進行有趣的可視化。
這種方法可以應用于任何問題,在這些問題中你擁有大量文檔集合,你想了解哪些是主要實體,它們出現在文檔中的什么位置,以及它們在做什么。例如,DocumentCloud 在其「View Entities」分析選項中使用了類似的方法。
分詞 & 詞性標注
從文本中提取意思的一種方法是分析單個單詞。將文本拆分為單詞的過程叫做分詞(tokenization)——得到的單詞稱為分詞(token)。標點符號也是分詞。句子中的每個分詞都有幾個可以用來分析的屬性。詞性標注就是一個例子:名詞可以是一個人,地方或者事物;動詞是動作或者發(fā)生;形容詞是修飾名詞的詞。利用這些屬性,通過統(tǒng)計最常見的名詞、動詞和形容詞,能夠直接地創(chuàng)建一段文本的摘要。
使用 spaCy,我們可以為一段文本進行分詞,并訪問每個分詞的詞性。作為一個應用示例,我們將使用以下代碼對上一段文本進行分詞,并統(tǒng)計最常見名詞出現的次數。我們還會對分詞進行詞形還原,這將為詞根形式賦予一個單詞,以幫助我們跨單詞形式進行標準化。
- from collections import Counter
- import spacy
- from tabulate import tabulate
- nlp = spacy.load('en_core_web_lg')
- text = """
- One way to extract meaning from text is to analyze individual words.
- The processes of breaking up a text into words is called tokenization --
- the resulting words are referred to as tokens.
- Punctuation marks are also tokens.
- Each token in a sentence has several attributes we can use for analysis.
- The part of speech of a word is one example: nouns are a person, place, or thing;
- verbs are actions or occurences; adjectives are words that describe nouns.
- Using these attributes, it's straightforward to create a summary of a piece of text
- by counting the most common nouns, verbs, and adjectives.
- """
- doc = nlp(text)
- noun_counter = Counter(token.lemma_ for token in doc if token.pos_ == 'NOUN')
- print(tabulate(noun_counter.most_common(5), headers=['Noun', 'Count']))
- Noun Count
- --------- -------
- word 5
- text 3
- token 3
- noun 3
- attribute 2
依存分析
單詞之間也是有關系的,這些關系有好幾種。例如,名詞可以做句子的主語,它在句子中執(zhí)行一個動作(動詞),例如「Jill 笑了」這句話。名詞也可以作為句子的賓語,它們接受句子主語施加的動作,例如「Jill laughed at John」中的 John。
依存分析是理解句子中單詞之間關系的一種方法。盡管在句子「Jill laughed at John」中,Jill 和 John 都是名詞,但是 Jill 是發(fā)出 laughing 這個動作的主語,而 John 是承受這個動作的賓語。依存關系是一種更加精細的屬性,可以通過句子中單詞之間的關系來理解單詞。
單詞之間的這些關系可能變得特別復雜,這取決于句子結構。對句子做依存分析的結果是一個樹形數據結構,其中動詞是樹根。
讓我們來看一下「The quick brown fox jumps over the lazy do」這句話中的依存關系。
- nlp("The quick brown fox jumps over the lazy dog.")
- spacy.displacy.render(doc, style='dep', options={'distance' : 140}, jupyter=True)
依存關系也是一種分詞屬性,spaCy 有專門訪問不同分詞屬性的強大 API(https://spacy.io/api/token)。下面我們會打印出每個分詞的文本、它的依存關系及其父(頭)分詞文本。
- token_dependencies = ((token.text, token.dep_, token.head.text) for token in doc)
- print(tabulate(token_dependencies, headers=['Token', 'Dependency Relation', 'Parent Token']))
- Token Dependency Relation Parent Token
- ------- --------------------- --------------
- The det fox
- quick amod fox
- brown amod fox
- fox nsubj jumps
- jumps ROOT jumps
- over prep jumps
- the det dog
- lazy amod dog
- dog pobj over
- . punct jumps
作為分析的先導,我們會關心任何一個具有 nobj 關系的分詞,該關系表明它們是句子中的賓語。這意味著,在上面的示例句子中,我們希望捕獲到的是單詞「fox」。
命名實體識別
***是命名實體識別。命名實體是句子中的專有名詞。計算機已經相當擅長分析句子中是否存在命名實體,也能夠區(qū)分它們屬于哪一類別。
spaCy 在文檔水平處理命名實體,因為實體的名字可以跨越多個分詞。使用 IOB
(https://spacy.io/usage/linguistic-features#section-named-entities)把單個分詞標記為實體的一部分,如實體的開始、內部或者外部。
在下面的代碼中,我們在文檔水平使用 doc.ents 打印出了所有的命名實體。然后,我們會輸出每個分詞,它們的 IOB 標注,以及它的實體類型(如果它是實體的一部分的話)。
我們要使用的句子示例是「Jill laughed at John Johnson」。
- doc = nlp("Jill laughed at John Johnson.")
- entity_types = ((ent.text, ent.label_) for ent in doc.ents)
- print(tabulate(entity_types, headers=['Entity', 'Entity Type']))
- print()
- token_entity_info = ((token.text, token.ent_iob_, token.ent_type_,) for token in doc)
- print(tabulate(token_entity_info, headers=['Token', 'IOB Annotation', 'Entity Type']))
- Entity Entity Type
- ------------ -------------
- Jill PERSON
- John Johnson PERSON
- Token IOB Annotation Entity Type
- ------- ---------------- -------------
- Jill B PERSON
- laughed O
- at O
- John B PERSON
- Johnson I PERSON
- . O
實例:對《圣經》進行自然語言處理
上面提到的每個方法本身就很強大了,但如果將它們結合起來,遵循語言學的模式提取信息,就能發(fā)揮自然語言處理的真正力量。我們可以使用詞性標注、依存分析、實體命名識別的一部分來了解大量文本中的所有角色及其動作。因其文本長度和角色范圍之廣,《圣經》是一個很好的例子。
我們正在導入的數據每個《圣經》經文包含一個對象。經文被用作圣經部分的參考方案,通常包括一個或多個經文句子。我們會遍歷所有的經文,并提取其主題,確定它是不是一個人物,并提取這個人物所做的所有動作。
首先,讓我們從 GitHub 存儲庫中以 JSON 的形式加載圣經。然后,我們會從每段經文中抽取文本,通過 spaCy 發(fā)送文本進行依存分析和詞性標注,并存儲生成的文檔。
- import requests
- r = requests.get('https://github.com/tushortz/Bible/raw/master/json/kjv.json')
- bible_json = [line['fields'] for line in r.json()]
- print('Number of Verses:', len(bible_json))
- text_generator = (line['text'] for line in bible_json)
- %time verse_docs = [doc for doc in nlp.pipe(text_generator, n_threads=-1)]
我們已經用 3 分鐘多一點的時間將文本從 json 解析到了 verse_docs,大約每秒 160 個經文章節(jié)。作為參考,下面是 bible_json 前 3 行的內容。
- [{'book_id': 1,
- 'chapter': 1,
- 'comment': '',
- 'text': 'In the beginning God created the heaven and the earth.',
- 'verse': 1},
- {'book_id': 1,
- 'chapter': 1,
- 'comment': '',
- 'text': 'And the earth was without form, and void; and darkness was upon the face of the deep. And the Spirit of God moved upon the face of the waters.',
- 'verse': 2},
- {'book_id': 1,
- 'chapter': 1,
- 'comment': '',
- 'text': 'And God said, Let there be light: and there was light.',
- 'verse': 3}]
使用分詞屬性
為了提取角色和動作,我們將遍歷一段經文中的所有分詞,并考慮 3 個因素:
- 這個分詞是句子的主語嗎?(它的依存關系是不是 nsubj?)
- 它的父分詞是不是動詞?(通常是這樣的,但是有時候 POS 標注和依存分析之間會存在沖突,我們會安全地使用它。此外,我并不是語言學家,所以這里還會有一些奇怪的案例。)
- 一個分詞的命名實體是否為一個人物?我們不想提取任何不是人物的名詞。(為了簡便,我們僅僅會提取名字)
如果我們的分詞滿足以上 3 種條件,我們將會收集以下的屬性:
- 名詞/實體分詞的文本。
- 包含名詞和動詞的范圍。
- 動詞。
- 動詞出現在標準英語文本中的對數概率(使用對數的原因是這里的概率都很小)。
- 經文數量。
- actors_and_actions = []
- def token_is_subject_with_action(token):
- nsubj = token.dep_ == 'nsubj'
- head_verb = token.head.pos_ == 'VERB'
- person = token.ent_type_ == 'PERSON'
- return nsubj and head_verb and person
- for verse, doc in enumerate(verse_docs):
- for token in doc:
- if token_is_subject_with_action(token):
- span = doc[token.head.left_edge.i:token.head.right_edge.i+1]
- data = dict(name=token.orth_,
- spanspan=span.text,
- verb=token.head.lower_,
- log_prob=token.head.prob,
- verseverse=verse)
- actors_and_actions.append(data)
- print(len(actors_and_actions))
分析
我們已經獲得了提取到的所有角色及其動作的列表,現在我們做以下兩件事來快速分析:
- 找出每個角色最常做出的動作(動詞)
- 找出每個人最獨特的動作。我們將其確定為英文文本中出現概率***的動詞。
- import pandas as pd
- action_df = pd.DataFrame(actors_and_actions)
- print('Unique Names:', action_df['name'].nunique())
- most_common = (action_df
- .groupby(['name', 'verb'])
- .size()
- .groupby(level=0, group_keys=False)
- .nlargest(1)
- .rename('Count')
- .reset_index(level=1)
- .rename(columns={
- 'verb': 'Most Common'
- })
- )
- # exclude log prob < -20, those indicate absence in the model vocabulary
- most_unique = (action_df[action_df['log_prob'] > -20]
- .groupby(['name', 'verb'])['log_prob']
- .min()
- .groupby(level=0, group_keys=False)
- .nsmallest(1)
- .rename('Log Prob.')
- .reset_index(level = 1)
- .rename(columns={
- 'verb': 'Most Unique'
- })
- )
- # SO groupby credit
- # https: //stackoverflow.com/questions/27842613/pandas-groupby-sort-within-groups
讓我們看一下前 15 個角色的動詞數及其最常用的動詞。
- most_common.sort_values('Count', ascending=False).head(15)
貌似《圣經》里面很多人都說了很多話,而所羅門簡直是個例外,他做了很多事情。
那么從出現概率來看,最獨特的動詞是什么呢?(我們將在此處刪去重復項,以便每個單詞都是唯一的)
- (most_unique
- .drop_duplicates('Most Unique')
- .sort_values('Log Prob.', ascending=True)
- .head(15)
- )
看來我們要學習一些有趣的新詞匯了!我最喜歡的是 discomfited 和 ravin。
可視化
接下來可視化我們的結果。我們將選取行動最多、情節(jié)最多的前 50 個名字,這些行動發(fā)生在整篇文章中。我們還會在《圣經》每本書的開頭畫垂直線。姓名將按***出現的順序排序。
這可以讓我們知道圣經中每個角色最活躍的時候。
我們將添加一些分隔符來分隔《圣經》的不同部分。我自己并非研究《圣經》學者,所以我參考了如下分隔法
(https://www.thoughtco.com/how-the-books-of-the-bible-are-organized-363393):
《舊約》:
- 摩西五經或律法書:《創(chuàng)世紀》、《出埃及記》、《利未記》、《民數記》和《申命記》。
- 舊約歷史書:《約書亞記》、《士師記》、《路得記》、《撒慕耳記》上下、《列王記》上下、《歷代志》上下、《尼希米記》、《以斯拉記》、《以斯帖記》
- 詩歌智慧書:《約伯記》、《詩篇》、《箴言》、《傳道書》和《雅歌》;
- 大先知書:《以賽亞書》、《耶利米書》、《耶利米哀歌》、《以西結書》、《但以理書》、《何西阿書》、《約珥書》、《阿摩司書》、《俄巴底亞書》、《約拿書》、《彌迦書》、《那鴻書》、《哈巴谷書》、《西番雅書》、《哈該書》、《撒迦利亞書》、《瑪拉基書》。
《新約》:
- 福音書:《馬太福音》、《馬可福音》、《路加福音》、《約翰福音》
- 新約歷史書:《使徒行傳》
- 保羅書信:《羅馬書》、《哥林多前書》、《哥林多后書》、《加拉太書》、《以弗所書》、《腓立比書》、《歌羅西書》《帖撒羅尼迦前書》、《帖撒羅尼迦后書》、《提摩太前書》、《提摩太后書》、《提多書》、《腓利門書》、《希伯來書》、《雅各書》、《彼得前書》、《彼得后書》、《約翰壹書》、《約翰貳書》、《約翰叁書》和《猶大書》
- 語言/啟示錄:《啟示錄》
此外,我們還會用一條紅色的標志線分割《舊約》和《新約》。
- import seaborn as sns
- import matplotlib.pyplot as plt
- %matplotlib inline
- sns.set(context='notebook', style='dark')
- most_frequent_actors = list(action_df['name'].value_counts().index[:50])
- top_actors_df = action_df[action_df['name'].isin(most_frequent_actors)].copy()
- book_locations = (pd.DataFrame(bible_json)
- .reset_index()
- .groupby('book_id')['index']
- .min()
- .to_dict()
- )
- fig, ax = plt.subplots(figsize=(8,12), dpi=144*2)
- sns.stripplot(x='verse', y='name',
- data=top_actors_df, axax=ax,
- color='xkcd:cerulean',
- size=3, alpha=0.25, jitter=0.25)
- sns.despine(bottom=True, left=True)
- for book, verse_num in book_locations.items():
- ax.axvline(verse_num, alpha=1, lw=0.5, color='w')
- divisions = [1, 6, 18, 23, 40, 44, 45, 65]
- for div in divisions:
- ax.axvline(book_locations[div], alpha=0.5, lw=1.5, color='grey')
- ax.axvline(book_locations[40], alpha=0.5, lw=1.75, color='xkcd:coral')
- ax.set_xlim(left=-150)
- ax.set_title("Where Actions Occur in the Bible\nCharacters Sorted by First Appearance");
可視化分析
- 在《圣經》開頭的《創(chuàng)世紀》中,上帝(God)被密集地提到。
- 在《新約》中,主(Lord)不再作為一個實體使用。
- 我們***次看到保羅是在《使徒行傳》中被提及。(福音書后的***本書)
- 在《詩歌智慧書》里沒有提到很多實體。
- 耶穌的生活在《福音書》中被密集地記錄了下來。
- 彼拉多出現在《福音書》的末尾。
這種方法的問題
- 實體識別無法區(qū)分兩個名字相同的人掃羅王(《舊約》)直到《使徒行傳》的中途,保羅(使徒)一直被稱作掃羅
- 有些名詞不是實際的實體(如 Ye)
- 有些名詞可以使用更多的語境和全名(如 Pilate)
下一步
一如既往,有辦法擴展和改進這一分析。我在寫這篇文章的時候想到了以下幾點:
- 使用依存關系來尋找實體之間的關系,通過網絡分析的方法來理解角色。
- 改進實體提取,以捕獲單個名稱之外的實體。
- 對非人物實體及其語言關系進行分析——《圣經》中提到了哪些位置?
寫在結尾
僅僅通過使用文本中分詞級別的屬性我們就可以做一些很有趣的分析!在本文中,我們介紹了 3 種主要的 NLP 工具:
- 詞性標注——這個詞是什么類型?
- 依存分析——該詞和句子中的其他詞是什么關系?
- 命名實體識別——這是一個專有名詞嗎?
我們結合這三個工具來發(fā)現誰是《圣經》中的主要角色,以及他們采取的動作。并且我們還繪制了這些角色和動作的圖表,以了解每個角色的主要動作發(fā)生在何處。
原文鏈接:https://pmbaumgartner.github.io/blog/holy-nlp/
【本文是51CTO專欄機構“機器之心”的原創(chuàng)文章,微信公眾號“機器之心( id: almosthuman2014)”】