65.9K
CodeProject 正在变化。 阅读更多。
Home

NLTK 和机器学习用于情感分析

starIconstarIconstarIconstarIconstarIcon

5.00/5 (3投票s)

2020 年 5 月 29 日

CPOL

9分钟阅读

viewsIcon

10603

downloadIcon

96

本文是情感分析系列的第五篇,该系列文章使用 Python 和开源自然语言工具包。在本文中,我们将构建一个优化的机器学习模型。

本情感分析系列文章的目的是使用 Python 和开源自然语言工具包 (NLTK) 构建一个库,该库可以扫描 Reddit 帖子的回复,并检测发帖者是否使用了消极、敌对或其他不友好的语言。

机器学习位于统计推理、人工智能和计算机科学的交叉点,它使我们能够查看数据集并从中提取见解。监督学习是一个过程,我们从一个映射了输入和已标记的预期输出的数据集开始。

相比之下,无监督学习不使用标记,因此在这种方法中,我们需要在评估算法和迭代评估过程中发挥积极作用,以识别在生成标记结果中有意义的特征或定义性模式。

迭代式监督学习过程是什么样的?我们将研究的一些问题包括:

  • 我们试图回答什么问题?对于本项目,我们想知道用户是否对我们的品牌或我们在 Reddit 上分享的内容做出积极或消极的反应。
  • 我们的数据够多吗?在之前的示例中,我们只关注了一个帖子及其评论,因此我们需要组装一个测试框架来检查更多数据,以便进行评估和训练。
  • 我们可以识别数据集的哪些特征?特征是用于描述数据特征的已标记值。感叹号的使用可能是一个值得观察的特征,因为它可能表示有意义的强烈反应。
  • 我们将如何衡量成功?

这些问题看似巨大。但与许多编程问题一样,我们可以将解决方案分解为可行的步骤,并且有工具可以帮助我们完成每一步。

我们将首先将语料库分成多个部分,或称为集合:

  • 训练集
  • 测试集
  • 评估集

创建自定义语料库可能是一项艰巨的任务。它要求一个人首先将所有文本收集在一起,然后逐项检查以使用值进行标注或标记。例如,人工审阅者会将“这很酷”标记为积极,将“我不推荐”标记为消极。

在投入这项工作之前,值得四处看看是否存在可以使用的现有数据集。一旦找到,我们将把该数据集分成训练集和测试集。我们将使用训练集来调整算法并构建分类模型。然后,我们将使用测试集来评估分类模型的准确性。

我们可以查看不同的数据集拆分比例,但 75/25、80/20 或 90/10 的拆分很常见。也就是说,对于 1000 条记录,我们可以使用 800 条进行训练,200 条进行测试。

评估集将是我们想要与我们在《使用预训练的 VADER 模型进行 NLTK 情感分析》一文中执行的 VADER 分析进行比较的任何 Reddit 帖子。我们在这里讨论的内容不需要理解该分析,但它可能很有用。

为朴素贝叶斯分类器准备分析数据

在《寻找用于自然语言处理的数据》一文中,我们下载并查看了 NLTK 提供的电影评论语料库。我们了解到它是一系列简单的文本文件,这些文件已被分类为积极和消极值。

我们将再次使用该语料库作为训练集来构建朴素贝叶斯分类器。我们使用它是因为它在 NLTK 中易于访问,并且在我们专注于理解监督学习过程时,容易理解电影评论家对电影有积极或消极评论的概念。

我们首先将数据集构建为元组对,其中第一个元素是句子列表,第二个元素是简单的字符串标签,用于指示积极或消极的情感。

import random
from nltk.corpus import movie_reviews

# return a list of tuple pairs
#   a string of the raw review before tokenization
#   a string label of a pre-classified sentiment ('pos' or 'neg')
def get_labeled_dataset():
    dataset = []
    for label in movie_reviews.categories():
        for review in movie_reviews.fileids(label):
            dataset.append((movie_reviews.raw(review), label))

    random.shuffle(dataset)
    return dataset

请注意,由于此语料库的结构方式是积极评论然后消极评论连续排列,因此我们必须对其进行随机化。如果我们不进行随机化,所有积极评论将都在训练集中,所有消极评论都将在测试集中。这种分布将是倾斜的,而不是均匀分布的,这将给我们带来糟糕的结果。

我们还需要创建一个特征字典来帮助描述数据集。我们将花费大部分时间来调整这些特征,但我们将从一些简单的东西开始:字符数。

def get_features(text):
    features = {}

    # Feature #1 - verbosity
    features['verbosity'] = len(text)

    return features

特征预期是字符串标签键,具有简单的类型值,这非常适合 Python 字典。

使用朴素贝叶斯分类器分析情感

有了数据集和一些特征观察,我们现在可以进行分析了。我们将从 NLTK 中的朴素贝叶斯分类器开始,它更容易理解,因为它简单地假设训练集中具有最高概率的标签频率最可能匹配。

让我们评估结果

import nltk.classify
from nltk import NaiveBayesClassifier

def evaluate_model(dataset, train_percentage=0.9):
    feature_set = [(get_features(i), label) for (i, label) in dataset]
    count = int(len(feature_set) * train_percentage)
    train_set, test_set = feature_set[:count], feature_set[count:]
    classifier = NaiveBayesClassifier.train(train_set)
    return nltk.classify.accuracy(classifier, test_set)

dataset = get_labeled_dataset()
print(evaluate_model(dataset))
# Output: 0.53

我们获取数据集中的所有评论,并使用前面显示的 get_labeled_dataset 函数将其标记为积极或消极。然后,我们使用识别我们认为可能重要的特征的字典(例如,冗长性)替换评论,使用我们上面定义的 get_features 函数。

接下来,我们拆分数据集,使用 90% 来训练分类器。我们将剩余的 10% 保存到测试集中。

    count = int(len(feature_set) * train_percentage)
    train_set, test_set = feature_set[:count], feature_set[count:]

我们在训练数据集上运行朴素贝叶斯分类器。模型可以查看每个项目中的特征并进行猜测。猜测与实际标签匹配的次数越多,我们的模型就越准确。

    classifier = NaiveBayesClassifier.train(train_set)
    return nltk.classify.accuracy(classifier, test_set)

在这种情况下,准确率为 53%,这并不算令人印象深刻,但评论中的字符数似乎也不是一个有用的参考特征。冗长性可能是一个有用的参与度特征,但不是情感。

我们在《使用预训练的 VADER 模型进行 NLTK 情感分析》中使用了 VADER 分析来识别情感,但现在通过这种方法,我们可以判断这些极性分数在预测情感方面的准确性。

如果评论的 VADER 分数具有积极强度,我们期望这与人类为积极评论确定的值相匹配。我们可以将此分数作为特征添加到我们的模型中,并再次运行训练。

analyzer = SentimentIntensityAnalyzer()

def get_features(text):
    features = {}

    # Feature #1 - verbosity
    features['verbosity'] = len(text)

    # Feature #2 and #3 - lexical word choice
    scores = analyzer.polarity_scores(text)
    features['vader(pos)'] = scores['pos']
    features['vader(neg)'] = scores['neg']

    return features

如果我们这次使用三个特征运行 evaluate_model(dataset),我们会看到 62% 的准确率评估。这比仅使用冗长性作为特征有所改进,但不足以说我们有一个成功预测电影评论为积极或消极的模型。

接下来我们可以做什么来提高准确性?

一个有用的方法是查看预测错误。任何被错误地识别为积极或消极的电影评论都可能有助于识别我们应该考虑添加到模型中的其他特征。

分类模型错误分析

到目前为止,我们模型的准确率为 62%。那 38% 弄错的怎么样了?我们应该添加新特征吗?我们应该尝试不同的分类算法吗?数据有问题吗?

要理解实现更高准确性的下一步,这是一个反复试验的过程。查看我们的分类模型未能正确识别情感的实例可能很有信息。这些“预测错误”可以通过分析来获得新的见解,了解如何训练我们的模型,如果我们能识别出有助于区分被错误分类的测试数据的新特征。

让我们创建一个 evaluate_model() 方法的变体,以获得一些额外的见解。更改以红色突出显示。

def analyze_model(dataset, train_percentage=0.9):
    feature_set = [(get_features(i), label) for (i, label) in dataset]
    count = int(len(feature_set) * train_percentage)
    train_set, test_set = feature_set[:count], feature_set[count:]
    classifier = NaiveBayesClassifier.train(train_set)
    classifier.show_most_informative_features(5)

    accuracy = nltk.classify.accuracy(classifier, test_set)
    errors = []
    for (text, label) in dataset[count:]:
        guess = classifier.classify(get_features(text))
         
        if guess != label:
            tokens = nltk.word_tokenize(text)
            errors.append((label, guess, tokens[:10]))
    return (accuracy, errors)

当使用此变体时,首先要深入研究的是调用 show_most_informative_features()。输出如下所示:

Most Informative Features
    vader(pos) = 0.099             neg : pos    =      8.9 : 1.0
    vader(neg) = 0.055             pos : neg    =      7.1 : 1.0
    vader(pos) = 0.131             neg : pos    =      6.9 : 1.0
    vader(pos) = 0.176             pos : neg    =      6.4 : 1.0
    vader(pos) = 0.097             neg : pos    =      6.1 : 1.0

这为我们提供了最成功地预测结果的特征的有序列表。

第一项显示特征 vader(pos),当其值为 0.099 时,正确预测消极特征的可能性是 8.9 倍。

我们可以从这个输出中得出几个结论。

首先,我们在特征中使用了 0 到 1 之间的连续值。这种类型的算法在使用离散值,最好是二元值时效果会更好。我们可以将情感分数分组到存储桶中,作为表达它的特征。

其次,值得注意的是,冗长性并未显示为最有信息量的特征。当我们检验关于我们应该在模型中包含哪些特征的假设时,有时我们应该删除无用的特征。

我们对 analyze_model() 函数进行了第二次修改,将错误收集到数组中。这将显示预测和实际值不匹配的项目。它还显示电影评论本身的子集。以下是一些示例:

[
('pos', 'neg', ['susan', 'granger', "'s", 'review', 'of', '``', 'hearts', 'in', 'atlantis', '``']), 
('neg', 'pos', ['phil', '(', 'radmar', 'jao', ')', 'has', 'a', 'hairy', 'problem', '.']), 
('pos', 'neg', ['an', 'astonishingly', 'difficult', 'movie', 'to', 'watch', ',', 'the', 'last', 'temptation']), 
('neg', 'pos', ['while', 'watching', 'loser', ',', 'it', 'occurred', 'to', 'me', 'that', 'amy']),
...]

在某些情况下,我们预测的积极评论被标记为消极,而在其他情况下则相反。以红色突出显示的示例被标记为积极,但我们预测为消极。短语“一部令人震惊地难以观看的电影,最后的诱惑”听起来像是一条消极评论,尽管它被标记为积极,因此 warrants 仔细检查。

我们可以在 nltk_data/corpora/movie_reviews/pos_cv440_15243 中找到完整的评论。该评论包含诸如以下短语:

  • "一部令人震惊地难以观看的电影"
  • "中间部分很拖沓,什么都没发生"
  • "然而,这部电影有很多问题"

在我看来,这至少是一条褒贬不一的评论,它很好地说明了输入到我们机器学习模型中的数据的好坏,与其最初分配的标签一样好。识别与您的问题匹配的标记数据集很重要。

后续步骤

构建优化的机器学习模型是一个反复试验的过程,但现在我们有了一个明确的目标和实现该目标所需的步骤。

我们将在《通过数据标注改进 NLTK 情感分析》中构建自己的数据集。

要回顾我们为执行 VADER 和 NLTK 的 NLP 分析所采取的先前步骤,请参阅《使用预训练的 VADER 模型进行 NLTK 情感分析》。

© . All rights reserved.