NLTK 初學指南(三):基於 WordNet 的語義關係表示法 — 上下位詞結構篇

Youngmi huang
Sep 4, 2018 · 16 min read

本篇是『NLTK 初學指南 』的第三集,透過 WordNet 這個獨特的語義網絡,去找到字詞在整篇文本當中的上下位關係、同義詞、文法的處理(動詞時態、名詞單複數)等。

第一篇文:NLTK 初學指南(一):簡單易上手的自然語言工具箱−探索篇

第二篇文:NLTK 初學指南(二):由外而內,從語料庫到字詞拆解 — 上手篇


NLTK 全名是 Natural Language Tool Kit, 是一套基於 Python 的自然語言處理工具箱。在官方文件的說明十分友善,主要是以下這個網頁版電子書: Natural Language Processing with Python ,章節如下圖:

Outline of Natural Language Processing with Python

本篇為第二章節 Accessing Text Corpora and Lexical Resource 的後半段以及第三章節 Processing Raw Text ;如果喜歡實體工具書,也可以參考 O’Reilly 的版本。


WordNet — A Lexical Database for English

簡介

WordNet 是一個以英文為主的語義網絡,在普林斯頓大學認知科學實驗室的心理學教授 George A. Miller 的指導下建立和維護的是一個由「字詞 — 語義」建成的語義網絡,去儲存了字詞間的結構關係,透過上下位詞的關係建立數值化的特徵表示,像是字詞階層數、結構相似度等。以下是 「motorcar」 的例子,具體使用會在後面進行介紹。

WordNet 示意圖:以 motocar 出發的語義階層網絡

官方文件

在 WordNet 中,名詞,動詞,形容詞和副詞各自被組織成一個同義詞的集合 ( synsets ),每個同義詞集合都代表一個基本的語義概念,並且這些集合之間也由各種關係連接,形成網絡。

synset:同義詞集合

WordNet 將每一個字詞進行分組,而分組後得到的每一個具有相同意義的字詞組,就稱為 synset (同義詞集合)。同時,每一個 synset 都有一個簡短、概要的定義,記錄著不同 synset 的語義關係。

1. 找出同義詞集合

wn.synsets()

# 找synset,以 motorcar 為例
from nltk.corpus import wordnet as wn
wn.synsets('motorcar')
# Result
Synset('car.n.01')
# 也可以替換改找卡車的synset
wn.synsets('trunk')
# result
Synset('trunk.n.01'),
Synset('trunk.n.02'),
Synset('torso.n.01'),
Synset('luggage_compartment.n.01'),
Synset('proboscis.n.02')

在上面這個例子當中,car.n.01motorcar 這個字詞的 synset, n 代表了 car.n.01 是一個名詞類的 synset。

另外,「trunk」是多義詞的例子,一個多義詞會出現在它所代表每個意思的同義詞集合中。也就是說一個字詞與 synset 之間不一定是一對一的關係,如果這個字詞代表多種意義,就會有一個字詞,對應到多個 synset 的情況。

2. 找出同義詞

wn.synset('synsets id').lemma_names()

# synset 裡的字詞
wn.synset('car.n.01').lemma_names()
# Result
['car', 'auto', 'automobile', 'machine', 'motorcar']

在這個 synset 底下,有囊括了 carautoautomobilemachinemotorcar ,這些字詞可以視為是 motorcar 的同義詞。

如果今天要查找的字屬於多義詞,一個多義詞會有好幾個 synset,那麼該如何列舉所有的同義詞呢?

# 列舉多義字的同義字
for synset in wn.synsets('trunk'):
print(synset.lemma_names())
# Result:
['trunk', 'tree_trunk', 'bole']
['trunk']
['torso', 'trunk', 'body']
['luggage_compartment', 'automobile_trunk', 'trunk']
['proboscis', 'trunk']

3. 查詢 synset 的定義說明

wn.synset('synsets id').definiton()

# 查找 motorcar 所屬的 synset 定義
wn.synset('car.n.01').definition()
# Result
'a motor vehicle with four wheels; usually propelled by an internal combustion engine'
# 查找 trunk 所屬的 synset 定義
wn.synset('trunk.n.01').definition()
# Result
'the main stem of a tree; usually covered with bark; the bole is usually the part that is commercially useful for lumber'

以上的定義說明是 WordNet 拿來歸屬 synset 的依據,有點像是我們在做 label 問題時,所需要將邏輯紀錄的文件化說明。

4. hypernym/hyponym ( 上位詞與下位詞 )

wn.synset('synset id1').hypernyms()

wn.synset('synsets id').hyponyms()

對字詞做分類,是 WordNet 的一大特色與亮點,我們先看一個生物學上的例子,就可以更好理解上下位詞的意義。

界門綱目科屬種:是一個用生物分類學方法來對生物的物種分組和歸類的辦法。比如說:「人」使用這個分類方法由細類到大類排列如下:

Homo sapiens (智人) < Homo (人屬) < Hominids (人科) < Primates (靈長目) < Mammals (哺乳綱) < Chordates (脊索動物門) < Animals (動物界)

界門綱目科屬種:人類所屬分類的示意圖

如此我們就可以明顯看到「智人 — 人屬」的上下位關係。回到 WordNet 本身的特色:WordNet 是由「字詞 — 語義」建構而成的網絡,也可以理解成對字詞做分類的方法。以人的例子來看,「人屬」為「智人」的上位詞 ( hypernym ),而「智人」即為「人屬」的下位詞 ( hyponym )。

接下來,召喚前面出現過的 motorcar 在 WordNet 裡的架構圖,:

示意圖來自官方document,其實 artefact 應該是 artifact 才對。

可以看到 motor vehiclemotorcar 的上位詞 ,而hatch-backcompactgas guzzlermotorcar 的下位詞 ;上位詞所涵蓋的字詞數廣,分類是較粗的,下位詞是較細的分類。

# 尋找 motorcar 的上位詞組
motorcar = wn.synset('car.n.01')
types_of_motorcar = motorcar.hypernyms()
# 尋找 motorcar 的下位詞組
motorcar = wn.synset('car.n.01')
types_of_motorcar = motorcar.hyponyms()
# 找到下位詞組後後,再從 synset 找出單詞(以詞為中心)
sorted(lemma.name() for synset in types_of_motorcar for lemma in synset.lemmas())
# Result (僅列5項)
'electric',
'electric_automobile',
'electric_car',
'estate_car',
'gas_guzzler',
...

上下位詞不只可以找到上一位或是下一位,還可以找到 motorcar 這個詞的完整路徑(也就是上圖 motor vehicle 至 artefact 虛線所包含的 synsets + artifact 之上的上位詞 synsets ):

# 完整路徑(上位詞組再往上走)
motorcar = wn.synset('car.n.01')
motorcar.hypernym_paths()
# Result:兩組 path 的不同之處(以粗體表示)
[[Synset('entity.n.01'),
Synset('physical_entity.n.01'),
Synset('object.n.01'),
Synset('whole.n.02'),
Synset('artifact.n.01'),
Synset('instrumentality.n.03'),
Synset('container.n.01'),
Synset('wheeled_vehicle.n.01'),
Synset('self-propelled_vehicle.n.01'),
Synset('motor_vehicle.n.01'),
Synset('car.n.01')],
[Synset('entity.n.01'),
Synset('physical_entity.n.01'),
Synset('object.n.01'),
Synset('whole.n.02'),
Synset('artifact.n.01'),
Synset('instrumentality.n.03'),
Synset('conveyance.n.03'),
Synset('vehicle.n.01'),

Synset('wheeled_vehicle.n.01'),
Synset('self-propelled_vehicle.n.01'),
Synset('motor_vehicle.n.01'),
Synset('car.n.01')]]

那麼如何從 motorcar 這個階層直指最頂端的上位詞組 entity.n.01 呢?找到這個位置詞組的好處即可得知這個詞的最大分類。

# 直指頂端上位詞組
motorcar = wn.synset('car.n.01')
motorcar.root_hypernyms()
# Result
[Synset('entity.n.01')]

5. 找尋兩個 synset 之間的最低位共同詞組

wn.synset('synset id1').lowest_common_hypernyms('synset id2')

透過找尋 synset id1、以及 synset id2 的上位詞組,去找到一組最低位的共同上位詞組(有點像是尋找最小公倍數的概念),其應用是:共同分類的字詞表示。

官方文件是以三種鯨魚為例子,「露脊鯨」以及「小鬚鯨」最低位的共同上位詞組是 baleen_whale ,是「鬚鯨目」的意思,代表這兩種鯨魚隸屬於該分類底下。

# 以鯨魚為例
right = wn.synset("right_whale.n.01")
minke = wn.synset("minke_whale.n.01")
# 「露脊鯨」與「小鬚鯨」在上位詞組中最低位的詞組
right.lowest_common_hypernyms(minke)
# Result
[Synset('baleen_whale.n.01')]
Source: Atlantic (Northern) Right Whale
Source: Animal Legal Defense Fund Responds to Death of Kasatka the Orca
Source: Minke Whale

在自然語言處理的任務當中,在意的是個字詞、詞組之間的相同/相異程度,差異越小的會在越下位找,差異越大的會在越上位,才找到共同詞組,可以看以下例子:

# 露脊鯨 vs 虎鯨
orca = wn.synset("orca.n.01")
right.lowest_common_hypernyms(orca))
# Result
[Synset('whale.n.02')] //鯨目
# 露脊鯨 vs 陸龜
tortoise = wn.synset("tortoise.n.01")
right.lowest_common_hypernyms(tortoise)
# Result
[Synset('vertebrate.n.01')] //脊椎動物
# 露脊鯨 vs 小說
novel = wn.synset("novel.n.01")
right.lowest_common_hypernyms(novel)
# Result
[Synset('entity.n.01')] //實體

6. synset 之間的階層計算以及上下位結構的相似度

將字詞的上下位關係,轉為量化的數值表示:包含階層計算以及相似度。

wn.synset('synset id1').min_depth()

# 計算由當前 synset 而上的階層數
wn.synset('baleen_whale.n.01').min_depth()
wn.synset('whale.n.02').min_depth()
wn.synset('vertebrate.n.01').min_depth()
wn.synset('entity.n.01').min_depth()
# Result
14
13
8
0

wn.synset('synset id1').path_similarity()

# 上下位詞組結構的相似程度 (數字接近1代表path越像)
right.path_similarity(right) //露脊鯨和自己本身
right.path_similarity(minke) //露脊鯨和小鬚鯨
right.path_similarity(orca) //露脊鯨和虎鯨
right.path_similarity(tortoise) //露脊鯨和陸龜
right.path_similarity(novel) //露脊鯨和小說
# Result
1.0
0.25
0.16666666666666666
0.07692307692307693
0.043478260869565216

最後,補充兩個字詞處理的小技巧

在英文的世界裡,很常會遇到動詞的時態變化或是名詞的單複數變化等,「詞幹提取」、「詞性還原」即可進行字詞的預處理。(備註:使用上要視任務需求而定,若是詞性標註、或是專有名詞識別 ( NER ) 等可能較不適合進行這些前處理)

詞幹提取 ( Stemming )

通過一些簡單的規則,對字詞做初步處理,e.g. eat 這個單詞會有 eatingeateneats 等變化,在某些應用當中,我們是不需要特別區分 eateaten 之間的區別的,所以通常會用詞幹提取的方式,將這種語法上的變化歸類為相同的詞。

擁有基本規則的詞幹提取器是可以移除 -s/es-ing-ed的,像是 Porter StemmerLancaster Stemmer ;而較晚出現的 Snowball Stemmer,它是基於 Porter 的方式做改良,同時可以提取一些其他語言,像是法文、德文等。若想對詞幹提取這一塊進行更深入研究,可以參考 wiki

# 引用詞幹提取器
from nltk.stem import PorterStemmer
from nltk.stem import LancasterStemmer
from nltk.stem import SnowballStemmer
# 初始化
pst = PorterStemmer()
lst = LancasterStemmer()
snow = SnowballStemmer('english') //需定義語言
# 使用 (以Porter Stemmer為例)
pst.stem('eating')
pst.stem('eats')
# Result
eat
eat

詞性還原 ( Lemmatization )

有別於詞幹提取,詞性還原涵蓋了詞根的文法和變化形式,它運用了不同的標準化規則(像是上下文情境和詞性),來獲取相關的詞根 ( lemma ),將各種類型的詞的變形,歸成一致的形式。

# 詞性還原 
from nltk.stem import WordNetLemmatizer
wtlem = WordNetLemmatizer()
# 未指定詞性
wtlem.lemmatize('ate')
wtlem.lemmatize('eating')
# Result
ate
eating
# 指定詞性為動詞
wtlem.lemmatize('ate', 'v')
wtlem.lemmatize('eating', 'v')
# Result
eat
eat

小結

這次主要介紹的 WordNet ,練習後可以發現:其精髓是將字詞分類後以 「synset 」為中介,透過「synset」去做各種連結,像是尋找上下位詞組、計算字詞階層的相似度等。

這次在 NLTK 初學指南系列,介紹了從字詞出發的基本技巧、分詞與文章拆解、WordNet 語義網絡等主題。其實複雜的 NLP 任務的最底層,會與字詞表達方式文章拆解有關(e.g. 分詞、同異義詞處理、以及字詞在整篇文章的位置),因此,相信這些基本技巧上手之後,就能應用在 NLP 的相關任務上,若有興趣做得更細緻,就得在字詞表達與文章拆解上好好研究以及著墨了。

本篇程式碼已整理在 github,可參考 03_nltk_practice.ipynb 進行實作

資料來源

  1. Natural Language Processing with Python — Analyzing Text with the Natural Language Toolkit
  2. Accessing Text Corpora and Lexical Resources
  3. Processing Raw Text
  4. NLTK 基礎教程:用 NLTK 和 Python 庫構建機器學習應用
  5. 用Python也能輕鬆玩自然語言處理
  6. NLTK WordNet Lemmatizer: Shouldn’t it lemmatize all inflections of a word?
  7. 關於WordNet,我的一些用法和思路(二)

PyLadies Taiwan

Pyladies is an international mentorship group for women who love the Python programming language. https://tw.pyladies.com

Youngmi huang

Written by

Participate in data science field, fascinated by something new in FinTech. (Current: investment decision making, risk and fraud field with ML, Past: NLP)

PyLadies Taiwan

Pyladies is an international mentorship group for women who love the Python programming language. https://tw.pyladies.com

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade