机器学习之朴素贝叶斯(入门与实战)

目录

贝叶斯准则(贝叶斯公式):

自己动手写代码实现朴素贝叶斯之实例:使用Python进行文本分类

准备数据:从文本中构建词向量

训练算法:从词向量计算概率

测试算法:根据现实情况修改分类器

文档词袋模型

使用sklearn实现朴素贝叶斯之实例:

问题提出:

问题解答:

问题解析:


本文的目的是使你对朴素贝叶斯在理论上有一个入门的理解,在实战方面呢也有一个入门的小示例,看完以后你就可以开启你的进阶之路了。哈哈哈。

本文代码地址

朴素贝叶斯算法是统计学的一种分类方法,它是一种运用概率统计知识进行分类的算法。在许多场合,朴素贝叶斯方法可以与决策树、神经网络分类算法相媲美。该算法能运用到大型数据库中,而且方法简单,分类准确率高,速度快,由于贝叶斯定理假设一个属性值对给定类的影响独立于其它属性值,而此假设在实际情况中经常是不成立的,因此其分类准确率可能会下降。为此也衍生出很多降低独立性假设的贝叶斯分类算法,这里概不讨论。

贝叶斯准则(贝叶斯公式):

即当已知事件Bi的概率P(Bi)和事件Bi已发生条件下事件A的概率P(A│Bi),则可运用贝叶斯定理计算出在事件A发生条件下事件Bi的概率P(Bi│A)。

自己动手写代码实现朴素贝叶斯之实例:使用Python进行文本分类

准备数据:从文本中构建词向量

def loadDataSet():
    postingList = [['my' , 'dog' , 'has' , 'flea' , 'problems' , 'help' , 'please'] ,
                   ['maybe' , 'not' , 'take' , 'him' , 'to' , 'dog' , 'park' , 'stupid'] ,
                   ['my' , 'dalmation' , 'is' , 'so' , 'cute' , 'I' , 'love' , 'him'] ,
                   ['stop' , 'posting' , 'stupid' , 'worthless' , 'garbage'] ,
                   ['mr' , 'licks' , 'ate' , 'my' , 'steak' , 'how' , 'to' , 'stop' , 'him'] ,
                   ['quit' , 'buying' , 'worthless' , 'dog' , 'food' , 'stupid']]
    classVec=[0,1,0,1,0,1]
    return postingList,classVec

def createVocabList(dataset):
    vocabSet=set([])
    for document in dataset:
        #创建两个集合的并集
        vocabSet =vocabSet|set(document)
    return list(vocabSet)

def setOfWords2Vec(vocablist,inputset):
    returnVec=[0]*len(vocablist)
    for word in inputset:
        if word in vocablist:
            #这里如果词出现就为1,不出现就为0,出现多次仍为1
            returnVec[vocablist.index(word)]=1
        else:
            print("the word: %s is not in my Vocabulary!" %word)
    return returnVec

listOPosts,listClasses = loadDataSet()
myVocabList=createVocabList(listOPosts)
print(myVocabList)
print(setOfWords2Vec(myVocabList,listOPosts[0]))

# OUTPUTS:
# ['dalmation', 'worthless', 'buying', 'steak', 'mr', 'ate', 'park', 'my', 'take', 'is', 'not', 'love', 'stop', 'licks', 'dog', 'problems', 'food', 'please', 'to', 'flea', 'so', 'how', 'maybe', 'has', 'posting', 'I', 'quit', 'him', 'cute', 'stupid', 'help', 'garbage']
# [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0]

这里第一个函数loadDataSet()创建了一些实验样本。第二个函数createVocabList是根据实验样本创建全局词汇表。第三个函数setOfWords2Vec是根据全局词汇表及样本文档产生样本向量。

训练算法:从词向量计算概率

def trainNB0(trainMatrix , trainCategory) :
    numTrainDocs = len(trainMatrix)
    numWords = len(trainMatrix[0])
    # 侮辱性文档,即class为1,P(1)的基础概率
    pAbusive = sum(trainCategory) / float(numTrainDocs)
    p0Num = np.zeros(numWords);
    p1Num = np.zeros(numWords)
    p0Denom = 0.0;
    p1Denom = 0.0
    for i in range(numTrainDocs) :
        # 遍历每个文档,其对应的种类piNum的对应词位置数量相加,对应种类piDenom总词数量相加
        if trainCategory[i] == 1 :
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else :
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect=p1Num/p1Denom
    p0Vect=p0Num/p0Denom
    return p0Vect,p1Vect,pAbusive

trainMat=[ ]
for postinDoc in listOPosts:
    trainMat.append(setOfWords2Vec(myVocabList,postinDoc))

p0V,p1V,pAb=trainNB0(trainMat,listClasses)
print(pAb)
print(p0V)
print(p1V)
# OUTPUTS:
# 0.5
# [0.04166667 0.         0.04166667 0.04166667 0.04166667 0.04166667
#  0.04166667 0.125      0.04166667 0.         0.04166667 0.
#  0.04166667 0.04166667 0.         0.08333333 0.04166667 0.
#  0.04166667 0.         0.04166667 0.04166667 0.04166667 0.
#  0.         0.04166667 0.         0.04166667 0.04166667 0.04166667
#  0.         0.        ]
# [0.         0.05263158 0.         0.         0.         0.10526316
#  0.         0.         0.         0.05263158 0.         0.15789474
#  0.05263158 0.         0.05263158 0.05263158 0.         0.10526316
#  0.         0.05263158 0.         0.         0.         0.05263158
#  0.05263158 0.05263158 0.05263158 0.         0.         0.
#  0.05263158 0.05263158]

这里trainNB0函数的输入参数为文档矩阵和文档的标签所构成的向量。最后返回了在给定文档类别条件下单词出现的概率p0Vect和p1Vect,以及文档属于侮辱类的概率pAbusive,这里是一个二分类问题,所以可以通过1-P(1)来得到P(0)。对于多分类的问题,代码需要改动。

测试算法:根据现实情况修改分类器

利用贝叶斯分类器对文档进行分类时,需要计算多个概率的乘积以获得文档属于某个类别的概率,即计算p(w0|1)p(w1|1)p(w2|1)。如果其中一个概率值为0,那么最后的乘积也为0。为降低这种影响,可以将所有词的出现数初始化1,并将分母初始化2(如果是N分类此处为N),这种处理也被称为拉普拉斯平滑。

在文本编辑器中打开py文件,并将trainNB0()分子分母初始化值进行修改:

p0Num = np.ones(numWords); 
p1Num = np.ones(numWords)
p0Denom = 2.0;
p1Denom = 2.0

另一个遇到的问题就是下溢出,这是由于太多很小的数相乘造成的。当计算p(w0|ci)p(w1|ci)p(w2|ci)……p(wn|ci)时,由于大部分因子都非常小,所以程序会下溢出或得不到正确的答案。一种解决办法是对乘积取自然对数。在代数中有ln(a*b)=lna+lnb,于是通过求对数可以避免下溢出或者浮点数舍入导致的错误。

在文本编辑器中打开py文件,通过trainNB0()函数return前的两行代码,将上述做法用到分类器中:

p1Vect=np.log(p1Num/p1Denom)
p0Vect=np.log(p0Num/p0Denom)

然后我们给出了测试的代码。classifyNB函数通过对要分类的向量中对应分类的对数概率求和并比较,返回最可能的类别,即概率较大值的那个类别。testingNB函数是一个便利函数(convenience function),该函数封装所有操作。

def classifyNB(vec2Classify,p0Vec,p1Vec,pClass1):
    #这里vec2Classify和p1Vec是对应元素相乘,不是矩阵相乘
    p1=sum(vec2Classify*p1Vec)+np.log(pClass1)
    p0=sum(vec2Classify*p0Vec)+np.log(1-pClass1)
    if p1>p0:
        return 1
    else:
        return 0
def testingNB():
    listOPosts,listClasses=loadDataSet()
    myVocabList=createVocabList(listOPosts)
    trainMat=[]
    for postinDoc in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList,postinDoc))
    p0V,p1V,pAb=trainNB0(trainMat,listClasses)
    testEntry=['love','my','dalmation']
    thisDoc=setOfWords2Vec(myVocabList,testEntry)
    print("{} classified as: {}".format(testEntry,classifyNB(thisDoc,p0V,p1V,pAb)))
    testEntry=['stupid','garbage']
    thisDoc = setOfWords2Vec(myVocabList , testEntry)
    print("{} classified as: {}".format(testEntry , classifyNB(thisDoc , p0V , p1V , pAb)))
testingNB()
# OUTPUTS:
# ['love', 'my', 'dalmation'] classified as: 0
# ['stupid', 'garbage'] classified as: 1

这个例子非常简单,此例已完结,但是它展示了朴素贝叶斯分类器的工作原理。接下来,介绍文档词袋模型,这只是对代码的稍加修改,在某些情况下会使分类器工作的更好。

文档词袋模型

目前为止,我们将某个词的是否出现作为一个特征,这可以被描述为词集模型(set-of-words model)。如果把一个词在文档中次数作为一个特征,这种方法被称为词袋模型(bag-of-words model)。在词袋中,每个单词可以出现多次,而在词集中,每个单词只能出现一次 。为实现词袋模型,需要将函数setOfWords2Vec修改为函数bagOfWords2Vec。

def bagOfWords2Vec(vocabList,inputSet):
    returnVec = 0*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)]+=1
    return returnVec

朴素贝叶斯的确迷之简单,它基于最原始、最简单的假设。但是在文本分类方面它表现确实不错,比如过滤垃圾邮件啊,等等等等。当然在实现方面也有很多的Trick,比如进行交叉验证啊,移除高频词或停用词表(stop word list),我这里就都不展开叙述了,不然文章臭长,总之,本文的目的理论入门已经达成了。

那么问题来了,1,朴素贝叶斯它就只能应用于文档类型的数据吗?答案是否定的,2,使用朴素贝叶斯算法,必须自己写代码来实现吗?答案是否定的,我们有现成的类库啊,宝宝。接下来,通过一个问题的提出与解决带你回顾一下朴素贝叶斯算法。

使用sklearn实现朴素贝叶斯之实例:

问题提出:

自己建立随机的4个类别的2维数据(每类数据产生500个),譬如:
第1类数据x坐标的均值为0,标准差为1,y坐标的均值为0,标准差为1
第2类数据x坐标的均值为1,标准差为1,y坐标的均值为4,标准差为1.5
第3类数据x坐标的均值为8,标准差为2,y坐标的均值为5,标准差为1
第4类数据x坐标的均值为3,标准差为0.5,y坐标的均值为3,标准差为0.5
1.绘制这几类数据的散点图(scatter)
2.判断一个2D的点属于哪类?例如给定点 (1.5, 0.7)  (2.1, 6.1)  (6.5, 7.3)  (3.2, 3.3) 试试你的贝叶斯决策分类如何?
3.用70%的数据进行训练,输出测试集混淆矩阵。
你绘制的图应该类似如下图:

问题解答:

import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import GaussianNB
fig=plt.figure()

data1x=np.random.normal(0,1,500)
data1y=np.random.normal(0,1,500)
data2x=np.random.normal(1,1,500)
data2y=np.random.normal(4,1.5,500)
data3x=np.random.normal(8,2,500)
data3y=np.random.normal(5,1,500)
data4x=np.random.normal(3,0.5,500)
data4y=np.random.normal(3,0.5,500)

ax=fig.add_subplot(111)
ax.scatter(x=data1x,y=data1y,label='class1',color='w',linewidths=1,s=80, marker='o',edgecolors='dodgerblue')
ax.scatter(x=data2x,y=data2y,label='class2',color='w',linewidths=1,s=80,marker='^',edgecolors='orangered')
ax.scatter(x=data3x,y=data3y,label='class3',color='w',linewidths=1,s=80,marker='v',edgecolors='goldenrod')
ax.scatter(x=data4x,y=data4y,label='class4',color='purple',s=80,marker=(8,2))
ax.legend(loc='lower right')

plt.show()

X=np.concatenate((np.concatenate((data1x,data2x,data3x,data4x),axis=0).reshape(-1,1),np.concatenate((data1y,data2y,data3y,data4y),axis=0).reshape(-1,1)),axis=1)
_y=np.zeros(500)
y=np.concatenate((_y,_y+1,_y+2,_y+3),axis=0)

X_train,X_test,y_train,y_test=train_test_split(X,y,test_size=0.3)
clf=GaussianNB()
clf.fit(X_train,y_train)
print("在测试集上的正确率为:",clf.score(X_test,y_test))
print("预测四点标签为:",clf.predict([[1.5,0.7],[2.1,6.1],[6.5,7.3],[3.2,3.3]]))
print("预测四点各标签概率分布为:\n",clf.predict_proba([[1.5,0.7],[2.1,6.1],[6.5,7.3],[3.2,3.3]]))

from sklearn.metrics import confusion_matrix
confusion = confusion_matrix(clf.predict(X_test) , y_test)
print("混淆矩阵:其中横向表示predict,纵向表示真实标签\n",confusion)

OUTPUTS:

在测试集上的正确率为: 0.9583333333333334
预测四点标签为: [0. 1. 2. 3.]
预测四点各标签概率分布为:
 [[8.01973484e-01 1.98017770e-01 9.86131832e-07 7.75957432e-06]
 [1.46722726e-09 9.75911244e-01 2.40887011e-02 5.38330045e-08]
 [4.80189700e-22 2.24694815e-06 9.99997753e-01 4.95562296e-24]
 [2.29322398e-06 2.35268380e-02 2.29155759e-03 9.74179311e-01]]
混淆矩阵:其中横向表示predict,纵向表示真实标签
 [[135   7   0   0]
 [  5 134   1   4]
 [  0   0 151   3]
 [  0   4   1 155]]

问题解析:

sklearn 中实现了5种朴素贝叶斯分类器:

  • GaussianNB,高斯朴素贝叶斯,用于任意连续数据;
  • BernoulliNB,伯努利朴素贝叶斯,假定输入数据为二分类数据,计算每个类别中的非零元素个数。BernoulliNB可能在某些数据集上表现得更好,特别是那些文档较短的数据集。
  • MultinomialNB,多项式朴素贝叶斯,假定输入数据为计数数据(即每个特征代表某个对象的整数计数,比如一个单词在句子里出现的次数),计算每个类别中每个特征的平均值
  • ComplementNB,互补朴素贝叶斯,ComplementNB是标准多项式朴素贝叶斯(MNB)算法的一种改进,特别适用于不平衡数据集。
  • CategoricalNB,类朴素贝叶斯

GaussianNB 主要用于高维数据,BernoulliNB 和 MultinomialNB 广泛用于稀疏计数数据,比如文本。MultinomialNB 的性能通常要优于 BernoulliNB 。这里也不多说了,详见官方文档

我这里呢,就简单的使用了GaussianNB,给定数据分布,以及真实类别,fit模型,模型就大功告成了。进行预测类别啊,或者预测的概率分布都能给出。是不是比我们自己写代码来实现要方便多啦。。。哈哈哈

猜你喜欢

转载自blog.csdn.net/hbu_pig/article/details/109511830