机器学习算法(三):基于概率论的分类方法:朴素贝叶斯理论与python实现+经典应用(文档分类、垃圾邮件过滤)

算法学习笔记更新,本章内容是朴素贝叶斯,是一个用到概率论的分类方法。

算法简介

  朴素贝叶斯是贝叶斯决策的一部分,简单说,就是利用条件概率来完成分类。说起条件概率,猛地一下戳到了笔者的伤口。想当年,在学完概率论两年的日子里,它再也没有被我宠幸过。常言道:“谁会考完试了还去看书呢?” 没毛病,我就是那些考完试不看书的人之一。自信满满地走进了推免面试场,被一波数学问题轰炸的败下阵来,其中就包括条件概率。总结:学好概率论、学好线代,it is very important. 至少在一些高校面试的时候会被问道。说多了都是泪,往者不谏,来者可追,come on ,学起来!

  什么是条件概率,不念天书,说普通话,就是说有两个事情,暂设为A和B,问事件A发生的情况下,事件B发生的概率是多少,记为P(B|A)。或者说事件B发生的情况下,求事件A发生的概率,记为P(A|B)。
在这里插入图片描述

  举个例子,有一对夫妻,生了两个孩子,已知其中一个是女孩,求另外一个是男孩的概率?
   当然,不是1/2,有个条件,”已知其中一个是女孩“,这是一个条件概率题,我们来列举一下,两个孩子的所有可能为:男+男、男+女、女+女、女+男。
已知其中一个为女孩,则对应三种情况:男+女、女+女、女+男,另一个为男孩的概率,显然为2/3
这道题除了通过列举法列举出结果之外,用条件概率公式可以计算:
在这里插入图片描述

  由条件概率公式推导出乘法公式为:
在这里插入图片描述

条件概率是朴素贝叶斯进行分类的主要内容,下面我们通过文档分类的例子来具体理解算法分类过程以及条件概率在朴素贝叶斯分类种的应用。

使用朴素贝叶斯进行文档分类

问题描述:

  有一些数量有限的文档,一篇文档由有限数量的单词组成,这些文档的类别已知,对应侮辱类/非侮辱类。如果又填入几份文档,问新填入的文档类别是什么?

问题求解:

  对文档进行分类时,侮辱类还是非侮辱类,可以通过观察文档中的词,并把每个词出现或者不出现作为一个特征。当我们拥有一个词汇表(包含很多的词汇,内容不重复)时,每一份文档中的特征数目都和词汇表中的词数目一样多。举个例子,见下图
在这里插入图片描述
  朴素贝叶斯分类器,有两个假设:一、假设所有特征之间是独立的。这也是之所以称之为“朴素”的原因,简单,直接。也就是说每份文档中一个单词出现与否,不受其他单词影响,统计意义上是独立的。例如:I have a beautiful sister,或者,The ice cream is delicious, 句子中每一个单词出现的可能性跟其他单词均无关系。常识告诉我们,这个假设太武断了,ice cream与delicious或者其他味道的形容词,二者之间肯定不是彼此独立的,因为这些味道的形容词常常伴随着ice cream出现。二、假设每一个特征是同等重要的,也就是说每个特征赋予的权重是一样的,根据经验来说,这个假设也存在问题,拿文档分类的例子来说,一篇文档的类别可能不需要看完词汇表中所有单词,只根据少数的特征就能做出判断了。例如I want to tell you,you are so stupid, do you know?,这句话中,so stupid两个单词,这两个特征对于文档分类起的作用更大。因此,我们说,假设二也是存在问题的。虽然假设存在瑕疵,但是,这两个假设让问题变的简单,我们不必考虑特征之间的依赖性,也不必考虑特征权重。而且,朴素贝叶斯实际进行文档分类时,效果很好。

将上述条件概率公式进行转换,用于求文档分类问题。(用w替代B,ci替代A)

在这里插入图片描述

可以理解为现有一篇文档w,(w是文档词汇的组合向量,假设文档包含4个单词),求文档属于ci 的概率。若P(c1|w)> P(c2|w),则文档为第一类,否则为第二类。这就是朴素贝叶斯进行分类的依据。如果要对文档w进行分类,那么要计算出p(w|ci),朴素贝叶斯假设所有特征之间是独立的,所以
在这里插入图片描述
依据条件概率可知,还需要计算出p(c i),即训练集中两种类别的比例,这个很方便计算。清楚如何求解后,下面进行程序实现。

程序实现

  写程序之前,需要明确,程序要完成的功能。首先,我们有一些文档集,要通过这些文档集构建一张词汇表。通过这张词汇表,对每一篇文档进行文本到向量的转换,也就是说需要把一串串单词转换为向量。如果要对新的一篇文档Q进行分类,依照条件概率公式,需要计算出p(ci),即p( c1 )和p( c2 ),以及p(w | ci),计算这三个值的过程也就是训练分类器的过程。有了这些值,我们就可以计算P(ci | w),从而得出这篇文档输入每个类别的概率,通过比较概率,得出分类结果。

程序流程:加载数据集——构建词汇表——文本转向量——训练分类器——测试分类器

加载数据集

##############加载数据集###############
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']]
    postingLabel=[0,1,0,1,0,1]
    return postingList,postingLabel
   
postingList,postingLabel=loadDataSet()
print(postingList,'\n',postingLabel) 

程序输出

[['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']]
 [0, 1, 0, 1, 0, 1]

构建词汇表

#####构建词汇表######################
def createVocabList(postingList):
    vocabSet=set()
    for posting in postingList:
        vocabSet= vocabSet | set(posting)   ##集合并集,集合中元素唯一
    return list(vocabSet)

词条转向量

###将词条转换为文本向量##############
'''
一条一条的转换,调用一次转换一条
returnVec:返回文本转换后的向量,由0、1组成
'''
def word2vec(vocabList,posting):
    returnVec=[0]*len(vocabList)  #创建1*len(vocabList) 维度的数组,存储词条向量
    for word in posting: 
        #print(word)
        if word in vocabList:
            index=vocabList.index(word)
            returnVec[index]=1     #词汇表中存在该词,则对应位置为1,否则为0   
        else:
            print(word)
    return returnVec
    
#功能测试
postingList,postingLabels=loadDataSet()
vocablist=createVocabList(postingList)
print("词表长度为:",len(vocablist))
print("['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'] 这个字条转向量,结果为:")
print(word2vec(vocablist,postingList[0]))

功能测试:

词表长度为: 32
['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'] 这个字条转向量,结果为:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0]

训练分类器

def train(trainMat,postingLabels):
    '''
    计算trainMat中,每个词语属于侮辱类的概率p1A,以及非侮辱类的概率 p0A。
    计算方法:计算这个词与所有侮辱类文档词汇之和的比例。
    若一个词在三篇侮辱文档中同时出现,就是1+1+1为3,向量对应位置相加而得。若一个词在一篇侮辱文档中出现三次,向量还是1,因为在转为向量的时候,出现即为1,没有是0,并不统计次数。
    trainMat相当于训练集,用于计算p1A,p0A,
    '''
    numDoc=len(trainMat)
    numWord=len(trainMat[0])
    p1=zeros(numWord)     #初始p1,全0向量
    p0=zeros(numWord)      #初始p0,全0向量
    pAbusive=sum(postingLabels)/float(numDoc)  #计算侮辱类文档所占比例
    p1WordSum=0.0 
    p0WordSum=0.0
    for i in range(numDoc):
        if postingLabels[i]==1:
            p1+=trainMat[i]                       #向量相加
            p1WordSum+=sum(trainMat[i])           #统计所有侮辱性言论所包含的词的个数
        else:
            p0 +=trainMat[i]
            p0WordSum +=sum(trainMat[i])
    p1A=p1/p1WordSum                           #计算所有非侮辱类文档向量之和后,每个词对应的概率。比如,上述相加之后,向量为[3,1,0,9],p1Denom=13,列表里的值越高,证明这个词在多篇侮辱类文档中出现,相应地,这个词语属于侮辱类的概率也就越大,为9/13
    p0A=p0/p0WordSum
    return p1A,p0A,pAbusive

#########功能测试
postingList,postLabels=loadDataSet()
vocabList=createVocabList(postingList)
trainMat=[]
for posting in postingList:
    trainMat.append(word2vec(vocabList,posting))
p1A,p0A,pAbusive=train(trainMat,postLabels)
print("p1A= ",p1A)
print("p0A= ",p0A)
print("pAbusive= ",pAbusive)

程序输出:

 需要说明的一点:程序中的p1A对应p(w1 |c1)、p(w2 |c1)、p(w3 |c1)……  
                p0A对应p(w1 |c2)、p(w2 |c2)、p(w3 |c2)……
                pAbusive为侮辱类文档的占比,p(c1)`
p1A=  [0.         0.         0.         0.         0.05263158 0.
 0.         0.         0.05263158 0.05263158 0.         0.
 0.10526316 0.05263158 0.10526316 0.         0.05263158 0.
 0.05263158 0.05263158 0.         0.05263158 0.         0.
 0.05263158 0.         0.         0.05263158 0.05263158 0.
 0.05263158 0.15789474]
p0A=  [0.04166667 0.04166667 0.125      0.04166667 0.04166667 0.04166667
 0.04166667 0.04166667 0.         0.         0.04166667 0.04166667
 0.04166667 0.         0.         0.04166667 0.         0.04166667
 0.         0.08333333 0.04166667 0.         0.04166667 0.04166667
 0.         0.04166667 0.04166667 0.04166667 0.         0.04166667
 0.         0.        ]
pAbusive=  0.5

测试分类器:

#######构建分类器#######
def classify(p1A,p0A,inputVec,pClass1):
    #参照朴素贝叶斯公式
    p1=sum(p1A*inputVec)+log(pClass1)       #分母都是一样的,所以此处计算分子即可
    p0=sum(p0A*inputVec)+log(1.0-pClass1)

    if p1>p0:
        return 1
    else:
        return 0

###########测试函数#############
def test():
    postingList,postLabels=loadDataSet()  #加载数据集
    vocabList=createVocabList(postingList) #创建词汇表
    trainMat=[]     #创建存储训练集词条的向量
    for postVec in postingList:
        trainMat.append(word2vec(vocabList,postVec))   #填充训练集
    
    p1A,p0A,pAbusive=train(trainMat,postLabels)    #训练分类器,得到概率值

    testEntry=['stupid','garbage','love']    #测试实例一
    testVec=word2vec(vocabList,testEntry)    #转换为词条向量
    result=classify(p1A,p0A,testVec,pAbusive)   #扔入分类器中
    print(testEntry,'classified as ',result)    #输出分类结果

    testEntry2=['I','love','the','dog']
    testVec=array(word2vec(vocabList,testEntry2))  
    result=classify(p1A,p0A,testVec,pAbusive)
    print(testEntry2,'classified as ',result)

根据现实情况修改分类器:
(1)p1、p0初始化全为0,如果词汇表中的一些词语在待分类文档中不存在或者训练集中某一条文档不能全部包含词汇表中的词语(其实这两种情况本质上一样的),那么经过向量累加后,p1中会有0,如下图的情况:
在这里插入图片描述

在计算p(w1|ci)*p(w2|ci)*p(w3|ci)……*p(w9|ci)时,一项为0,会导致整个文档分类的概率结果为0。因此,为了避免这种情况,p1、p0初始化全为1,这种解决0概率的方法,叫做拉普拉斯平滑
(2)通过上面程序输出,我们可以看到,p1、p0都是很小的浮点数,概率相乘,p(w1|ci)*p(w2|ci)*p(w3|ci)p(w4|ci)……,当项数很多时,容易导致下溢出。中学时代我们学过ln(xy)=lnx+lny,可以通过取自然对数来避免下溢出。在分类器中,进行如下更改

#更改一:将train函数里面下面两行:
 p1=zeros(numWord)     #初始p1,全0向量
 p0=zeros(numWord)      #初始p0,全0向量
更改为:
p1=ones(numWord)     #初始p1,全1向量
p0=ones(numWord)      #初始p0,全1向量

#更改二::将train函数里下面两行
 p1A=p1/p1WordSum 
 p0A=p0/p0WordSum
#更改为:
 p1A=numpy.log(p1/p1WordSum)   
 p0A=numpy.log(p0/p0WordSum)

修改之后,执行测试函数test(),测试结果如下:其中1 代表侮辱类,0代表非侮辱类

['stupid', 'garbage', 'love'] classified as  1
['I', 'love', 'the', 'dog'] classified as  0

使用朴素贝叶斯过滤垃圾邮件

问题描述
  在文档分类的例子中,输入数据是切分好的字符串列表,如[‘I’, ‘love’, ‘the’, ‘dog’],实际情况中,我们往往要经过一些处理后才能得到这样的字符串列表。在过滤垃圾邮件问题时,邮件存储在txt文件中,我们需要从中读取字符串,进行切分处理,从而实现词条到向量的转换。

程序实现:

#############词袋模型VS词集模型#############
def bagOfWords2VecMN(vocabList,inputSet):  
    '''
    词袋模型VS词集模型:
    词袋模型:文档中的词语在词表中出现一次就+1
    词集模型:出现为1,不出现为0,不累加
    '''  
    returnVec=[0]*len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)]+=1
    return returnVec

###########################文本解析函数###########
def textParse(bigString):
    import re
    listOfToken=re.split('\\W*',bigString)     #除单词字符、数字以外的符号均当作切分符
    return [tok.lower() for tok in listOfToken if len(tok)>0]
    
########################邮件过滤测试####################
def emailTest():
    docList=[];labelList=[];vocabList=[]
    ##读取两个文件夹的文件
    for i in range(1,26):
        wordList=textParse(open('email/spam/%d.txt' %i).read())
        docList.append(wordList) 
        labelList.append(1)
        wordList=textParse(open('email/ham/%d.txt' %i).read())
        docList.append(wordList)
        labelList.append(0)
    ##创建词表
    vocabList=createVocabList(docList)
    '''
    留存交叉验证:随机选择数据的一部分作为训练集,剩余的部分作为测试集。
    '''
    #创建训练集和测试集合,集合里面存储的是文件索引
    trainSet=list(range(30))  #训练集邮件数量设置为30,可以通过调整,观察分类结果
    testSet=[]
    for i in range(10):
        randIndex=int(random.uniform(0,len(trainSet)))
        testSet.append(randIndex)
        del(trainSet[randIndex])
    trainMat=[]
    tr_label=[]
    for index in trainSet:
        trainMat.append(word2vec(vocabList,docList[index]))
        tr_label.append(labelList[index])
    p1A,p0A,pAbusive=train(array(trainMat),array(tr_label))
    errorCount=0.0
    for test_index in testSet:
        test_Vec=word2vec(vocabList,docList[test_index])
        result=classify(p1A,p0A,test_Vec,pAbusive)
        if result!=labelList[test_index]:
            errorCount+=1
    print('错误率为%f'%(errorCount/len(testSet)))

emailTest()

为了更精确的评估分类器分类准确率,可以进行多次迭代,求平均错误率。将测试集邮件数量设置为30时,运行多次,测试结果为

错误率为0.500000
错误率为0.300000
错误率为0.300000
错误率为0.100000

总结:

算法优点:
   1、概率理论发展历史悠久,使得模型有稳定的分类效率
   2、算法简单,好理解,并且对缺失值不敏感
算法缺点:
  考虑现实情况,两个假设存在问题。当数据集特征关联性较大时,分类效果未必会好。

就到这了~~~ 码字有些累了,想不起来的知识点以后再补充吧~

独学无朋,则孤陋而难成,如果有不理解的地方,欢迎与我交流,我们可以共同学习!学海无涯,个人整理,内容难免会有纰漏,欢迎道友指正,感激不尽!

本文参考学习资料:《机器学习实战》(peter Harrington 著)

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 你可以使用Python的scikit-learn库来实现朴素贝叶斯分类器。以下是一个简单的示例代码: ```python from sklearn.naive_bayes import GaussianNB import numpy as np # 准备数据 X = np.array([[1, 2], [3, 4], [5, 6], [7, 8]]) y = np.array([0, 0, 1, 1]) # 创建分类器对象 clf = GaussianNB() # 训练分类器 clf.fit(X, y) # 预测新数据 print(clf.predict([[1, 1], [4, 5]])) ``` 在这个例子,我们准备了一个包含4个样本的数据集X和对应的标签y。然后我们创建了一个GaussianNB对象作为分类器,用fit()方法对其进行训练,并用predict()方法对新数据进行分类预测。 ### 回答2: 朴素贝叶斯分类器是一种常用的机器学习算法,用于进行分类任务。它基于贝叶斯公式和特征之间的独立性假设,可用于文本分类垃圾邮件过滤、情感分析等应用Python有多个库可以实现朴素贝叶斯分类器,如scikit-learn。下面以该库为例,简要介绍如何使用朴素贝叶斯分类器进行分类。 首先,导入需要的库: ```python from sklearn.naive_bayes import MultinomialNB from sklearn.feature_extraction.text import CountVectorizer ``` 然后,准备训练数据和测试数据。训练数据是一组已经标记好的数据,每个数据包含一条文本和对应的类别。测试数据是用来评估分类器性能的数据。 接下来,对文本数据进行向量化处理。使用CountVectorizer将文本转换为特征向量,以便分类器能够处理。比如,将每个文本表示为词频向量。 ```python vectorizer = CountVectorizer() train_X = vectorizer.fit_transform(train_texts) test_X = vectorizer.transform(test_texts) ``` 然后,创建朴素贝叶斯分类器对象,并用训练数据拟合分类器模型。 ```python classifier = MultinomialNB() classifier.fit(train_X, train_labels) ``` 最后,使用训练好的分类器对测试数据进行分类预测。 ```python predictions = classifier.predict(test_X) ``` 以上就是使用Python朴素贝叶斯分类器进行分类的简要流程。通过调整特征提取方式、调整模型参数等,我们可以改进分类器的性能。同时,学习更多关于朴素贝叶斯分类器的理论和实践知识也有助于更好地使用该算法。 ### 回答3: 朴素贝叶斯分类器是一种基于概率论和贝叶斯定理的分类算法。它的名字的“朴素”是因为它假设特征之间相互独立,即每个特征对分类结果的影响是相互独立的。这种假设简化了模型的计算过程,使朴素贝叶斯分类器能够高效地进行分类。 在Python,可以使用scikit-learn库朴素贝叶斯分类器来实现。具体步骤如下: 1. 导入所需库: ```python from sklearn.datasets import load_iris from sklearn.naive_bayes import GaussianNB from sklearn.model_selection import train_test_split from sklearn.metrics import accuracy_score ``` 2. 加载数据集: ```python iris = load_iris() X = iris.data # 特征数据 y = iris.target # 分类结果 ``` 3. 划分训练集和测试集: ```python X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42) ``` 4. 创建朴素贝叶斯分类器对象: ```python nb_classifier = GaussianNB() ``` 5. 拟合数据并进行预测: ```python nb_classifier.fit(X_train, y_train) y_pred = nb_classifier.predict(X_test) ``` 6. 计算分类准确率: ```python accuracy = accuracy_score(y_test, y_pred) ``` 朴素贝叶斯分类器在处理文本分类垃圾邮件过滤等任务上表现良好。虽然它假设特征之间相互独立,但实际应用还是能取得不错的效果。当特征之间的依赖性较强时,可以使用其他更复杂的分类算法来取代朴素贝叶斯分类器。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

且听风吟~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值