【自然语言处理】文本情感分析

文本情感分析

1 任务目标

1.1 案例简介

情感分析旨在挖掘文本中的主观信息,它是自然语言处理中的经典任务。在本次任务中,我们将在影评文本数据集(Rotten Tomato)上进行情感分析,通过实现课堂讲授的模型方法,深刻体会自然语言处理技术在生活中的应用。

同学们需要实现自己的情感分析器,包括特征提取器(可以选择词袋模型、n-gram模型或词向量模型)、简单的线性分类器以及梯度下降函数。随后在数据集上进行训练和验证。我们提供了代码框架,同学们只需补全model.py中的两个函数。

1.2 数据说明

我们使用来自Rotten Tomato的影评文本数据。其中训练集data_rt.train和测试集data_rt.test均包含了3554条影评,每条影评包含了文本和情感标签。示例如下:

+1 visually , 'santa clause 2' is wondrously creative .

其中,+1 表示这条影评蕴涵了正面感情,后面是影评的具体内容。

1.3 数据特征提取

TODO:补全featureExtractor函数

在这个步骤中,同学们需要读取给定的训练和测试数据集,并提取出文本中的特征,输出特征向量。

同学们可以选择选择词袋模型、n-gram模型或词向量模型中的一种,也可以对比三者的表现有何差异。

1.4 训练分类器

TODO:补全learnPredictor函数

我们提供的训练数据集中,每句话的标签在文本之前,其中+1表示这句话蕴涵了正面感情,-1表示这句话蕴涵了负面感情。因此情感分析问题就成为一个分类问题。

我们采用最小化hinge loss的方法训练分类器,假设我们把每条影评文本 x x x映射为对应的特征向量 ϕ ( x ) \phi(x) ϕ(x),hinge loss的定义为
L ( x , y ; w ) = max ⁡ ( 0 , 1 − w ⋅ ϕ ( x ) y ) L(x,y; \mathbf{w})=\max(0,1-\mathbf{w}\cdot\phi(x)y) L(x,y;w)=max(0,1wϕ(x)y)
同学们需要实现一个简单的线性分类器,并推导出相应的梯度下降函数。

1.5 实验与结果分析

在训练集上完成训练后,同学们需要在测试集上测试分类器性能。本小节要求同学们画出训练集上的损失函数下降曲线和测试集的最终结果,并对结果进行分析。

1.6 评分要求

同学们需要提交源代码和实验报告。实验报告中应包含两部分内容:

  • 对hinge loss反向传播的理论推导,请写出参数的更新公式。
  • 对实验结果的分析,请描述采用的模型结构、模型在训练集上的损失函数下降曲线和测试集的最终结果,并对结果进行分析。分析可以从模型的泛化能力、参数对模型性能的影响以及不同特征的影响等方面进行。

2 代码构建

2.1 数据特征提取

  1. 词袋模型

    词袋模型将文本中的每个单词作为一个特征,特征值为该单词在文本中出现的次数。

    def extractFeatures_bow(x):
        features = collections.defaultdict(float)
        for word in x.split():
            features[word] += 1.0
        return features
    
  2. n-gram模型

    n-gram模型考虑了文本中的n个连续单词,将它们作为特征。在本实验中,我们使用了2-gram模型。

    def extractFeatures_ngram(x, n=2):
        features = collections.defaultdict(float)
        words = x.split()
        for i in range(len(words) - n + 1):
            ngram = ' '.join(words[i:i+n])
            features[ngram] += 1.0
        return features
    
  3. 词向量模型

    本实验词向量模型采用了预训练的 word2vec 模型(FastText Word Embeddings)加载词向量。

    # 使用预训练的word2vec模型(FastText Word Embeddings)加载词向量 
    def loadWord2VecModel(filename):
        wordVectors = {}
        with open(filename, 'r', encoding='utf-8') as f:
            for line in f:
                values = line.split()
                word = values[0]
                vector = np.array(values[1:], dtype='float32')
                wordVectors[word] = vector
        return wordVectors
    
    wiki_news= './data/wiki-news-300d-1M.vec'
    wordVectors = loadWord2VecModel(wiki_news)
    

    词向量模型使用预训练的词向量(如FastText、Word2Vec)将文本中的单词表示为向量,并将这些向量的平均值作为文本的特征向量。

    def extractFeatures_wordvec(x):
        # 将句子x拆分成单个字符或单词
        tokens = x.split()
        # 句子的词向量表示
        vectors = [wordVectors[token] for token in tokens if token in wordVectors.keys()]
        # 将句子中的每个单词转换为对应的词向量,然后将这些词向量的平均值作为该句子的特征向量
        if vectors:
            # 将句子中的每个单词转换为对应的词向量,然后将这些词向量的平均值作为该句子的特征向量
            vectors = np.mean(vectors, axis=0)
        else:
            # 处理句子中所有单词都不在模型中的情况
            vectors = np.zeros(len(next(iter(wordVectors.values()))))
        # 将特征向量表示为字典
        featureDict = {}
        for i, value in enumerate(vectors):
            featureDict[f'feature_{i}'] = value
        
        return featureDict
    

2.2 训练分类器

训练数据集中,每句话的标签在文本之前,其中+1表示这句话蕴涵了正面感情,-1表示这句话蕴涵了负面感情。因此情感分析问题就成为一个分类问题。

在情感分析任务中,我们的目标是学习一个分类器,将文本表示为特征向量,并根据这些特征向量对文本进行分类。我们使用的是最小化hinge loss的方法训练分类器。

  1. 参数更新公式推导

    假设我们的分类器参数为 w \mathbf{w} w,对于给定的训练样本 ( x i , y i ) (x_i, y_i) (xi,yi),其中 x i x_i xi 是文本的特征向量, y i y_i yi 是其对应的情感标签(+1 或 -1)。

    我们的目标是最小化hinge loss,即:

    L ( x i , y i ; w ) = max ⁡ ( 0 , 1 − w ⋅ ϕ ( x i ) ⋅ y i ) L(x_i, y_i; \mathbf{w}) = \max(0, 1 - \mathbf{w} \cdot \phi(x_i) \cdot y_i) L(xi,yi;w)=max(0,1wϕ(xi)yi)

    其中 ϕ ( x i ) \phi(x_i) ϕ(xi) 是文本 x i x_i xi 的特征向量。

    我们使用随机梯度下降法来更新参数 w \mathbf{w} w。参数的更新公式可以通过对hinge loss进行梯度下降来得到。梯度的计算需要考虑 hinge loss 在 w ⋅ ϕ ( x i ) ⋅ y i ≤ 1 \mathbf{w} \cdot \phi(x_i) \cdot y_i \leq 1 wϕ(xi)yi1 w ⋅ ϕ ( x i ) ⋅ y i > 1 \mathbf{w} \cdot \phi(x_i) \cdot y_i > 1 wϕ(xi)yi>1 两种情况下的情况:

    w ⋅ ϕ ( x i ) ⋅ y i ≤ 1 \mathbf{w} \cdot \phi(x_i) \cdot y_i \leq 1 wϕ(xi)yi1 时,梯度为:

    ∂ L ∂ w = − ϕ ( x i ) ⋅ y i \frac{\partial L}{\partial \mathbf{w}} = - \phi(x_i) \cdot y_i wL=ϕ(xi)yi

    w ⋅ ϕ ( x i ) ⋅ y i > 1 \mathbf{w} \cdot \phi(x_i) \cdot y_i > 1 wϕ(xi)yi>1 时,梯度为零。

    因此,参数的更新公式为:

    $$
    \mathbf{w} := \mathbf{w} + \eta \cdot \left{
    \begin{array}{ll}

    • \phi(x_i) \cdot y_i & \text{if } \mathbf{w} \cdot \phi(x_i) \cdot y_i \leq 1 \
      0 & \text{otherwise}
      \end{array}
      \right.
      $$

    其中 η \eta η 是学习率。

  2. 代码

    代码中同时加入了绘制曲线部分。

    def learnPredictor(trainExamples, testExamples, featureExtractor, numIters, eta):
        '''
        给定训练数据和测试数据,特征提取器|featureExtractor|、训练轮数|numIters|和学习率|eta|,
        返回学习后的权重weights
        你需要实现随机梯度下降优化权重
        '''
        weights = collections.defaultdict(float)
        trainErrors = []
        testErrors = []
        trainLosses = []
        testLosses = []
    
        for i in range(numIters):
            totalTrainLoss = 0
            totalTestLoss = 0
    
            for x, y in trainExamples:
                featureVector = featureExtractor(x)
                predicted = dotProduct(featureVector, weights)
                loss = max(0, 1 - predicted * y)
                totalTrainLoss += loss
                if loss > 0:
                    for feature, value in featureVector.items():
                        weights[feature] += eta * value * y
    
            for x, y in testExamples:
                featureVector = featureExtractor(x)
                predicted = dotProduct(featureVector, weights)
                loss = max(0, 1 - predicted * y)
                totalTestLoss += loss
    
            trainError = evaluatePredictor(trainExamples, lambda x: (1 if dotProduct(featureExtractor(x), weights) >= 0 else -1))
            testError = evaluatePredictor(testExamples, lambda x: (1 if dotProduct(featureExtractor(x), weights) >= 0 else -1))
    
            trainErrors.append(trainError)
            testErrors.append(testError)
            trainLosses.append(totalTrainLoss / len(trainExamples))
            testLosses.append(totalTestLoss / len(testExamples))
    
            print("At iteration %d, loss on training set is %f, loss on test set is %f, error rate on training set is %f, error rate on test set is %f" %
                  (i, totalTrainLoss / len(trainExamples), totalTestLoss / len(testExamples), trainError, testError))
    
        plt.figure(figsize=(12, 5))
    
        plt.subplot(1, 2, 1)
        plt.plot(range(numIters), trainLosses, label="Train Loss")
        plt.plot(range(numIters), testLosses, label="Test Loss")
        plt.xlabel("Epoch")
        plt.ylabel("Loss")
        plt.title("Loss vs. Epoch")
        plt.legend()
    
        plt.subplot(1, 2, 2)
        plt.plot(range(numIters), trainErrors, label="Train Error Rate")
        plt.plot(range(numIters), testErrors, label="Test Error Rate")
        plt.xlabel("Epoch")
        plt.ylabel("Error Rate")
        plt.title("Error Rate vs. Epoch")
        plt.legend()
    
        plt.tight_layout()
        plt.show()
    
        return weights
    

3 训练结果

3.1 词袋模型训练结果

  1. 训练代码

    设置超参数,训练轮次epoch为30,学习率为0.01。

    def BOW_Model(numIters, eta):
        trainExamples = readExamples('data/data_rt.train')
        testExamples = readExamples('data/data_rt.test')
        featureExtractor = extractFeatures_bow
        weights = learnPredictor(trainExamples, testExamples, featureExtractor, numIters=numIters, eta=eta)
        trainError = evaluatePredictor(trainExamples, lambda x : (1 if dotProduct(featureExtractor(x), weights) >= 0 else -1))
        testError = evaluatePredictor(testExamples, lambda x : (1 if dotProduct(featureExtractor(x), weights) >= 0 else -1))
        print ("train error = %s, test error = %s" % (trainError, testError))
    
    BOW_Model(30, 0.01)
    
  2. 训练结果

    image-20240603231245392

    train error = 0.013787281935846933, test error = 0.27124366910523356
    

3.2 n-gram模型训练结果

  1. 训练代码

    设置超参数,训练轮次epoch为30,学习率为0.01。

    def Ngram_Model(numIters, eta):
        trainExamples = readExamples('data/data_rt.train')
        testExamples = readExamples('data/data_rt.test')
        featureExtractor = extractFeatures_ngram
        weights = learnPredictor(trainExamples, testExamples, featureExtractor, numIters=numIters, eta=eta)
        trainError = evaluatePredictor(trainExamples, lambda x : (1 if dotProduct(featureExtractor(x), weights) >= 0 else -1))
        testError = evaluatePredictor(testExamples, lambda x : (1 if dotProduct(featureExtractor(x), weights) >= 0 else -1))
        print ("train error = %s, test error = %s" % (trainError, testError))
    
    Ngram_Model(30, 0.01)
    
  2. 训练结果

    image-20240603231336449

    train error = 0.0005627462014631402, test error = 0.33061339335959483
    

    可以看到,使用n-gram模型的收敛速度比词袋模型稍快,但训练结果却更差,明显产生了过拟合。

3.3 词向量模型训练结果

  1. 训练代码

    设置超参数,训练轮次epoch为30,学习率为0.01。

    def Word2Vec_Model(numIters, eta):
        trainExamples = readExamples('data/data_rt.train')
        testExamples = readExamples('data/data_rt.test')
        featureExtractor = extractFeatures_wordvec
        weights = learnPredictor(trainExamples, testExamples, featureExtractor, numIters=numIters, eta=eta)
        trainError = evaluatePredictor(trainExamples, lambda x : (1 if dotProduct(featureExtractor(x), weights) >= 0 else -1))
        testError = evaluatePredictor(testExamples, lambda x : (1 if dotProduct(featureExtractor(x), weights) >= 0 else -1))
        print ("train error = %s, test error = %s" % (trainError, testError))
    
    Word2Vec_Model(30, 0.01)
    
  2. 训练结果

    image-20240603231418657

    train error = 0.22031513787281937, test error = 0.236916150815982
    

    可以看到,使用预训练模型的词向量训练结果最好,得到的erro值最低,并且没有过拟合。

4 总结

以下是各特征提取方法的最终训练和测试误差:

特征提取方法训练误差测试误差
词袋模型0.01380.2712
n-gram模型0.00060.3306
词向量模型0.22030.2369

从表格可以看出,n-gram模型在训练集上的误差最低,但在测试集上的误差却最高,表明其可能过拟合了训练数据。词袋模型在训练集和测试集上的误差较为平衡,而词向量模型在测试集上的表现最好,尽管其在训练集上的误差较高。

  1. 性能:

    • 词袋模型在训练集上表现很好,但在测试集上有较高的误差,可能存在一定的过拟合。

    • n-gram模型在训练集上表现最佳,但在测试集上表现最差,明显过拟合。

    • 词向量模型在测试集上的误差最低,泛化能力最好。

  2. 泛化能力:

    • 词袋模型和n-gram模型都有一定的过拟合风险,尤其是在处理复杂文本时。

    • 词向量模型由于捕捉到词语的语义关系,能够更好地泛化到未见过的数据。

  3. 权重可解释性:

    • 词袋模型的权重最易解释,可以直接看到每个词对情感预测的影响。

    • n-gram模型的权重解释性相对较低,但仍能提供一定的短语信息。

    • 词向量模型的权重解释性最差,但它在捕捉语义信息方面表现最好。

根据以上分析,我们可以得出以下结论:

  • 如果需要一个解释性较强的模型,可以选择词袋模型。
  • 如果需要一个泛化能力较好的模型,可以选择词向量模型。
  • n-gram模型在小数据集上容易过拟合,不建议在数据较少或分布复杂时使用。

实际应用中,可以根据具体需求选择合适的特征提取方法和模型,并通过调整学习率、正则化等超参数来优化模型性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/677459.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

企业数字化转型顶层设计与企业架构TOGAF9.2认证【鉴定级】

什么是TOGAF TOGAF由国际标准权威组织 The Open Group制定。The Open Group于1993年开始应客户要求制定系统架构的标准,在1995年发表The Open Group Architecture Framework (TOGAF) 架构框架。2022年4月25日发布了TOGAF的最新版本10,目前,T…

如何用TCC方案轻松实现分布式事务一致性

本文作者:小米,一个热爱技术分享的29岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货! 哈喽,大家好!我是小米,一个热爱技术的活力小青年,今天要和大家分享的是一种在分布式系统中实现事务的一种经典方案——TCC(Try Confirm Canc…

【机器学习】朴素贝叶斯算法及其应用探索

🌈个人主页: 鑫宝Code 🔥热门专栏: 闲话杂谈| 炫酷HTML | JavaScript基础 ​💫个人格言: "如无必要,勿增实体" 文章目录 朴素贝叶斯算法及其应用探索引言1. 朴素贝叶斯基本概念1.1 贝叶斯定理回顾1.2 朴…

Docker compose 部署Grafana+Prometheus实现java应用JVM监控

这里是小奏,觉得文章不错可以关注公众号小奏技术 背景 最近可能要对一些java应用进行JVM监控,比如一些中间件 实际如果是普通应用比较简单的就是上相对来说重量一点的skywalking、armas 这种监控比较全,啥都有。 当然如果我们要轻量一点只监控JVM就可…

Centos 报错 One of the configured repositories failed

目录预览 一、问题描述二、原因分析三、解决方案四、参考链接 一、问题描述 使用yum update更新命令就出现下面问题,系统是刚安装的,然后修改了一下IP变成手动。(排查问题前,先回顾自己做了哪些操作,方便进一步排错&a…

accelerate笔记:实验跟踪

Accelerate支持七种集成的跟踪器: TensorBoardWandBCometMLAimMLFlowClearMLDVCLive要使用这些跟踪器,可以通过在 Accelerator 类的 log_with 参数中传入所选类型来实现 from accelerate import Accelerator from accelerate.utils import LoggerTypeac…

iPhone邮件软件使用经验分享

前言:由于没有看邮件的习惯,导致错过了很重要的邮件(ー〃),虽然最后努力弥补了损失,但仍然心有余悸,在手机的邮件软件上可以添加多个邮件账号,再也不会错过重要消息了(ง •̀_•́)ง‼ 一、QQ等其他电子…

IDEA debug 调试使用小tips

1、Show Execution Point (ALT F10):回到当前执行到的那一行,有时候在代码间和不同包和文件中点来点去,可能会找不到执行到什么地方了,点这个就跳转回来了 2、Step Over (F8):下一步,这个按钮是一行一行往…

最新消息:Stable Diffusion 3将于下周开源,快快申请!(内附地址)

🧙‍♂️ 诸位好,吾乃斜杠君,编程界之翘楚,代码之大师。算法如流水,逻辑如棋局。 📜 吾之笔记,内含诸般技术之秘诀。吾欲以此笔记,传授编程之道,助汝解技术难题。 &#…

数字马力社招测试面试经历,期望17K

面试感受:面试官人挺好的,虽然不是其他面经提到的会有循序渐进的引导或者如沐春风的感觉,不是聊天式,是问答式,只是是我的回答往往过于简单,所以中间的停顿时间就稍有尴尬。 面试评价:个人表现…

自定义 DSL 流程图(含XML 描述邮件,XML 描述流程图)

什么是 DSL? 领域特定语言(英语:domain-specific language、DSL)指的是专注于某个应用程序领域的计算机语言。又译作领域专用语言。不同于普通的跨领域通用计算机语言(GPL),领域特定语言只用在某些特定的领域。 比如用…

数据挖掘实战-基于长短期记忆网络(LSTM)的黄金价格预测模型 | 97% 准确度

🤵‍♂️ 个人主页:艾派森的个人主页 ✍🏻作者简介:Python学习者 🐋 希望大家多多支持,我们一起进步!😄 如果文章对你有帮助的话, 欢迎评论 💬点赞&#x1f4…

Java学习【认识异常】

Java学习【认识异常】 认识异常异常的种类异常的作用 异常的处理方式JVM默认的处理方式捕获异常finally 多个异常的处理异常中的方法抛出异常 自定义异常 认识异常 在Java中,将程序执行过程中发生的不正常行为称为异常 异常的种类 Error代表的是系统级别的错误&a…

【Java数据结构】详解Stack与Queue(四)

🔒文章目录: 1.❤️❤️前言~🥳🎉🎉🎉 2.用队列实现栈 3.用栈实现队列 4.栈和队列存放null 5.总结 1.❤️❤️前言~🥳🎉🎉🎉 Hello, Hello~ 亲爱的朋友…

Leetcode:整数转罗马数字

题目链接:12. 整数转罗马数字 - 力扣(LeetCode) 普通版本(模拟) 条件分析:罗马数字由 7 个不同的单字母符号组成,每个符号对应一个具体的数值。此外,减法规则还给出了额外的 6 个复…

FIFO读写端口位宽不同时的数据输出

非对称长宽比指的是FIFO的读写端口的数据宽度不同,呈现比例关系;非对称长宽比允许FIFO的输入和输出深度不同,支持如下的写读宽高比:1:8、1:4、1:2、1:1、2:1、4:1、8:1。 只有以下几种FIFO支持非对称长宽比: 对于非对称长宽比&a…

容器化部署fastdfs文件存储

目录 一、软件信息 二、构建fastdfs镜像 三、docker 启动fdfs服务 四、k8s部署fdfs服务 1、fdfs部署文件 五、外部服务访问 一、软件信息 fastdfs版本:fastdfs:V5.11 libfastcommon版本: V1.0.36 fastdfs-nginx-module版本:V1.20 nginx版本&…

自动驾驶---Control之LQR控制

1 前言 在前面的系列博客文章中为读者阐述了很多规划相关的知识(可参考下面专栏),本篇博客带领读者朋友们了解控制相关的知识,后续仍会撰写规控相关文档。 在控制理论的发展过程中,人们逐渐认识到对于线性动态系统的控…

Websocket服务端结合内网穿透发布公网实现远程访问发送信息

文章目录 1. Java 服务端demo环境2. 在pom文件引入第三包封装的netty框架maven坐标3. 创建服务端,以接口模式调用,方便外部调用4. 启动服务,出现以下信息表示启动成功,暴露端口默认99995. 创建隧道映射内网端口6. 查看状态->在线隧道,复制所创建隧道的公网地址加端口号7. 以…

Audition 2024 for Mac/Win:音频录制与编辑的卓越之选

随着数字媒体的不断发展,音频内容创作已经成为各行各业中不可或缺的一部分。无论是音乐制作、广播节目、播客录制还是影视配音,都需要高品质的音频录制和编辑工具来实现专业水准的作品。在这个充满竞争的时代,要想在音频创作领域脱颖而出&…