本篇文章是我們學(xué)習(xí) Python 及其在機(jī)器學(xué)(ML)和人工智能(AI)中的應(yīng)用系列的第六個(gè)模塊。在上一個(gè)模塊中,我們討論了使用 OpenCV 進(jìn)行圖像識(shí)別。現(xiàn)在我們就來(lái)看看自然語(yǔ)言工具包(NLTK)能夠做些什么?
安裝
第一種方法,可以用Anaconda來(lái)安裝NLTK:
conda install nltk
第二種方法,可以用pip,在Jupyter Notebook的單元中運(yùn)行安裝NLTK:
!pip install --upgrade nltk
如果以下 Python 代碼運(yùn)行沒(méi)有錯(cuò)誤,則說(shuō)明安裝是成功的:
import nltk
NLTK 附帶了大量可以下載的數(shù)據(jù)(語(yǔ)料庫(kù)、語(yǔ)法、模型等),所以只需運(yùn)行以下的 Python 命令就會(huì)出現(xiàn)一個(gè)顯示交互式下載窗口:
ntlk.download()
對(duì)于此模塊,你還需要安裝“停用詞”的語(yǔ)料庫(kù)。下載后,還要再創(chuàng)建一個(gè)名為NLTK_DATA包含下載目錄路徑的環(huán)境變量(如果你進(jìn)行集中安裝,則不需要;有關(guān)安裝數(shù)據(jù)的完整指南,請(qǐng)參閱文檔)。
文本分類(lèi)
對(duì)文本進(jìn)行分類(lèi)意味著要為其分配標(biāo)簽。我們可以采用多種方式對(duì)文本進(jìn)行分類(lèi),例如情感分析(正面/負(fù)面/中性)、垃圾郵件分類(lèi)(垃圾郵件/非垃圾郵件)、按文檔主題等。
在本模塊中,我們將使用大型電影評(píng)論數(shù)據(jù)集演練文本分類(lèi)示例,該數(shù)據(jù)集提供 25,000 條電影評(píng)論(正面和負(fù)面)用于訓(xùn)練和相同數(shù)量的測(cè)試。
NLTK 提供了一個(gè)樸素貝葉斯分類(lèi)器來(lái)處理機(jī)器學(xué)習(xí)工作。我們的工作主要是編寫(xiě)一個(gè)從文本中提取“特征”的函數(shù)。分類(lèi)器使用這些特征來(lái)執(zhí)行其分類(lèi)。
我們的函數(shù)稱(chēng)為feature extractor,它接受一個(gè)字符串(文本)作為參數(shù),并返回一個(gè)將特征名稱(chēng)映射到它們的值的字典,稱(chēng)為feature set。
對(duì)于電影評(píng)論,我們的特征將是前N 個(gè)詞(不包括停用詞)。因此,特征提取器將返回一個(gè)特征集,其中包含這N 個(gè)單詞作為鍵,并返回一個(gè)布爾值,表示它們的存在或不存在作為值。
第一步是瀏覽評(píng)論,存儲(chǔ)所有單詞(停用詞除外),并找到最常用的單詞。
首先,這個(gè)輔助函數(shù)接受一個(gè)文本并輸出它的非停用詞:
import nltk
import nltk.sentiment.util
from nltk.corpus import stopwords
import nltk.sentiment.util
stop = set(stopwords.words("english"))
def extract_words_from_text(text):
tokens = nltk.word_tokenize(text)
tokens_neg_marked = nltk.sentiment.util.mark_negation(tokens)
return [t for t in tokens_neg_marked
if t.replace("_NEG", "").isalnum() and
t.replace("_NEG", "") not in stop]
word_tokenize
將文本拆分為一個(gè)標(biāo)記列表(仍然保留標(biāo)點(diǎn)符號(hào))。
mark_negation
用 _NEG 標(biāo)記否定后的標(biāo)記。所以,例如,“我不喜歡這個(gè)?!?nbsp;在標(biāo)記化和標(biāo)記否定之后變成這個(gè):
["I", "did", "not", "enjoy_NEG", "this_NEG", "."]
.
最后一行刪除所有停用詞(包括否定詞)和標(biāo)點(diǎn)符號(hào)。文中還有很多沒(méi)用的詞,比如“我”或者“這個(gè)”,但是這個(gè)過(guò)濾就足夠我們演示了。
接下來(lái),我們構(gòu)建從評(píng)論文件中讀取的所有單詞的列表。我們保留一個(gè)單獨(dú)的正面和負(fù)面詞列表,以確保在我們選取最重要的詞時(shí)保持平衡。(我還在沒(méi)有將單詞列表分開(kāi)的情況下對(duì)其進(jìn)行了測(cè)試,結(jié)果發(fā)現(xiàn)大多數(shù)正面評(píng)論都被歸類(lèi)為負(fù)面評(píng)論。)同時(shí),我們還可以創(chuàng)建所有正面評(píng)論和所有負(fù)面評(píng)論的列表。
import os
positive_files = os.listdir("aclImdb/train/pos")
negative_files = os.listdir("aclImdb/train/neg")
positive_words = []
negative_words = []
positive_reviews = []
negative_reviews = []
for pos_file in positive_files:
with open("aclImdb/train/pos/" + pos_file, "r") as f:
txt = f.read().replace("<br />", " ")
positive_reviews.append(txt)
positive_words.extend(extract_words_from_text(txt))
for neg_file in negative_files:
with open("aclImdb/train/neg/" + neg_file, "r") as f:
txt = f.read().replace("<br />", " ")
negative_reviews.append(txt)
negative_words.extend(extract_words_from_text(txt))
運(yùn)行此代碼可能需要一段時(shí)間,因?yàn)橛泻芏辔募?/p>
然后,我們只保留正面和負(fù)面詞列表中的前N 個(gè)詞(在本例中為 2000 個(gè)詞)并將它們組合起來(lái)。
N = 2000
freq_pos = nltk.FreqDist(positive_words)
top_word_counts_pos = sorted(freq_pos.items(), key=lambda kv: kv[1], reverse=True)[:N]
top_words_pos = [twc[0] for twc in top_word_counts_pos]
freq_neg = nltk.FreqDist(negative_words)
top_word_counts_neg = sorted(freq_neg.items(), key=lambda kv: kv[1], reverse=True)[:N]
top_words_neg = [twc[0] for twc in top_word_counts_neg]
top_words = list(set(top_words_pos + top_words_neg))
現(xiàn)在我們可以編寫(xiě)一個(gè)特征提取器。如前所述,它應(yīng)該返回一個(gè)字典,其中每個(gè)最上面的單詞作為鍵,True
或者False
作為值,這取決于該單詞是否存在于文本中。
def extract_features(text):
text_words = extract_words_from_text(text)
return { w: w in text_words for w in top_words }
然后我們創(chuàng)建一個(gè)訓(xùn)練集,我們將其提供給樸素貝葉斯分類(lèi)器。訓(xùn)練集應(yīng)該是一個(gè)元組列表,其中每個(gè)元組的第一個(gè)元素是特征集,第二個(gè)元素是標(biāo)簽。
training = [(extract_features(review), "pos") for review in positive_reviews] + [(extract_features(review), "neg") for review in negative_reviews]
上面的行占用大量 RAM 并且速度很慢,因此您可能希望通過(guò)獲取評(píng)論列表的一部分來(lái)使用評(píng)論的子集。
訓(xùn)練分類(lèi)器很簡(jiǎn)單:
classifier = nltk.NaiveBayesClassifier.train(training)
要立即對(duì)評(píng)論進(jìn)行分類(lèi),請(qǐng)classify
在新功能集上使用該方法:
print(classifier.classify(extract_features("Your review goes here.")))
如果你想要查看每個(gè)標(biāo)簽的概率,可以用prob_classify
替代:
def get_prob_dist(text):
prob_dist = classifier.prob_classify(extract_features(text))
return { "pos": prob_dist.prob("pos"), "neg": prob_dist.prob("neg") }
print(get_prob_dist("Your review goes here."))
分類(lèi)器具有基于測(cè)試集確定模型準(zhǔn)確性的內(nèi)置方法。該測(cè)試集的形狀與訓(xùn)練集相同。電影評(píng)論數(shù)據(jù)集有一個(gè)單獨(dú)的目錄,其中包含可用于此目的的評(píng)論。
test_positive = os.listdir("aclImdb/test/pos")[:2500]
test_negative = os.listdir("aclImdb/test/neg")[:2500]
test = []
for pos_file in test_positive:
with open("aclImdb/test/pos/" + pos_file, "r") as f:
txt = f.read().replace("<br />", " ")
test.append((extract_features(txt), "pos"))
for neg_file in test_negative:
with open("aclImdb/test/neg/" + neg_file, "r") as f:
txt = f.read().replace("<br />", " ")
test.append((extract_features(txt), "neg"))
print(nltk.classify.accuracy(classifier, test))
使用 N = 2000,在訓(xùn)練集中有 5000 條正面評(píng)論和 5000 條負(fù)面評(píng)論,我用這段代碼獲得了大約 85% 的準(zhǔn)確率。
結(jié)論
在本模塊中,我們研究了 NLTK 在文本分類(lèi)中的工作原理,并使用情感分析進(jìn)行了演示。你也可以用相同的方式將其應(yīng)用到其他至少有兩個(gè)標(biāo)簽的分類(lèi)。