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

文本情感分析

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模型在小数据集上容易过拟合,不建议在数据较少或分布复杂时使用。

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

ModuleNotFoundError: No module named 'py_pkg_1'错误是由于无法找到名为'py_pkg_1'的Python模块引起的。这可能是由于以下几个原因导致的: 1. 该模块尚未安装:请确保你已经正确安装了'py_pkg_1'模块。你可以使用pip命令来安装该模块。例如,运行`pip install py_pkg_1`来安装。 2. 模块的安装路径不正确:如果你已经安装了'py_pkg_1'模块,但仍然出现了该错误,那么可能是因为模块的安装路径不在Python解释器的搜索路径中。你可以通过`pip show py_pkg_1`命令来查看该模块的安装路径,并确保路径正确。 3. Python环境配置问题:在某些情况下,Python的环境配置可能会导致模块无法被正确加载。你可以尝试重新配置Python环境,确保正确设置PYTHONPATH等环境变量。 请注意,在提供的引用内容中,并没有相关信息表明'py_pkg_1'是一个实际存在的Python模块。所以这只是一个例子,具体情况可能因实际代码和环境而异。如果你需要更具体的帮助,请提供更多详细信息,例如模块的实际名称、安装方式、代码片段等。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [解决ModuleNotFoundError: No module named ‘pkg_resources](https://blog.csdn.net/witton/article/details/119904922)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [使用pyinstaller打包exe文件及问题解决.docx](https://download.csdn.net/download/GHenry/12419322)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

住在天上的云

如果您喜欢我的文章,欢迎打赏哦

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值