运维工具 – 大众点评评论文本挖掘基础案例实践

释放双眼,带上耳机,听听看~!

一、爬虫

整体思路

爬取大众点评十大热门糖水店的评论,爬取网页后从html页面中把需要的字段信息(顾客id、评论时间、评分、评论内容、口味、环境、服务、店铺ID)提取出来并存储到MYSQL数据库中。

https://github.com/wangkaikai07/dianping_textmining (实践可正常运行与学习)

https://github.com/datawhalechina/fun-rec

网页爬取和解析

链接格式为”http://www.dianping.com/shop/” + shopID + “/review_all/” + pi,如:http://www.dianping.com/shop/518986/review_all/p1 ,一页评论有20条。我们使用for循环构造链接URL,使用requests库发起请求并把html页面爬取下来,通过BeautifulSoup和re库解析页面提取信息。

我们发现完整的评论都存储在’div’,’main-review’中,且部分页面口味、环境、服务并不是每一页都有,因此需要使用try…except…防止程序中断,BeautifulSoup部分代码如下:

for item in soup('div','main-review'):
    cus_id = item.find('a','name').text.strip()
    comment_time = item.find('span','time').text.strip()
    comment_star = item.find('span',re.compile('sml-rank-stars')).get('class')[1]
    cus_comment = item.find('div',"review-words").text.strip()
    scores = str(item.find('span','score'))
    try:
        kouwei = re.findall(r'口味:([\u4e00-\u9fa5]*)',scores)[0]
        huanjing = re.findall(r'环境:([\u4e00-\u9fa5]*)',scores)[0]
        fuwu = re.findall(r'服务:([\u4e00-\u9fa5]*)',scores)[0]
        except:
            kouwei = huanjing = fuwu = '无'

数据存储

我们使用MYSQL数据库,安装教程参考菜鸟教程,python连接MYSQL数据推荐使用pymysql,同样是推荐菜鸟教程菜鸟教程。我们需要先建立一个数据库和表,然后连接并定义游标,然后写对应的sql语句,最后执行事务,存储部分的代码如下:

#连接MYSQL数据库
db = pymysql.connect("localhost","root","","TESTDB" )
cursor = db.cursor()
#存储爬取到的数据
def save_data(data_dict):
    sql = '''INSERT INTO DZDP(cus_id, comment_time, comment_star, cus_comment, kouwei, huanjing,           fuwu, shopID) VALUES(%s,%s,%s,%s,%s,%s,%s,%s)'''
    value_tup = (data_dict['cus_id']
                 ,data_dict['comment_time']
                 ,data_dict['comment_star']
                 ,data_dict['cus_comment']
                 ,data_dict['kouwei']
                 ,data_dict['huanjing']
                 ,data_dict['fuwu']
                 ,data_dict['shopID']
                 )
    try:
        cursor.execute(sql,value_tup)
        db.commit()
    except:
        print('数据库写入失败')
    return

反爬虫对抗

  1. 修改请求头中浏览器信息:使用fake_useragent第三方库,修改request中的headers参数,用法如下:from fake_useragent import UserAgent ua = UserAgent() headers = {‘User-Agent’:ua.random}
  2. 设置跳转路径:在访问评论时,一般的浏览行为是从某一页跳转到下一页这样的,而不是直接通过连接访问,为了更好的伪装成一个正常的访问,我们需要设置一下跳转的路径,修改headers中的Referer参数headers = { ‘User-Agent’:ua.random, ‘Cookie’:cookie, ‘Referer’: ‘http://www.dianping.com/shop/518986/review_all’ }
  3. 设置Cookies:评论数据需要登录后才能获取,下面介绍一种非常简单方便的绕过登录的方法。
    • 在网页上进行登录
    • 使用Chrome浏览器的开发者工具,查询当前请求的cookie
    • 复制浏览器中的cookie,使用此cookie对我们的请求进行伪装
  4. 使用IP代理池:这里使用西刺代理的免费代理,构建一个爬虫爬取西刺代理的ip,然后进行验证,筛掉不可用的ip,构建出ip池供后续调用,代码来自网络。但是经过测试,大众点评对一个账号不同ip访问监控非常严格,使用IP代理池不更换账号的话,死的更快,封你账号,然而构建账号池比较麻烦,我们先暂缓。
  5. 降低爬取频率:一个简单又有效的方法就是降低爬取频率,毕竟高频率的爬取对服务器也是一个考验,如果对速度的要求不是很高的话,建议把频率放慢一点,你好我好大家好!import random import time time.sleep(6*random.random() + 4)
  6. 设置断点续传:即使降低了爬取频率,有时还是会被美团的网络工程师抓到的,小哥哥饶命啊~。因此我们需要一个断点续传的小功能,避免每次都从头开始爬。思路是建一个文本文件,存储当前爬取的进度,每次运行程序时都出当前进度开始,详见代码~

二、探索性分析与文本数据预处理

探索性分析

  1. 查看数据大小以及基础信息 ,浏览数据
  2. 样本分布
  3. 各店铺评分分布
  4. 点评数的的时间分布
  5. 查看评论长度对结果影响

数据预处理

  1. 去除非文本数据:可以看出,爬虫获取的数据非常多类似“\xa0”的非文本数据,而且都还有一些无意义的干扰数据,如结尾的“收起评论”#除去非文本数据和无意义文本 data[‘cus_comment’] = data[‘cus_comment’].str.replace(r'[^\u4e00-\u9fa5]’,”).str.replace(‘收起评论’,”)
  2. 中文分词:中文文本数据处理,怎么能离开中文分词呢,我们使用jieba库,简单又好用。这里我们把文本字符串处理为以空格区隔的分词字符串#中文分词 import jieba data[‘cus_comment’] = data[‘cus_comment’].apply(lambda x:’ ‘.join(jieba.cut(x)))
  3. 去除停用词:文本中有很多无效的词,比如“着”,“和”,还有一些标点符号,这些我们不想在文本分析的时候引入,因此需要去掉,因为wordcloud和TF-IDF都支持停用词,因此就不额外处理了

词云展示

运维工具 – 大众点评评论文本挖掘基础案例实践

三、文本的情感分析

先上结果:

糖水店的评论文本 模型预测的情感评分
‘糖水味道不错,滑而不腻,赞一个,下次还会来’ 0.91
‘味道一般,没啥特点’ 0.52
‘排队老半天,环境很差,味道一般般’ 0.05

模型的效果还可以的样子,yeah~接下来我们好好讲讲怎么做的哈,我们通过爬虫爬取了大众点评广州8家最热门糖水店的3W条评论信息以及评分作为训练数据,前面的分析我们得知样本很不均衡。接下来我们的整体思路就是:文本特征提取(TF-IDF)—机器学习建模—模型评价。

我们先不处理样本不均衡问题,直接建模后查看结果,接下来我们再按照两种方法处理样本不均衡,对比结果。

文本特征提取(TF-IDF)

模型不能直接处理文本数据,因此需要先把文本数据转为向量,方法有词库表示法、TF-IDF、word2vec等,推荐一篇文章,总结得不错 https://zhuanlan.zhihu.com/p/44917421。

#使用TF-IDF进行文本转向量处理
from sklearn.feature_extraction.text import TfidfVectorizer
tv = TfidfVectorizer(stop_words=stopwords, max_features=3000, ngram_range=(1,2))
tv.fit(x_train)

机器学习建模

这里我们使用文本分类的经典算法朴素贝叶斯算法,而且朴素贝叶斯算法的计算量较少。特征值是评论文本经过TF-IDF处理的向量,标签值评论的分类共两类,好评是1,差评是0。情感评分为分类器预测分类1的概率值。

#计算分类效果的准确率
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import roc_auc_score, f1_score
classifier = MultinomialNB()
classifier.fit(tv.transform(x_train), y_train)
classifier.score(tv.transform(x_test), y_test)

>>>0.9275308869629356

可以看出,准确率非常不错的样子

#从大众点评网找两条评论来测试一下
test1 = '很好吃,环境好,所有员工的态度都很好,上菜快,服务也很好,味道好吃,都是用蒸馏水煮的,推荐,超好吃' #5星好评
test2 = '糯米外皮不绵滑,豆沙馅粗躁,没有香甜味。12元一碗不值。' #1星差评
print('好评实例的模型预测情感得分为{}\n差评实例的模型预测情感得分为{}'.format(ceshi(classifier,test1),ceshi(classifier,test2)))

>>>好评实例的模型预测情感得分为0.8638082706675478
>>>差评实例的模型预测情感得分为0.7856544482460911

点评网上的实际测试中,5星好评模型预测出来了,1星差评缺预测错误。为什么呢?我们查看一下混淆矩阵


1
2
[  46,  385]
[   8, 4984]

可以看出,负类的预测非常不准,433单准确预测为负类的只有15.7%,应该是由于数据不平衡导致的,模型的默认阈值为输出值的中位数。比如逻辑回归的输出范围为[0,1],当某个样本的输出大于0.5就会被划分为正例,反之为反例。在数据的类别不平衡时,采用默认的分类阈值可能会导致输出全部为正例,产生虚假的高准确度,导致分类失败。

处理样本不均衡问题的方法,首先可以选择调整阈值,使得模型对于较少的类别更为敏感,或者选择合适的评估标准,比如ROC或者F1,而不是准确度(accuracy)。另外一种方法就是通过采样(sampling)来调整数据的不平衡。其中欠采样抛弃了大部分正例数据,从而弱化了其影响,可能会造成偏差很大的模型,同时,数据总是宝贵的,抛弃数据是很奢侈的。另外一种是过采样,下面我们就使用过采样方法来调整。

样本数据不平衡

最简单的过采样方法,就是简单复制法。但单纯的重复了反例,会过分强调已有的反例。如果其中部分点标记错误或者是噪音,那么错误也容易被成倍的放大。因此最大的风险就是对反例过拟合。

#把0类样本复制10次,构造训练集
index_tmp = y_train==0
y_tmp = y_train[index_tmp]
x_tmp = x_train[index_tmp]
x_train2 = pd.concat([x_train,x_tmp,x_tmp,x_tmp,x_tmp,x_tmp,x_tmp,x_tmp,x_tmp,x_tmp,x_tmp])
y_train2 = pd.concat([y_train,y_tmp,y_tmp,y_tmp,y_tmp,y_tmp,y_tmp,y_tmp,y_tmp,y_tmp,y_tmp])

#使用过采样样本(简单复制)进行模型训练,并查看准确率
clf2 = MultinomialNB()
clf2.fit(tv.transform(x_train2), y_train2)
y_pred2 = clf2.predict_proba(tv.transform(x_test))[:,1]
roc_auc_score(y_test,y_pred2)

>>>0.9049699937533463

查看此时的混淆矩阵


1
2
[ 331,  100]
[ 637, 4355]

可以看出,即使是简单粗暴的复制样本来处理样本不平衡问题,负样本的识别率大幅上升了,变为77%,满满的幸福感呀。还有SMOTE过采样算法,SMOTE是在局部区域通过K-近邻生成了新的反例。相较于简单的过采样,SMOTE降低了过拟合风险,但同时运算开销加大,详细请看具体代码~

模型评估测试

我们把3W条数据都拿来训练,数据量变多了,模型效果应该会更好

def fenxi(strings):
    strings_fenci = fenci(pd.Series([strings]))
    return float(clf.predict_proba(tv2.transform(strings_fenci))[:,1])

#到网上找一条差评来测试一下
fenxi('糯米外皮不绵滑,豆沙馅粗躁,没有香甜味。12元一碗不值。')

>>>0.28900092243477077

只用到了简单的机器学习,就做出了不错的情感分析效果,知识的力量真是强大呀,666~


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import pandas as pd
from matplotlib import pyplot as plt
import jieba
data = pd.read_csv('data.csv')
data.head()
#构建label值
def zhuanhuan(score):
    if score > 3:
        return 1
    elif score < 3:
        return 0
    else:
        return None
   
#特征值转换
data['target'] = data['stars'].map(lambda x:zhuanhuan(x))
data_model = data.dropna()

#切分测试集、训练集
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(data_model['cus_comment'], data_model['target'], random_state=3, test_size=0.25)

#引入停用词
infile = open("stopwords.txt",encoding='utf-8')
stopwords_lst = infile.readlines()
stopwords = [x.strip() for x in stopwords_lst]

#中文分词
def fenci(train_data):
    words_df = train_data.apply(lambda x:' '.join(jieba.cut(x)))
    return words_df
 
x_train[:5]
#使用TF-IDF进行文本转向量处理
from sklearn.feature_extraction.text import TfidfVectorizer
tv = TfidfVectorizer(stop_words=stopwords, max_features=3000, ngram_range=(1,2))
tv.fit(x_train)
#计算分类效果的准确率
from sklearn.naive_bayes import MultinomialNB
from sklearn.metrics import roc_auc_score, f1_score
classifier = MultinomialNB()
classifier.fit(tv.transform(x_train), y_train)
classifier.score(tv.transform(x_test), y_test)
#计算分类器的AUC值
y_pred = classifier.predict_proba(tv.transform(x_test))[:,1]
roc_auc_score(y_test,y_pred)

#计算一条评论文本的情感评分
def ceshi(model,strings):
    strings_fenci = fenci(pd.Series([strings]))
    return float(model.predict_proba(tv.transform(strings_fenci))[:,1])

#从大众点评网找两条评论来测试一下
test1 = '很好吃,环境好,所有员工的态度都很好,上菜快,服务也很好,味道好吃,都是用蒸馏水煮的,推荐,超好吃' #5星好评
test2 = '糯米外皮不绵滑,豆沙馅粗躁,没有香甜味。12元一碗不值。' #1星差评
test3 = '糯米外皮不绵滑,对应这样的价格,有些吃亏。' #1星差评
print('好评实例的模型预测情感得分为{}\n差评实例的模型预测情感得分为{}'.format(ceshi(classifier,test1),ceshi(classifier,test3)))

from sklearn.metrics import confusion_matrix
y_predict = classifier.predict(tv.transform(x_test))
cm = confusion_matrix(y_test, y_predict)
cm
data['target'].value_counts()

#把0类样本复制10次,构造训练集
index_tmp = y_train==0
y_tmp = y_train[index_tmp]
x_tmp = x_train[index_tmp]
x_train2 = pd.concat([x_train,x_tmp,x_tmp,x_tmp,x_tmp,x_tmp,x_tmp,x_tmp,x_tmp,x_tmp,x_tmp])
y_train2 = pd.concat([y_train,y_tmp,y_tmp,y_tmp,y_tmp,y_tmp,y_tmp,y_tmp,y_tmp,y_tmp,y_tmp])

#使用过采样样本(简单复制)进行模型训练,并查看准确率
clf2 = MultinomialNB()
clf2.fit(tv.transform(x_train2), y_train2)
y_pred2 = clf2.predict_proba(tv.transform(x_test))[:,1]
roc_auc_score(y_test,y_pred2)

#查看此时的混淆矩阵
y_predict2 = clf2.predict(tv.transform(x_test))
cm = confusion_matrix(y_test, y_predict2)
cm

#ceshi(clf2,'排队人太多,环境不好,口味一般')
ceshi(clf2,'糯米外皮不绵滑,对应这样的价格,不错')

#使用SMOTE进行样本过采样处理 fit_sample  fit_resample
from imblearn.over_sampling import SMOTE
oversampler=SMOTE(random_state=0)
x_train_vec = tv.transform(x_train)
x_resampled, y_resampled = oversampler.fit_resample(x_train_vec, y_train)


#原始的样本分布
y_train.value_counts()



#经过SMOTE算法过采样后的样本分布情况
pd.Series(y_resampled).value_counts()


#使用过采样样本(SMOTE)进行模型训练,并查看准确率
clf3 = MultinomialNB()
clf3.fit(x_resampled, y_resampled)
y_pred3 = clf3.predict_proba(tv.transform(x_test))[:,1]
roc_auc_score(y_test,y_pred3)

#查看此时的准确率
y_predict3 = clf3.predict(tv.transform(x_test))
cm = confusion_matrix(y_test, y_predict3)
cm

#到网上找一条差评来测试一下情感评分的预测效果
test3 = '糯米外皮不绵滑,豆沙馅粗躁,没有香甜味。12元一碗不值。'
test3 = '糯米外皮不绵滑,对应这样的价格,不怎么合适,好难吃'

ceshi(clf3,test3)


#词向量训练
tv2 = TfidfVectorizer(stop_words=stopwords, max_features=3000, ngram_range=(1,2))
tv2.fit(data_model['cus_comment'])

#SMOTE插值
X_tmp = tv2.transform(data_model['cus_comment'])
y_tmp = data_model['target']
sm = SMOTE(random_state=0)
X,y = sm.fit_resample(X_tmp, y_tmp)

clf = MultinomialNB()
clf.fit(X, y)

def fenxi(strings):
    strings_fenci = fenci(pd.Series([strings]))
    return float(clf.predict_proba(tv2.transform(strings_fenci))[:,1])

#到网上找一条差评来测试一下
fenxi('糯米外皮不绵滑,豆沙馅粗躁,没有香甜味。12元一碗不值。')

在所有的机器学习分类算法中,朴素贝叶斯和其他绝大多数的分类算法都不同。对于大多数的分类算法,比如决策树,KNN,逻辑回归,支持向量机等,他们都是判别方法,也就是直接学习出特征输出Y和特征X之间的关系,要么是决策函数Y=f(X)Y=f(X),要么是条件分布P(Y|X)P(Y|X)。但是朴素贝叶斯却是生成方法,也就是直接找出特征输出Y和特征X的联合分布P(X,Y)P(X,Y),然后用P(Y|X)=P(X,Y)/P(X)P(Y|X)=P(X,Y)/P(X)得出。

    朴素贝叶斯很直观,计算量也不大,在很多领域有广泛的应用,这里我们就对朴素贝叶斯算法原理做一个小结。

1. 朴素贝叶斯相关的统计学知识

    在了解朴素贝叶斯的算法之前,我们需要对相关必须的统计学知识做一个回顾。

    贝叶斯学派很古老,但是从诞生到一百年前一直不是主流。主流是频率学派。频率学派的权威皮尔逊和费歇尔都对贝叶斯学派不屑一顾,但是贝叶斯学派硬是凭借在现代特定领域的出色应用表现为自己赢得了半壁江山。

    贝叶斯学派的思想可以概括为先验概率+数据=后验概率。也就是说我们在实际问题中需要得到的后验概率,可以通过先验概率和数据一起综合得到。数据大家好理解,被频率学派攻击的是先验概率,一般来说先验概率就是我们对于数据所在领域的历史经验,但是这个经验常常难以量化或者模型化,于是贝叶斯学派大胆的假设先验分布的模型,比如正态分布,beta分布等。这个假设一般没有特定的依据,因此一直被频率学派认为很荒谬。虽然难以从严密的数学逻辑里推出贝叶斯学派的逻辑,但是在很多实际应用中,贝叶斯理论很好用,比如垃圾邮件分类,文本分类。

    我们先看看条件独立公式,如果X和Y相互独立,则有:

P(X,Y)=P(X)P(Y)P(X,Y)=P(X)P(Y)

    我们接着看看条件概率公式:

P(Y|X)=P(X,Y)/P(X)P(Y|X)=P(X,Y)/P(X)

P(X|Y)=P(X,Y)/P(Y)P(X|Y)=P(X,Y)/P(Y)

或者说:

P(Y|X)=P(X|Y)P(Y)/P(X)P(Y|X)=P(X|Y)P(Y)/P(X)

接着看看全概率公式

P(X)=∑kP(X|Y=Yk)P(Yk)其中∑kP(Yk)=1P(X)=∑kP(X|Y=Yk)P(Yk)其中∑kP(Yk)=1

从上面的公式很容易得出贝叶斯公式:

P(Yk|X)=P(X|Yk)P(Yk)∑kP(X|Y=Yk)P(Yk)P(Yk|X)=P(X|Yk)P(Yk)∑kP(X|Y=Yk)P(Yk)

 2. 朴素贝叶斯的模型

    从统计学知识回到我们的数据分析。假如我们的分类模型样本是:

(x(1)1,x(1)2,…x(1)n,y1),(x(2)1,x(2)2,…x(2)n,y2),…(x(m)1,x(m)2,…x(m)n,ym)(x1(1),x2(1),…xn(1),y1),(x1(2),x2(2),…xn(2),y2),…(x1(m),x2(m),…xn(m),ym)

    即我们有m个样本,每个样本有n个特征,特征输出有K个类别,定义为C1,C2,…,CKC1,C2,…,CK。

    从样本我们可以学习得到朴素贝叶斯的先验分布P(Y=Ck)(k=1,2,…K)P(Y=Ck)(k=1,2,…K),接着学习到条件概率分布P(X=x|Y=Ck)=P(X1=x1,X2=x2,…Xn=xn|Y=Ck)P(X=x|Y=Ck)=P(X1=x1,X2=x2,…Xn=xn|Y=Ck),然后我们就可以用贝叶斯公式得到X和Y的联合分布P(X,Y)了。联合分布P(X,Y)定义为:

P(X,Y=Ck)=P(Y=Ck)P(X=x|Y=Ck)=P(Y=Ck)P(X1=x1,X2=x2,…Xn=xn|Y=Ck)(1)(2)(1)P(X,Y=Ck)=P(Y=Ck)P(X=x|Y=Ck)(2)=P(Y=Ck)P(X1=x1,X2=x2,…Xn=xn|Y=Ck)

    从上面的式子可以看出P(Y=Ck)P(Y=Ck)比较容易通过最大似然法求出,得到的P(Y=Ck)P(Y=Ck)就是类别CkCk在训练集里面出现的频数。但是P(X1=x1,X2=x2,…Xn=xn|Y=Ck)P(X1=x1,X2=x2,…Xn=xn|Y=Ck)很难求出,这是一个超级复杂的有n个维度的条件分布。朴素贝叶斯模型在这里做了一个大胆的假设,即X的n个维度之间相互独立,这样就可以得出:

P(X1=x1,X2=x2,…Xn=xn|Y=Ck)=P(X1=x1|Y=Ck)P(X2=x2|Y=Ck)…P(Xn=xn|Y=Ck)P(X1=x1,X2=x2,…Xn=xn|Y=Ck)=P(X1=x1|Y=Ck)P(X2=x2|Y=Ck)…P(Xn=xn|Y=Ck)

    从上式可以看出,这个很难的条件分布大大的简化了,但是这也可能带来预测的不准确性。你会说如果我的特征之间非常不独立怎么办?如果真是非常不独立的话,那就尽量不要使用朴素贝叶斯模型了,考虑使用其他的分类方法比较好。但是一般情况下,样本的特征之间独立这个条件的确是弱成立的,尤其是数据量非常大的时候。虽然我们牺牲了准确性,但是得到的好处是模型的条件分布的计算大大简化了,这就是贝叶斯模型的选择。

    最后回到我们要解决的问题,我们的问题是给定测试集的一个新样本特征(x(test)1,x(test)2,…x(test)n(x1(test),x2(test),…xn(test),我们如何判断它属于哪个类型?

    既然是贝叶斯模型,当然是后验概率最大化来判断分类了。我们只要计算出所有的K个条件概率P(Y=Ck|X=X(test))P(Y=Ck|X=X(test)),然后找出最大的条件概率对应的类别,这就是朴素贝叶斯的预测了。

3. 朴素贝叶斯的推断过程

    上节我们已经对朴素贝叶斯的模型也预测方法做了一个大概的解释,这里我们对朴素贝叶斯的推断过程做一个完整的诠释过程。

    我们预测的类别CresultCresult是使P(Y=Ck|X=X(test))P(Y=Ck|X=X(test))最大化的类别,数学表达式为:

Cresult=argmaxCkP(Y=Ck|X=X(test))=argmaxCkP(X=X(test)|Y=Ck)P(Y=Ck)/P(X=X(test))(3)(4)(3)Cresult=argmax⏟CkP(Y=Ck|X=X(test))(4)=argmax⏟CkP(X=X(test)|Y=Ck)P(Y=Ck)/P(X=X(test))

    由于对于所有的类别计算P(Y=Ck|X=X(test))P(Y=Ck|X=X(test))时,上式的分母是一样的,都是P(X=X(test)P(X=X(test),因此,我们的预测公式可以简化为:

Cresult=argmaxCkP(X=X(test)|Y=Ck)P(Y=Ck)Cresult=argmax⏟CkP(X=X(test)|Y=Ck)P(Y=Ck)   

    接着我们利用朴素贝叶斯的独立性假设,就可以得到通常意义上的朴素贝叶斯推断公式:

Cresult=argmaxCkP(Y=Ck)∏j=1nP(Xj=X(test)j|Y=Ck)Cresult=argmax⏟CkP(Y=Ck)∏j=1nP(Xj=Xj(test)|Y=Ck)

4. 朴素贝叶斯的参数估计

    在上一节中,我们知道只要求出P(Y=Ck)和P(Xj=X(test)j|Y=Ck)(j=1,2,…n)P(Y=Ck)和P(Xj=Xj(test)|Y=Ck)(j=1,2,…n),我们通过比较就可以得到朴素贝叶斯的推断结果。这一节我们就讨论怎么通过训练集计算这两个概率。

    对于P(Y=Ck)P(Y=Ck),比较简单,通过极大似然估计我们很容易得到P(Y=Ck)P(Y=Ck)为样本类别CkCk出现的频率,即样本类别CkCk出现的次数mkmk除以样本总数m。

    对于P(Xj=X(test)j|Y=Ck)(j=1,2,…n)P(Xj=Xj(test)|Y=Ck)(j=1,2,…n),这个取决于我们的先验条件:

    a) 如果我们的XjXj是离散的值,那么我们可以假设XjXj符合多项式分布,这样得到P(Xj=X(test)j|Y=Ck)P(Xj=Xj(test)|Y=Ck) 是在样本类别CkCk中,特征X(test)jXj(test)出现的频率。即:

P(Xj=X(test)j|Y=Ck)=mkjtestmkP(Xj=Xj(test)|Y=Ck)=mkjtestmk

    其中mkmk为样本类别CkCk总的特征计数,而mkjtestmkjtest为类别为CkCk的样本中,第j维特征X(test)jXj(test)出现的计数。

    某些时候,可能某些类别在样本中没有出现,这样可能导致P(Xj=X(test)j|Y=Ck)P(Xj=Xj(test)|Y=Ck)为0,这样会影响后验的估计,为了解决这种情况,我们引入了拉普拉斯平滑,即此时有:

P(Xj=X(test)j|Y=Ck)=mkjtest+λmk+OjλP(Xj=Xj(test)|Y=Ck)=mkjtest+λmk+Ojλ   

    其中λλ 为一个大于0的常数,常常取为1。OjOj为第j个特征的取值个数。

 

    b)如果我们我们的XjXj是非常稀疏的离散值,即各个特征出现概率很低,这时我们可以假设XjXj符合伯努利分布,即特征XjXj出现记为1,不出现记为0。即只要XjXj出现即可,我们不关注XjXj的次数。这样得到P(Xj=X(test)j|Y=Ck)P(Xj=Xj(test)|Y=Ck) 是在样本类别CkCk中,X(test)jXj(test)出现的频率。此时有:

P(Xj=X(test)j|Y=Ck)=P(Xj=1|Y=Ck)X(test)j+(1−P(Xj=1|Y=Ck))(1−X(test)j)P(Xj=Xj(test)|Y=Ck)=P(Xj=1|Y=Ck)Xj(test)+(1−P(Xj=1|Y=Ck))(1−Xj(test))

    其中,X(test)jXj(test)取值为0和1。

  

    c)如果我们我们的XjXj是连续值,我们通常取XjXj的先验概率为正态分布,即在样本类别CkCk中,XjXj的值符合正态分布。这样P(Xj=X(test)j|Y=Ck)P(Xj=Xj(test)|Y=Ck)的概率分布是:

P(Xj=X(test)j|Y=Ck)=12πσ2k−−−−√exp(−(X(test)j−μk)22σ2k)P(Xj=Xj(test)|Y=Ck)=12πσk2exp(−(Xj(test)−μk)22σk2)

    其中μk和σ2kμk和σk2是正态分布的期望和方差,可以通过极大似然估计求得。μkμk为在样本类别CkCk中,所有XjXj的平均值。σ2kσk2为在样本类别CkCk中,所有XjXj的方差。对于一个连续的样本值,带入正态分布的公式,就可以求出概率分布了。

5.  朴素贝叶斯算法过程

    我们假设训练集为m个样本n个维度,如下:

(x(1)1,x(1)2,…x(1)n,y1),(x(2)1,x(2)2,…x(2)n,y2),…(x(m)1,x(m)2,…x(m)n,ym)(x1(1),x2(1),…xn(1),y1),(x1(2),x2(2),…xn(2),y2),…(x1(m),x2(m),…xn(m),ym)

    共有K个特征输出类别,分别为C1,C2,…,CKC1,C2,…,CK,每个特征输出类别的样本个数为m1,m2,…,mKm1,m2,…,mK,在第k个类别中,如果是离散特征,则特征XjXj各个类别取值为mkjlmkjl。其中l取值为1,2,…Sj1,2,…Sj,SjSj为特征j不同的取值数。

    输出为实例X(test)X(test)的分类。

    算法流程如下:

    1) 如果没有Y的先验概率,则计算Y的K个先验概率:P(Y=Ck)=(mk+λ)/(m+Kλ)P(Y=Ck)=(mk+λ)/(m+Kλ),否则P(Y=Ck)P(Y=Ck)为输入的先验概率。

    2) 分别计算第k个类别的第j维特征的第l个个取值条件概率:P(Xj=xjl|Y=Ck)P(Xj=xjl|Y=Ck)

      a)如果是离散值:

P(Xj=xjl|Y=Ck)=mkjl+λmk+SjλP(Xj=xjl|Y=Ck)=mkjl+λmk+Sjλ

      λλ可以取值为1,或者其他大于0的数字。

      b)如果是稀疏二项离散值:

P(Xj=xjl|Y=Ck)=P(j|Y=Ck)xjl+(1−P(j|Y=Ck)(1−xjl)P(Xj=xjl|Y=Ck)=P(j|Y=Ck)xjl+(1−P(j|Y=Ck)(1−xjl)

       此时ll只有两种取值。

      c)如果是连续值不需要计算各个l的取值概率,直接求正态分布的参数:

P(Xj=xj|Y=Ck)=12πσ2k−−−−√exp(−(xj−μk)22σ2k)P(Xj=xj|Y=Ck)=12πσk2exp(−(xj−μk)22σk2)

      需要求出μk和σ2kμk和σk2。 μkμk为在样本类别CkCk中,所有XjXj的平均值。σ2kσk2为在样本类别CkCk中,所有XjXj的方差。

    3)对于实例X(test)X(test),分别计算:

P(Y=Ck)∏j=1nP(Xj=x(test)j|Y=Ck)P(Y=Ck)∏j=1nP(Xj=xj(test)|Y=Ck)

    4)确定实例X(test)X(test)的分类CresultCresult

Cresult=argmaxCkP(Y=Ck)∏j=1nP(Xj=X(test)j|Y=Ck)Cresult=argmax⏟CkP(Y=Ck)∏j=1nP(Xj=Xj(test)|Y=Ck)

     从上面的计算可以看出,没有复杂的求导和矩阵运算,因此效率很高。

6.  朴素贝叶斯算法小结

    朴素贝叶斯算法的主要原理基本已经做了总结,这里对朴素贝叶斯的优缺点做一个总结。

    朴素贝叶斯的主要优点有:

    1)朴素贝叶斯模型发源于古典数学理论,有稳定的分类效率。

    2)对小规模的数据表现很好,能个处理多分类任务,适合增量式训练,尤其是数据量超出内存时,我们可以一批批的去增量训练。

    3)对缺失数据不太敏感,算法也比较简单,常用于文本分类。

    朴素贝叶斯的主要缺点有:   

    1) 理论上,朴素贝叶斯模型与其他分类方法相比具有最小的误差率。但是实际上并非总是如此,这是因为朴素贝叶斯模型给定输出类别的情况下,假设属性之间相互独立,这个假设在实际应用中往往是不成立的,在属性个数比较多或者属性之间相关性较大时,分类效果不好。而在属性相关性较小时,朴素贝叶斯性能最为良好。对于这一点,有半朴素贝叶斯之类的算法通过考虑部分关联性适度改进。

    2)需要知道先验概率,且先验概率很多时候取决于假设,假设的模型可以有很多种,因此在某些时候会由于假设的先验模型的原因导致预测效果不佳。

    3)由于我们是通过先验和数据来决定后验的概率从而决定分类,所以分类决策存在一定的错误率。

    4)对输入数据的表达形式很敏感。

    以上就是朴素贝叶斯算法的一个总结,希望可以帮到朋友们。


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
import warnings
warnings.filterwarnings("ignore")
import pandas as pd
import jieba
df_news = pd.read_csv('data.csv')
df_news = df_news.dropna()  #删除缺失值
df_news.head()
df_news['content']=df_news['cus_comment'].str.replace('\d+', '',regex=True) #替换
df_news['content']=df_news['cus_comment'].str.replace('[A-z]', '',regex=True)
content = df_news.content.values.tolist() #将每一篇文章转换成一个list
'''
#中文分词和去停用词
import csv
stopwords=pd.read_csv('stopwords.txt',header=None,quoting = csv.QUOTE_NONE,delimiter="\t")
stopwords.head()
stopwords =  stopwords[0].tolist()
stopwords.append('\r\n')
'''
content_S = []
for line in content:
    current_segment = jieba.lcut(line) #对每一篇文章进行分词,返回的列表
    #current_segment=[segment for segment in current_segment if segment not in stopwords] #去停用词后的列表
    segments=" ".join(current_segment) #把数组中的所有元素放入一个字符串
    content_S.append(segments) #保存分词的结果
df=pd.DataFrame({'contents_clean':content_S,'label':df_news['stars'].map(lambda x:zhuanhuan(x))})
df.head()#去掉停用词后
#分数据集
from sklearn.model_selection import train_test_split
x_train, x_test, y_train, y_test = train_test_split(df['contents_clean'].values, df['label'].values, random_state=1)

#提取特征,训练模型,评估模型
from sklearn.feature_extraction.text import CountVectorizer
vec = CountVectorizer()
train_feature = vec.fit_transform(x_train) #训练集转换为向量
train_feature  #<3750x87289 sparse matrix of type '<class 'numpy.int64'>' with 461523 stored elements in Compressed Sparse Row format>
test_feature = vec.transform(x_test) #测试集转换为向量
test_feature.shape #(1250, 87289)

from sklearn.naive_bayes import MultinomialNB #贝叶斯模型,多项式
classifier = MultinomialNB()
classifier.fit(train_feature, y_train) #训练
classifier.score(test_feature, y_test) #准确率 #0.8248

#TF-IDF模型
from sklearn.feature_extraction.text import TfidfVectorizer
vectorizer = TfidfVectorizer()
vectorizer.fit(x_train)
train_feature=vectorizer.transform(x_train)
test_feature = vectorizer.transform(x_test)
classifier = MultinomialNB()
classifier.fit(train_feature, y_train)
classifier.score(test_feature, y_test) #0.8264

#用网格搜索优化
from sklearn.model_selection import GridSearchCV
params={'alpha':[0.1,0.2,0.3,0.4]}
classifier = MultinomialNB()
grid_search=GridSearchCV(classifier,param_grid=params,cv=10)
grid_search.fit(train_feature, y_train)
grid_search.best_params_ #{'alpha': 0.1}
grid_search.score(test_feature, y_test)#模型得分,准确率 # 0.828

label_mapping = {"汽车": 1, "财经": 2, "科技": 3, "健康": 4, "体育":5, "教育": 6,"文化": 7,"军事": 8,"娱乐": 9,"时尚": 0}
from sklearn.metrics import classification_report
pred_test=grid_search.predict(test_feature) #预测结果
print(classification_report(y_test, pred_test))

#分类报告
from sklearn.metrics import classification_report
pred_test=grid_search.predict(test_feature) #预测结果
print(classification_report(y_test, pred_test))
#混淆矩阵
from sklearn import metrics
print(metrics.confusion_matrix(y_test, pred_test))#混淆矩阵,不过感觉0.828分类精度还不是很高哦,以后看看别的算法能不能更高

#https://zhuanlan.zhihu.com/p/395049069

给TA打赏
共{{data.count}}人
人已打赏
安全运维

安全运维之道:发现、解决问题的有效闭环

2024-4-14 20:59:36

安全运维

稳定性建设 – 架构优化的关键策略

2025-2-11 17:15:56

个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索