人工智能和机器学习系列(六) NLTK自然语言处理

陪你演戏 2021-08-25 10:09:09 浏览数 (2388)
反馈

本篇文章是我们学习 Python 及其在机器学(ML)和人工智能(AI)中的应用系列的第六个模块。在上一个模块中,我们讨论了使用 OpenCV 进行图像识别。现在我们就来看看自然语言工具包(NLTK)能够做些什么?

安装

第一种方法,可以用Anaconda来安装NLTK:

conda install nltk

第二种方法,可以用pip,在Jupyter Notebook的单元中运行安装NLTK:

!pip install --upgrade nltk

如果以下 Python 代码运行没有错误,则说明安装是成功的:

import nltk

NLTK 附带了大量可以下载的数据(语料库、语法、模型等),所以只需运行以下的 Python 命令就会出现一个显示交互式下载窗口:

ntlk.download()

对于此模块,你还需要安装“停用词”的语料库。下载后,还要再创建一个名为NLTK_DATA包含下载目录路径的环境变量(如果你进行集中安装,则不需要;有关安装数据的完整指南,请参阅文档)。

文本分类

对文本进行分类意味着要为其分配标签。我们可以采用多种方式对文本进行分类,例如情感分析(正面/负面/中性)、垃圾邮件分类(垃圾邮件/非垃圾邮件)、按文档主题等。

在本模块中,我们将使用大型电影评论数据集演练文本分类示例,该数据集提供 25,000 条电影评论(正面和负面)用于训练和相同数量的测试。

NLTK 提供了一个朴素贝叶斯分类器来处理机器学习工作。我们的工作主要是编写一个从文本中提取“特征”的函数。分类器使用这些特征来执行其分类。

我们的函数称为feature extractor,它接受一个字符串(文本)作为参数,并返回一个将特征名称映射到它们的值的字典,称为feature set。

对于电影评论,我们的特征将是前N 个词(不包括停用词)。因此,特征提取器将返回一个特征集,其中包含这N 个单词作为键,并返回一个布尔值,表示它们的存在或不存在作为值。

第一步是浏览评论,存储所有单词(停用词除外),并找到最常用的单词。

首先,这个辅助函数接受一个文本并输出它的非停用词:

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 将文本拆分为一个标记列表(仍然保留标点符号)。

mark_negation用 _NEG 标记否定后的标记。所以,例如,“我不喜欢这个。” 在标记化和标记否定之后变成这个:

["I", "did", "not", "enjoy_NEG", "this_NEG", "."].

最后一行删除所有停用词(包括否定词)和标点符号。文中还有很多没用的词,比如“我”或者“这个”,但是这个过滤就足够我们演示了。

接下来,我们构建从评论文件中读取的所有单词的列表。我们保留一个单独的正面和负面词列表,以确保在我们选取最重要的词时保持平衡。(我还在没有将单词列表分开的情况下对其进行了测试,结果发现大多数正面评论都被归类为负面评论。)同时,我们还可以创建所有正面评论和所有负面评论的列表。

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))

运行此代码可能需要一段时间,因为有很多文件。

然后,我们只保留正面和负面词列表中的前N 个词(在本例中为 2000 个词)并将它们组合起来。

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))

现在我们可以编写一个特征提取器。如前所述,它应该返回一个字典,其中每个最上面的单词作为键,True或者False作为值,这取决于该单词是否存在于文本中。

def extract_features(text):
    text_words = extract_words_from_text(text)
    return { w: w in text_words for w in top_words }

然后我们创建一个训练集,我们将其提供给朴素贝叶斯分类器。训练集应该是一个元组列表,其中每个元组的第一个元素是特征集,第二个元素是标签。

training = [(extract_features(review), "pos") for review in positive_reviews] + [(extract_features(review), "neg") for review in negative_reviews]

上面的行占用大量 RAM 并且速度很慢,因此您可能希望通过获取评论列表的一部分来使用评论的子集。

训练分类器很简单:

classifier = nltk.NaiveBayesClassifier.train(training)

要立即对评论进行分类,请classify在新功能集上使用该方法:

print(classifier.classify(extract_features("Your review goes here.")))

如果你想要查看每个标签的概率,可以用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."))

分类器具有基于测试集确定模型准确性的内置方法。该测试集的形状与训练集相同。电影评论数据集有一个单独的目录,其中包含可用于此目的的评论。

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,在训练集中有 5000 条正面评论和 5000 条负面评论,我用这段代码获得了大约 85% 的准确率。

结论

在本模块中,我们研究了 NLTK 在文本分类中的工作原理,并使用情感分析进行了演示。你也可以用相同的方式将其应用到其他至少有两个标签的分类。

0 人点赞