- 自然語言工具包教程
- 自然語言工具包 - 首頁
- 自然語言工具包 - 簡介
- 自然語言工具包 - 入門
- 自然語言工具包 - 文字分詞
- 訓練分詞器和過濾停用詞
- 在WordNet中查詢單詞
- 詞幹提取和詞形還原
- 自然語言工具包 - 單詞替換
- 同義詞和反義詞替換
- 語料庫讀取器和自定義語料庫
- 詞性標註基礎
- 自然語言工具包 - 一元語法標註器
- 自然語言工具包 - 組合標註器
- 自然語言工具包 - 更多NLTK標註器
- 自然語言工具包 - 解析
- 組塊和資訊提取
- 自然語言工具包 - 變換組塊
- 自然語言工具包 - 變換樹
- 自然語言工具包 - 文字分類
- 自然語言工具包資源
- 自然語言工具包 - 快速指南
- 自然語言工具包 - 有用資源
- 自然語言工具包 - 討論
自然語言工具包 - 快速指南
自然語言工具包 - 簡介
什麼是自然語言處理 (NLP)?
人類藉助語言進行交流,包括說、讀和寫。換句話說,我們人類可以用自然語言進行思考、計劃和決策。那麼,在人工智慧、機器學習和深度學習時代,人類能否用自然語言與計算機/機器進行交流呢?開發NLP應用對我們來說是一個巨大的挑戰,因為計算機需要結構化資料,而另一方面,人類語言是非結構化的,往往具有歧義性。
自然語言處理是計算機科學的一個子領域,更具體地說,是人工智慧的一個子領域,它使計算機/機器能夠理解、處理和操縱人類語言。簡單來說,NLP是機器分析、理解和推匯出像印地語、英語、法語、荷蘭語等人類自然語言意義的一種方式。
它是如何工作的?
在深入研究NLP的工作原理之前,我們必須瞭解人類如何使用語言。每天,我們人類使用數百或數千個單詞,其他人類會對其進行解釋並做出相應的回答。對人類來說,這是一種簡單的交流,不是嗎?但我們知道,單詞的含義遠不止於此,我們總是從我們所說的內容和我們所說的方式中推匯出上下文。這就是為什麼我們可以說,NLP並非專注於語音調製,而是利用上下文模式。
讓我們用一個例子來理解:
Man is to woman as king is to what? We can interpret it easily and answer as follows: Man relates to king, so woman can relate to queen. Hence the answer is Queen.
人類如何知道哪個詞是什麼意思?這個問題的答案是,我們透過經驗學習。但是,機器/計算機如何學習相同的內容呢?
讓我們透過以下簡單的步驟來理解:
首先,我們需要用足夠的資料來餵養機器,以便機器能夠從經驗中學習。
然後,機器將使用深度學習演算法,從我們之前提供的資料以及其周圍的資料中建立單詞向量。
然後,透過對這些單詞向量進行簡單的代數運算,機器將能夠像人類一樣提供答案。
NLP的組成部分
下圖表示自然語言處理 (NLP) 的組成部分:
形態分析
形態分析是NLP的第一個組成部分。它包括將語言輸入的塊分解成對應於段落、句子和單詞的標記集。例如,像“everyday”這樣的單詞可以分解成兩個子詞標記,“every-day”。
句法分析
句法分析是第二個組成部分,也是NLP最重要的組成部分之一。這個組成部分的目的如下:
檢查句子是否格式良好。
將其分解成一個結構,該結構顯示不同單詞之間的句法關係。
例如,“學校去學生那裡”之類的句子會被句法分析器拒絕。
語義分析
語義分析是NLP的第三個組成部分,用於檢查文字是否有意義。它包括從文字中提取確切的含義,或者我們可以說字典含義。例如,“這是一個熱的冰淇淋”之類的句子會被語義分析器丟棄。
語用分析
語用分析是NLP的第四個組成部分。它包括將先前組成部分(即語義分析)獲得的物件引用與每個上下文中存在的實際物件或事件相匹配。例如,“把水果放在桌子上的籃子裡”這句話可能有兩種語義解釋,因此語用分析器將在這兩種可能性之間進行選擇。
NLP應用示例
NLP作為一項新興技術,衍生出各種我們現在經常看到的人工智慧形式。對於當今和未來日益複雜的認知應用,在人機之間建立無縫互動介面將繼續成為NLP的首要任務。以下是NLP的一些非常有用的應用。
機器翻譯
機器翻譯 (MT) 是自然語言處理最重要的應用之一。MT基本上是一個將一種源語言或文字翻譯成另一種語言的過程。機器翻譯系統可以是雙語的或多語的。
反垃圾郵件
由於不需要的電子郵件數量急劇增加,垃圾郵件過濾器變得非常重要,因為它構成了抵禦此問題的首要防線。透過將誤報和漏報問題視為主要問題,NLP的功能可用於開發垃圾郵件過濾系統。
N元語法建模、詞幹提取和貝葉斯分類是一些現有的可用於垃圾郵件過濾的NLP模型。
資訊檢索和網路搜尋
大多數搜尋引擎(如谷歌、雅虎、必應、WolframAlpha等)都將其機器翻譯 (MT) 技術建立在NLP深度學習模型之上。這種深度學習模型允許演算法閱讀網頁上的文字,解釋其含義並將其翻譯成另一種語言。
自動文字摘要
自動文字摘要是一種建立較長文字文件的簡短、準確摘要的技術。因此,它有助於我們更快地獲取相關資訊。在這個數字時代,我們迫切需要自動文字摘要,因為網際網路上的資訊氾濫成災,而且這種情況不會停止。NLP及其功能在開發自動文字摘要中發揮著重要作用。
語法糾正
拼寫檢查和語法糾正是Microsoft Word等文字處理軟體的一個非常有用的功能。自然語言處理 (NLP) 被廣泛用於此目的。
問答
問答是自然語言處理 (NLP) 的另一個主要應用,它專注於構建能夠自動回答使用者用自然語言提出的問題的系統。
情感分析
情感分析是自然語言處理 (NLP) 的另一個重要應用。顧名思義,情感分析用於:
識別多個帖子中的情感,以及
識別未明確表達情感的地方的情感。
亞馬遜、eBay等線上電子商務公司正在使用情感分析來識別客戶線上的意見和情感。這將幫助他們瞭解客戶對他們的產品和服務的看法。
語音引擎
Siri、Google Voice、Alexa等語音引擎都建立在NLP之上,以便我們可以用自然語言與它們進行交流。
實施NLP
為了構建上述應用程式,我們需要具備特定技能,並對語言以及高效處理語言的工具有很好的理解。為此,我們有各種可用的開源工具。其中一些是開源的,而另一些則是由組織開發的,用於構建他們自己的NLP應用程式。以下是某些NLP工具的列表:
自然語言工具包 (NLTK)
Mallet
GATE
OpenNLP
UIMA
Gensim
斯坦福工具包
這些工具大多數是用Java編寫的。
自然語言工具包 (NLTK)
在上述NLP工具中,NLTK在易用性和概念解釋方面得分很高。Python的學習曲線非常快,而NLTK是用Python編寫的,因此NLTK也有非常好的學習工具包。NLTK集成了大多數任務,如分詞、詞幹提取、詞形還原、標點符號、字元計數和單詞計數。它非常優雅且易於使用。
自然語言工具包 - 入門
為了安裝NLTK,我們必須在計算機上安裝Python。您可以訪問連結www.python.org/downloads併為您的作業系統(即Windows、Mac和Linux/Unix)選擇最新版本。有關Python的基本教程,您可以參考連結www.tutorialspoint.com/python3/index.htm。
現在,一旦您在計算機系統上安裝了Python,讓我們瞭解如何安裝NLTK。
安裝NLTK
我們可以在不同的作業系統上安裝NLTK,如下所示:
在Windows上
為了在Windows作業系統上安裝NLTK,請按照以下步驟操作:
首先,開啟Windows命令提示符並導航到pip資料夾的位置。
接下來,輸入以下命令來安裝NLTK:
pip3 install nltk
現在,從Windows開始選單開啟PythonShell,並鍵入以下命令來驗證NLTK的安裝:
Import nltk
如果沒有任何錯誤,則表示您已成功在安裝了Python3的Windows作業系統上安裝了NLTK。
在Mac/Linux上
為了在Mac/Linux作業系統上安裝NLTK,請編寫以下命令:
sudo pip install -U nltk
如果您的計算機上沒有安裝pip,請按照以下說明首先安裝pip:
首先,使用以下命令更新包索引:
sudo apt update
現在,鍵入以下命令來安裝python 3的pip:
sudo apt install python3-pip
透過Anaconda
為了透過Anaconda安裝NLTK,請按照以下步驟操作:
首先,要安裝Anaconda,請訪問連結https://www.anaconda.com/download,然後選擇您需要安裝的Python版本。
在你的電腦系統上安裝好Anaconda後,開啟它的命令提示符並輸入以下命令:
conda install -c anaconda nltk
你需要檢視輸出結果並輸入“yes”。NLTK 將被下載並安裝到你的 Anaconda 包中。
下載 NLTK 的資料集和包
現在我們已經在電腦上安裝了 NLTK,但為了使用它,我們需要下載其中可用的資料集(語料庫)。一些重要的可用資料集包括 **stpwords、gutenberg、framenet_v15** 等等。
我們可以使用以下命令下載所有 NLTK 資料集:
import nltk nltk.download()
你將看到以下 NLTK 下載視窗。
現在,點選下載按鈕下載資料集。
如何執行 NLTK 指令碼?
下面是一個例子,我們使用 **PorterStemmer** nltk 類實現 Porter Stemmer 演算法。透過這個例子,你可以理解如何執行 NLTK 指令碼。
首先,我們需要匯入自然語言工具包 (nltk)。
import nltk
現在,匯入 **PorterStemmer** 類來實現 Porter Stemmer 演算法。
from nltk.stem import PorterStemmer
接下來,建立一個 Porter Stemmer 類的例項,如下所示:
word_stemmer = PorterStemmer()
現在,輸入你想要進行詞幹提取的單詞:
word_stemmer.stem('writing')
輸出
'write'
word_stemmer.stem('eating')
輸出
'eat'
自然語言工具包 - 文字分詞
什麼是分詞?
它可以定義為將一段文字分解成更小部分的過程,例如句子和單詞。這些更小的部分稱為詞元。例如,在一個句子中,單詞是一個詞元,在一個段落中,句子是一個詞元。
眾所周知,NLP 用於構建情感分析、問答系統、語言翻譯、智慧聊天機器人、語音系統等應用程式,因此,為了構建這些應用程式,理解文字中的模式至關重要。上面提到的詞元在查詢和理解這些模式方面非常有用。我們可以將分詞視為其他步驟(例如詞幹提取和詞形還原)的基礎步驟。
NLTK 包
**nltk.tokenize** 是 NLTK 模組提供的用於實現分詞過程的包。
將句子分詞為單詞
將句子分割成單詞或從字串建立單詞列表是每個文字處理活動的重要組成部分。讓我們藉助 **nltk.tokenize** 包提供的各種函式/模組來理解它。
word_tokenize 模組
**word_tokenize** 模組用於基本的分詞。以下示例將使用此模組將句子分割成單詞。
示例
import nltk
from nltk.tokenize import word_tokenize
word_tokenize('Tutorialspoint.com provides high quality technical tutorials for free.')
輸出
['Tutorialspoint.com', 'provides', 'high', 'quality', 'technical', 'tutorials', 'for', 'free', '.']
TreebankWordTokenizer 類
上面使用的 **word_tokenize** 模組基本上是一個包裝函式,它將 tokenize() 函式作為 **TreebankWordTokenizer** 類的例項呼叫。它將給出與使用 word_tokenize() 模組將句子分割成單詞時相同的輸出。讓我們看看上面實現的相同示例:
示例
首先,我們需要匯入自然語言工具包 (nltk)。
import nltk
現在,匯入 **TreebankWordTokenizer** 類來實現分詞演算法:
from nltk.tokenize import TreebankWordTokenizer
接下來,建立一個 TreebankWordTokenizer 類的例項,如下所示:
Tokenizer_wrd = TreebankWordTokenizer()
現在,輸入你想要轉換成詞元的句子:
Tokenizer_wrd.tokenize( 'Tutorialspoint.com provides high quality technical tutorials for free.' )
輸出
[ 'Tutorialspoint.com', 'provides', 'high', 'quality', 'technical', 'tutorials', 'for', 'free', '.' ]
完整的實現示例
讓我們看看下面的完整實現示例
import nltk
from nltk.tokenize import TreebankWordTokenizer
tokenizer_wrd = TreebankWordTokenizer()
tokenizer_wrd.tokenize('Tutorialspoint.com provides high quality technical
tutorials for free.')
輸出
[ 'Tutorialspoint.com', 'provides', 'high', 'quality', 'technical', 'tutorials','for', 'free', '.' ]
分詞器最重要的約定是分開處理縮寫詞。例如,如果我們為此目的使用 word_tokenize() 模組,它將給出如下輸出:
示例
import nltk
from nltk.tokenize import word_tokenize
word_tokenize('won’t')
輸出
['wo', "n't"]]
**TreebankWordTokenizer** 的這種約定是不可接受的。這就是為什麼我們有兩個替代的分詞器,即 **PunktWordTokenizer** 和 **WordPunctTokenizer**。
WordPunktTokenizer 類
另一種分詞器,它將所有標點符號分成單獨的詞元。讓我們透過以下簡單的示例來理解它:
示例
from nltk.tokenize import WordPunctTokenizer
tokenizer = WordPunctTokenizer()
tokenizer.tokenize(" I can't allow you to go home early")
輸出
['I', 'can', "'", 't', 'allow', 'you', 'to', 'go', 'home', 'early']
將文字分詞為句子
在本節中,我們將文字/段落分割成句子。NLTK 提供 **sent_tokenize** 模組來實現此目的。
為什麼需要它?
我們腦海中出現的一個顯而易見的問題是,當我們有分詞器時,為什麼我們需要句子分詞器,或者為什麼我們需要將文字分詞成句子。假設我們需要計算句子中的平均詞數,我們該如何做到這一點?為了完成此任務,我們需要句子分詞和分詞。
讓我們透過以下簡單的示例來了解句子分詞器和分詞器之間的區別:
示例
import nltk from nltk.tokenize import sent_tokenize text = "Let us understand the difference between sentence & word tokenizer. It is going to be a simple example." sent_tokenize(text)
輸出
[ "Let us understand the difference between sentence & word tokenizer.", 'It is going to be a simple example.' ]
使用正則表示式進行句子分詞
如果你覺得分詞器的輸出不可接受,並且想要完全控制如何分詞文字,我們可以使用正則表示式進行句子分詞。NLTK 提供 **RegexpTokenizer** 類來實現這一點。
讓我們透過下面的兩個例子來理解這個概念。
在第一個示例中,我們將使用正則表示式來匹配字母數字詞元加上單引號,這樣我們就不會拆分像 **“won’t”** 這樣的縮寫詞。
示例 1
import nltk
from nltk.tokenize import RegexpTokenizer
tokenizer = RegexpTokenizer("[\w']+")
tokenizer.tokenize("won't is a contraction.")
tokenizer.tokenize("can't is a contraction.")
輸出
["won't", 'is', 'a', 'contraction'] ["can't", 'is', 'a', 'contraction']
在第一個示例中,我們將使用正則表示式在空格處進行分詞。
示例 2
import nltk
from nltk.tokenize import RegexpTokenizer
tokenizer = RegexpTokenizer('/s+' , gaps = True)
tokenizer.tokenize("won't is a contraction.")
輸出
["won't", 'is', 'a', 'contraction']
從上面的輸出中,我們可以看到標點符號保留在詞元中。引數 gaps = True 表示模式將識別要進行分詞的間隙。另一方面,如果我們使用 gaps = False 引數,則該模式將用於識別詞元,這可以在以下示例中看到:
import nltk
from nltk.tokenize import RegexpTokenizer
tokenizer = RegexpTokenizer('/s+' , gaps = False)
tokenizer.tokenize("won't is a contraction.")
輸出
[ ]
它將給我們一個空白輸出。
訓練分詞器和過濾停用詞
為什麼要訓練自己的句子分詞器?
這是一個非常重要的問題,如果我們有 NLTK 的預設句子分詞器,為什麼我們需要訓練一個句子分詞器?這個問題的答案在於 NLTK 的預設句子分詞器的質量。NLTK 的預設分詞器基本上是一個通用分詞器。雖然它執行良好,但對於非標準文字(也許是我們的文字)或具有獨特格式的文字來說,它可能不是一個好的選擇。為了分詞此類文字並獲得最佳結果,我們應該訓練自己的句子分詞器。
實現示例
對於這個示例,我們將使用 webtext 語料庫。我們將從此語料庫中使用的文字檔案包含如下所示的格式為對話的文字:
Guy: How old are you? Hipster girl: You know, I never answer that question. Because to me, it's about how mature you are, you know? I mean, a fourteen year old could be more mature than a twenty-five year old, right? I'm sorry, I just never answer that question. Guy: But, uh, you're older than eighteen, right? Hipster girl: Oh, yeah.
我們將此文字檔案儲存為 training_tokenizer。NLTK 提供了一個名為 **PunktSentenceTokenizer** 的類,藉助它我們可以對原始文字進行訓練以生成自定義句子分詞器。我們可以透過讀取檔案或使用 **raw()** 方法從 NLTK 語料庫中獲取原始文字。
讓我們看看下面的例子,以便更深入地瞭解它:
首先,從 **nltk.tokenize** 包中匯入 **PunktSentenceTokenizer** 類:
from nltk.tokenize import PunktSentenceTokenizer
現在,從 **nltk.corpus** 包中匯入 **webtext** 語料庫
from nltk.corpus import webtext
接下來,使用 **raw()** 方法,從 **training_tokenizer.txt** 檔案中獲取原始文字,如下所示:
text = webtext.raw('C://Users/Leekha/training_tokenizer.txt')
現在,建立一個 **PunktSentenceTokenizer** 的例項,並列印文字檔案中分詞的句子,如下所示:
sent_tokenizer = PunktSentenceTokenizer(text) sents_1 = sent_tokenizer.tokenize(text) print(sents_1[0])
輸出
White guy: So, do you have any plans for this evening? print(sents_1[1]) Output: Asian girl: Yeah, being angry! print(sents_1[670]) Output: Guy: A hundred bucks? print(sents_1[675]) Output: Girl: But you already have a Big Mac...
完整的實現示例
from nltk.tokenize import PunktSentenceTokenizer
from nltk.corpus import webtext
text = webtext.raw('C://Users/Leekha/training_tokenizer.txt')
sent_tokenizer = PunktSentenceTokenizer(text)
sents_1 = sent_tokenizer.tokenize(text)
print(sents_1[0])
輸出
White guy: So, do you have any plans for this evening?
為了理解 NLTK 的預設句子分詞器和我們自己訓練的句子分詞器之間的區別,讓我們使用預設句子分詞器,即 sent_tokenize(),對同一個檔案進行分詞。
from nltk.tokenize import sent_tokenize
from nltk.corpus import webtext
text = webtext.raw('C://Users/Leekha/training_tokenizer.txt')
sents_2 = sent_tokenize(text)
print(sents_2[0])
Output:
White guy: So, do you have any plans for this evening?
print(sents_2[675])
Output:
Hobo: Y'know what I'd do if I was rich?
藉助輸出結果的差異,我們可以理解為什麼訓練我們自己的句子分詞器是有用的。
什麼是停用詞?
文字中存在的一些常用詞,但在句子的含義中並沒有貢獻。這些詞對於資訊檢索或自然語言處理的目的來說根本不重要。“the”和“a”是最常見的停用詞。
NLTK 停用詞語料庫
實際上,自然語言工具包帶有一個停用詞語料庫,其中包含許多語言的詞表。讓我們透過以下示例來了解其用法:
首先,從 nltk.corpus 包中匯入 stopwords 語料庫:
from nltk.corpus import stopwords
現在,我們將使用英語語言的停用詞
english_stops = set(stopwords.words('english'))
words = ['I', 'am', 'a', 'writer']
[word for word in words if word not in english_stops]
輸出
['I', 'writer']
完整的實現示例
from nltk.corpus import stopwords
english_stops = set(stopwords.words('english'))
words = ['I', 'am', 'a', 'writer']
[word for word in words if word not in english_stops]
輸出
['I', 'writer']
查詢支援的語言的完整列表
藉助以下 Python 指令碼,我們還可以找到 NLTK 停用詞語料庫支援的語言的完整列表:
from nltk.corpus import stopwords stopwords.fileids()
輸出
[ 'arabic', 'azerbaijani', 'danish', 'dutch', 'english', 'finnish', 'french', 'german', 'greek', 'hungarian', 'indonesian', 'italian', 'kazakh', 'nepali', 'norwegian', 'portuguese', 'romanian', 'russian', 'slovene', 'spanish', 'swedish', 'tajik', 'turkish' ]
在WordNet中查詢單詞
什麼是 WordNet?
WordNet 是普林斯頓大學建立的大型英語詞彙資料庫。它是 NLTK 語料庫的一部分。名詞、動詞、形容詞和副詞都被分組到同義詞集,即認知同義詞。這裡每個同義詞集表達一個不同的含義。以下是 WordNet 的一些用例:
- 它可以用來查詢單詞的定義
- 我們可以找到一個單詞的同義詞和反義詞
- 可以使用 WordNet 探索詞語關係和相似性
- 對於具有多種用途和定義的單詞進行詞義消歧
如何匯入 WordNet?
可以使用以下命令匯入 WordNet:
from nltk.corpus import wordnet
對於更簡潔的命令,請使用以下命令:
from nltk.corpus import wordnet as wn
Synset 例項
Synset 是表達相同概念的同義詞的組合。當你使用 WordNet 來查詢單詞時,你將得到一個 Synset 例項列表。
wordnet.synsets(word)
為了獲取 Synsets 列表,我們可以使用 **wordnet.synsets(word)** 在 WordNet 中查詢任何單詞。例如,在下一個 Python 示例中,我們將查詢“dog”的 Synset 以及 Synset 的一些屬性和方法:
示例
首先,匯入 wordnet,如下所示:
from nltk.corpus import wordnet as wn
現在,提供你想要查詢 Synset 的單詞:
syn = wn.synsets('dog')[0]
在這裡,我們使用 name() 方法來獲取 synset 的唯一名稱,該名稱可用於直接獲取 Synset:
syn.name() Output: 'dog.n.01'
接下來,我們使用 definition() 方法,它將給我們提供單詞的定義:
syn.definition() Output: 'a member of the genus Canis (probably descended from the common wolf) that has been domesticated by man since prehistoric times; occurs in many breeds'
另一種方法是 examples(),它將給我們提供與單詞相關的示例:
syn.examples() Output: ['the dog barked all night']
完整的實現示例
from nltk.corpus import wordnet as wn
syn = wn.synsets('dog')[0]
syn.name()
syn.definition()
syn.examples()
獲取上位詞
Synsets 以繼承樹狀結構組織,其中 **上位詞** 表示更抽象的術語,而 **下位詞** 表示更具體的術語。重要的一點是,這棵樹可以一直追溯到根上位詞。讓我們透過以下示例來理解這個概念:
from nltk.corpus import wordnet as wn
syn = wn.synsets('dog')[0]
syn.hypernyms()
輸出
[Synset('canine.n.02'), Synset('domestic_animal.n.01')]
在這裡,我們可以看到犬科動物和家養動物是“狗”的上位詞。
現在,我們可以找到“狗”的下位詞,如下所示:
syn.hypernyms()[0].hyponyms()
輸出
[
Synset('bitch.n.04'),
Synset('dog.n.01'),
Synset('fox.n.01'),
Synset('hyena.n.01'),
Synset('jackal.n.01'),
Synset('wild_dog.n.01'),
Synset('wolf.n.01')
]
從上面的輸出中,我們可以看到“狗”只是“家養動物”的眾多下位詞之一。
為了找到所有這些的根,我們可以使用以下命令:
syn.root_hypernyms()
輸出
[Synset('entity.n.01')]
從上面的輸出中,我們可以看到它只有一個根。
完整的實現示例
from nltk.corpus import wordnet as wn
syn = wn.synsets('dog')[0]
syn.hypernyms()
syn.hypernyms()[0].hyponyms()
syn.root_hypernyms()
輸出
[Synset('entity.n.01')]
WordNet 中的詞元
在語言學中,單詞的規範形式或形態形式稱為詞元。為了找到一個單詞的同義詞和反義詞,我們也可以在 WordNet 中查詢詞元。讓我們看看如何操作。
查詢同義詞
使用 lemma() 方法,我們可以找到一個 Synset 的同義詞數量。讓我們將此方法應用於“dog” synset:
示例
from nltk.corpus import wordnet as wn
syn = wn.synsets('dog')[0]
lemmas = syn.lemmas()
len(lemmas)
輸出
3
上述輸出顯示“dog”有三個詞素。
獲取第一個詞素的名稱如下:
lemmas[0].name() Output: 'dog'
獲取第二個詞素的名稱如下:
lemmas[1].name() Output: 'domestic_dog'
獲取第三個詞素的名稱如下:
lemmas[2].name() Output: 'Canis_familiaris'
實際上,一個 Synset 代表一組含義相似的詞素,而一個詞素代表一個不同的詞形。
查詢反義詞
在 WordNet 中,一些詞素也有反義詞。例如,單詞“good”共有 27 個 synset,其中 5 個的詞素有反義詞。讓我們找到反義詞(當單詞“good”用作名詞和形容詞時)。
示例 1
from nltk.corpus import wordnet as wn
syn1 = wn.synset('good.n.02')
antonym1 = syn1.lemmas()[0].antonyms()[0]
antonym1.name()
輸出
'evil'
antonym1.synset().definition()
輸出
'the quality of being morally wrong in principle or practice'
上面的例子顯示,單詞“good”用作名詞時,第一個反義詞是“evil”。
示例 2
from nltk.corpus import wordnet as wn
syn2 = wn.synset('good.a.01')
antonym2 = syn2.lemmas()[0].antonyms()[0]
antonym2.name()
輸出
'bad'
antonym2.synset().definition()
輸出
'having undesirable or negative qualities’
上面的例子顯示,單詞“good”用作形容詞時,第一個反義詞是“bad”。
詞幹提取和詞形還原
什麼是詞幹提取?
詞幹提取是一種透過去除詞綴來提取單詞基本形式的技術。就像把樹的枝條砍掉到樹幹一樣。例如,單詞eating、eats、eaten的詞幹是eat。
搜尋引擎使用詞幹提取來索引單詞。這就是為什麼搜尋引擎可以只儲存詞幹,而不是儲存單詞的所有形式。這樣,詞幹提取減少了索引的大小並提高了檢索精度。
各種詞幹提取演算法
在 NLTK 中,stemmerI(具有stem()方法)介面包含我們將接下來介紹的所有詞幹提取器。讓我們透過下圖來理解它
Porter 詞幹提取演算法
這是最常見的詞幹提取演算法之一,它基本上旨在去除和替換英語單詞的常用字尾。
PorterStemmer 類
NLTK 具有PorterStemmer類,藉助它我們可以輕鬆地為想要提取詞幹的單詞實現 Porter 詞幹提取演算法。此類知道幾種常用的詞形和字尾,藉助這些字尾,它可以將輸入詞轉換為最終詞幹。生成的詞幹通常是一個較短的單詞,具有相同的詞根含義。讓我們來看一個例子:
首先,我們需要匯入自然語言工具包 (nltk)。
import nltk
現在,匯入 **PorterStemmer** 類來實現 Porter Stemmer 演算法。
from nltk.stem import PorterStemmer
接下來,建立一個 Porter Stemmer 類的例項,如下所示:
word_stemmer = PorterStemmer()
現在,輸入您想要提取詞幹的單詞。
word_stemmer.stem('writing')
輸出
'write'
word_stemmer.stem('eating')
輸出
'eat'
完整的實現示例
import nltk
from nltk.stem import PorterStemmer
word_stemmer = PorterStemmer()
word_stemmer.stem('writing')
輸出
'write'
Lancaster 詞幹提取演算法
它是在蘭開斯特大學開發的,也是另一種非常常見的詞幹提取演算法。
LancasterStemmer 類
NLTK 具有LancasterStemmer類,藉助它我們可以輕鬆地為想要提取詞幹的單詞實現 Lancaster 詞幹提取演算法。讓我們來看一個例子:
首先,我們需要匯入自然語言工具包 (nltk)。
import nltk
現在,匯入LancasterStemmer類來實現 Lancaster 詞幹提取演算法
from nltk.stem import LancasterStemmer
接下來,建立一個LancasterStemmer類的例項,如下所示:
Lanc_stemmer = LancasterStemmer()
現在,輸入您想要提取詞幹的單詞。
Lanc_stemmer.stem('eats')
輸出
'eat'
完整的實現示例
import nltk
from nltk.stem import LancatserStemmer
Lanc_stemmer = LancasterStemmer()
Lanc_stemmer.stem('eats')
輸出
'eat'
正則表示式詞幹提取演算法
藉助這種詞幹提取演算法,我們可以構建我們自己的詞幹提取器。
RegexpStemmer 類
NLTK 具有RegexpStemmer類,藉助它我們可以輕鬆地實現正則表示式詞幹提取演算法。它基本上接受一個正則表示式,並去除與表示式匹配的任何字首或字尾。讓我們來看一個例子:
首先,我們需要匯入自然語言工具包 (nltk)。
import nltk
現在,匯入RegexpStemmer類來實現正則表示式詞幹提取演算法。
from nltk.stem import RegexpStemmer
接下來,建立一個RegexpStemmer類的例項,並提供要從單詞中去除的字尾或字首,如下所示:
Reg_stemmer = RegexpStemmer(‘ing’)
現在,輸入您想要提取詞幹的單詞。
Reg_stemmer.stem('eating')
輸出
'eat'
Reg_stemmer.stem('ingeat')
輸出
'eat'
Reg_stemmer.stem('eats')
輸出
'eat'
完整的實現示例
import nltk
from nltk.stem import RegexpStemmer
Reg_stemmer = RegexpStemmer()
Reg_stemmer.stem('ingeat')
輸出
'eat'
Snowball 詞幹提取演算法
這是另一個非常有用的詞幹提取演算法。
SnowballStemmer 類
NLTK 具有SnowballStemmer類,藉助它我們可以輕鬆地實現 Snowball 詞幹提取演算法。它支援 15 種非英語語言。為了使用這個詞幹提取類,我們需要使用我們正在使用的語言的名稱建立一個例項,然後呼叫 stem() 方法。讓我們來看一個例子:
首先,我們需要匯入自然語言工具包 (nltk)。
import nltk
現在,匯入SnowballStemmer類來實現 Snowball 詞幹提取演算法
from nltk.stem import SnowballStemmer
讓我們看看它支援的語言:
SnowballStemmer.languages
輸出
( 'arabic', 'danish', 'dutch', 'english', 'finnish', 'french', 'german', 'hungarian', 'italian', 'norwegian', 'porter', 'portuguese', 'romanian', 'russian', 'spanish', 'swedish' )
接下來,使用您想要使用的語言建立一個 SnowballStemmer 類的例項。這裡,我們正在為“法語”建立詞幹提取器。
French_stemmer = SnowballStemmer(‘french’)
現在,呼叫 stem() 方法並輸入您想要提取詞幹的單詞。
French_stemmer.stem (‘Bonjoura’)
輸出
'bonjour'
完整的實現示例
import nltk from nltk.stem import SnowballStemmer French_stemmer = SnowballStemmer(‘french’) French_stemmer.stem (‘Bonjoura’)
輸出
'bonjour'
什麼是詞形還原?
詞形還原技術類似於詞幹提取。詞形還原後得到的輸出稱為“詞素”,它是詞根詞,而不是詞幹提取的輸出。詞形還原後,我們將得到一個含義相同的有效單詞。
NLTK 提供WordNetLemmatizer類,它是wordnet語料庫的簡單包裝器。此類使用morphy()函式到WordNet CorpusReader類來查詢詞素。讓我們用一個例子來理解它:
示例
首先,我們需要匯入自然語言工具包 (nltk)。
import nltk
現在,匯入WordNetLemmatizer類來實現詞形還原技術。
from nltk.stem import WordNetLemmatizer
接下來,建立一個WordNetLemmatizer類的例項。
lemmatizer = WordNetLemmatizer()
現在,呼叫 lemmatize() 方法並輸入您想要查詢詞素的單詞。
lemmatizer.lemmatize('eating')
輸出
'eating'
lemmatizer.lemmatize('books')
輸出
'book'
完整的實現示例
import nltk
from nltk.stem import WordNetLemmatizer
lemmatizer = WordNetLemmatizer()
lemmatizer.lemmatize('books')
輸出
'book'
詞幹提取和詞形還原的區別
讓我們藉助以下示例來了解詞幹提取和詞形還原之間的區別:
import nltk
from nltk.stem import PorterStemmer
word_stemmer = PorterStemmer()
word_stemmer.stem('believes')
輸出
believ
import nltk
from nltk.stem import WordNetLemmatizer
lemmatizer = WordNetLemmatizer()
lemmatizer.lemmatize(' believes ')
輸出
believ
兩個程式的輸出都說明了詞幹提取和詞形還原之間的主要區別。PorterStemmer類從單詞中去除“es”。另一方面,WordNetLemmatizer類查詢一個有效的單詞。簡單來說,詞幹提取技術只關注單詞的形式,而詞形還原技術關注單詞的含義。這意味著應用詞形還原後,我們將始終得到一個有效的單詞。
自然語言工具包 - 單詞替換
詞幹提取和詞形還原可以被認為是一種語言壓縮。同樣,詞語替換可以被認為是文字規範化或錯誤校正。
但是為什麼我們需要詞語替換?假設如果我們談論分詞,那麼它在處理縮寫(如 can’t、won’t 等)時存在問題。因此,為了處理此類問題,我們需要詞語替換。例如,我們可以用它們的擴充套件形式替換縮寫。
使用正則表示式的詞語替換
首先,我們將替換與正則表示式匹配的單詞。但是為此,我們必須對正則表示式以及 python re 模組有基本的瞭解。在下面的示例中,我們將使用正則表示式將縮寫替換為其擴充套件形式(例如,“can’t”將被替換為“cannot”)。
示例
首先,匯入必要的包 re 來處理正則表示式。
import re from nltk.corpus import wordnet
接下來,定義您選擇的替換模式,如下所示:
R_patterns = [ (r'won\'t', 'will not'), (r'can\'t', 'cannot'), (r'i\'m', 'i am'), r'(\w+)\'ll', '\g<1> will'), (r'(\w+)n\'t', '\g<1> not'), (r'(\w+)\'ve', '\g<1> have'), (r'(\w+)\'s', '\g<1> is'), (r'(\w+)\'re', '\g<1> are'), ]
現在,建立一個可用於替換單詞的類:
class REReplacer(object):
def __init__(self, pattern = R_patterns):
self.pattern = [(re.compile(regex), repl) for (regex, repl) in patterns]
def replace(self, text):
s = text
for (pattern, repl) in self.pattern:
s = re.sub(pattern, repl, s)
return s
儲存此 python 程式(例如 repRE.py)並從 python 命令提示符執行它。執行後,當您想替換單詞時,匯入 REReplacer 類。讓我們看看怎麼做。
from repRE import REReplacer
rep_word = REReplacer()
rep_word.replace("I won't do it")
Output:
'I will not do it'
rep_word.replace("I can’t do it")
Output:
'I cannot do it'
完整的實現示例
import re
from nltk.corpus import wordnet
R_patterns = [
(r'won\'t', 'will not'),
(r'can\'t', 'cannot'),
(r'i\'m', 'i am'),
r'(\w+)\'ll', '\g<1> will'),
(r'(\w+)n\'t', '\g<1> not'),
(r'(\w+)\'ve', '\g<1> have'),
(r'(\w+)\'s', '\g<1> is'),
(r'(\w+)\'re', '\g<1> are'),
]
class REReplacer(object):
def __init__(self, patterns=R_patterns):
self.patterns = [(re.compile(regex), repl) for (regex, repl) in patterns]
def replace(self, text):
s = text
for (pattern, repl) in self.patterns:
s = re.sub(pattern, repl, s)
return s
現在,一旦您儲存了上述程式並執行它,您可以匯入該類並按如下方式使用它:
from replacerRE import REReplacer
rep_word = REReplacer()
rep_word.replace("I won't do it")
輸出
'I will not do it'
文字處理前的替換
在處理自然語言處理 (NLP) 時,一種常見的做法是在文字處理之前清理文字。在這方面,我們也可以使用我們在前面示例中建立的REReplacer類作為文字處理(即分詞)之前的初步步驟。
示例
from nltk.tokenize import word_tokenize
from replacerRE import REReplacer
rep_word = REReplacer()
word_tokenize("I won't be able to do this now")
Output:
['I', 'wo', "n't", 'be', 'able', 'to', 'do', 'this', 'now']
word_tokenize(rep_word.replace("I won't be able to do this now"))
Output:
['I', 'will', 'not', 'be', 'able', 'to', 'do', 'this', 'now']
在上面的 Python 程式碼中,我們可以輕鬆理解在不使用和使用正則表示式替換的情況下,單詞分詞器的輸出之間的區別。
重複字元的刪除
我們在日常語言中是否嚴格遵循語法?不,我們不是。例如,有時我們會寫“Hiiiiiiiiiiii Mohan”來強調“Hi”這個詞。但是計算機系統不知道“Hiiiiiiiiiiii”是“Hi”這個詞的變體。在下面的示例中,我們將建立一個名為rep_word_removal的類,該類可用於刪除重複的單詞。
示例
首先,匯入必要的包 re 來處理正則表示式
import re from nltk.corpus import wordnet
現在,建立一個可用於刪除重複單詞的類:
class Rep_word_removal(object):
def __init__(self):
self.repeat_regexp = re.compile(r'(\w*)(\w)\2(\w*)')
self.repl = r'\1\2\3'
def replace(self, word):
if wordnet.synsets(word):
return word
repl_word = self.repeat_regexp.sub(self.repl, word)
if repl_word != word:
return self.replace(repl_word)
else:
return repl_word
儲存此 python 程式(例如 removalrepeat.py)並從 python 命令提示符執行它。執行後,當您想刪除重複的單詞時,匯入Rep_word_removal類。讓我們看看怎麼做?
from removalrepeat import Rep_word_removal
rep_word = Rep_word_removal()
rep_word.replace ("Hiiiiiiiiiiiiiiiiiiiii")
Output:
'Hi'
rep_word.replace("Hellooooooooooooooo")
Output:
'Hello'
完整的實現示例
import re
from nltk.corpus import wordnet
class Rep_word_removal(object):
def __init__(self):
self.repeat_regexp = re.compile(r'(\w*)(\w)\2(\w*)')
self.repl = r'\1\2\3'
def replace(self, word):
if wordnet.synsets(word):
return word
replace_word = self.repeat_regexp.sub(self.repl, word)
if replace_word != word:
return self.replace(replace_word)
else:
return replace_word
現在,一旦您儲存了上述程式並執行它,您可以匯入該類並按如下方式使用它:
from removalrepeat import Rep_word_removal
rep_word = Rep_word_removal()
rep_word.replace ("Hiiiiiiiiiiiiiiiiiiiii")
輸出
'Hi'
同義詞和反義詞替換
用常見的同義詞替換單詞
在處理 NLP 時,尤其是在頻率分析和文字索引的情況下,在不丟失含義的情況下壓縮詞彙總是很有益的,因為它節省了大量的記憶體。為了實現這一點,我們必須定義單詞與其同義詞的對映。在下面的示例中,我們將建立一個名為word_syn_replacer的類,該類可用於用其常見的同義詞替換單詞。
示例
首先,匯入必要的包re來處理正則表示式。
import re from nltk.corpus import wordnet
接下來,建立一個接受單詞替換對映的類:
class word_syn_replacer(object): def __init__(self, word_map): self.word_map = word_map def replace(self, word): return self.word_map.get(word, word)
儲存此 python 程式(例如 replacesyn.py)並從 python 命令提示符執行它。執行後,當您想用常見的同義詞替換單詞時,匯入word_syn_replacer類。讓我們看看怎麼做。
from replacesyn import word_syn_replacer
rep_syn = word_syn_replacer ({‘bday’: ‘birthday’)
rep_syn.replace(‘bday’)
輸出
'birthday'
完整的實現示例
import re from nltk.corpus import wordnet class word_syn_replacer(object): def __init__(self, word_map): self.word_map = word_map def replace(self, word): return self.word_map.get(word, word)
現在,一旦您儲存了上述程式並執行它,您可以匯入該類並按如下方式使用它:
from replacesyn import word_syn_replacer
rep_syn = word_syn_replacer ({‘bday’: ‘birthday’)
rep_syn.replace(‘bday’)
輸出
'birthday'
上述方法的缺點是,我們必須在 Python 字典中硬編碼同義詞。我們有兩種更好的替代方案,即 CSV 和 YAML 檔案。我們可以將我們的同義詞詞彙表儲存在上述任何檔案中,並可以從中構建word_map字典。讓我們藉助示例來理解這個概念。
使用 CSV 檔案
為了為此目的使用 CSV 檔案,該檔案應包含兩列,第一列包含單詞,第二列包含用於替換它的同義詞。讓我們將此檔案儲存為syn.csv。在下面的示例中,我們將建立一個名為CSVword_syn_replacer的類,它將擴充套件replacesyn.py檔案中的word_syn_replacer,並將用於從syn.csv檔案構建word_map字典。
示例
首先,匯入必要的包。
import csv
接下來,建立一個接受單詞替換對映的類:
class CSVword_syn_replacer(word_syn_replacer):
def __init__(self, fname):
word_map = {}
for line in csv.reader(open(fname)):
word, syn = line
word_map[word] = syn
super(Csvword_syn_replacer, self).__init__(word_map)
執行後,當您想用常見的同義詞替換單詞時,匯入CSVword_syn_replacer類。讓我們看看怎麼做?
from replacesyn import CSVword_syn_replacer rep_syn = CSVword_syn_replacer (‘syn.csv’) rep_syn.replace(‘bday’)
輸出
'birthday'
完整的實現示例
import csv
class CSVword_syn_replacer(word_syn_replacer):
def __init__(self, fname):
word_map = {}
for line in csv.reader(open(fname)):
word, syn = line
word_map[word] = syn
super(Csvword_syn_replacer, self).__init__(word_map)
現在,一旦您儲存了上述程式並執行它,您可以匯入該類並按如下方式使用它:
from replacesyn import CSVword_syn_replacer rep_syn = CSVword_syn_replacer (‘syn.csv’) rep_syn.replace(‘bday’)
輸出
'birthday'
使用 YAML 檔案
正如我們使用 CSV 檔案一樣,我們也可以為此目的使用 YAML 檔案(我們必須安裝 PyYAML)。讓我們將檔案儲存為syn.yaml。在下面的示例中,我們將建立一個名為YAMLword_syn_replacer的類,它將擴充套件replacesyn.py檔案中的word_syn_replacer,並將用於從syn.yaml檔案構建word_map字典。
示例
首先,匯入必要的包。
import yaml
接下來,建立一個接受單詞替換對映的類:
class YAMLword_syn_replacer(word_syn_replacer): def __init__(self, fname): word_map = yaml.load(open(fname)) super(YamlWordReplacer, self).__init__(word_map)
執行後,當您想用常見的同義詞替換單詞時,匯入YAMLword_syn_replacer類。讓我們看看怎麼做?
from replacesyn import YAMLword_syn_replacer rep_syn = YAMLword_syn_replacer (‘syn.yaml’) rep_syn.replace(‘bday’)
輸出
'birthday'
完整的實現示例
import yaml class YAMLword_syn_replacer(word_syn_replacer): def __init__(self, fname): word_map = yaml.load(open(fname)) super(YamlWordReplacer, self).__init__(word_map)
現在,一旦您儲存了上述程式並執行它,您可以匯入該類並按如下方式使用它:
from replacesyn import YAMLword_syn_replacer rep_syn = YAMLword_syn_replacer (‘syn.yaml’) rep_syn.replace(‘bday’)
輸出
'birthday'
反義詞替換
眾所周知,反義詞是與另一個單詞具有相反含義的單詞,同義詞替換的反義詞稱為反義詞替換。在本節中,我們將處理反義詞替換,即使用 WordNet 用明確的反義詞替換單詞。在下面的示例中,我們將建立一個名為word_antonym_replacer的類,它具有兩種方法,一種用於替換單詞,另一種用於刪除否定詞。
示例
首先,匯入必要的包。
from nltk.corpus import wordnet
接下來,建立名為word_antonym_replacer的類:
class word_antonym_replacer(object):
def replace(self, word, pos=None):
antonyms = set()
for syn in wordnet.synsets(word, pos=pos):
for lemma in syn.lemmas():
for antonym in lemma.antonyms():
antonyms.add(antonym.name())
if len(antonyms) == 1:
return antonyms.pop()
else:
return None
def replace_negations(self, sent):
i, l = 0, len(sent)
words = []
while i < l:
word = sent[i]
if word == 'not' and i+1 < l:
ant = self.replace(sent[i+1])
if ant:
words.append(ant)
i += 2
continue
words.append(word)
i += 1
return words
儲存此 python 程式(例如 replaceantonym.py)並從 python 命令提示符執行它。執行後,當您想用明確的反義詞替換單詞時,匯入word_antonym_replacer類。讓我們看看怎麼做。
from replacerantonym import word_antonym_replacer rep_antonym = word_antonym_replacer () rep_antonym.replace(‘uglify’)
輸出
['beautify''] sentence = ["Let us", 'not', 'uglify', 'our', 'country'] rep_antonym.replace _negations(sentence)
輸出
["Let us", 'beautify', 'our', 'country']
完整的實現示例
nltk.corpus import wordnet
class word_antonym_replacer(object):
def replace(self, word, pos=None):
antonyms = set()
for syn in wordnet.synsets(word, pos=pos):
for lemma in syn.lemmas():
for antonym in lemma.antonyms():
antonyms.add(antonym.name())
if len(antonyms) == 1:
return antonyms.pop()
else:
return None
def replace_negations(self, sent):
i, l = 0, len(sent)
words = []
while i < l:
word = sent[i]
if word == 'not' and i+1 < l:
ant = self.replace(sent[i+1])
if ant:
words.append(ant)
i += 2
continue
words.append(word)
i += 1
return words
現在,一旦您儲存了上述程式並執行它,您可以匯入該類並按如下方式使用它:
from replacerantonym import word_antonym_replacer rep_antonym = word_antonym_replacer () rep_antonym.replace(‘uglify’) sentence = ["Let us", 'not', 'uglify', 'our', 'country'] rep_antonym.replace _negations(sentence)
輸出
["Let us", 'beautify', 'our', 'country']
語料庫讀取器和自定義語料庫
什麼是語料庫?
語料庫是在自然交流環境中產生的,以結構化格式的大型機器可讀文字集合。Corpora 是 Corpus 的複數形式。語料庫可以以多種方式派生,如下所示:
- 來自最初為電子的文字
- 來自口語的記錄
- 來自光學字元識別等等
語料庫代表性、語料庫平衡、抽樣、語料庫大小是設計語料庫時起重要作用的因素。一些最流行的用於 NLP 任務的語料庫是 TreeBank、PropBank、VarbNet 和 WordNet。
如何構建自定義語料庫?
下載NLTK時,我們也安裝了NLTK資料包。因此,我們的計算機上已經安裝了NLTK資料包。如果討論Windows系統,我們假設此資料包安裝在**C:\natural_language_toolkit_data**;如果討論Linux、Unix和Mac OS X系統,我們假設此資料包安裝在**/usr/share/natural_language_toolkit_data**。
在下面的Python示例中,我們將建立自定義語料庫,它必須位於NLTK定義的路徑之一中,因為NLTK才能找到它。為了避免與官方NLTK資料包衝突,讓我們在我們的主目錄中建立一個名為custom_natural_language_toolkit_data的目錄。
import os, os.path
path = os.path.expanduser('~/natural_language_toolkit_data')
if not os.path.exists(path):
os.mkdir(path)
os.path.exists(path)
輸出
True
現在,讓我們檢查我們的主目錄中是否存在natural_language_toolkit_data目錄:
import nltk.data path in nltk.data.path
輸出
True
由於輸出為True,這意味著我們的主目錄中存在**nltk_data**目錄。
現在,我們將建立一個名為**wordfile.txt**的詞表檔案,並將其放在**nltk_data**目錄下的名為corpus的資料夾中**(~/nltk_data/corpus/wordfile.txt)**,然後使用**nltk.data.load**載入它:
import nltk.data nltk.data.load(‘corpus/wordfile.txt’, format = ‘raw’)
輸出
b’tutorialspoint\n’
語料庫讀取器
NLTK提供各種CorpusReader類。我們將在接下來的Python示例中介紹它們。
建立詞表語料庫
NLTK具有**WordListCorpusReader**類,該類提供對包含單詞列表的檔案的訪問。對於下面的Python示例,我們需要建立一個詞表檔案,可以是CSV檔案或普通文字檔案。例如,我們建立了一個名為“list”的檔案,其中包含以下資料:
tutorialspoint Online Free Tutorials
現在,讓我們例項化一個**WordListCorpusReader**類,從我們建立的檔案“list”中生成單詞列表。
from nltk.corpus.reader import WordListCorpusReader
reader_corpus = WordListCorpusReader('.', ['list'])
reader_corpus.words()
輸出
['tutorialspoint', 'Online', 'Free', 'Tutorials']
建立詞性標註語料庫
NLTK具有**TaggedCorpusReader**類,我們可以用它來建立一個詞性標註語料庫。實際上,詞性標註是識別單詞詞性標籤的過程。
標註語料庫最簡單的格式之一是“word/tag”的形式,例如brown語料庫中的以下摘錄:
The/at-tl expense/nn and/cc time/nn involved/vbn are/ber astronomical/jj ./.
在上面的摘錄中,每個單詞都有一個標籤表示其詞性。例如,**vb**表示動詞。
現在,讓我們例項化一個**TaggedCorpusReader**類,從包含上述摘錄的檔案“list.pos”中生成詞性標註詞。
from nltk.corpus.reader import TaggedCorpusReader
reader_corpus = TaggedCorpusReader('.', r'.*\.pos')
reader_corpus.tagged_words()
輸出
[('The', 'AT-TL'), ('expense', 'NN'), ('and', 'CC'), ...]
建立分塊短語語料庫
NLTK具有**ChunkedCorpusReader**類,我們可以用它來建立一個分塊短語語料庫。實際上,一個分塊是句子中的一個短語。
例如,我們有以下來自標註的treebank語料庫的摘錄:
[Earlier/JJR staff-reduction/NN moves/NNS] have/VBP trimmed/VBN about/ IN [300/CD jobs/NNS] ,/, [the/DT spokesman/NN] said/VBD ./.
在上面的摘錄中,每個分塊都是一個名詞短語,但不在括號中的單詞是句子樹的一部分,而不是任何名詞短語子樹的一部分。
現在,讓我們例項化一個**ChunkedCorpusReader**類,從包含上述摘錄的檔案“list.chunk”中生成分塊短語。
from nltk.corpus.reader import ChunkedCorpusReader
reader_corpus = TaggedCorpusReader('.', r'.*\.chunk')
reader_corpus.chunked_words()
輸出
[
Tree('NP', [('Earlier', 'JJR'), ('staff-reduction', 'NN'), ('moves', 'NNS')]),
('have', 'VBP'), ...
]
建立分類文字語料庫
NLTK具有**CategorizedPlaintextCorpusReader**類,我們可以用它來建立一個分類文字語料庫。當我們擁有大量的文字語料庫並希望將其分成不同的部分時,它非常有用。
例如,brown語料庫有幾個不同的類別。讓我們使用以下Python程式碼找出它們:
from nltk.corpus import brown^M brown.categories()
輸出
[ 'adventure', 'belles_lettres', 'editorial', 'fiction', 'government', 'hobbies', 'humor', 'learned', 'lore', 'mystery', 'news', 'religion', 'reviews', 'romance', 'science_fiction' ]
對語料庫進行分類最簡單的方法之一是為每個類別建立一個檔案。例如,讓我們看看來自**movie_reviews**語料庫的兩個摘錄:
movie_pos.txt
The thin red line is flawed but it provokes.
movie_neg.txt
A big-budget and glossy production cannot make up for a lack of spontaneity that permeates their tv show.
因此,從以上兩個檔案中,我們有兩個類別,即**pos**和**neg**。
現在讓我們例項化一個**CategorizedPlaintextCorpusReader**類。
from nltk.corpus.reader import CategorizedPlaintextCorpusReader
reader_corpus = CategorizedPlaintextCorpusReader('.', r'movie_.*\.txt',
cat_pattern = r'movie_(\w+)\.txt')
reader_corpus.categories()
reader_corpus.fileids(categories = [‘neg’])
reader_corpus.fileids(categories = [‘pos’])
輸出
['neg', 'pos'] ['movie_neg.txt'] ['movie_pos.txt']
詞性標註基礎
什麼是詞性標註?
標註,一種分類,是對詞元的描述進行自動賦值。我們將描述符稱為“標籤”,它表示詞性的一部分(名詞、動詞、副詞、形容詞、代詞、連線詞及其子類別)、語義資訊等等。
另一方面,如果我們談論詞性(POS)標註,它可以定義為將句子從單詞列表的形式轉換為元組列表的過程。這裡,元組的形式為(單詞,標籤)。我們也可以將詞性標註稱為為給定單詞分配詞性之一的過程。
下表顯示了Penn Treebank語料庫中使用的最常見的詞性標註:
| 序號 | 標籤 | 描述 |
|---|---|---|
| 1 | NNP | 專有名詞,單數 |
| 2 | NNPS | 專有名詞,複數 |
| 3 | PDT | 前限定詞 |
| 4 | POS | 所有格結尾 |
| 5 | PRP | 人稱代詞 |
| 6 | PRP$ | 所有格代詞 |
| 7 | RB | 副詞 |
| 8 | RBR | 副詞,比較級 |
| 9 | RBS | 副詞,最高階 |
| 10 | RP | 語氣助詞 |
| 11 | SYM | 符號(數學或科學符號) |
| 12 | TO | to |
| 13 | UH | 感嘆詞 |
| 14 | VB | 動詞,原型 |
| 15 | VBD | 動詞,過去時 |
| 16 | VBG | 動詞,現在分詞/動名詞 |
| 17 | VBN | 動詞,過去分詞 |
| 18 | WP | 疑問代詞 |
| 19 | WP$ | 所有格疑問代詞 |
| 20 | WRB | 疑問副詞 |
| 21 | # | 磅符號 |
| 22 | $ | 美元符號 |
| 23 | . | 句末標點 |
| 24 | , | 逗號 |
| 25 | : | 冒號,分號 |
| 26 | ( | 左括號 |
| 27 | ) | 右括號 |
| 28 | " | 直雙引號 |
| 29 | ' | 左單引號 |
| 30 | " | 左雙引號 |
| 31 | ' | 右單引號 |
| 32 | " | 右雙引號 |
示例
讓我們透過一個Python實驗來理解它:
import nltk from nltk import word_tokenize sentence = "I am going to school" print (nltk.pos_tag(word_tokenize(sentence)))
輸出
[('I', 'PRP'), ('am', 'VBP'), ('going', 'VBG'), ('to', 'TO'), ('school', 'NN')]
為什麼要進行詞性標註?
詞性標註是NLP的重要組成部分,因為它作為進一步NLP分析的先決條件,如下所示:
- 分塊
- 句法分析
- 資訊提取
- 機器翻譯
- 情感分析
- 語法分析和詞義消歧
TaggerI - 基類
所有標註器都位於NLTK的nltk.tag包中。這些標註器的基類是**TaggerI**,這意味著所有標註器都繼承自此類。
**方法** - TaggerI類具有以下兩種方法,所有子類都必須實現:
**tag()方法** - 正如其名稱所示,此方法將單詞列表作為輸入,並返回標註單詞列表作為輸出。
**evaluate()方法** - 使用此方法,我們可以評估標註器的準確性。
詞性標註的基線
詞性標註的基線或基本步驟是**預設標註**,可以使用NLTK的DefaultTagger類執行。預設標註只是為每個詞元分配相同的詞性標籤。預設標註還提供了一個基線來衡量準確性改進。
DefaultTagger類
預設標註是使用**DefaultTagging**類執行的,它只接受一個引數,即我們要應用的標籤。
它是如何工作的?
如前所述,所有標註器都繼承自**TaggerI**類。**DefaultTagger**繼承自**SequentialBackoffTagger**,它是**TaggerI類**的子類。讓我們透過下圖來了解它:
作為**SeuentialBackoffTagger**的一部分,**DefaultTagger**必須實現choose_tag()方法,該方法接受以下三個引數。
- 詞元列表
- 當前詞元的索引
- 前一個詞元列表,即歷史記錄
示例
import nltk
from nltk.tag import DefaultTagger
exptagger = DefaultTagger('NN')
exptagger.tag(['Tutorials','Point'])
輸出
[('Tutorials', 'NN'), ('Point', 'NN')]
在這個例子中,我們選擇了一個名詞標籤,因為它是最常見的詞類。此外,當我們選擇最常見的詞性標籤時,**DefaultTagger**也最有用。
準確性評估
**DefaultTagger**也是評估標註器準確性的基線。這就是為什麼我們可以將它與**evaluate()**方法一起使用來衡量準確性的原因。**evaluate()**方法將標記詞元列表作為黃金標準來評估標註器。
以下是一個例子,我們使用上面建立的預設標註器exptagger來評估treebank語料庫標記句子的子集的準確性:
示例
import nltk
from nltk.tag import DefaultTagger
exptagger = DefaultTagger('NN')
from nltk.corpus import treebank
testsentences = treebank.tagged_sents() [1000:]
exptagger.evaluate (testsentences)
輸出
0.13198749536374715
上面的輸出顯示,透過為每個標籤選擇NN,我們可以對treebank語料庫的1000個條目進行測試,實現大約13%的準確率。
標記句子列表
NLTK的**TaggerI**類除了標記單個句子之外,還提供了一個**tag_sents()**方法,我們可以用它來標記句子列表。以下是在其中我們標記了兩個簡單句子的示例
示例
import nltk
from nltk.tag import DefaultTagger
exptagger = DefaultTagger('NN')
exptagger.tag_sents([['Hi', ','], ['How', 'are', 'you', '?']])
輸出
[
[
('Hi', 'NN'),
(',', 'NN')
],
[
('How', 'NN'),
('are', 'NN'),
('you', 'NN'),
('?', 'NN')
]
]
在上面的例子中,我們使用了我們之前建立的名為exptagger的預設標註器。
取消標記句子
我們也可以取消標記句子。NLTK為此提供nltk.tag.untag()方法。它將接收一個標記的句子作為輸入,並提供一個沒有標籤的單詞列表。讓我們看一個例子:
示例
import nltk
from nltk.tag import untag
untag([('Tutorials', 'NN'), ('Point', 'NN')])
輸出
['Tutorials', 'Point']
自然語言工具包 - 一元語法標註器
什麼是Unigram標註器?
顧名思義,unigram標註器只使用單個單詞作為其上下文來確定詞性(Part-of-Speech)標籤。簡單來說,Unigram標註器是一個基於上下文的標註器,其上下文是一個單詞,即Unigram。
它是如何工作的?
NLTK為此提供了一個名為**UnigramTagger**的模組。但在深入瞭解其工作原理之前,讓我們藉助下圖瞭解其層次結構:
從上圖可以看出,**UnigramTagger**繼承自**NgramTagger**,它是**ContextTagger**的子類,後者繼承自**SequentialBackoffTagger**。
**UnigramTagger**的工作原理透過以下步驟解釋:
正如我們所看到的,**UnigramTagger**繼承自**ContextTagger**,它實現了一個**context()**方法。此**context()**方法與**choose_tag()**方法接受相同的三個引數。
**context()**方法的結果將是單詞詞元,它將進一步用於建立模型。建立模型後,單詞詞元也用於查詢最佳標籤。
透過這種方式,**UnigramTagger**將從標記的句子列表中構建上下文模型。
訓練Unigram標註器
NLTK 的UnigramTagger可以透過在初始化時提供已標註句子的列表來進行訓練。在下面的例子中,我們將使用樹庫語料庫的已標註句子。我們將使用該語料庫的前 2500 個句子。
示例
首先從 nltk 匯入 UniframTagger 模組:
from nltk.tag import UnigramTagger
接下來,匯入您想要使用的語料庫。這裡我們使用的是 treebank 語料庫:
from nltk.corpus import treebank
現在,獲取用於訓練的句子。我們將前 2500 個句子用於訓練,並將對其進行標註:
train_sentences = treebank.tagged_sents()[:2500]
接下來,將 UnigramTagger 應用於用於訓練的句子:
Uni_tagger = UnigramTagger(train_sentences)
取一些句子,數量等於或少於訓練目的的句子數(即 2500 個),用於測試。這裡我們取前 1500 個句子用於測試:
test_sentences = treebank.tagged_sents()[1500:] Uni_tagger.evaluate(test_sents)
輸出
0.8942306156033808
在這裡,我們得到了大約 89% 的準確率,這是一個使用單個詞查詢來確定詞性標籤的標註器。
完整的實現示例
from nltk.tag import UnigramTagger from nltk.corpus import treebank train_sentences = treebank.tagged_sents()[:2500] Uni_tagger = UnigramTagger(train_sentences) test_sentences = treebank.tagged_sents()[1500:] Uni_tagger.evaluate(test_sentences)
輸出
0.8942306156033808
覆蓋上下文模型
從上圖顯示的UnigramTagger 層次結構中,我們知道所有繼承自ContextTagger的標註器,而不是訓練它們自己的模型,都可以使用預構建的模型。這個預構建的模型只是一個簡單的 Python 字典,它將上下文鍵對映到標籤。對於UnigramTagger,上下文鍵是單個單詞;對於其他NgramTagger子類,它將是元組。
我們可以透過向UnigramTagger類傳遞另一個簡單的模型來覆蓋此上下文模型,而不是傳遞訓練集。讓我們透過下面的一個簡單示例來了解它:
示例
from nltk.tag import UnigramTagger
from nltk.corpus import treebank
Override_tagger = UnigramTagger(model = {‘Vinken’ : ‘NN’})
Override_tagger.tag(treebank.sents()[0])
輸出
[
('Pierre', None),
('Vinken', 'NN'),
(',', None),
('61', None),
('years', None),
('old', None),
(',', None),
('will', None),
('join', None),
('the', None),
('board', None),
('as', None),
('a', None),
('nonexecutive', None),
('director', None),
('Nov.', None),
('29', None),
('.', None)
]
由於我們的模型只包含“Vinken”作為唯一的上下文鍵,您可以從上面的輸出中觀察到,只有這個詞得到了標註,其他所有詞的標籤都是 None。
設定最小頻率閾值
為了決定給定上下文中哪個標籤最有可能,ContextTagger類使用出現頻率。即使上下文詞和標籤只出現一次,它也會預設這樣做,但是我們可以透過向UnigramTagger類傳遞一個cutoff值來設定最小頻率閾值。在下面的示例中,我們在之前訓練 UnigramTagger 的方法中傳遞了 cutoff 值:
示例
from nltk.tag import UnigramTagger from nltk.corpus import treebank train_sentences = treebank.tagged_sents()[:2500] Uni_tagger = UnigramTagger(train_sentences, cutoff = 4) test_sentences = treebank.tagged_sents()[1500:] Uni_tagger.evaluate(test_sentences)
輸出
0.7357651629613641
自然語言工具包 - 組合標註器
組合標註器
組合標註器或將標註器彼此連結是 NLTK 的重要特性之一。組合標註器背後的主要概念是,如果一個標註器不知道如何標註一個詞,它將被傳遞給連結的標註器。為了實現這個目的,SequentialBackoffTagger為我們提供了回退標註功能。
回退標註
如前所述,回退標註是SequentialBackoffTagger的重要特性之一,它允許我們將標註器組合起來,這樣如果一個標註器不知道如何標註一個詞,該詞將被傳遞給下一個標註器,依此類推,直到沒有剩餘的回退標註器可供檢查。
它是如何工作的?
實際上,SequentialBackoffTagger的每個子類都可以接受一個“backoff”關鍵字引數。這個關鍵字引數的值是另一個SequentialBackoffTagger例項。現在,每當初始化這個SequentialBackoffTagger類時,都會建立一個回退標註器的內部列表(自身作為第一個元素)。此外,如果給定一個回退標註器,則會將此回退標註器的內部列表附加到其中。
在下面的示例中,我們在上面訓練UnigramTagger的 Python 方法中使用DefaulTagger作為回退標註器。
示例
在這個例子中,我們使用DefaulTagger作為回退標註器。每當UnigramTagger無法標註一個詞時,回退標註器(在本例中為DefaulTagger)將用“NN”對其進行標註。
from nltk.tag import UnigramTagger
from nltk.tag import DefaultTagger
from nltk.corpus import treebank
train_sentences = treebank.tagged_sents()[:2500]
back_tagger = DefaultTagger('NN')
Uni_tagger = UnigramTagger(train_sentences, backoff = back_tagger)
test_sentences = treebank.tagged_sents()[1500:]
Uni_tagger.evaluate(test_sentences)
輸出
0.9061975746536931
從上面的輸出中,您可以看到透過添加回退標註器,準確率提高了大約 2%。
使用 pickle 儲存標註器
正如我們所看到的,訓練標註器非常繁瑣,而且也需要時間。為了節省時間,我們可以將訓練好的標註器序列化(pickle),以便以後使用。在下面的示例中,我們將對我們已經訓練好的名為“Uni_tagger”的標註器執行此操作。
示例
import pickle
f = open('Uni_tagger.pickle','wb')
pickle.dump(Uni_tagger, f)
f.close()
f = open('Uni_tagger.pickle','rb')
Uni_tagger = pickle.load(f)
NgramTagger 類
從前面單元中討論的層次結構圖中,UnigramTagger繼承自NgramTagger類,但我們還有NgramTagger類的另外兩個子類:
BigramTagger 子類
實際上,n 元語法是 n 個專案的子序列,因此,顧名思義,BigramTagger子類檢視兩個專案。第一個專案是前一個已標註的詞,第二個專案是當前已標註的詞。
TrigramTagger 子類
與BigramTagger類似,TrigramTagger子類檢視三個專案,即前兩個已標註的詞和一個當前已標註的詞。
實際上,如果我們像使用 UnigramTagger 子類一樣單獨應用BigramTagger和TrigramTagger子類,它們的表現都很差。讓我們在下面的例子中看看。
使用 BigramTagger 子類
from nltk.tag import BigramTagger from nltk.corpus import treebank train_sentences = treebank.tagged_sents()[:2500] Bi_tagger = BigramTagger(train_sentences) test_sentences = treebank.tagged_sents()[1500:] Bi_tagger.evaluate(test_sentences)
輸出
0.44669191071913594
使用 TrigramTagger 子類
from nltk.tag import TrigramTagger from nltk.corpus import treebank train_sentences = treebank.tagged_sents()[:2500] Tri_tagger = TrigramTagger(train_sentences) test_sentences = treebank.tagged_sents()[1500:] Tri_tagger.evaluate(test_sentences)
輸出
0.41949863394526193
您可以將我們之前使用的 UnigramTagger(準確率約為 89%)的效能與 BigramTagger(準確率約為 44%)和 TrigramTagger(準確率約為 41%)進行比較。原因是 Bigram 和 Trigram 標註器無法從句子的第一個詞學習上下文。另一方面,UnigramTagger 類不關心之前的上下文,並猜測每個詞最常見的標籤,因此能夠具有較高的基準準確率。
組合 n 元語法標註器
從上面的例子可以看出,當我們將 Bigram 和 Trigram 標註器與回退標註結合使用時,它們可以做出貢獻。在下面的示例中,我們將 Unigram、Bigram 和 Trigram 標註器與回退標註結合使用。該概念與之前結合 UnigramTagger 和回退標註器的方案相同。唯一的區別是我們使用了來自 tagger_util.py 的名為 backoff_tagger() 的函式(如下所示)來進行回退操作。
def backoff_tagger(train_sentences, tagger_classes, backoff=None):
for cls in tagger_classes:
backoff = cls(train_sentences, backoff=backoff)
return backoff
示例
from tagger_util import backoff_tagger
from nltk.tag import UnigramTagger
from nltk.tag import BigramTagger
from nltk.tag import TrigramTagger
from nltk.tag import DefaultTagger
from nltk.corpus import treebank
train_sentences = treebank.tagged_sents()[:2500]
back_tagger = DefaultTagger('NN')
Combine_tagger = backoff_tagger(train_sentences,
[UnigramTagger, BigramTagger, TrigramTagger], backoff = back_tagger)
test_sentences = treebank.tagged_sents()[1500:]
Combine_tagger.evaluate(test_sentences)
輸出
0.9234530029238365
從上面的輸出中,我們可以看到它將準確率提高了大約 3%。
更多自然語言工具包標註器
詞綴標註器
ContextTagger 子類的另一個重要類是 AffixTagger。在 AffixTagger 類中,上下文是單詞的字首或字尾。這就是 AffixTagger 類可以根據單詞開頭或結尾的固定長度子字串學習標籤的原因。
它是如何工作的?
它的工作取決於名為 affix_length 的引數,該引數指定字首或字尾的長度。預設值為 3。但它是如何區分 AffixTagger 類學習的是單詞的字首還是字尾的呢?
affix_length=正數 - 如果 affix_lenght 的值為正數,則表示 AffixTagger 類將學習單詞的字首。
affix_length=負數 - 如果 affix_lenght 的值為負數,則表示 AffixTagger 類將學習單詞的字尾。
為了更清楚地說明,在下面的示例中,我們將對已標註的 treebank 句子使用 AffixTagger 類。
示例
在這個例子中,AffixTagger 將學習單詞的字首,因為我們沒有為 affix_length 引數指定任何值。該引數將採用預設值 3:
from nltk.tag import AffixTagger from nltk.corpus import treebank train_sentences = treebank.tagged_sents()[:2500] Prefix_tagger = AffixTagger(train_sentences) test_sentences = treebank.tagged_sents()[1500:] Prefix_tagger.evaluate(test_sentences)
輸出
0.2800492099250667
讓我們在下面的例子中看看當我們將 affix_length 引數的值設定為 4 時,準確率是多少:
from nltk.tag import AffixTagger from nltk.corpus import treebank train_sentences = treebank.tagged_sents()[:2500] Prefix_tagger = AffixTagger(train_sentences, affix_length=4 ) test_sentences = treebank.tagged_sents()[1500:] Prefix_tagger.evaluate(test_sentences)
輸出
0.18154947354966527
示例
在這個例子中,AffixTagger 將學習單詞的字尾,因為我們將為 affix_length 引數指定負值。
from nltk.tag import AffixTagger from nltk.corpus import treebank train_sentences = treebank.tagged_sents()[:2500] Suffix_tagger = AffixTagger(train_sentences, affix_length = -3) test_sentences = treebank.tagged_sents()[1500:] Suffix_tagger.evaluate(test_sentences)
輸出
0.2800492099250667
Brill 標註器
Brill 標註器是一種基於轉換的標註器。NLTK 提供了BrillTagger類,這是第一個不是SequentialBackoffTagger子類的標註器。與之相反,BrillTagger使用一系列規則來糾正初始標註器的結果。
它是如何工作的?
要使用BrillTaggerTrainer訓練BrillTagger類,我們定義以下函式:
def train_brill_tagger(initial_tagger, train_sentences, **kwargs) -
templates = [ brill.Template(brill.Pos([-1])), brill.Template(brill.Pos([1])), brill.Template(brill.Pos([-2])), brill.Template(brill.Pos([2])), brill.Template(brill.Pos([-2, -1])), brill.Template(brill.Pos([1, 2])), brill.Template(brill.Pos([-3, -2, -1])), brill.Template(brill.Pos([1, 2, 3])), brill.Template(brill.Pos([-1]), brill.Pos([1])), brill.Template(brill.Word([-1])), brill.Template(brill.Word([1])), brill.Template(brill.Word([-2])), brill.Template(brill.Word([2])), brill.Template(brill.Word([-2, -1])), brill.Template(brill.Word([1, 2])), brill.Template(brill.Word([-3, -2, -1])), brill.Template(brill.Word([1, 2, 3])), brill.Template(brill.Word([-1]), brill.Word([1])), ] trainer = brill_trainer.BrillTaggerTrainer(initial_tagger, templates, deterministic=True) return trainer.train(train_sentences, **kwargs)
正如我們所看到的,此函式需要initial_tagger和train_sentences。它接受一個initial_tagger引數和一個模板列表,這些模板實現了BrillTemplate介面。BrillTemplate介面位於nltk.tbl.template模組中。其中一個實現是brill.Template類。
基於轉換的標註器的主要作用是生成轉換規則,以糾正初始標註器的輸出,使其更符合訓練句子。讓我們看看下面的工作流程:
示例
對於此示例,我們將使用combine_tagger(我們在組合標註器時建立的,在上一個方法中從NgramTagger類的回退鏈中),作為initial_tagger。首先,讓我們使用Combine.tagger評估結果,然後將其用作initial_tagger來訓練 brill 標註器。
from tagger_util import backoff_tagger
from nltk.tag import UnigramTagger
from nltk.tag import BigramTagger
from nltk.tag import TrigramTagger
from nltk.tag import DefaultTagger
from nltk.corpus import treebank
train_sentences = treebank.tagged_sents()[:2500]
back_tagger = DefaultTagger('NN')
Combine_tagger = backoff_tagger(
train_sentences, [UnigramTagger, BigramTagger, TrigramTagger], backoff = back_tagger
)
test_sentences = treebank.tagged_sents()[1500:]
Combine_tagger.evaluate(test_sentences)
輸出
0.9234530029238365
現在,讓我們看看當使用Combine_tagger作為initial_tagger來訓練 brill 標註器時的評估結果:
from tagger_util import train_brill_tagger brill_tagger = train_brill_tagger(combine_tagger, train_sentences) brill_tagger.evaluate(test_sentences)
輸出
0.9246832510505041
我們可以注意到,BrillTagger類的準確率比Combine_tagger略有提高。
完整的實現示例
from tagger_util import backoff_tagger
from nltk.tag import UnigramTagger
from nltk.tag import BigramTagger
from nltk.tag import TrigramTagger
from nltk.tag import DefaultTagger
from nltk.corpus import treebank
train_sentences = treebank.tagged_sents()[:2500]
back_tagger = DefaultTagger('NN')
Combine_tagger = backoff_tagger(train_sentences,
[UnigramTagger, BigramTagger, TrigramTagger], backoff = back_tagger)
test_sentences = treebank.tagged_sents()[1500:]
Combine_tagger.evaluate(test_sentences)
from tagger_util import train_brill_tagger
brill_tagger = train_brill_tagger(combine_tagger, train_sentences)
brill_tagger.evaluate(test_sentences)
輸出
0.9234530029238365 0.9246832510505041
TnT 標註器
TnT 標註器(代表 Trigrams’nTags)是一種基於二階馬爾可夫模型的統計標註器。
它是如何工作的?
我們可以透過以下步驟瞭解 TnT 標註器的工作原理:
首先,基於訓練資料,TnT 標註器維護多個內部FreqDist和ConditionalFreqDist例項。
之後,這些頻率分佈將對 unigrams、bigrams 和 trigrams 進行計數。
現在,在標註過程中,它將使用頻率計算每個單詞可能標籤的機率。
這就是為什麼它不構建 NgramTagger 的回退鏈,而是使用所有 n 元語法模型來選擇每個單詞的最佳標籤。讓我們在下面的示例中評估 TnT 標註器的準確率:
from nltk.tag import tnt from nltk.corpus import treebank train_sentences = treebank.tagged_sents()[:2500] tnt_tagger = tnt.TnT() tnt_tagger.train(train_sentences) test_sentences = treebank.tagged_sents()[1500:] tnt_tagger.evaluate(test_sentences)
輸出
0.9165508316157791
我們的準確率略低於 Brill 標註器的準確率。
請注意,我們需要先呼叫train(),然後才能呼叫evaluate(),否則我們將得到 0% 的準確率。
自然語言工具包 - 解析
句法分析及其在 NLP 中的相關性
單詞“句法分析”(其起源於拉丁語單詞“pars”(意思是“部分”))用於從文字中提取確切的含義或詞典含義。它也稱為句法分析或語法分析。透過比較形式語法的規則,句法分析檢查文字的意義。例如,“給我熱的冰淇淋”這樣的句子將被句法分析器或語法分析器拒絕。
從這個意義上說,我們可以將句法分析或語法分析定義如下:
它可以定義為分析符合形式語法規則的自然語言符號串的過程。
我們可以透過以下幾點了解句法分析在 NLP 中的相關性:
句法分析器用於報告任何語法錯誤。
它有助於從常見錯誤中恢復,以便可以繼續處理程式的其餘部分。
藉助句法分析器可以建立句法樹。
句法分析器用於建立符號表,這在 NLP 中起著重要作用。
句法分析器還用於生成中間表示 (IR)。
深度句法分析與淺層句法分析
| 深度句法分析 | 淺層句法分析 |
|---|---|
| 在深度句法分析中,搜尋策略將為句子提供完整的句法結構。 | 它是從給定任務中分析句法資訊的有限部分的任務。 |
| 它適用於複雜的 NLP 應用。 | 它可以用於不太複雜的 NLP 應用。 |
| 對話系統和摘要是使用深度句法分析的 NLP 應用示例。 | 資訊抽取和文字挖掘是使用深度解析的NLP應用示例。 |
| 它也稱為完全解析。 | 它也稱為分塊。 |
各種型別的解析器
如上所述,解析器基本上是對語法的程式化解釋。它在搜尋各種樹的空間後,為給定的句子找到最佳樹。讓我們看看下面的一些可用解析器:
遞迴下降解析器
遞迴下降解析是最直接的解析形式之一。以下是關於遞迴下降解析器的一些重要幾點:
它遵循自頂向下的過程。
它試圖驗證輸入流的語法是否正確。
它從左到右讀取輸入句子。
遞迴下降解析器的一個必要操作是從輸入流中讀取字元並將它們與語法中的終結符匹配。
移進規約解析器
以下是關於移進規約解析器的一些重要幾點:
它遵循簡單的自底向上的過程。
它試圖找到與語法產生式右側對應的單詞和短語序列,並將它們替換為產生式的左側。
上述尋找單詞序列的嘗試將持續到整個句子被規約。
簡單來說,移進規約解析器從輸入符號開始,嘗試構建解析樹直到起始符號。
圖表解析器
以下是關於圖表解析器的一些重要幾點:
它主要用於或適合於模糊語法,包括自然語言的語法。
它將動態規劃應用於解析問題。
由於動態規劃,部分假設的結果儲存在一個稱為“圖表”的結構中。
“圖表”也可以重複使用。
正則表示式解析器
正則表示式解析是最常用的解析技術之一。以下是關於正則表示式解析器的一些重要幾點:
顧名思義,它使用基於詞性標註字串之上的語法形式定義的正則表示式。
它基本上使用這些正則表示式來解析輸入句子並從中生成解析樹。
示例
以下是正則表示式解析器的實際示例:
import nltk
sentence = [
("a", "DT"),
("clever", "JJ"),
("fox","NN"),
("was","VBP"),
("jumping","VBP"),
("over","IN"),
("the","DT"),
("wall","NN")
]
grammar = "NP:{<DT>?<JJ>*<NN>}"
Reg_parser = nltk.RegexpParser(grammar)
Reg_parser.parse(sentence)
Output = Reg_parser.parse(sentence)
Output.draw()
輸出
依存句法分析
依存句法分析 (DP) 是一種現代解析機制,其主要概念是每個語言單位,即單詞,透過直接連結相互關聯。這些直接連結實際上是語言學中的“依存關係”。例如,下圖顯示了句子“John can hit the ball”的依存語法。
NLTK 包
我們有以下兩種使用 NLTK 進行依存句法分析的方法:
機率投影依存句法分析器
這是我們可以使用 NLTK 進行依存句法分析的第一種方法。但是這個解析器受限於使用有限的訓練資料進行訓練。
斯坦福解析器
這是我們可以使用 NLTK 進行依存句法分析的另一種方法。斯坦福解析器是一種最先進的依存句法分析器。NLTK 圍繞它有一個包裝器。要使用它,我們需要下載以下兩樣東西:
語言模型(目標語言)。例如,英語語言模型。
示例
下載模型後,我們可以透過 NLTK 按如下方式使用它:
from nltk.parse.stanford import StanfordDependencyParser
path_jar = 'path_to/stanford-parser-full-2014-08-27/stanford-parser.jar'
path_models_jar = 'path_to/stanford-parser-full-2014-08-27/stanford-parser-3.4.1-models.jar'
dep_parser = StanfordDependencyParser(
path_to_jar = path_jar, path_to_models_jar = path_models_jar
)
result = dep_parser.raw_parse('I shot an elephant in my sleep')
depndency = result.next()
list(dependency.triples())
輸出
[ ((u'shot', u'VBD'), u'nsubj', (u'I', u'PRP')), ((u'shot', u'VBD'), u'dobj', (u'elephant', u'NN')), ((u'elephant', u'NN'), u'det', (u'an', u'DT')), ((u'shot', u'VBD'), u'prep', (u'in', u'IN')), ((u'in', u'IN'), u'pobj', (u'sleep', u'NN')), ((u'sleep', u'NN'), u'poss', (u'my', u'PRP$')) ]
組塊和資訊提取
什麼是分塊?
分塊是自然語言處理中的一個重要過程,用於識別詞性 (POS) 和短語。簡單來說,透過分塊,我們可以得到句子的結構。它也稱為部分解析。
分塊模式和非分塊
分塊模式是詞性 (POS) 標籤的模式,用於定義構成分塊的單詞型別。我們可以藉助修改後的正則表示式來定義分塊模式。
此外,我們還可以定義分塊中不應該包含哪些單詞的模式,這些未分塊的單詞稱為非分塊。
實現示例
在下面的示例中,除了解析句子“the book has many chapters”的結果外,還有一個用於名詞短語的語法,它結合了分塊和非分塊模式:
import nltk
sentence = [
("the", "DT"),
("book", "NN"),
("has","VBZ"),
("many","JJ"),
("chapters","NNS")
]
chunker = nltk.RegexpParser(
r'''
NP:{<DT><NN.*><.*>*<NN.*>}
}<VB.*>{
'''
)
chunker.parse(sentence)
Output = chunker.parse(sentence)
Output.draw()
輸出
如上所示,指定分塊的模式是使用大括號,如下所示:
{<DT><NN>}
要指定非分塊,我們可以反轉大括號,如下所示:
}<VB>{.
現在,對於特定的短語型別,這些規則可以組合成一個語法。
資訊抽取
我們已經學習了可用於構建資訊抽取引擎的詞性標註器和解析器。讓我們看看一個基本的資訊抽取流程:
資訊抽取有很多應用,包括:
- 商業智慧
- 簡歷收集
- 媒體分析
- 情感檢測
- 專利搜尋
- 電子郵件掃描
命名實體識別 (NER)
命名實體識別 (NER) 實際上是一種提取一些最常見的實體(如姓名、組織、位置等)的方法。讓我們看一個例子,它包含所有預處理步驟,例如句子標記化、詞性標註、分塊、NER,並遵循上圖中提供的流程。
示例
Import nltk file = open ( # provide here the absolute path for the file of text for which we want NER ) data_text = file.read() sentences = nltk.sent_tokenize(data_text) tokenized_sentences = [nltk.word_tokenize(sentence) for sentence in sentences] tagged_sentences = [nltk.pos_tag(sentence) for sentence in tokenized_sentences] for sent in tagged_sentences: print nltk.ne_chunk(sent)
一些修改後的命名實體識別 (NER) 也可以用於提取諸如產品名稱、生物醫學實體、品牌名稱等等之類的實體。
關係抽取
關係抽取是另一個常用的資訊抽取操作,它是提取各種實體之間不同關係的過程。可能存在不同的關係,例如繼承、同義詞、類似物等,其定義取決於資訊需求。例如,如果我們想查詢一本書的作者,那麼作者身份就是作者姓名和書名之間的關係。
示例
在下面的示例中,我們使用與命名實體關係 (NER) 一樣在上面圖示的 IE 流程,並使用基於 NER 標籤的關係模式對其進行擴充套件。
import nltk
import re
IN = re.compile(r'.*\bin\b(?!\b.+ing)')
for doc in nltk.corpus.ieer.parsed_docs('NYT_19980315'):
for rel in nltk.sem.extract_rels('ORG', 'LOC', doc, corpus = 'ieer',
pattern = IN):
print(nltk.sem.rtuple(rel))
輸出
[ORG: 'WHYY'] 'in' [LOC: 'Philadelphia'] [ORG: 'McGlashan & Sarrail'] 'firm in' [LOC: 'San Mateo'] [ORG: 'Freedom Forum'] 'in' [LOC: 'Arlington'] [ORG: 'Brookings Institution'] ', the research group in' [LOC: 'Washington'] [ORG: 'Idealab'] ', a self-described business incubator based in' [LOC: 'Los Angeles'] [ORG: 'Open Text'] ', based in' [LOC: 'Waterloo'] [ORG: 'WGBH'] 'in' [LOC: 'Boston'] [ORG: 'Bastille Opera'] 'in' [LOC: 'Paris'] [ORG: 'Omnicom'] 'in' [LOC: 'New York'] [ORG: 'DDB Needham'] 'in' [LOC: 'New York'] [ORG: 'Kaplan Thaler Group'] 'in' [LOC: 'New York'] [ORG: 'BBDO South'] 'in' [LOC: 'Atlanta'] [ORG: 'Georgia-Pacific'] 'in' [LOC: 'Atlanta']
在上面的程式碼中,我們使用了名為 ieer 的內建語料庫。在這個語料庫中,句子已經被標註到命名實體關係 (NER)。在這裡,我們只需要指定我們想要的關係模式和我們希望關係定義的 NER 型別。在我們的示例中,我們定義了組織和位置之間的關係。我們提取了所有這些模式的組合。
自然語言工具包 - 變換組塊
為什麼要轉換分塊?
到目前為止,我們已經從句子中得到了分塊或短語,但是我們應該如何處理它們呢?一項重要的任務是轉換它們。但是為什麼呢?這是為了做到以下幾點:
- 語法糾正和
- 重新排列短語
過濾掉無關/無用的詞
例如,如果你想判斷一個短語的含義,那麼有很多常用的詞,例如“the”、“a”,是無關緊要或無用的。例如,請看以下短語:
“The movie was good”。
這裡最重要的詞是“movie”和“good”。其他詞,“the”和“was”都是無用或無關緊要的。因為沒有它們,我們也可以得到短語的相同含義。“Good movie”。
在下面的 Python 程式碼示例中,我們將學習如何藉助詞性標籤去除無用/無關緊要的詞並保留重要的詞。
示例
首先,透過檢視treebank語料庫中的停用詞,我們需要決定哪些詞性標籤是重要的,哪些是不重要的。讓我們看看以下無關緊要的單詞和標籤表:
| 單詞 | 標籤 |
|---|---|
| a | DT |
| All | PDT |
| An | DT |
| And | CC |
| Or | CC |
| That | WDT |
| The | DT |
從上表可以看出,除了 CC 之外,所有其他標籤都以 DT 結尾,這意味著我們可以透過檢視標籤的字尾來過濾掉無關緊要的單詞。
對於此示例,我們將使用名為filter()的函式,它接受單個分塊並返回一個不包含任何無關緊要標記單詞的新分塊。此函式會過濾掉以 DT 或 CC 結尾的任何標籤。
示例
import nltk
def filter(chunk, tag_suffixes=['DT', 'CC']):
significant = []
for word, tag in chunk:
ok = True
for suffix in tag_suffixes:
if tag.endswith(suffix):
ok = False
break
if ok:
significant.append((word, tag))
return (significant)
現在,讓我們在 Python 程式碼示例中使用此函式 filter() 來刪除無關緊要的單詞:
from chunk_parse import filter
filter([('the', 'DT'),('good', 'JJ'),('movie', 'NN')])
輸出
[('good', 'JJ'), ('movie', 'NN')]
動詞修正
很多時候,在現實世界語言中,我們會看到不正確的動詞形式。例如,“is you fine?”是不正確的。這個句子中的動詞形式不正確。句子應該是“are you fine?”NLTK 為我們提供了一種透過建立動詞修正對映來糾正此類錯誤的方法。這些修正對映根據分塊中是否存在複數或單數名詞而使用。
示例
要實現 Python 程式碼示例,我們首先需要定義動詞修正對映。讓我們建立兩個對映,如下所示:
複數到單數對映
plural= {
('is', 'VBZ'): ('are', 'VBP'),
('was', 'VBD'): ('were', 'VBD')
}
單數到複數對映
singular = {
('are', 'VBP'): ('is', 'VBZ'),
('were', 'VBD'): ('was', 'VBD')
}
如上所示,每個對映都有一個標記的動詞,它對映到另一個標記的動詞。我們示例中的初始對映涵蓋了is 到 are,was 到 were的基本對映,反之亦然。
接下來,我們將定義一個名為verbs()的函式,你可以在其中傳遞帶有不正確動詞形式的分塊,並將得到一個已更正的分塊。為了完成此操作,verb()函式使用一個名為index_chunk()的輔助函式,該函式將搜尋分塊以查詢第一個標記單詞的位置。
讓我們看看這些函式:
def index_chunk(chunk, pred, start = 0, step = 1):
l = len(chunk)
end = l if step > 0 else -1
for i in range(start, end, step):
if pred(chunk[i]):
return i
return None
def tag_startswith(prefix):
def f(wt):
return wt[1].startswith(prefix)
return f
def verbs(chunk):
vbidx = index_chunk(chunk, tag_startswith('VB'))
if vbidx is None:
return chunk
verb, vbtag = chunk[vbidx]
nnpred = tag_startswith('NN')
nnidx = index_chunk(chunk, nnpred, start = vbidx+1)
if nnidx is None:
nnidx = index_chunk(chunk, nnpred, start = vbidx-1, step = -1)
if nnidx is None:
return chunk
noun, nntag = chunk[nnidx]
if nntag.endswith('S'):
chunk[vbidx] = plural.get((verb, vbtag), (verb, vbtag))
else:
chunk[vbidx] = singular.get((verb, vbtag), (verb, vbtag))
return chunk
將這些函式儲存在安裝了 Python 或 Anaconda 的本地目錄中的 Python 檔案中並執行它。我將其儲存為verbcorrect.py。
現在,讓我們在一個詞性標註的is you fine分塊上呼叫verbs()函式:
from verbcorrect import verbs
verbs([('is', 'VBZ'), ('you', 'PRP$'), ('fine', 'VBG')])
輸出
[('are', 'VBP'), ('you', 'PRP$'), ('fine','VBG')]
消除短語中的被動語態
另一個有用的任務是從短語中消除被動語態。這可以透過圍繞動詞交換單詞來完成。例如,“the tutorial was great”可以轉換為“the great tutorial”。
示例
為了實現這一點,我們定義了一個名為eliminate_passive()的函式,它將使用動詞作為樞軸點來交換分塊的右側和左側。為了找到要圍繞其旋轉的動詞,它還將使用上面定義的index_chunk()函式。
def eliminate_passive(chunk):
def vbpred(wt):
word, tag = wt
return tag != 'VBG' and tag.startswith('VB') and len(tag) > 2
vbidx = index_chunk(chunk, vbpred)
if vbidx is None:
return chunk
return chunk[vbidx+1:] + chunk[:vbidx]
現在,讓我們在一個詞性標註的the tutorial was great分塊上呼叫eliminate_passive()函式:
from passiveverb import eliminate_passive
eliminate_passive(
[
('the', 'DT'), ('tutorial', 'NN'), ('was', 'VBD'), ('great', 'JJ')
]
)
輸出
[('great', 'JJ'), ('the', 'DT'), ('tutorial', 'NN')]
交換名詞基數
眾所周知,像 5 這樣的基數詞在一個塊中被標記為 CD。這些基數詞經常出現在名詞之前或之後,但出於規範化的目的,始終將它們放在名詞之前是有用的。例如,日期 **1月5日** 可以寫成 **5月1日**。讓我們透過以下示例來理解它。
示例
為了實現這一點,我們定義了一個名為 **swapping_cardinals()** 的函式,它將交換出現在名詞之後任何基數詞與名詞。這樣,基數詞就會出現在名詞的前面。為了與給定的標籤進行相等性比較,它使用了一個我們命名為 **tag_eql()** 的輔助函式。
def tag_eql(tag):
def f(wt):
return wt[1] == tag
return f
現在我們可以定義 swapping_cardinals() 了 -
def swapping_cardinals (chunk):
cdidx = index_chunk(chunk, tag_eql('CD'))
if not cdidx or not chunk[cdidx-1][1].startswith('NN'):
return chunk
noun, nntag = chunk[cdidx-1]
chunk[cdidx-1] = chunk[cdidx]
chunk[cdidx] = noun, nntag
return chunk
現在,讓我們在日期 **“1月5日”** 上呼叫 **swapping_cardinals()** 函式 -
from Cardinals import swapping_cardinals()
swapping_cardinals([('Janaury', 'NNP'), ('5', 'CD')])
輸出
[('10', 'CD'), ('January', 'NNP')]
10 January
自然語言工具包 - 變換樹
以下是轉換樹的兩個原因 -
- 修改深層解析樹,以及
- 展平深層解析樹
將樹或子樹轉換為句子
我們將要討論的第一個方法是將樹或子樹轉換回句子或塊字串。這非常簡單,讓我們在以下示例中看看 -
示例
from nltk.corpus import treebank_chunk tree = treebank_chunk.chunked_sents()[2] ' '.join([w for w, t in tree.leaves()])
輸出
'Rudolph Agnew , 55 years old and former chairman of Consolidated Gold Fields PLC , was named a nonexecutive director of this British industrial conglomerate .'
深層樹展平
巢狀短語的深層樹不能用於訓練塊,因此我們必須在使用之前展平它們。在下面的示例中,我們將使用 **treebank** 語料庫中的第 3 個解析句子,它是一個巢狀短語的深層樹。
示例
為了實現這一點,我們定義了一個名為 **deeptree_flat()** 的函式,它將接收單個樹,並返回一個新樹,該新樹只保留最低級別的樹。為了完成大部分工作,它使用了一個我們命名為 **childtree_flat()** 的輔助函式。
from nltk.tree import Tree
def childtree_flat(trees):
children = []
for t in trees:
if t.height() < 3:
children.extend(t.pos())
elif t.height() == 3:
children.append(Tree(t.label(), t.pos()))
else:
children.extend(flatten_childtrees([c for c in t]))
return children
def deeptree_flat(tree):
return Tree(tree.label(), flatten_childtrees([c for c in tree]))
現在,讓我們在來自 **treebank** 語料庫的第 3 個解析句子(它是巢狀短語的深層樹)上呼叫 **deeptree_flat()** 函式。我們將這些函式儲存在名為 deeptree.py 的檔案中。
from deeptree import deeptree_flat from nltk.corpus import treebank deeptree_flat(treebank.parsed_sents()[2])
輸出
Tree('S', [Tree('NP', [('Rudolph', 'NNP'), ('Agnew', 'NNP')]),
(',', ','), Tree('NP', [('55', 'CD'),
('years', 'NNS')]), ('old', 'JJ'), ('and', 'CC'),
Tree('NP', [('former', 'JJ'),
('chairman', 'NN')]), ('of', 'IN'), Tree('NP', [('Consolidated', 'NNP'),
('Gold', 'NNP'), ('Fields', 'NNP'), ('PLC',
'NNP')]), (',', ','), ('was', 'VBD'),
('named', 'VBN'), Tree('NP-SBJ', [('*-1', '-NONE-')]),
Tree('NP', [('a', 'DT'), ('nonexecutive', 'JJ'), ('director', 'NN')]),
('of', 'IN'), Tree('NP',
[('this', 'DT'), ('British', 'JJ'),
('industrial', 'JJ'), ('conglomerate', 'NN')]), ('.', '.')])
構建淺層樹
在上一節中,我們透過只保留最低級別的子樹來展平巢狀短語的深層樹。在本節中,我們將只保留最高級別的子樹,即構建淺層樹。在下面的示例中,我們將使用 **treebank** 語料庫中的第 3 個解析句子,它是一個巢狀短語的深層樹。
示例
為了實現這一點,我們定義了一個名為 **tree_shallow()** 的函式,它將透過只保留頂層子樹標籤來消除所有巢狀子樹。
from nltk.tree import Tree
def tree_shallow(tree):
children = []
for t in tree:
if t.height() < 3:
children.extend(t.pos())
else:
children.append(Tree(t.label(), t.pos()))
return Tree(tree.label(), children)
現在,讓我們在來自 **treebank** 語料庫的第 3 個解析句子(它是巢狀短語的深層樹)上呼叫 **tree_shallow()** 函式。我們將這些函式儲存在名為 shallowtree.py 的檔案中。
from shallowtree import shallow_tree from nltk.corpus import treebank tree_shallow(treebank.parsed_sents()[2])
輸出
Tree('S', [Tree('NP-SBJ-1', [('Rudolph', 'NNP'), ('Agnew', 'NNP'), (',', ','),
('55', 'CD'), ('years', 'NNS'), ('old', 'JJ'), ('and', 'CC'),
('former', 'JJ'), ('chairman', 'NN'), ('of', 'IN'), ('Consolidated', 'NNP'),
('Gold', 'NNP'), ('Fields', 'NNP'), ('PLC', 'NNP'), (',', ',')]),
Tree('VP', [('was', 'VBD'), ('named', 'VBN'), ('*-1', '-NONE-'), ('a', 'DT'),
('nonexecutive', 'JJ'), ('director', 'NN'), ('of', 'IN'), ('this', 'DT'),
('British', 'JJ'), ('industrial', 'JJ'), ('conglomerate', 'NN')]), ('.', '.')])
我們可以透過獲取樹的高度來檢視差異 -
from nltk.corpus import treebank tree_shallow(treebank.parsed_sents()[2]).height()
輸出
3
from nltk.corpus import treebank treebank.parsed_sents()[2].height()
輸出
9
樹標籤轉換
在解析樹中,存在各種在塊樹中不存在的 **Tree** 標籤型別。但在使用解析樹來訓練分塊器時,我們希望透過將一些樹標籤轉換為更常見的標籤型別來減少這種多樣性。例如,我們有兩個替代的 NP 子樹,即 NP-SBL 和 NP-TMP。我們可以將它們都轉換為 NP。讓我們看看如何在以下示例中做到這一點。
示例
為了實現這一點,我們定義了一個名為 **tree_convert()** 的函式,它接受以下兩個引數 -
- 要轉換的樹
- 標籤轉換對映
此函式將返回一個新樹,其中所有匹配的標籤都根據對映中的值進行了替換。
from nltk.tree import Tree
def tree_convert(tree, mapping):
children = []
for t in tree:
if isinstance(t, Tree):
children.append(convert_tree_labels(t, mapping))
else:
children.append(t)
label = mapping.get(tree.label(), tree.label())
return Tree(label, children)
現在,讓我們在來自 **treebank** 語料庫的第 3 個解析句子(它是巢狀短語的深層樹)上呼叫 **tree_convert()** 函式。我們將這些函式儲存在名為 **converttree.py** 的檔案中。
from converttree import tree_convert
from nltk.corpus import treebank
mapping = {'NP-SBJ': 'NP', 'NP-TMP': 'NP'}
convert_tree_labels(treebank.parsed_sents()[2], mapping)
輸出
Tree('S', [Tree('NP-SBJ-1', [Tree('NP', [Tree('NNP', ['Rudolph']),
Tree('NNP', ['Agnew'])]), Tree(',', [',']),
Tree('UCP', [Tree('ADJP', [Tree('NP', [Tree('CD', ['55']),
Tree('NNS', ['years'])]),
Tree('JJ', ['old'])]), Tree('CC', ['and']),
Tree('NP', [Tree('NP', [Tree('JJ', ['former']),
Tree('NN', ['chairman'])]), Tree('PP', [Tree('IN', ['of']),
Tree('NP', [Tree('NNP', ['Consolidated']),
Tree('NNP', ['Gold']), Tree('NNP', ['Fields']),
Tree('NNP', ['PLC'])])])])]), Tree(',', [','])]),
Tree('VP', [Tree('VBD', ['was']),Tree('VP', [Tree('VBN', ['named']),
Tree('S', [Tree('NP', [Tree('-NONE-', ['*-1'])]),
Tree('NP-PRD', [Tree('NP', [Tree('DT', ['a']),
Tree('JJ', ['nonexecutive']), Tree('NN', ['director'])]),
Tree('PP', [Tree('IN', ['of']), Tree('NP',
[Tree('DT', ['this']), Tree('JJ', ['British']), Tree('JJ', ['industrial']),
Tree('NN', ['conglomerate'])])])])])])]), Tree('.', ['.'])])
自然語言工具包 - 文字分類
什麼是文字分類?
文字分類,顧名思義,是將文字或文件片段進行分類的方法。但是這裡的問題是為什麼我們需要使用文字分類器?一旦檢查了文件或文字片段中的詞語使用情況,分類器就能決定應該為其分配哪個類別標籤。
二元分類器
顧名思義,二元分類器將在兩個標籤之間進行決策。例如,正面或負面。在這種情況下,文字或文件片段可以是其中一個標籤,但不能同時是兩個標籤。
多標籤分類器
與二元分類器相反,多標籤分類器可以為文字或文件片段分配一個或多個標籤。
標記的與未標記的特徵集
特徵名到特徵值的鍵值對映稱為特徵集。標記的特徵集或訓練資料對於分類訓練非常重要,以便它以後可以對未標記的特徵集進行分類。
| 標記的特徵集 | 未標記的特徵集 |
|---|---|
| 它是一個看起來像 (feat, label) 的元組。 | 它本身就是一個特徵。 |
| 它是一個具有已知類別標籤的例項。 | 如果沒有關聯的標籤,我們可以稱之為例項。 |
| 用於訓練分類演算法。 | 一旦訓練完成,分類演算法就可以對未標記的特徵集進行分類。 |
文字特徵提取
文字特徵提取,顧名思義,是將單詞列表轉換為分類器可用的特徵集的過程。我們必須將我們的文字轉換為 **'dict'** 樣式的特徵集,因為自然語言工具包 (NLTK) 期望 **'dict'** 樣式的特徵集。
詞袋 (BoW) 模型
BoW 是 NLP 中最簡單的模型之一,用於從文字或文件片段中提取特徵,以便它可以用於建模,例如在 ML 演算法中。它基本上根據例項的所有單詞構建單詞存在特徵集。此方法背後的概念是,它不關心單詞出現的次數或單詞的順序,它只關心單詞是否存在於單詞列表中。
示例
對於此示例,我們將定義一個名為 bow() 的函式 -
def bow(words): return dict([(word, True) for word in words])
現在,讓我們在單詞上呼叫 **bow()** 函式。我們將這些函式儲存在名為 bagwords.py 的檔案中。
from bagwords import bow bow(['we', 'are', 'using', 'tutorialspoint'])
輸出
{'we': True, 'are': True, 'using': True, 'tutorialspoint': True}
訓練分類器
在前面的章節中,我們學習瞭如何從文字中提取特徵。所以現在我們可以訓練一個分類器了。第一個也是最簡單的分類器是 **NaiveBayesClassifier** 類。
樸素貝葉斯分類器
為了預測給定特徵集屬於特定標籤的機率,它使用貝葉斯定理。貝葉斯定理的公式如下。
$$P(A|B)=\frac{P(B|A)P(A)}{P(B)}$$這裡,
**P(A|B)** - 它也稱為後驗機率,即給定第二個事件 B 發生的情況下,第一個事件 A 發生的機率。
**P(B|A)** - 它是第一個事件 A 發生後第二個事件 B 發生的機率。
**P(A), P(B)** - 它也稱為先驗機率,即第一個事件 A 或第二個事件 B 發生的機率。
為了訓練樸素貝葉斯分類器,我們將使用來自 NLTK 的 **movie_reviews** 語料庫。此語料庫包含兩類文字,即:**pos** 和 **neg**。這些類別使在其上訓練的分類器成為二元分類器。語料庫中的每個檔案都由兩個組成,一個是正面影評,另一個是負面影評。在我們的示例中,我們將每個檔案作為訓練和測試分類器的單個例項。
示例
為了訓練分類器,我們需要一個標記的特徵集列表,其形式為 [(**featureset**, **label**)]。這裡的 **featureset** 變數是一個 **dict**,而 label 是 **featureset** 的已知類別標籤。我們將建立一個名為 **label_corpus()** 的函式,它將接收一個名為 **movie_reviews** 的語料庫,以及一個名為 **feature_detector** 的函式(預設為 **詞袋**)。它將構建並返回一個 {label: [featureset]} 形式的對映。之後,我們將使用此對映來建立標記的訓練例項和測試例項列表。
import collections
def label_corpus(corp, feature_detector=bow):
label_feats = collections.defaultdict(list)
for label in corp.categories():
for fileid in corp.fileids(categories=[label]):
feats = feature_detector(corp.words(fileids=[fileid]))
label_feats[label].append(feats)
return label_feats
在上述函式的幫助下,我們將得到一個 **{label:fetaureset}** 對映。現在我們將定義另一個名為 **split** 的函式,它將接收從 **label_corpus()** 函式返回的對映,並將每個特徵集列表拆分為標記的訓練例項和測試例項。
def split(lfeats, split=0.75):
train_feats = []
test_feats = []
for label, feats in lfeats.items():
cutoff = int(len(feats) * split)
train_feats.extend([(feat, label) for feat in feats[:cutoff]])
test_feats.extend([(feat, label) for feat in feats[cutoff:]])
return train_feats, test_feats
現在,讓我們在我們的語料庫 movie_reviews 上使用這些函式 -
from nltk.corpus import movie_reviews from featx import label_feats_from_corpus, split_label_feats movie_reviews.categories()
輸出
['neg', 'pos']
示例
lfeats = label_feats_from_corpus(movie_reviews) lfeats.keys()
輸出
dict_keys(['neg', 'pos'])
示例
train_feats, test_feats = split_label_feats(lfeats, split = 0.75) len(train_feats)
輸出
1500
示例
len(test_feats)
輸出
500
我們已經看到,在 **movie_reviews** 語料庫中,有 1000 個 pos 檔案和 1000 個 neg 檔案。我們最終還得到了 1500 個標記的訓練例項和 500 個標記的測試例項。
現在讓我們使用它的 **train()** 類方法來訓練 **NaïveBayesClassifier** -
from nltk.classify import NaiveBayesClassifier NBC = NaiveBayesClassifier.train(train_feats) NBC.labels()
輸出
['neg', 'pos']
決策樹分類器
另一個重要的分類器是決策樹分類器。在這裡,為了訓練它,**DecisionTreeClassifier** 類將建立一個樹結構。在這個樹結構中,每個節點對應一個特徵名,分支對應特徵值。沿著分支向下,我們將到達樹的葉子,即分類標籤。
為了訓練決策樹分類器,我們將使用相同的訓練和測試特徵,即我們從 **movie_reviews** 語料庫建立的 **train_feats** 和 **test_feats** 變數。
示例
為了訓練這個分類器,我們將呼叫 **DecisionTreeClassifier.train()** 類方法,如下所示 -
from nltk.classify import DecisionTreeClassifier decisiont_classifier = DecisionTreeClassifier.train( train_feats, binary = True, entropy_cutoff = 0.8, depth_cutoff = 5, support_cutoff = 30 ) accuracy(decisiont_classifier, test_feats)
輸出
0.725
最大熵分類器
另一個重要的分類器是 **MaxentClassifier**,它也稱為 **條件指數分類器** 或 **邏輯迴歸分類器**。在這裡,為了訓練它,**MaxentClassifier** 類將使用編碼將標記的特徵集轉換為向量。
為了訓練決策樹分類器,我們將使用相同的訓練和測試特徵,即我們從 **movie_reviews** 語料庫建立的 **train_feats** 和 **test_feats** 變數。
示例
為了訓練這個分類器,我們將呼叫 **MaxentClassifier.train()** 類方法,如下所示 -
from nltk.classify import MaxentClassifier maxent_classifier = MaxentClassifier .train(train_feats,algorithm = 'gis', trace = 0, max_iter = 10, min_lldelta = 0.5) accuracy(maxent_classifier, test_feats)
輸出
0.786
Scikit-learn 分類器
Scikit-learn 是最好的機器學習 (ML) 庫之一。它實際上包含各種用途的各種 ML 演算法,但它們都具有以下相同的擬合設計模式 -
- 將模型擬合到資料
- 並使用該模型進行預測
在這裡,我們將使用 NLTK 的 **SklearnClassifier** 類,而不是直接訪問 scikit-learn 模型。此類是 scikit-learn 模型的包裝類,使其符合 NLTK 的 Classifier 介面。
我們將遵循以下步驟來訓練 **SklearnClassifier** 類 -
**步驟 1** - 首先,我們將像在之前的配方中那樣建立訓練特徵。
**步驟 2** - 現在,選擇並匯入 Scikit-learn 演算法。
**步驟 3** - 接下來,我們需要使用選擇的演算法構造一個 **SklearnClassifier** 類。
**步驟 4** - 最後,我們將使用我們的訓練特徵來訓練 **SklearnClassifier** 類。
讓我們在下面的 Python 示例中實現這些步驟 -
from nltk.classify.scikitlearn import SklearnClassifier from sklearn.naive_bayes import MultinomialNB sklearn_classifier = SklearnClassifier(MultinomialNB()) sklearn_classifier.train(train_feats) <SklearnClassifier(MultinomialNB(alpha = 1.0,class_prior = None,fit_prior = True))> accuracy(sk_classifier, test_feats)
輸出
0.885
測量精度和召回率
在訓練各種分類器時,我們也測量了它們的準確性。但是除了準確性之外,還有許多其他指標用於評估分類器。這兩個指標是 **精度** 和 **召回率**。
示例
本例將計算前面訓練的NaiveBayesClassifier類的精確率和召回率。為此,我們將建立一個名為metrics_PR()的函式,它將接受兩個引數:一個是被訓練的分類器,另一個是標記的測試特徵。這兩個引數與計算分類器精度時傳遞的引數相同。
import collections
from nltk import metrics
def metrics_PR(classifier, testfeats):
refsets = collections.defaultdict(set)
testsets = collections.defaultdict(set)
for i, (feats, label) in enumerate(testfeats):
refsets[label].add(i)
observed = classifier.classify(feats)
testsets[observed].add(i)
precisions = {}
recalls = {}
for label in classifier.labels():
precisions[label] = metrics.precision(refsets[label],testsets[label])
recalls[label] = metrics.recall(refsets[label], testsets[label])
return precisions, recalls
讓我們呼叫此函式來查詢精確率和召回率:
from metrics_classification import metrics_PR nb_precisions, nb_recalls = metrics_PR(nb_classifier,test_feats) nb_precisions['pos']
輸出
0.6713532466435213
示例
nb_precisions['neg']
輸出
0.9676271186440678
示例
nb_recalls['pos']
輸出
0.96
示例
nb_recalls['neg']
輸出
0.478
分類器與投票的組合
組合分類器是提高分類效能的最佳方法之一。而投票是組合多個分類器的最佳方法之一。對於投票,我們需要奇數個分類器。在下面的Python示例中,我們將組合三個分類器,即NaiveBayesClassifier類、DecisionTreeClassifier類和MaxentClassifier類。
為此,我們將定義一個名為voting_classifiers()的函式,如下所示。
import itertools
from nltk.classify import ClassifierI
from nltk.probability import FreqDist
class Voting_classifiers(ClassifierI):
def __init__(self, *classifiers):
self._classifiers = classifiers
self._labels = sorted(set(itertools.chain(*[c.labels() for c in classifiers])))
def labels(self):
return self._labels
def classify(self, feats):
counts = FreqDist()
for classifier in self._classifiers:
counts[classifier.classify(feats)] += 1
return counts.max()
讓我們呼叫此函式來組合三個分類器並查詢精度:
from vote_classification import Voting_classifiers combined_classifier = Voting_classifiers(NBC, decisiont_classifier, maxent_classifier) combined_classifier.labels()
輸出
['neg', 'pos']
示例
accuracy(combined_classifier, test_feats)
輸出
0.948
從以上輸出可以看出,組合分類器的精度高於單個分類器。