微博评论情感分类-实验报告

个人机器学习课程实验记录,最后修改于2023.5.18

一、引言

如今,发达的网络时代带动了微博娱乐平台的发展。对微博评论进行情感分析不仅可以把握社会人群的观点态度,还可以提高管理部门舆情监控水平。

为了提高微博评价分析的准确性,可以使用朴素贝叶斯算法结合卡方检验对微博评论进行分析。

受用户习惯的影响,微博文本通常由短文本和表情符号组成。由于表情符号所表达的情绪通常简单明了,而且远不如汉字丰富,因此它们通常可以更准确地表达用户情绪。

但是受本次实验各方面的限制,暂不考虑表情符号以及标点符号等对表达出的情绪的影响,而是使用全文字分词,通过词向量来判断评论用户的情绪。

二、问题定义

通过对微博评论进行预处理、分词以及特征选择等,建立特征词典,构建每条评论的特征向量。之后利用分类算法,如朴素贝叶斯、SVM等,针对训练集的特征向量以及类标签进行训练,得到分类模型,并通过计算在测试集上的预测准确率、召回率等对分类器的分类效果以及不同参数影响进行性能评估。

需要对train.csv文件进行训练,创建能够实现微博评论四种不同情绪分类的模型。在期末检查项目的时候,运行老师给出的测试集,由助教根据分类的准确率进行评分。注意:算法不能使用现有的框架,如不能使用SK-Learn,需要自己编程实现分类算法。

数据概览:

带情感标注的中文文本数据,包含 4 种情感:喜悦,愤怒、厌恶、低落。

数据格式:

字段 说明
label 0 喜悦,1 愤怒,2 厌恶,3 低落
review 微博内容
在这里插入图片描述

三、研究思路及算法过程

算法思想

朴素贝叶斯

朴素贝叶斯法是基于贝叶斯定理与特征条件独立性假设的分类方法。对于给定的训练集,首先基于特征条件独立假设学习输入输出的联合概率分布(朴素贝叶斯法这种通过学习得到模型的机制,显然属于生成模型);然后基于此模型,对给定的输入 x,利用贝叶斯定理求出后验概率最大的输出 y。

朴素贝叶斯的公式为:

在这里插入图片描述

对于朴素贝叶斯分类,总结流程如下:

对已知的特征矩阵和标签矩阵,我们定义:

在这里插入图片描述

假设这些样本总共分为了3类,分别为class1,class2,class3。

第一步:

通过样本集的类别分布,对每个类别计算先验概率。也就是需要计算出这些样本分属3个类别各自的比 例,比如:

在这里插入图片描述

类似这样,分别计算出p(class2),p(class3)

第二步:

计算每个类别下每个特征属性值的出现概率。比如,当为类别1时,特征1的值为f1时的概率P(f1丨class1)为:

在这里插入图片描述

第三步:

计算每个样本所属每个类别的概率。比如对于第一个样本,根据它每个特征值的情况,依次从第二步的 结果中提取对应值并相乘,得到它分别属于这3个类别的概率,即:

在这里插入图片描述

第四步:

比较样本预测属于这三个类别的概率值,找出最大概率对应的分类作为这个样本最终的预测分类结果。

研究思路

本实验,在设计过程中主要考虑以下几点:

①文本分类属于有监督的学习,需要整理样本,确定样本数目以及记录样本标签。

②针对样本需要进行分词操作得到评论的词语表示。

③因为分词后每条评论中包含的词语是很多的,这些词并不都是表征能力强的词,所以需要根据词性、 词长短等过滤掉大部分的无关词。

④如何表征评论呢?在本实验中,我采用的特征提取模型是向量空间模型(VSM),即将样本转换为向 量。为了能实现这种转换,需要进行确定特征词典和得到特征向量的过程。

⑤虽然可以将所有样本的词都提取出来作为词典,但随着样本数目的增多,词典规模可能达到万级、千万级甚至亿级,这么大的维度可能会带来维度灾难,因此就要想办法从大量的特征中选择一些有代表性 的特征而又不影响分类的效果,这个环节,我采用了目前领域内认为比较好的卡方检验方法得到每类中的关键词。

实验步骤

1、文件预处理

1-1、除去重复语句

在对train.csv进行观察时,我发现其中会有重复的review内容,但是对应的label却不一样。如下:(一条label为2而另一条为3)
在这里插入图片描述在这里插入图片描述

可通过如下代码判断是否有重复内容并输出:

from collections import Counter
import csv

dataset = open("train_after.csv", encoding='gb18030')

lines = []
for line in dataset.readlines():
    lines.append(line[2:])
dataset.close()

newlines = dict(Counter(lines))
ls = [key for key, value in newlines.items() if value > 1]  # 展示重复元素
# dic = {key: value for key, value in newlines.items() if value >1}  # 展现重复元素和重复次数

print(ls)

在这里插入图片描述

在本实验中,我决定出现重复时保留前一条语句及标签,将后续再重复的直接整行删掉。

import csv
import pandas as pd

frame = pd.read_csv('train.csv', encoding='gb18030')
data = frame.drop_duplicates(subset=['review'], keep='first', inplace=False)
data.to_csv('train_after.csv', encoding='gb18030')

# df.drop_duplicates(subset=['A', 'B', 'C'], keep='first', inplace=True)
# subset:表示要进去重的列名,默认为 None。
# keep:有三个可选参数,分别是 first、last、False,默认为 first,表示只保留第一次出现的重复项,删除其余重复项,last 表示只保留最后一次出现的重复项,False 则表示删除所有重复项。
# inplace:布尔值参数,默认为 False 表示删除重复项后返回一个副本,若为 Ture 则表示直接在原数据上删除重复项。

处理后得到文件train_after.csv结果:

在这里插入图片描述

在这里插入图片描述

1-2、读取文件时的编码选择

由于微博评论的多样性,最后实验得到的结果是采用gb18030编码可以正确读入,UTF-8或者gbk等则不够适用。

file = open("train_after.csv", encoding='gb18030')

2、数据分词与过滤

2-1、分词

首先利用jieba_fast库将完整的句子划分成不同的词语。

  1. jieba库:jieba是一个开源的中文分词库,实现了基于前缀词典和HMM模型的中文分词算法。它具有高效、简单易用和功能强大等特点,支持对中文文本进行分词、词性标注、关键词提取、文本相似度计算等操作。

  2. jieba_fast库:jieba_fast是jieba的一个快速版,由于jieba在处理大规模文本时效率较低,jieba_fast库采用了更高效的算法实现,提高了分词速度。jieba_fast的分词效果和jieba相比没有太大的差异,但是它的词典加载速度更快、占用内存更少。

异同点:

1、算法实现:jieba库采用前缀词典和HMM模型实现分词,jieba_fast采用更高效的算法实现,提高了分词速度。

2、分词效果:jieba库和jieba_fast库的分词效果基本一致,但是在处理一些特定的文本时可能会有细微的差异。

3、内存占用:jieba_fast库占用的内存比jieba库少。

4、速度:jieba_fast库的分词速度比jieba库更快,但是在处理较小的文本时,两者速度差异不大

5、安装上:jieba库由python编写,有专门的python包,直接安装即可。

jieba_fast则是由C语言编写,安装后没法直接使用,还需要通过特定的vc++编译器进行编译,将其转为python可用的格式。(网上很多教程都说jieba_fast正确运行需要安装特定版本的VS,但是太麻烦了,我后来根据(https://blog.csdn.net/Draymond_666/article/details/85091235)这篇文章解决了安装中出现的问题)

处理思路:

1、加载词典dict.txt。jieba分词自带了一个叫做dict.txt的词典,里面有2万多条词,包含了词条出现的次 数(这个次数是于作者自己基于 人民日报语料等资源训练得出来的)和词性。

2、依据词典dict.txt,基于Trie树结构实现高效的词图扫描,生成句子中汉字所有可能成词情况所构成的 有向无环图(DAG)。

3、对于词典中未收录词,采用了基于汉字成词能力的HMM模型,使用viterbi算法预测分词。

4、已收录词和未收录词全部分词完毕后,使用动态规划寻找DAG的最大概率路径,找出基于词频的最 大切分组合。

5、输出分词结果。

        # 利用正则表达式u'[\u4e00-\u9fa5]+'过滤掉输入数据中的所有非中文字符;
        #\u4e00和\u9fa5正好是中文编码的开始和结束的两个值
        mytext = "".join(re.findall(u'[\u4e00-\u9fa5]+', line[2:]))
        content = (" ".join(jieba.cut(mytext))).strip('\n').split(' ')
2-2、过滤

将review中的其它对词义影响不大的内容均删掉

        stop = [row.strip() for row in open(
            'stopwords.txt', 'r', encoding='gb18030').readlines()]  # 读取停用词
        content = [item for item in content if item not in stop]  # 去除停用词
        content = [item for item in content if len(item) > 1]  # 一字词语过滤

这里的“stopwords.txt”采用了哈工大停用词表(中文语境中最常用的停用词表之一)

停用词:
是指在文本分析和信息检索中无需进行分析和检索的常用词语。这些词语通常是高频词,例如“的”、“是”、“我”、“你”等等。由于这些词语在大多数情况下不具备特定含义,因此在文本分析或信息检索中忽略它们可以提高算法的效率和准确性。因此,在进行文本处理或信息检索时,通常会先对文本进行停用词过滤,将这些常用词语从文本中移除后再进行后续的处理。

一字词语过滤:
是指在文本处理中,将只包含一个汉字的词语从文本中移除的过程。这些单字词语通常是由于拼写错误、打字错误或者其他原因引入文本中的,它们很少具有特定含义或者没有实际意义,因此在进行文本分析或信息检索时,将这些无用的单字词语从文本中过滤掉可以提高算法的准确性和效率。

3、特征提取和表达

在自然语言处理(NLP)中,特征提取和表达是指将文本数据转换为可用于机器学习或深度学习算法的数字表示的过程。

特征提取是将文本数据转换为有意义的特征向量的过程。这些特征向量可以用于训练机器学习模型,以便处理自然语言文本。特征提取可以包括词频、词向量、n-gram、句法分析、情感分析等。

特征表达是将文本数据编码为数字的过程。这些数字可以用于训练深度学习模型。特征表达可以包括词嵌入、语言模型、卷积神经网络、循环神经网络等。 特征提取和表达是NLP中非常重要的步骤,因为它们有助于将自然语言文本转换为计算机可以理解和处理的形式。这使得机器能够自动处理文本数据,并从中学习和提取信息。

3-1、统计词频信息

针对上一步得到的每条评论中的保留词,为了方便之后进行卡方检验以及tfidf的计算,这里我统计了两 种词频信息,分别为:

1、每个类别中出现词语的词频信息。(wordtimes)

2、每个类别中出现的词各自在多少篇评论中出现的信息。(classtimes)

这里需要注意,并不只是简单的统计。在统计完某篇评论中出现词语的词频之后,还要做进一步判断, 如果某个词在这篇评论中仅仅出现一次,我们就认为这种词语对评论所属类别决定意义不大,可以忽略。

而且,jieba_fast分词会把空格也看做一个词语,所以这里还需要忽略分词结果中的空格词。

python中的字典是一种可变容器模型,可存储任意类型对象,当然也就可以存储文本字符串,它具有键 key和值上value结构,便于快速定位词语及对应值。考虑到词典结构的优势,在本实验中,我利用 dictionary结构来统计文本信息,其中.key存储词语,.value存储词语的词频信息。

word_fd = FreqDist()  # 可统计所有词的词频
    con_word_fd = ConditionalFreqDist()  # 可统计积极文本中的词频和消极文本中的词频
    for word in label0Words:
        word_fd[word] += 1
        con_word_fd['0'][word] += 1
    for word in label1Words:
        word_fd[word] += 1
        con_word_fd['1'][word] += 1
    for word in label2Words:
        word_fd[word] += 1
        con_word_fd['2'][word] += 1
    for word in label3Words:
        word_fd[word] += 1
        con_word_fd['3'][word] += 1
3-2、卡方检验

卡方检验(Chi-squared test)是一种用于确定样本与总体是否存在显著差异的统计方法。它可以用于检验两个分类变量之间是否存在关联性。

在卡方检验中,我们会比较观察到的数据和期望的数据之间的差异,从而得出一个卡方值。卡方值越大,意味着观察到的数据与期望的数据越不一致。我们还需要计算一个自由度,它表示可以自由变动的数据的数量。自由度越高,意味着结果越不确定。

在进行卡方检验时,我们需要首先确定零假设和备择假设。零假设是指两个分类变量之间不存在关联性,备择假设则是指两个变量之间存在关联性。我们计算卡方值并比较其与卡方分布表中的临界值,以确定是否拒绝零假设。

卡方检验在数据分析、医学、生物学等领域中得到了广泛的应用。它可以用于检验样本与总体之间的差异,以及检验两个或多个分类变量之间是否存在关联性。

根据卡方检验的原理,假设当前这个词为kv,在本实验中特征选择中的几个观察值分别为:

①在该类别中包括这个词的文档数目。命名为kv_in_class。

②在该类别外包括这个词的文档数目。命名为kv_out_class。

③在该类别中不包含这个词的文档数目。命名为not_kv_in_class。

④在该类别外不包含这个词的文档数目。命名为not_kv_out_class。

对应于独立样本四格表各部分表示如图所示:
在这里插入图片描述

“词t与类别c有关系“的卡方检验的计算公式是这样的:

在这里插入图片描述

因为A+C、B+D、N对于每个词的计算值是一样的,而且我们主要是想通过比较CHI 的大小,找到与类别 c更有关或者说在类别c中更具表征性的词语,以此筛选关键特征词,对求CHI的准确计算值并没有要求。 所以在本实验中,为了减少计算量,提高计算效率,我将CHI的计算比较公式简化成如下形式:

在这里插入图片描述

关于CHI的计算,主要根据之前得到的各类别中词各自在多少条评论中出现的信息得到。

for word, freq in word_fd.items():
        label0_score = BigramAssocMeasures.chi_sq(con_word_fd['0'][word], (freq,label0_word_count), total_word_count)
        label0_word[word] = label0_score
        label1_score = BigramAssocMeasures.chi_sq(con_word_fd['1'][word], (freq,label1_word_count), total_word_count)
        label1_word[word] = label1_score
        label2_score = BigramAssocMeasures.chi_sq(con_word_fd['2'][word], (freq,label2_word_count), total_word_count)
        label2_word[word] = label2_score
        label3_score = BigramAssocMeasures.chi_sq(con_word_fd['3'][word], (freq,label3_word_count), total_word_count)
        label3_word[word] = label3_score
3-3、利用CHI筛选词语

遍历每个类别中的每个保留词,计算该词的CHI值,存储于CHI_dic[kv]中,通过sorted(CHI_dic,key= CHI_dic.get,reverse=True),根据CHI值大小对本类别中出现的所有词进行降序排列,存储于相应列表中。

因为每类评论的数目并不相等,这样也会导致在训练和测试时各类评论数目不平均,所以在本实验中, 每类中保留的特征词的数目根据评论数目多少决定。

	vocabList = []
    ll = []

    ll.append(sorted(label0_word.items(), key=lambda item: item[1], reverse=True)[
               :int(labelNum[0] * number)])
    ll.append(sorted(label1_word.items(), key=lambda item: item[1], reverse=True)[
               :int(labelNum[1] * number)])
    ll.append(sorted(label2_word.items(), key=lambda item: item[1], reverse=True)[
               :int(labelNum[2] * number)])
    ll.append(sorted(label3_word.items(), key=lambda item: item[1], reverse=True)[
               :int(labelNum[3] * number)])
3-4、构建特征词典和特征向量

把每类中的保留词取并集即为此时的特征词典。很明显,此时的特征维度已经大大降低了。用特征词典 对每条评论中的保留词进行筛选,也就是说,每条评论中和本类主题关系不大的词都已经被忽略了,然 后构建特征向量,将在特征词典中出现的保留词置1,否则置0。

    for item in ll:
        for w, f in item:
            vocabList.append(w)
    vocabList = list(set(vocabList))

    return vocabList

def Words_to_vec(vocabList, wordSet):
    """
    1.函数说明:根据vocabList词汇表 将每个评价分词后再进行向量化 即出现为1 不出现为0
    2.vocablit: 词汇表
    3.wordSet: 生成的词向量
    return:返回的词向量
    """
    featureVec = [0] * len(vocabList)

    for word in wordSet:
        if word in vocabList:
            # 如果在词汇表中的话 便将其所在位置赋为1
            featureVec[vocabList.index(word)] = 1
        else:
            pass

    return featureVec

4、训练模型

在本实验中,我采用的是常用的K折交叉验证,它的思想是将原始数据分成K组(一般是均分),将每个子 集数据分别做一次验证集,其余的K-1组子集数据作为训练集,这样会得到K个模型,用这K个模型最终的 验证集的分类准确率的平均数作为此K-CV下分类器的性能指标。

本实验中,评论共有4个类别,根据交叉验证分好训练集和测试集之后,需要针对训练集得到一些关键值,完成模型建立。

①根据训练样本的真实标签情况,分别记录训练样本中分属不同类别的评论的数目,存储于列表 class_amount的class_amount[0],class_amount[1],… ,class_amount[3]。

②根据①得到的训练样本中分属不同类别的评论的数目,分别记录训练样本分属不同类别的评论数目占训练样本总数的概率,存储于列表p_class_amount的p_class_amount[0],p_class_amount[1],… , p_class_amount[3]。

③遍历每个类别下的所有特征属性,分别记录针对当前类别的评论,每个特征属性的分布情况,在这里,我只考虑了属性值等于0和不等于0两种情况,也就是说,每个特征属性只有两种取值。

这样分别得到每个类别内的训练样本中,每个特征属性为1时的样本数目,存储在列表 feature_list_class0_1,feature_list_class1_1,…,feature_list_class3_1中。其中,每个列表为特征 数目×1的存储结构。 并将所占此类别内样本的比例存储在列表p_feature_list_class0_1,p_feature_list_class1_1,…, p_feature_list_class3_1中。其中,每个列表为特征数目×1的存储结构。

这里需要说明,多数情况下,该比例是对概率的一个良好的估计。但因为特征向量稀疏,甚至存在在某个类别的样本中,某个特征值并没有等于1的情况,这将导致其对应的概率为0,这样在测试中,会造成如下影响:将来的查询此概率项将会在贝叶斯分类器中占统治地位,因为贝叶斯公式中计算的其他所有概率项都将乘以此0值。

为了避免此问题,所以需要采用一种平滑技术,在本实验中,我采用的是拉普拉斯平滑,其原理是假定了统一先验概率。例如由

在这里插入图片描述

修改为:
在这里插入图片描述

简化即为:

在这里插入图片描述

事实上,分子加1和分母加2背后的基本原理是这样的:在执行实际的试验之前,我们假设已经有两次试验,一次成功和一次失败。

由以上步骤,完成朴素贝叶斯的训练过程。即针对训练集的样本和特征属性的关系进行了学习。

def trainNB(trainMat, trainLabel):
    """
    1.函数说明:朴素贝叶斯训练函数
    2.trainMat:训练文本的词向量矩阵
    3.trainLable:训练数据的类别标签
    4.return:
        pVecList:
            p0vec:label为0的评论
            p1vec:label为1的评论
            p2vec:label为2的评论
            p3vec:label为3的评论
        pList:对应概率
    """
    # 训练集的数量
    numTraindocs = len(trainMat)
    # 单词数
    numWords = len(trainMat[0])
    # 各类情感类评论数量及概率
    p0Num = 0
    p1Num = 0
    p2Num = 0
    p3Num = 0

    for label in trainLabel:
        if label == 0:
            p0Num = p0Num + 1
        elif label == 1:
            p1Num = p1Num + 1
        elif label == 2:
            p2Num = p2Num + 1
        else:
            p3Num = p3Num + 1

    p0 = p0Num / float(numTraindocs)
    p1 = p1Num / float(numTraindocs)
    p2 = p2Num / float(numTraindocs)
    p3 = p3Num / float(numTraindocs)

    label0Num = np.ones(numWords)
    label1Num = np.ones(numWords)
    label2Num = np.ones(numWords)
    label3Num = np.ones(numWords)

    for i in range(numTraindocs):
        if trainLabel[i] == 0:
            label0Num += trainMat[i]
        elif trainLabel[i] == 1:
            label1Num += trainMat[i]
        elif trainLabel[i] == 2:
            label2Num += trainMat[i]
        else:
            label3Num += trainMat[i]

    p0vec = label0Num / (p0Num + 2)
    p1vec = label1Num / (p1Num + 2)
    p2vec = label2Num / (p2Num + 2)
    p3vec = label3Num / (p3Num + 2)

    pVec = np.array([p0vec, p1vec, p2vec, p3vec])
    p = np.array([p0, p1, p2, p3])

    return pVec, p
5、测试过程

根据交叉验证得到的测试集,依次将测试集中的每个样本送入训练阶段得到的朴素贝叶斯分类模型中, 得到每个样本最可能所属的类别。具体过程如下:

①针对每个测试样本,首先初始化测试样本分属每个类别的概率,存储于列表belong_classn中,其中, 列表为类别数目×1的存储结构。

②分别将训练过程中得到的训练样本分属不同类别的评论数目占训练样本总数的概率p_class_amount依 次存储到belong_classn中。

③得到当前测试样本中,特征属性值等于1的特征的索引。

④根据训练过程中得到的每个类别内的训练样本中,每个特征属性为1时的样本数目占此类别内样本的比 例p_feature_list_class0_1,p_feature_list_class1_1,…,p_feature_list_class3_1,将③中索引对应 的概率值相乘并更新对应的belong_classn。

⑤得到当前测试样本中,特征属性值等于0的特征的索引。

⑥根据训练过程中得到的每个类别内的训练样本中,每个特征属性为1时的样本数目所占此类别内样本的 比例,通过(1-③中索引对应的概率值)得到相应特征属性为0时的样本数目所占此类别内样本的比例,相乘并更新对应的belong_classn。

⑦考虑到很多概率值都很小,这导致最终求得的列表belong_classn中的值特别小,甚至可能最终小到不可比,所以在这里,我采用了取log的方式,便于之后分属各个类别的概率值之间的大小比较。

⑧找到分属各个类别概率值的最大值,作为当前测试样本的预测分类结果。

def classifyNB(vec2Classify, pVec, p):
    """
    1.函数说明:分类 比较p0、p1、p2、p3的大小 并返回相应的预测类别
    2.vec2Classify:返回的词汇表对应的词向量
    """

    p0 = sum(np.log(vec2Classify *
             pVec[0] + (1 - vec2Classify) * (1 - pVec[0]))) + np.log(p[0])
    p1 = sum(np.log(vec2Classify *
             pVec[1] + (1 - vec2Classify) * (1 - pVec[1]))) + np.log(p[1])
    p2 = sum(np.log(vec2Classify *
             pVec[2] + (1 - vec2Classify) * (1 - pVec[2]))) + np.log(p[2])
    p3 = sum(np.log(vec2Classify *
             pVec[3] + (1 - vec2Classify) * (1 - pVec[3]))) + np.log(p[3])

    pmax = max(p0, p1, p2, p3)
    if p0 == pmax:
        return 0
    elif p1 == pmax:
        return 1
    elif p2 == pmax:
        return 2
    else:
        return 3

四、实验结果与讨论

评估方式-交叉验证

要评估分类效果的好坏,对于原始数据我们要将其划分为train-data和test-data。train-data用于训练, test-data用于测试正确率(validation error)。80%train-data,20%test-data。

但是为了避免偶然性的影响,不能只做出随机一次划分, 得到一个validation error,就作为衡量这个算法好坏的标准。必须进行多次随机的划分,分别在其上面 计算出各自的validation error。这样通过某种结合方式有效利用这一组validation,就可以较好的、准确的衡量算法的好坏。

所以,在本实验中用到了交叉验证的方法,可以有效消除一次检验所带来的波动,得出比较合理的分类正确率。

交叉验证是用来验证分类器的性能的一种统计分析方法,基本思想是把在某种意义下将原始数据 (dataset)进行分组,一部分作为训练集(train set),另一部分作为验证集(validation set),首先用训练集 对分类器进行训练,再利用验证集来测试训练得到的模型(model)。

使用交叉验证方法的目的有 3 个:

①从有限的学习数据中获取尽可能多的有效信息;

②从多个方向开始学习样本, 可以有效的避免陷入局部最小值;

③无论是训练样本还是测试样本都得到了尽可能多的学习,可以在一定程度上避免过拟合问题。

在本实验中,采用的是常用的K折交叉验证。 正确率、精确率和召回率。

  • 正确率(Accuracy):正确率是指分类器正确分类的样本数与总样本数之比。正确率越高,说明分类器分类效果越好。
  • 精确率(Precision):精确率是指分类器正确分类为正类的样本数与分类器分类出的所有正类样本数之比。精确率越高,说明分类器将负样本误判为正样本的概率越小。
  • 召回率(Recall):召回率是指分类器正确分类为正类的样本数与真实正类样本总数之比。召回率越高,说明分类器将正样本误判为负样本的概率越小。

通常,我们利用正确率(accuracy)来评价分类算法。正确率确实是一个很好很直观的评价指标,但是有时候正确率高并不能代表一个算法就好。

因为在本实验中我们的数据分布不均衡,类别为0的评论很多, 而类别为1、2、3的评论很少,完全错分类别1、2、3依然可以达到很高的正确率却忽视了我们关注的东西。所以,在本实验中,采用正确率、精确率和召回率三者一同来评价分类效果。

关于正确率、精确率和召回率的概念,这里会涉及到几个模型评价术语,现在假设我们的分类目标只有两类,则会得到四种情况: 1)True positives(TP):真实为正类,预测为正类的样本数;

2)False positives(FP):真实为负类,预测为正类的样本数;

3)False negatives(FN):真实为正类,预测为负类的样本数;

4)True negatives(TN):真实为负类,预测为负类的样本数。

由此得到四者的关系如图所示:

在这里插入图片描述

正确率、精确率、召回率、F1值的计算公式分别为:

1)正确率(accuracy)

accuracy = (TP+TN)/(TP+FN+FP+TN) 即被分对的样本数除以所有的样本数 。

2)精确率(precision)

precision=TP/(TP+FP) 即被分为正例的示例中实际为正例的比例。是针对我们预测结果而言的。

3)召回率(recall)

recall=TP/(TP+FN) 即得到有多少个正例被分为正例的比例。是针对我们原来的样本而言的。

4)F1值(F-score)

F-score=precision*recall

测试结果

在这里插入图片描述

总体准确率能达到63%左右。

相关讨论

通过这次实验,让我对文本处理分类方面有了更深的理解,对朴素贝叶斯理解也更加透彻。

这是我第一次动手实际接触NLP相关的实验,最开始查资料的时候我是看的阿里云天池的《Datawhale零基础入门NLP赛事》系列文章,因为之前参加过Datawhale零基础入门的其他赛事,这系列的文章对于零基础的同学写的很详细。然后发现原来处理文本上有这么多种方法:

在这里插入图片描述

最开始我的想法是采用Bert之类很高级的方法,但是认真考虑了一下硬件以及运行时间方面,感觉很难实现。最后决定用TF-IDF和SVM进行,我猜SVM的分类效果应该会比朴素贝叶斯更好(虽然我对朴素贝叶斯最熟悉)。在这个方向上磨了很久,最后实际运行的时候发现跑了好一会儿之后显示电脑显存不够(…多么痛的领悟),我对于代码优化方面也很菜。尝试租了服务器,但是还得一个一个安装相关的库也很麻烦。

最后就决定还是换回朴素贝叶斯,有了现在这个版本的代码。虽然我对准确率不是很满意,但是能跑出结果就是好事情T T现在这个建模+测试整个跑一遍大概需要45-60min左右。单独测试的话几分钟就好。(代码运行相关详解在readme.md)

五、主要结论

通过此次实验,让我对朴素贝叶斯有了更深刻的理解,发现了朴素贝叶斯的应用范围原来有这么广。同时,我还学习到了中文的分词以及停用词的使用,使分类更加的准确,也知道了如何构建特征词向量,训练模型后导出等等。

六、参考文章

Datawhale零基础入门NLP:

1、https://tianchi.aliyun.com/notebook/118252

2、https://tianchi.aliyun.com/notebook/118253

3、https://tianchi.aliyun.com/notebook/118254

4、https://tianchi.aliyun.com/notebook/118255

5、https://tianchi.aliyun.com/notebook/118268

6、https://tianchi.aliyun.com/notebook/118258

7、https://tianchi.aliyun.com/notebook/118259

8、https://tianchi.aliyun.com/notebook/118260

停用词:

9、https://gitee.com/cyys/chinese-stop-words-list?_from=gitee_search

其它:

10、https://blog.csdn.net/ancientear/article/details/112533856

11、https://blog.csdn.net/m0_43432638/article/details/122142472

12、https://blog.csdn.net/weixin_42163563/article/details/119808700

13、https://blog.csdn.net/weixin_44016035/article/details/114953363

14、https://blog.csdn.net/qwe1110/article/details/103391632

15、https://blog.csdn.net/qq_38364053/article/details/85784648

16、Sebastiani, Fabrizio. “Machine learning in automated text categorization.” ACM computing surveys (CSUR) 34.1 (2002): 1-47.

17、Christopher D. Manning, Prabhakar Raghavan & Hinrich Schütze, Introduction to Information Retrieval

  • 1
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值