贝叶斯机器学习系列:
贝叶斯①——贝叶斯原理篇(联合概率&条件概率&贝叶斯定理&拉普拉斯平滑)
贝叶斯②——贝叶斯3种分类模型及Sklearn使用(高斯&多项式&伯努利)
贝叶斯④——Sklean新闻分类(TF-IDF)
贝叶斯⑤——搜狗新闻分类实战(jieba + TF-IDF + 贝叶斯)
贝叶斯⑥——银行借贷模型(贝叶斯与决策树对比)
虽然sklearn能直接调用贝叶斯分类,但是若能用Python代码敲出来,那也是非常有成就感的一件事,而且能进一步加深对算法过程的理解~
① loadData():导入分类样本,如果样本是文档,先用jieba进行分词
② vecabulary():用集合生成词典(set|set会生成并集且自动删除重复元素)
③ Word2Vetorm()&Word2Vetor_m():将样本转化成向量(用词典中的index[词语]获得下标),前者是伯努利模型,出现记录1,不出现记录0;后者是多项式模型,出现多少次就记多少,未出现则是0
④ cal_prob()&cal_prob_m():前者是用伯努利模型计算先验概率和类条件概率,后者是用多项式,这里使用了拉普拉斯平滑(参数为1)来解决零概率问题,以及取log对数化乘法为加法,简化计算
⑤ naive_byes_classify():生成分类器, 对样本分别计算不同类别的概率,概率更大者即是最终类别
⑥ testNB():总函数,将之前的函数全都调用一遍,得到分类器,再对测试样本进行预测
① 导入训练样本
def loadData():
postingList = [['伤心', '难受', '难过', '伤心', '郁闷'],
['高兴', '愉悦', '欢欣', '开心', '开心', '明亮'],
['难受', '低沉', '平静', '伤心'],
['愉悦', '运动', '笑容', '明媚', '欢乐'],
['低沉', '真坏', '噘嘴', '生气', '伤心'],
['真好', '开心', '愉悦', '笑容', '甜美']]
classVec = [0,1,0,1,0,1] # 类别
return postingList,classVec # 返回已经分过词的文本矩阵和类别
② 根据训练样本生成词典
def vecabulary(dataset):
vecabulary=set([]) # 使用集合生成词典
for data in dataset:
vecabulary = set(data) | vecabulary # set|set可以生成并集并自动删除重复元素
return list(vecabulary) # 将集合变为列表
③ 根据词典将训练样本向量化(伯努利或多项式)
# 伯努利模型,出现为1,不出现为0
def Word2Vetor(dataset,vecabulary):
sample = []
for data in dataset:
x = [0]*len(vecabulary)
for feature in data:
if feature in vecabulary:
x[vecabulary.index(feature)]=1 # 这一步很灵活,以词典中词语的下标来作为样本中词语的下标
sample.append(x) # 将样本组合成矩阵
return sample
# 多项式模型,计算出现次数,没有出现的是0
def Word2Vetor_m(dataset,vecabulary):
sample = []
for data in dataset:
x = [0]*len(vecabulary)
for feature in data:
if feature in vecabulary:
x[vecabulary.index(feature)] +=1 # 与伯努利区别在于这一步,伯努利是出现了就是1,不管出现多少次,这里是出现多少次就加多少
sample.append(x)
return sample
④ 计算类别概率和类条件概率(伯努利和多项式)
# 伯努利模型
def cal_prob(dataset,classVec):
category_1 = sum(classVec)/len(classVec) # 伯努利模型是以类别数/总数作为类别概率
Pvec_1 = np.ones(len(dataset[0]))
Pvec_0 = np.ones(len(dataset[0]))
Pnum_1,Pnum_0 = 2.0,2.0 # 伯努利模型中分母是加上2,来减少分子加1后带来的概率偏差
for i, data in enumerate(dataset):
if classVec[i] == 1: # 对于类别1
Pvec_1 += data # 样本向量加上1,拉普拉斯平滑
Pnum_1 += sum(data) # 类别1中单词总数
else:
Pvec_0 += data
Pnum_0 += sum(data)
Prob_1 = np.log(Pvec_1/Pnum_1) # 类别1的条件概率
Prob_0 = np.log(Pvec_0/Pnum_0) # 类别0的条件概率
return category_1,Prob_1,Prob_0
# 多项式模型
def cal_prob_m(dataset,classVec):
Pvec_1 = np.ones(len(dataset[0]))
Pvec_0 = np.ones(len(dataset[0]))
s = len(dataset[0]) # 多项式分母是加上训练样本的单词总数,这里用词典的长度来作为单词数量
Pnum_1,Pnum_0 =np.array([s]*len(dataset[0])),np.array([s]*len(dataset[0]))
for i, data in enumerate(dataset):
if classVec[i] == 1:
Pvec_1 += data
Pnum_1 += sum(data)
else:
Pvec_0 += data
Pnum_0 += sum(data)
Prob_1 = np.log(Pvec_1/sum(Pnum_1)) # 多项式类条件概率是用某单词在此类文档中出现的次数之和/此类文档的单词总数(包括重复的),这里取对数是为了后面方便计算
Prob_0 = np.log(Pvec_0/sum(Pnum_0))
category_1 = sum(Pvec_1)/(sum(Pnum_1) + sum(Pnum_0)) # 多项式类别概率是用类别所有词的出现次数之和/整个训练样本所有词的出现次数之和,也就是类别单词总数/训练样本单词总数
return category_1,Prob_1,Prob_0
⑤ 生成贝叶斯模型
def naive_byes_classify(vec,category_1,Prob_1,Prob_0):
test_class = [0]*len(vec) # 先假设样本是一个全为0的向量
for i,ve in enumerate(vec):
test_1 = sum(Prob_1*ve) + np.log(category_1) # 代入计算属于每一类别的概率
test_0 = sum(Prob_0*ve) + np.log(1-category_1)
if test_1 > test_0: # 概率大的即为分类结果
test_class[i]=1
return test_class
⑥ 调用及测试
def testNB():
postingList, classVec = loadData()
veca = vecabulary(postingList)
print(veca[:10])
dataset = Word2Vetor(postingList, veca)
print(dataset[:1])
category_1, Prob_1, Prob_0 = cal_prob(dataset, classVec)
print('训练集结果:', naive_byes_classify(dataset, category_1, Prob_1, Prob_0))
test_1 = [['开心', '快乐', '明媚', '平静', '欢乐'],
['低沉', '难受', '伤心']]
print('测试集结果:', naive_byes_classify(Word2Vetor(test_1,veca), category_1, Prob_1, Prob_0))
伯努利模型结果如下
训练样本的分类准确率是100% ,测试集类别也是100%
多项式模型结果如下:
训练样本模型分类准确率为100%,但是测试集分类准确率仅为50%,准确性不及伯努利。
① 当训练样本数较少且文本单词量过少时,多项式模型中会出现大量的0或1,这个与伯努利是一样的,但是如果有极个别词出现次数特别多,就会导致概率向这个词倾斜,而伯努利因为只记录出现与否,不会记录次数,所以就不会有这个问题。
② 但是鉴于伯努利没有将样本的词频信息纳入考量,因此在样本较多且文档较长(比如新闻分类)时,多项式模型的劣势会被削弱,优势就能体现出来
因此,在训练样本数较少且文本单词量过少时,伯努利模型优于多项式模型;当样本足够丰富且文本单词量较多时,多项式模型优于伯努利模型