使用NLTK进行文本分类

安装

NLTK可以通过Anaconda安装:

conda install nltk

或者使用pip,在Jupyter Notebook单元格中运行此命令:

!pip install --upgrade nltk

如果以下Python代码运行无误,则表示安装成功:

import nltk

NLTK附带了很多数据(语料库、语法、模型等),可以通过运行这个Python命令来显示一个交互式下载窗口:

nltk.download()

对于这个模块,需要安装"stopwords"语料库。下载后,创建一个名为NLTK_DATA的环境变量,包含下载目录的路径(如果进行集中安装,则不需要这样做;参考文档以获取有关安装数据的完整指南)。

文本分类

对文本进行分类意味着给它分配一个标签。文本分类有多种方式,如情感分析(积极/消极[/中性])、垃圾邮件分类(垃圾邮件/非垃圾邮件)、按文档主题分类等。

在这个模块中,将通过一个文本分类示例来了解NLTK的使用,使用的是大型电影评论数据集,它提供了25,000个电影评论(积极和消极的)用于训练,以及相同数量的评论用于测试。

NLTK提供了一个朴素贝叶斯分类器来处理机器学习工作。主要任务是编写一个函数,从文本中提取"特征"。分类器使用这些特征进行分类。

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

对于电影评论,特征将是前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 this."在标记化和标记否定词之后变成这样:

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

最后一行移除了所有停用词(包括否定词)和标点符号。文本中仍然有很多无用的词,如"I"或"This",但这种过滤对于演示已经足够了。

接下来,构建一个从评论文件中读取的所有单词的列表。分别保留积极和消极单词的列表,以确保在取前N个单词时保持平衡。(也测试了不分开保留单词列表,结果发现大多数积极评论被分类为消极。)同时,还可以创建所有积极评论和所有消极评论的列表。

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("", "") 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("", "") 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))

现在可以编写一个特征提取器。如前所述,它应该返回一个字典,每个顶级单词作为键,以及一个布尔值作为值,取决于该单词是否出现在文本中。

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("", "") 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("", "") test.append((extract_features(txt), "neg")) print(nltk.classify.accuracy(classifier, test))

使用N=2000,训练集中有5000个积极评论和5000个消极评论,使用这段代码达到了大约85%的准确率。

沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485