说明:这是一个机器学习实战项目(附带数据+代码+文档+代码讲解),如需数据+代码+文档+代码讲解可以直接到文章最后获取。
1.需求分析
在新浪微博上有很多关于新冠疫情的微博消息以及评论信息,那么这些文本数据究竟是一个怎样的观点,人们对这次疫情持什么样的态度:积极、消极还是中性。接下来通过建立贝叶斯分类模型来进行微博评论数据的情感分析。
2.数据采集
本数据是从微博上抓取的数据:
数据集:data.xlsx
在实际应用中,根据自己的数据进行替换即可。
特征数据:微博正文
标签数据:分类(积极 消极客观)
3.数据预处理
1)原始数据描述:
2)数据去重与空值处理:
去重前:
去重后:
4.数据分析过程
4.1 分词处理
微博作为一个社交平台,不仅拥有能够迅速的传播特点,并且成为了商家用来推广产品的重点发布平台。在微博中,大量广告、营销类账号的出现,给微博的情感分析造成很大的困难。所以,针对微博的文本预处理变得极其重要。
4.1.1 分词
中文的分词是将一个汉字序列变成一个个单独的有意义的词汇。文本挖掘首先要以中文分词为前提。目前常用的中文分词软件主要有以下几种:
(1)BosonNLP:玻森的中文语义开放平台,主要提供了一个方便、性能强大的中文自然语言分析的平台。
(2)NLPIR:是中国科学院计算机研究所的一个产品,积累了多年研究工作,暂时是目前世界上最好的中文分词工具。
(3)结巴分词:一款开源在GitHub上面的中文分词工具,提供了python、java等多语言的接口,而且能够识别繁体字,立志成为最好的中文分词工具。
(4)IKAnalyzer:一款开源的java分词工具,最初是以lucene为应用主体的,之后结合了词典分词和语法分析算法的分词组件。
本文中将采用中科院的NLPIR和开源的JIEBA进行分词,其中NLPIR的分类例子如下:
不让我上去疫情高风险地区我还不会自己吗![doge][doge][doge](投稿:@还没怀上的葛一他麻麻)http://t.cn/RqKTebK
如果不降噪进行分词:
['(', '不', '@还没怀上的葛一他麻麻', '投稿', '抢', '!', '会', '自己', '还', 'RqKTebK', '疫情', '上', '高风险',’高风险’,’地区’, '[doge]', '让', ':','http://t.cn/', ')', '吗', '我']
4.1.2 删除URL
垃圾微博中的内容一般较短,而且一般文字后面都带有链接,由此才能将用户导向网页的入口,如下面几条:
【领 10 元 优惠 券】【券 后价 19.9元】【包邮】 粉丝福利购:http://t.cn/R6j6YyX "
"神奇 口罩 =26.8 领券拍:http://t.cn/R6lLnsV http://t.cn/R6n9kRO "
"买口罩?找优惠?上莫莉口罩!http://t.cn/RxmHa1i "
"医用口罩 =49 领券拍:http://t.cn/R6nIheq http://t.cn/R6nI2Fp "
"防病毒 口罩 =58 领券拍:http://t.cn/R6CjMuM http://t.cn/R6QsX8f "
由上述内容可以知道,一般情况下的普通URL链接都是较长的字符串,如果保持原链接会占用微博的资源。因此,微博希望能够将原本的“长链接”变成缩短的短链接。微博中使用散列(hash)索引,将原始链接对应成一个较短的、一一对应的字幕、数字串组合。如:
http://t.cn/R6QsX8f
其原链接为:
http://shop.sc.weibo.com/h5/goods/index?iid=110054105972300003317973
URL中带有有用的信息很少,一般都是广告的导向和用户的定位。我们使用SQL从数据库中查找含有URL的微博数量统计。
统计结果
由此可见,URL在微博中的引用量是0.2%,量不是很大,在情感分析前,要对微博文本进行适当的清理,从而去除这些无用的URL,降低这些URL对情感分析的影响。
4.1.3 删除用户名
微博文本中的用户名一般用来提醒该用户,但是,大部分微博用户的用户名毫无规律性,如:@real__pcyyyyy、@CloverH静、@baekhyunee7永远像25岁一样年轻等,对分词器来说有较大的影响,比如JIEBA分词会把“@baekhyunee7永远像25岁一样年轻”拆分成['@', 'baekhyunee7', '永远', '像', '25', '岁', '一样', '年轻'],其中“年轻”会对用来构建的词性特征造成影响,所以,对于用户名的出去也是极其重要的。
4.1.4 去除停用词
停用词(STOP WORDS),在词典中的意思为:对文本中表达的意义并不起什么作用的词语。在SEO中,为了节省存储空间和提高检索速度,搜索引擎会在搜索时自动忽略某些字或词,这些字或词便是停用词。
停用词在一定程度上相当于过滤词,但是过滤词的范围比较大,通常包含色情、政治、暴力等敏感信息,停用词则没有这个限制。通常情况下,停用词可分为两类:
(1)使用广泛,过于频繁的一些单词。比如“我”、“你”之类的词几乎在很多文档中都会出现,对于搜索引擎来说,这类词无法保证准确的搜索结果,还会降低效率。
(2)文档中使用频率很高,但是实际意义不大的词。主要包括语气助词、副词、介词、连词等,在文本表达中没有变现出任何意义。为了增加情感分析的准确性,我们需要对微博文本中去除停用词。
分词结果展示(部分):
4.2 词频统计与词云图
主要是通过JIEBA工具分词后,通过循环进行词频统计,为了更方便的展示词频的效果,方便进行查看,制作成词云图。
词频统计结果展示(部分):
词云图:
5利用贝叶斯定理情感分析
贝叶斯方法作为一个历史悠久,有着坚实的理论基础的机器学习方法,不仅能够在同时处理很多问题时直接而又高效,而且很多高级自然语言处理模型也能够从它演化而来。贝叶斯方法,是研究自然语言处理问题的一个极其优秀的切入口。其流程主要如下图4.1。
朴素贝叶斯情感分析基本流程
准备工作阶段:此阶段主要是对文本进行预处理,先对样本进行标注,之后根据词频筛选部分的特征词。该阶段输入的事所有待分类的样本,然后得出特征属性和训练样本。朴素贝叶斯的分类器的准确性主要由筛选出来的特征属性来决定。
分类器训练阶段:根据样本中的频率,然后由每个特征计算出每个类别的先验概率。此阶段主要是根据公式的机械计算。此阶段是朴素贝叶斯分类最重要的一个环节。
应用阶段:该阶段主要将测试样本进行输入,然后由分类器来计算出分类的记过。
5.1 贝叶斯定理
贝叶斯公式就一行:
而它其实是由以下的联合概率公式推导出来:
其中P(Y)为先验概率,P(Y/X)叫做后验概率,P(Y,X)一般称作联合概率。
算法描述:
标准的朴素贝叶斯分类算法的执行过程。
朴素贝叶斯文本分类流程
本文中:将不同的经过数据预处理(去掉无关类的微博)后,将剩下的微博分为三类:POS(积极)、NEG(消极)、Neural(客观)。
(1)一般情况下,积极即带有取向上、努力等思想和表现,如:全民防控 众志成城tyy生日快乐[噢耶][噢耶][噢耶]
(2)而消极则代表了负面的,不思进取的,失落等情感,如:对面疫情,有时候觉得人活着挺难的,不想说话[闭嘴]
(3)而客观的就表示仅仅陈述,不含有情感色彩的,如:疫情期间,一百本可以充实自己的外国图书,书荒的Mark
对于文本分类,常用的朴素贝叶斯主要存在三种不同的贝叶斯模型:高斯模型、多变量的伯努利模型和多项式模型
(1)高斯模型——它假设特征是正态分布的。它的一般使用场景是,给出人物的宽度和高度,判断这个人的性别。而情感分析,从给定推文文本中提取出词语的个数,不适合正态分布。
(2)伯努利模型——和高斯模型相似,更适于判断词语是否出现二值特征,而不是词频统计。
(3)多项式模型——它假设特征就是出现次数。这和我们是相关的,因为我们会把推文中的词频当做特征。
5.1.1 高斯朴素贝叶斯
某些特征很可能是连续型变量,比如说物体的长度和人的身高,这些特征可以转换成离散型的值。比如我们将人的身高进行划分,如表1。同时,我们也可以将身高用三个特征表示f1,f2,f3,结果如表2所示。
表1身高特征划分方法一
表2身高特征划分方法二
不过这些方式都不够细腻,高斯模型可以解决此类问题。高斯模型假设这些一个特征的所有属于某个类别的观测值符合高斯分布。
5.1.2 伯努利贝叶斯
伯努利贝叶斯(Bernoulli Naïve Bayes,BNB)是最早基于朴素贝叶斯模型对文本进行分类的算法。模型中,一篇文本会被表示成欧式空间中的一个二进制变量,即,如果文本中在指示变量中出现的话,则将改值标为1,否则为0.给定测试文本d=<w1,w2···wm>,如果属性条件独立,则BNB根据下面的公式选出最大的后验概率来对文本进行分类:
从之前别人的研究可以表明伯努利贝叶斯在数据量比较少的时候性能较好,但是,当数据量大的时候,性能远远比不上多项式模型。
5.1.3 多项式朴素贝叶斯定理
多项式朴素贝叶斯(Multinomial Naïve Bayes,MNB)是为了改进多变量伯努利朴素贝叶斯而提出的。该模型中,文本的表示是欧式空间中的一个带有单词频率信息的向量,当一个词语在一篇文章中出现时,该文本的对应的词语频率为1,否则为0。
在条件独立的情况下,给定测试文档d=<w1,w2···wm>,多项式朴素贝叶斯根据下面的极大后验概率来对文本进行分类:
5.2 本项目中的多项式朴素贝叶斯
5.2.1 算法过程
训练集TrainingSet={(x1,y1), (x2,y2)…(xn,yn)}包含N条训练数据,其中xi=(x1i,x2i,x3i,......xni), T是M维向量,yi={c1,c2,c3,......ck}属于K类中的一类。
首先,我们来计算先验概率p(y=ck)
其中I(x)为指示函数,若括号内成立,则计1,否则为0。
接下来计算分子中的条件概率,设M维特征的第j维有L个取值,则某维特征的某个取值aj1,在给定某分类ck下的条件概率为:
通过学到的概率,给定未分类新实例X,就可以通过上述概率进行计算,得到该实例属于各类的后验概率p(y=ck|X),因为对所有的类来说,分母的值都相同,所以只计算分子部分即可,具体步骤如下:
计算该实例属于y=ck类的概率。
确定该实例所属的分类y。
其中,最大的便是我们的分类。
5.2.2 拉普拉斯平滑
在计算先验概率和后验概率的时候,从样本中算出的概率值很有为0,会导致相乘的结果为0,这显然是不合理的,因为不能因为一个事件没有检测到就判断改事件的概率为0。我们可以通过分子和分母都分别加入一个平滑因子aa,就可以避免这个问题。更新过后的先验概率公式变为:
K表示类别,公式变为如下:
Lj是第j维特征的最大取值
可以证明,改进以后的仍然是概率。平滑因子kk=0即为实现的最大似然估计,这时会出现在本节开始时提到的0概率问题;而kk=1则避免了0概率问题,这种方法被称为拉普拉斯平滑。
拉普拉斯平滑曲线
5.3 实现过程
5.3.1 分词
结巴分词,是Github中开源的中文分词组件,该分词器能够支持三种分词模式:
(1) 精确模式:能够将句子用最精确的方法切开,普遍适用于文本分析;
(2) 全模式:能够把文本中所有成词的词语都扫描出来,虽然速度非常快,但是不能解决歧义。
(3) 搜索引擎模式:在精确模式的基础上,会对长词再次切分,提高召回率,一般适用于搜索引擎分词。
本实验是基于标注的情感分析,故使用结巴分词的精确模式来进行分词。
分词的结果:
5.3.2 特征提取
跟第所述的一样,使用Python统计筛选出频率最高的词。得出的结果使用词云如图所示:
词云
5.3.3 向量化
假如上述中的特征提取中构建出来的单词特征为[‘喜欢’,’失望’,’快乐’,’越来越好’,’晚安’],长度为m,矢量化的时候如果一条微博为:生日快乐,晚安。那么,构建出来的矩阵为:[0,0,1,0,1]。
说明:
1.如果为n条微博,则构建出来则是n*m的矩阵。
2.如果一条微博的某个特征出现次数多于一次,则进行累加,如,快乐快乐,矢量化之后变成:[0,0,2,0,0,0]。
5.3.4 朴素贝叶斯分类
本文中将微博的情感分为三类,分别用数字代表某一类结果,其中1表示积极,-1表示消极,0表示客观。经过之前的去标签、分词和向量化之后,样本均变成了numpy中的数组,下面将使用多项式朴素贝叶斯进行训练。其伪代码如下图所示。
朴素贝叶斯
5.3.5 测试及计算
之后使用从数据源中选取几个句子用来测试,结果如下(后面的标号表示分类结果,其中1表示积极,-1表示消极,0示客观):
疫情当前,不惊扰别人的宁静,就是慈悲;不伤害别人的自尊,就是善良。 ---- 0
疫情在家里面,与大哥太有缘分!竟是同天生日!!希望大哥的模特演绎之路能够越走越远越走越顺越走越好!!! ---- 1
全民抗疫,萌萌哒 ---- 1
在疫情中,现在的年轻人,连点小事都做不好 ---- -1
新冠病毒来袭,丁丁美人飞机临时取消,只能明天再见了[失望] ---- -1
疫情中,默默看书 ---- 0
今年又遇到了疫情,一天又一天,今夕是何年[失望] ---- -1
疫情来了,还好这只傻狗没有被学校的捕狗大队抓走[悲伤] ---- -1
全民抗击病毒,相比游戏可以凭运气 ---- 0
面对疫情病毒,生活中却全是坑,绕不开[哈哈] ---- 1
哎,疫情又要在家,现实比不过一个网?[拜拜] -----1
众志成城,抗击疫情,最最亲爱的自己,最最亲爱的世界,晚安[兔子][月亮] ---- 0
面对病毒,有点想哭 ---- -1
疫情在家,发现只要过了星期三就变得好快[晕][晕] ---- -1
疫情在家,虽然下雨,健身不能停,瑜伽课加游泳八百米,代餐奶昔不裹腹啊,快睡吧,睡着了就不饿了! http://t.cn/R2Wx9Wb ---- 0
6情感倾向饼状图
针对,贝叶斯分析的结果,制作饼状图,如下图所示:
通过饼状图分析,情感倾向大部分人是客观状态的,表名人们看到这次全民抗疫还是比较客观的;再者就是积极状态的人,说明在这次抗击疫情时,人民还是比较积极的;消极的人很少。
说明:进入作者个人主页,可以查看更多文章内容,点击作者头像,可进入作者个人主页:
项目代码:
with open(path, encoding="utf-8") as fp:
for line in fp:
for word in jieba.cut(line.strip()):
p = re.compile(r'\W')
result = p.sub("", word)
if not result or result == ' ': # 空字符
kw_list = sorted(d, key=lambda x: d[x], reverse=True)
size = int(len(kw_list) * 0.2)
mood = set(kw_list[:size])
return list(mood - set(stop))
# 项目资源如下:
# https://docs.qq.com/sheet/DTVd0Y2NNQUlWcmd6?tab=BB08J2
def setOfWordsListToVecTor(vocabularyList, train_mood_array): # 将所有微博准备向量化
for i in range(len(train_mood_array)):
vocabMarked = setOfWordsToVecTor(vocabularyList, train_mood_array[i])
vocabMarkedList.append(vocabMarked)
return vocabMarkedList