机器学习笔记(六)——朴素贝叶斯构建“饥饿站台”豆瓣短评情感分类器_nor_matrix=generate("./data/normal_less.csv","./da-程序员宅基地

技术标签: 算法  python  机器学习  

前文回顾

上一篇文章介绍了朴素贝叶斯算法的相关知识,包括以下几方面:

  • 朴素贝叶斯算法的基本原理
  • 公式推导贝叶斯准则(条件概率公式)
  • 构建训练、测试简易文本分类算法
  • 拉普拉斯平滑修正

其中公式推导这一部分较为重要,利用条件概率解决问题也是朴素贝叶斯的基本思想,所以理解贝叶斯准则如何得到,以及如何应用十分重要,也是后期构建算法的基础。

现实生活中朴素贝叶斯算法应用广泛,如文本分类,垃圾邮件的分类,信用评估,钓鱼网站检测等等;就文本分类而言,在众多分类算法,朴素贝叶斯分类算法也是学习效率和分类效果较好的分类器之一,因为朴素贝叶斯原理简单,构建算法相对容易,并且具有很好的可解释性。

但是朴素贝叶斯算法特点是假设所有特征的出现相互独立互不影响,每一特征都同等重要。但事实上这个假设在现实世界中并不成立:首先,相邻的两个词之间的必然联系,不能独立;其次,对一篇文章来说,其中的某一些代表词就确定它的主题,不需要通读整篇文章、查看所有词。所以需要采用合适的方法进行特征选择,这样朴素贝叶斯分类器才能达到更高的分类效率。

本文背景

本文利用朴素贝叶斯方法构建一个情感分类器,用于判断一个未知的语句,其所表达的是正面情绪or负面情绪,并通过比对预测结果和真实结果,得到该分类器的准确率。

最近在抖音上偶然看了一部电影的片段——饥饿站台,背景是在未来的反乌托邦国度中,囚犯们被关押在垂直堆叠的牢房里,饥肠辘辘地看着食物从上层落下,靠近顶层的人吃得饱饱的,而位于底层的人则因饥饿而变得激进,主要讲述了人性黑暗和饥渴的一面。个人认为这部电影还是不错的,所以我选择了豆瓣上这部电影的短评作为本文数据,但是比较遗憾的是爬取的数据并不多,但主要是讲述的是思想嘛。

豆瓣爬虫相对容易,所以爬虫部分不过多概述,我这里用的是requestsBeautifulSoup结合,但需要注意的是模拟登陆部分,如果不进行模拟登陆只能获取前10页的短评,而模拟登陆后可获取共24页短评。小Tip:热门短评和最新短评是不冲突的,最新短评可获取100条,这样数据样本能多一些。

最后得到的数据集共580个样本、三个属性,截图如下:
在这里插入图片描述

文本预处理

在这个构建情感分类器的小实战中,算法部分并不是很复杂,很大一部分都是上文提及过的,而更多操作是在预处理数据集。如果是公共数据源上获取的数据集,可能只需要进行简单处理,因为大部分问题数据集的作者已经解决,但是个人爬虫得到的数据集,存在的问题相对较多,我们希望的是将所有短评文本转化成以词汇组成的列表格式,下面对文本进行预处理。

在原始数据集中,rating这一列是由评分+推荐指数构成,格式不是我们需要的,所以这里利用一个自定义函数,将其划分成1-5五个等级,我们可以将评分等级视为其对应短评的情感分类。

#将评分划分成1-5五个等级
def rating(e):
   if '50' in e:
       return 5
   elif '40' in e:
       return 4
   elif '30' in e:
       return 3
   elif '20' in e:
       return 2
   elif '10' in e:
       return 1
   else:
       return 'none'
# 利用map方法依据rating函数创建新一列
data['new_rating'] = data['rating'].map(rating)

划分等级之后,我们需要对每一条短评情感标注,这里选择删去评分等级为3的短评,原因是无法确定其短评情绪的类别。然后将评分等级为4、5的短评用1标注,视为正面情绪;将评分等级为1、2的短评用0标注,视为负面情绪。

# 删去评分为3的短评,判定评分为3的情感持中性
data = data[data['new_rating'] != 3]
#将4、5评分标注成1,视为正面情绪;将1、2评分标注成0,视为负面情绪
data['sentiment'] = data['new_rating'].apply(lambda x: +1 if x > 3 else 0)

下图为五个评分等级的占比饼图,可以看出3分的占比是比较大的,所以删去评分为3的操作让数据集损失很多数据样本;4分、5分的占比要远多于1分、2分,所以数据集中正面情绪占比极大,电影可能是真不错,但是也凸显出了数据比例不均衡的问题。

在这里插入图片描述
爬虫获取的短评可能包含很多英文符号、单词、字母,这些对于中文情感分析是没有任何帮助的,所以在分词之前,利用两个自定义函数删去短评中的符号和英文字母,这里没有对数字操作是因为下文停用词中包含了删去数字的操作,jieba分词模式选择默认的精准模式,精准模式可以将句子精确地切开,比较适合文本分析。

# 删去短评中的符号、英文字母
punc = '~`!#$%^&*()_+-=|\';":/.,?><~·!@#¥%……&*()——+-=“:’;、。,?》《{}'
def remove_fuhao(e):
    return re.sub(r"[%s]+" % punc, " ", e)
def remove_letter(new_short):
    return re.sub(r'[a-zA-Z]+', '', new_short)
# 利用jieba切割文本
def cut_word(text):
    text = jieba.cut(str(text))
    return ' '.join(text)
# 同apply方法依据以上三个自定义函数为依据创建新一列
data['new_short'] = data['short'].apply(remove_fuhao).apply(remove_letter).apply(cut_word)

短评切分后一定会产生许多无关情感的词汇,例如一个、这个、人们等等,所以停用词函数的作用就是将此类词汇从短评中过滤掉。该函数主要思想是将短评按空格切分成词汇,然后遍历这个词汇列表,如果一个词汇未出现在停用词表中、词汇长度大于1、词汇不为Tab,则将连接至字符串outstr中;如果某个词汇已经存在于outstr,则不再添加,达到去重的效果。

文末提供中文停用词表获取方式

# 读取停用词表函数
def stopwordslist(filepath):
    stopwords = [line.strip() for line in open(filepath, 'r', encoding='utf-8').readlines()]
    return stopwords
# 将短评中的停用词删去
def sentence_div(text):
    # 将短评按空格划分成单词并形成列表
    sentence = text.strip().split()
    # 加载停用词的路径
    stopwords = stopwordslist(r'中文停用词表.txt')
    #创建一个空字符串
    outstr = ' '
    # 遍历短评列表中每个单词
    for word in sentence:
        if word not in stopwords: # 判断词汇是否在停用词表里
            if len(word) > 1:  # 单词长度要大于1
                if word != '\t':  # 单词不能为tab
                    if word not in outstr:  # 去重:如果单词在outstr中则不加入
                        outstr += ' '  # 分割
                        outstr += word # 将词汇加入outstr
    #返回字符串
    return outstr
data['the_short'] = data['new_short'].apply(sentence_div)

可能有一条短评说的很多都是是废话,恰巧都被停用词函数过滤了,剩下的词汇较少对这条短评的情感分析帮助很小,所以这里将词汇数量少于4个的短评删去;由于上面依据自定义函数创建了许多新的属性,内容过于冗杂,所以选出情感分析需要的两列(处理后的短评和情感标注)合并成一个新的DataFrame

data['split'] = data['the_short'].apply(lambda x: 1 if len(x.split()) > 3 else 0)
data = data[~data['split'].isin(['0'])]
# 将需要的两列数据索引出,合并成一个新的DataFrame
new_data1 = data.iloc[:, 3]
new_data2 = data.iloc[:, 5]
new_data = pd.DataFrame({
    'short': new_data2, 'sentiment': new_data1})

经过预处理的数据集只剩下了280个样本,截图如下:
在这里插入图片描述
上文提及过一个问题,短评正面情绪所占比例要远大于负面情绪,为了避免测试数据集中的样本全为正面情绪,所以这里采用随机选择的方式划分数据集。利用random库中的sample方法随机选择**10%**的数据的索引作为测试数据集的索引,剩下的部分作为训练数据集的索引;然后按照两类索引将数据集切割成两部分,并分别保存。

def splitDataSet(new_data):
    # 获取数据集中随机的10%作为测试集,获取测试数据集的索引
    test_index = random.sample(new_data.index.tolist(), int(len(new_data.index.tolist()) * 0.10))
    # 剩下的部分作为训练集,获取训练数据集的索引
    train_index = [i for i in new_data.index.tolist() if i not in test_index]
    #分别索引出训练集和测试集
    test_data = new_data.iloc[test_index]
    train_data = new_data.iloc[train_index]
    # 分别保存为csv文件
    train_data.to_csv('bayes_train.csv', encoding='utf_8_sig', index=False)
    test_data.to_csv('bayes_test.csv', encoding='utf_8_sig', index=False)

构建分类器

构建分类器部分与上一篇文章的代码会冲突,所以下面的算法部分不会过多讲述其原理;如果你刚接触朴素贝叶斯或者想了解其原理,推荐先观看上一篇文章:机器学习笔记(五)——轻松看透朴素贝叶斯;如果你对朴素贝叶斯原理已经足够理解了,若只对源码和数据感兴趣可以直接跳过此部分划到文末哟。

构建词向量

loadDataSet函数的作用是将短评转化成所需要的词条向量格式,即每一条短评的词汇构成一个列表,再将所有列表添加至一个列表中,构成一个词条集合,classVec是由短评对应的情感标注构成的列表。

def loadDataSet(filename):
    data = pd.read_csv(filename)
    postingList = []
    #文本语句切分
    for sentence in data['short']:
        word = sentence.strip().split()# split方法返回一个列表
        postingList.append(word)#  将每个词汇列表添至一个列表中
    #类别标签的向量
    classVec = data['sentiment'].values.tolist()
    return postingList,classVec

createVocabList函数的作用是通过set方法已经取并集的方式返回一个包含文本中所有出现的不重复词的集合。

#创建词汇表
def createVocabList(dataSet):
    #创建一个空的不重复列表
    vocabSet = set([])
    for document in dataSet:
        #取两者并集
        vocabSet = vocabSet | set(document)
    return list(vocabSet)

setOfWords2Vec函数的作用是将短评向量化,输入参数为总词汇表和某个短评,输出的是文本向量,向量的元素包括1或0,分别表示词汇表中的单词是否出现在输入的文本中,思路是首先创建一个同词汇表等长的向量,并将其元素都设置为0,然后遍历输入文本的单词,若词汇表中出现了本文的单词,则将其对应位置上的0置换为1。

#词条向量化函数
def setOfWords2Vec(vocabList, inputSet):
    #创建一个元素都为0的向量
    returnVec = [0] * len(vocabList)
    for word in inputSet:
        if word in vocabList:
            #若词汇表包含该词汇,则将该位置的0变为1
            returnVec[vocabList.index(word)] = 1
    return returnVec

getMat函数的作用是将所有处理后的词条向量汇总合并成一个词条向量矩阵,方便测试算法时调用。

#词条向量汇总
def getMat(inputSet):
    trainMat = []
    vocabList = createVocabList(inputSet)
    for Set in inputSet:
        returnVec = setOfWords2Vec(vocabList,Set)
        trainMat.append(returnVec)
    return trainMat

训练算法

trainNB函数的输入参数包括短评矩阵trainMatrix和每个词条的情感标注所构成的向量trainCategory。首先短评属于正面情绪的概率只需要将正面情绪短评的个数除以总词条个数即可;计算P(W | C1)和P(W | C0)时,需要将其分子和分母初始化,遍历输入文本时,一旦某个词语(正面情绪or负面情绪)在某一文档中出现,则该词对应的个数(p1Num或p0Num)就加1,并且在总文本中,该词条的总次数也相应加1。

def trainNB(trainMatrix,trainCategory):
    #训练文本数量
    numTrainDocs = len(trainMatrix)
    #每篇文本的词条数
    numWords = len(trainMatrix[0])
    #文档属于正面情绪(1)的概率
    pAbusive = sum(trainCategory)/float(numTrainDocs)
    #创建两个长度为numWords的零数组
    p0Num = np.ones(numWords)
    p1Num = np.ones(numWords)
    #分母初始化
    p0Denom = 2.0
    p1Denom = 2.0
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            #统计正面情绪的条件概率所需的数据,即P(w0|1),P(w1|1),P(w2|1)···
            p1Num += trainMatrix[i]
            #print(p1Num)
            p1Denom += sum(trainMatrix[i])
            #print(p1Denom)
        else:
            #统计负面情绪的条件概率所需的数据,即P(w0|0),P(w1|0),P(w2|0)···
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    #计算词条出现的概率
    p1Vect = np.log(p1Num/p1Denom)
    p0Vect = np.log(p0Num/p0Denom)
    #print("\n",p0Vect,"\n\n",p1Vect,"\n\n",pAbusive)
    return p1Vect,p0Vect,pAbusive

测试算法

classifyNB函数是一个判断类别的函数,输入参数为向量格式的测试数据和训练函数trainNB的三个返回值,如p1的概率大于p0的概率则代表该测试数据为正面情绪,返回值为1;反之则是负面情绪,返回值为0。

def classifyNB(ClassifyVec, p1V,p0V,pAb):
    #将对应元素相乘
    print(pAb)
    p1 = sum(ClassifyVec * p1V) + np.log(pAb)
    p0 = sum(ClassifyVec * p0V) + np.log(1.0 - pAb)
    print('p1:',p1)
    print('p0:',p0)
    if p1 > p0:
        return 1
    else:
        return 0

testNB为测试函数,通过调用上述函数对测试集进行预测,并通过比较真实结果和测试结果以得到分类器的准确率。

def testNB():
    #加载训练集数据
    train_postingList,train_classVec = loadDataSet('bayes_train4.csv')
    #创建词汇表
    vocabSet = createVocabList(train_postingList)
    #将训练样本词条向量汇总
    trainMat = getMat(train_postingList)
    #训练算法
    p1V,P0V,PAb = trainNB(trainMat,train_classVec)
    #加载测试集数据
    test_postingList,test_classVec = loadDataSet('bayes_test4.csv')
    # 将测试文本向量化
    predict = []
    for each_test in test_postingList:
        testVec = setOfWords2Vec(vocabSet,each_test)
        #判断类别
        if classifyNB(testVec,p1V,P0V,PAb):
            print(each_test,"正面情绪")
            predict.append(1)
        else:
            print(each_test,"负面情绪")
            predict.append(0)
    corr = 0.0
    for i in range(len(predict)):
        if predict[i] == test_classVec[i]:
            corr += 1
    print("朴素贝叶斯分类器准确率为:" + str(round((corr/len(predict)*100),2)) + "%")

最后程序运行截图如下:
在这里插入图片描述
因为我们是利用随机选择的方法划分训练集与测试集,所以每次运行程序,朴素贝叶斯分类器的准确率都会改变,可以多运行几次取其平均值作为该分类器的准确率。最后附上依据该数据集绘制的词云图,不知道这部电影的体裁能不能引起你的兴趣的呢?
在这里插入图片描述

总结

在利用朴素贝叶斯算法进行类似的情感分析或者文本分类时,尽可能要保持原始数据充足,像上文580条原始数据经过文本预处理之后只剩下280条。只有数据充足,模型才能有具有实用性,数据太少会导致模型的准确率浮动较大,并且也具有极高的偶然性。

关注公众号【喵说Python】后台回复“饥饿站台”可获取源码和数据供参考,感谢阅读。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_43434202/article/details/105196393

智能推荐

使用HDFS API实现hadoop HDFS文件系统的基本操作_利用hdfs文件系统开放的api对hdfs系统进行文件的创建和读写-程序员宅基地

文章浏览阅读1.3k次。下面介绍使用hadoop的HDFS分布式文件系统的java API实现基本的文件操作,比如:创建文件、修给文件、创建目录或者文件夹、从本地系统上传文件到HDFS系统中、从HDFS文件系统中下载文件到本地系统。集成开发环境IDE工具:eclispe(已经安装了hadoop插件)说明:下面main函数中的路径:/user/liangyihuai 以及/usr/是在HDFS上面的路_利用hdfs文件系统开放的api对hdfs系统进行文件的创建和读写

php编译时pcre的作用,linux安装swoole扩展 pcre.h导致编译make failed问题-程序员宅基地

文章浏览阅读372次。1.首先我们要安装swoole扩展的话,需要把它的包下载下来,下载地址是:2.下载下来之后进行解压:unzip swoole-src-master.zip3.解压之后打开解压的目录,我是解压在目录/opt下面的,所以cd /Download/swoole-src-master4.然后使用phpize重新编译php,执行命令:/usr/bin/phpize如果你找不到phpize文件在哪,可以用指令..._编译php pcre出错

java order()_Java中的OrderByDecending(LINQ)等效项 - java-程序员宅基地

文章浏览阅读241次。嗨,我是一名使用Java的C#开发人员。问题很简单:我如何才能将下面的c#代码写入Java并仍能正常工作:myCoffeeList.OrderByDescending(x => x.Name?.ToLower()?.Trim() == sender.Text.ToLower()?.Trim()));我的sender.Text基本上是一个文本框。文本的值类似于Cappuccino。上面的代码做..._java中list.orderbydescending

VMware Workstation Pro 虚拟机与本机文件共享的三种方式_vmware怎么把本地文件共享到虚拟机-程序员宅基地

文章浏览阅读3.9k次,点赞2次,收藏9次。VMware Workstation Pro 虚拟机与本机文件共享的三种方式一、准备工作1.一台安装完成VMware Workstation Pro 虚拟机的电脑(注:未安装VMware Workstation Pro 虚拟机的可以看我之前发的进行安装)2.确保本机有网络连接二、实时共享第一种方式:电脑命令符方式1.我们 win+r 键调出电脑的命令符窗口,然后输入 mstsc 点击确认(图-1)图-12.进入窗口,然后查看我们虚拟机的网络IP地址,输入窗口(图-2、图-_vmware怎么把本地文件共享到虚拟机

android RecyclerView (二) ItemDecoration 详解-程序员宅基地

文章浏览阅读166次。RecyclerView 已经推出了一年多了,日常开发中也已经彻底从 ListView 迁移到了 RecyclerView,但前两天有人在一个安卓群里面问了个关于最顶上的 item view 加蒙层的问题,被人用 ItemDecoration 完美解决。此时我发现自己对 RecyclerView 的使用一直太过基本,更深入更强大的功能完全没有涉及,像 I..._android recycler itemde

js词法作用域、函数作用域和块级作用域以及闭包_js函数作用域和块作用域区别-程序员宅基地

文章浏览阅读642次。1.函数作用域链:函数内部变量可以访问函数外部变量,但是函数外部变量不可以访问函数内部变量。2.通过闭包使得外部变量访问函数变量,<script> function f1(){ var n = 123; return function () { return n; } }var a = f1()();console.log(a);// 123</script>_js函数作用域和块作用域区别

随便推点

Apache Thrift系列详解(二) - 网络服务模型_tserver apache-程序员宅基地

文章浏览阅读982次。前言Thrift提供的网络服务模型:单线程、多线程、事件驱动,从另一个角度划分为:阻塞服务模型、非阻塞服务模型。阻塞服务模型:TSimpleServer、TThreadPoolServer。非阻塞服务模型:TNonblockingServer、THsHaServer和TThreadedSelectorServer。TServer类的层次关系:正文TServerTSe..._tserver apache

黑马程序员--String等-程序员宅基地

文章浏览阅读50次。-------------------android培训、java培训、期待与您交流!------------------主要内容:String、Buffer、Builder,基本数据类型对象包装类及新特性-----------------------------------------------------------------------------------------------...

你的编程能力从什么时候开始突飞猛进?_编程能力 突飞猛进-程序员宅基地

文章浏览阅读5.9k次,点赞13次,收藏16次。1、学习一门新的编程语言的时候我的第一份工作是 Android 攻城师,做了有两年左右,发现自己平时的工作几乎都是在设计页面布局,这不是我理想的工作(我的理想工作是不上班 [/ 坏笑])。因为经常和 Java 后端人员接触,感觉他们工作挺炫酷的,敲几行代码,在浏览器输入一个地址,就能打开一个自己设计的网页,简直太牛 P 了。因此决定向 Java 后端发展。之后又有爬取数据的需求,有利用业余时间学..._编程能力 突飞猛进

数据中心如何选址-程序员宅基地

文章浏览阅读260次。拥有一个有效运作的数据中心,对于当今的企业来说是至关重要的,因此要记住,为了获得高可靠性,数据中心选址很重要的第一个步骤。集中的数据中心可以让企业更好运营业务,同时也降低了成本,如果这些数据中心可靠性高,就可以满足其业务的需求。可用性是数据中心设计的三大重点之一,另外两个重点是可扩展性和灵活性。而数据中心的故障将会影响整个企业的业务运营,因此确保企业的数..._住房选址数据库建立

常用docker指令_docker pru-程序员宅基地

文章浏览阅读148次。清理镜像与容器docker container prune //删除所有停止运行的容器docker image prune -a //删除所有未被容器使用的镜像使用镜像启动容器docker run --name image_name -d -p 6379:6379 container_name //镜像启动命令image_name:镜像名container_name:启动之后的容器..._docker pru

Qt项目在Qt Creator下编译运行正常,但是直接执行应用程序,内建的动态库找不到...-程序员宅基地

文章浏览阅读1.5k次。为什么80%的码农都做不了架构师?>>> ..._qt creator调用第三方库时提示找不到文件