python3.5.2之《机器学习实战》之朴素贝叶斯——python3.5.2+Eclipse4.5.2+pydev5.2.0

'''
    这一次的代码是朴素贝叶斯分类:我们将充分利用python的文本处理能力将文档切分为词向量,然后利用
    词向量对文档进行分类。测试垃圾邮件的过滤效果。
    贝叶斯决策理论的核心思想是:选择高概率对应的类别。
    ***********************************************
    全文:条件概率公式和利用条件概率公式分类
    条件概率公式的内容:P(A|B) = P(A,B)/P(B) = P(B|A)*P(A)/(B)
    如何利用这个进行分类:假设c1和类c2,那么我们需要计算概率p(c1|x,y)和p(c2|x,y)的大小并进行比较:
        如果:p(c1|x,y)>p(c2|x,y),则(x,y)属于类c1;p(c1|x,y)<p(c2|x,y),则(x,y)属于类c2
    那么对于这些概率就行含义解释:P(x,y|c) 是说:已知这个类别是C,能取到(x,y)的概率;那么反过来P(c|x,y)的
    含义是什么,用同样的思路来解释,就是:在给定(x,y)的条件下,这个点属于C的概率值,那么这样的概率应该如何计算?
    就用贝叶斯准则来变换计算:p(ci|x,y)=p(x,y|ci)*p(ci)/p(x,y)。只要得到了这三个概率,就可以求出来。
    ***********************************************
    1.先说样本的特征与数量的关系
        由统计学可以知道,当一个特征需要N个样本时候,那么10个特征就需要N^10样本数,
    但是,如果特征之间是相互独立的,样本数就可以从N^10降到N*10。
    ***********************************************
    2.什么是朴素呢
        #理解之一#:“独立”。这种独立是指统计意义上的独立不是真正的特征相互没有关系的独立。
    举个例子,比如单词bread出现在delicious和heathy后面的频率是相同的:这种假设并不正确!
    实际情况上是delicious后面的改了大的多——这也是朴素的一个含义。
        #理解之二#:每个特征是同等重要的。这也是有问题的,比如一个评论是否涉及恐吓,只需看几个特征就行,
    不需要全部的单词。
    ***********************************************
    下面做的是文本分类
'''

"""
    拆分文本,准备数据
    要从文本中获取特征,显然我们需要先拆分文本,这里的文本指的是来自文本的词条,每个词条是字符的任意组合。词条可以为单词,当然也可以是URL,IP地址或者其他任意字符串。将文本按照词条进行拆分,根据词条是否在词汇列表中出现,将文档组成成词条向量,向量的每个值为1或者0,其中1表示出现,0表示未出现。
    先给出文本转换为词向量的过程
    注意:****************************************************
    贝叶斯分类器是有两个的,这里只考虑出不出现,其他不考虑
    ****************我们考虑词语的出现次数*************************
"""
#导入numpy
from numpy import *

#目的:创建实验样本
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'],
    ['my','licks','ate','my','steak','how','to','stop','him'],
    ['quit','buying','worthless','dog','food','stupid']]
    #由人工标注的每篇文档的类标签,1代表侮辱,0代表正常言论
    classVec = [0,1,0,1,0,1]
    return postingList, classVec

#统计所有文档中出现的词条列表
#目的:创建一个包含所有文档中出现的不重复的词汇的列表
def createVocabList(dataSet):
    #创建一个存放词条的集合
    vocabSet = set([])
    #遍历文档集合中的每一篇文档
    for document in dataSet:
        #将文档列表转换为集合的形式,保证每一个词条的唯一性,所以用集合。
        #然后与vocabSet取并集,向vocabSet中添加没有出现的新词条
        vocabSet = vocabSet | set(document)
    #把集合转换为列表,便于接下来的处理
    return list(vocabSet)
    #上面的列表向量是特征,就是我们需要对照的。下面的程序会输出对应的1和0
    #有的是1有的是0,如果敏感的有1就是违法的。

#根据词条列表中的词条是否在文档中出现(1,或者0),把文档转换为词条向量
#@vocabSet : 包含训练文档中所有的出现词条。这就是词汇表
#@inputSet : 某个文档
def setOfWord2Vec(vocabSet , inputSet):
    #新建一个长度为vocabSet的列表,并且每个维度元素初始化为0
    returnVec = [0]*len(vocabSet)
    #遍历文档中的每一个词条
    for word in inputSet:
        #如果词条在词条列表中出现
        if word in vocabSet:
            #通过列表获取当前word的索引(下标
            #将词条向量中的对应下标的项由0改为1
            returnVec[vocabSet.index(word)] = 1
        else:
            print('这个单词: &s 没有出现在这些文档里面!!! ' % 'word')
    #返回input转化后的词条向量
    return returnVec
'''
    注释:****************************************************
    上面用的是词集模型,即对于一篇文档,将文档中是否出现某一词条作为特征,即特征只能为0不出现或者1出现;然后,一篇文档中词条的出现次数也可能具有重要的信息,于是我们可以采用词袋模型,在词袋向量中每个词可以出现多次,这样,在将文档转为向量时,每当遇到一个单词时,它会增加词向量中的对应值
    代码如下:
    def bagOfWords2VecMN(vocabList,inputSet):
    #词袋向量
    returnVec=[0]*len(vocabList)
    #遍历文档中的每一个词条
    for word in inputSet:
        if word in vocabList:
            #某词每出现一次,次数加1
            returnVec[vocabList.index(word)]+=1
    return returnVec
'''
    
    
'''
    下面是训练算法——从词向量计算概率
    ****************************************************
    现在我要重写贝叶斯准则:把原来的(x,y)改成w,这个w是向量,里面包含了多个数值,也就是词汇表
    数值的个数和词汇表中的词的个数相同:p(ci|w)=p(w|ci)*p(ci)/p(w)
    我们使用上述的公式,对每个类进行计算这个值,然后比较这两个概率值的大小,就可以确定这词条属于哪个类
    ****************************************************
    计算流程:首先对于类别i(侮辱性或者非侮辱性的留言)中文档数除以总的文档数来计算概率p(ci);
    然后给予条件独立假设,讲w展开为一个个独立的特征(朴素的特征,相互独立),那么上述公式可以写作:
        p(w|ci)=p(w0|ci)*p(w1|ci)*...p(wN|ci),简化了计算过程
        p(w|ci)=p(w0|ci)*p(w1|ci)*...p(wN|ci),简化了计算过程
    函数的伪代码:
        计算每个类别文档的数目
        计算每个类别占总文档数目的比例#p(ci)
        
        对每一篇文档:  
            对每一个类别:
                如果词条出现在文档中->增加该词条的计数值#统计每个类别中出现的词条的次数
                增加所有词条的计数值#统计每个类别的文档中出现的词条总数 
            对每一个类别:
                对每个词条:
                    将各个词条出现的次数除以类别中出现的总词条数目得到条件概率
            返回每个类别各个词条的条件概率和每个类别所占的比例
    注释:****************************************************
    向量表示:a = array([1, 2]) 是列向量
            b = array([[1,2]]) 是行向量
            c = array([[1,2],[1,2]])是一个2x2的矩阵 
'''
#训练算法,从词向量计算概率p(w0|ci)...及p(ci)
#@trainMatrix : 矩阵——由每篇文档的词条向量组成的文档矩阵
#是通过上面转换为数字的矩阵
#@trainCategory : 向量——每篇文档的类标签组成的向量
#上面两个可以认为是loadDataSet()函数的翻版
def trainNB0(trainMatrix, trainCategory):
    #获取文档矩阵中的文档数目
    numTrainDocs = len(trainMatrix)
    #获取词条向量的长度
    numWords = len(trainMatrix[0])
    #所有文档中属于类别1的所占的比例P(c1)
    '''
    注释:由于这二元分类的问题,所以求出p(1),另外一个1-p(1)就行了。
    对于多元分类是需要修改的。
    ****************************************************
    sum()方法:
# sum的最常见用法计算一个序列的累加和
print(sum([1, 2, 3]))
# 6
 
# # sum 函数原型是 sum(iterable, start), 还可以给个初始值
print(sum([1, 2, 3], 10))
# 16
 
# 另外一个比较Hack一些的用法, 展开2层的嵌套列表
iterable = [[1, 2], [3, 4], [5, 6], [7, 8]]
start = [0]
print(sum(iterable, start))
# [0, 1, 2, 3, 4, 5, 6, 7, 8]
 
# sum函数是和下面的过程是等效的
for element in iterable:
    start += element
print(start)
# [0, 1, 2, 3, 4, 5, 6, 7, 8]
 
# 实际上sum可以看成reduce()的特殊情况
from functools import reduce
from operator import add
start = []
print(reduce(add, iterable, start))
# [1, 2, 3, 4, 5, 6, 7, 8]    
    '''
    #因为侮辱就是1,不侮辱就是0,求和就是侮辱的综合
    pAbusive = sum(trainCategory)/float(numTrainDocs)
    #创建一个长度为词条向量等长的列表
    p0Num = zeros(numWords);p1Num = zeros(numWords)
    p0Denom = 0.0 ;p1Denom = 0.0
    #遍历每一篇文档的词条向量
    for i in range(numTrainDocs):
        #如果这个词条向量对应的标签为1
        if trainCategory[i] == 1:
            #统计出所有类别为1的词条向量中各个词条出现的次数
            p1Num += trainMatrix[i]
            #统计类别为1的词条向量
            #意思是统计类1所有文档中出现单词的数目
            p1Denom += sum(trainMatrix[i])
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    #利用Numpy数组计算p(wi|c1)
    p1Vect = p1Num/p1Denom
    #利用Numpy数组计算p(wi|c0)
    p0Vect = p0Num/p0Denom
    return p0Vect,p1Vect,pAbusive
'''
    算法是需要改进的
    ****************************************************
    其中之一是:如果任意一项为0,那么后面的乘积也是0.为了降低这个影响,我们一般采用拉普拉斯平滑
    分子上添加a,一般是1,分母上添加ka,k是类别总数,这样,所有的词的出现数初始化为1,坟墓初始化为2*1=2
    #p0Num=ones(numWords);p1Num=ones(numWords)
    #p0Denom=2.0;p1Denom=2.0
    ****************************************************
    2)解决下溢出问题
    正如上面所述,由于有太多很小的数相乘。计算概率时,由于大部分因子都非常小,最后相乘的结果四舍五入为0,造成下溢出或者得不到准确的结果,所以,我们可以对成绩取自然对数,即求解对数似然概率。这样,可以避免下溢出或者浮点数舍入导致的错误。同时采用自然对数处理不会有任何损失。
    #p0Vect=log(p0Num/p0Denom);p1Vect=log(p1Num/p1Denom)
'''
    
#朴素贝叶斯分类函数
#@vec2Classify:待测试的词条向量
#@p0Vec :类别0所有文档中各个词条出现的频数p(wi|c0)
#@p1Vec : 类别1所有文档中各个词条出现的频数p(wi|c1)
#@pClass1 : 类别为1的文档占文档总数比例
def classifyNB(vec2Classify , p0Vec ,p1Vec, pClass1):
    #根据朴素贝叶斯分类函数分别计算待分类文档属于类1和类0的概率
    p1 = sum(vec2Classify*p1Vec)+log(pClass1)
    p0 = sum(vec2Classify*p0Vec)+log(1.0-pClass1)
    if p1 > p0:
        return 1
    else:
        return 0

#分类测试整体函数
def testingNB():
    #由数据集获取文档矩阵个类标签向量
    listOPosts,listClasses = loadDataSet()
    #统计所有文档中出现的词条,存入词条列表
    myVocabLists = createVocabList(listOPosts)
    #创建新的列表
    trainMat = []
    for postinDoc in listOPosts:
        #将每篇文档利用words2Vec函数转换为词条向量,存入文档矩阵中
        trainMat.append(setOfWord2Vec(myVocabLists,postinDoc))
    #将文档矩阵和类标签向量转为Numpy的数组形式,方便下来的概率计算
    #调用训练函数,得到相应的概率值
    p0V,p1V,pAb = trainNB0(array(trainMat) , array(listClasses))
    #测试文档
    testEntry = ['love','my','dalmation']
    #将测试文档转为词条向量,并且转为numpy数组的形式
    thisDoc = array(setOfWord2Vec(myVocabLists,testEntry))
    #利用贝叶斯分类函数对测试文档进行分类并且打印
    print(testEntry , 'classified as:' , classifyNB(thisDoc,p0V,p1V,pAb))
    #第二个测试文档
    testEntry1 = ['stupid','garbage']
    #同样转为词条向量,并转为Numpy数组的形式
    thisDoc1=array(setOfWord2Vec(myVocabLists,testEntry1))
    print(testEntry1,'classified as:',classifyNB(thisDoc1,p0V,p1V,pAb))
    

'''
    1.对于一个文本字符串,可以使用python的split()方法对文本进行切割
    2.使用正则表达式切分句子:
        import re 
        re.compile('\\w*')——得到一系列词组成的词表,但是里面的空字符串还是要去掉,通过长度等于0的字符。
    3.由于我们是统计某一词是否出现,不考虑其大小写,所有还可以将所有词转为小写字符,即lower(),相应的,转为大写字符为upper()
    4.需要注意的是,由于是URL,因而可能会出现en和py这样的单词。当对URL进行切分时,会得到很多的词,因此在实现时也会过滤掉长度小于3的词。当然,也可以根据自己的实际需要来增加相应的文本解析函数。
'''
#贝叶斯算法实例:过滤垃圾邮件

#处理数据长字符串
#1 对长字符串进行分割,分隔符为除单词和数字之外的任意符号串
#2 将分割后的字符串中所有的大写字母变成小写lower(),并且只
#3 保留单词长度大于3的单词
def textParse(bigString):
    #导入正则表达式库
    import re
    #把字符串按照字母划分,排除了符号,空格等等
    listOfTokens = re.split(r'\W*',bigString)
    #格式:结果 for x in 列表或者别的 if x与条件
    return [tok.lower() for tok in listOfTokens if len(tok)>2]

def spamTest():
    #新建三个列表
    docList = [];classList = [];fullTest = []
    #1  由1到26
    for i in range(1,26):
        #打开并且读取指定目录下的文本文中的长字符串,并且进行处理返回
        '''
                            这里坑爹的是文档《23.Txt》中,“SciFinance?is ”改成“SciFinance is ”即可
        另外'D://Document And Settings3/lqz/Desktop/Walden2.txt'只有:的是双杠的
        '''
        wordList = textParse(open('email/spam/%d.txt' %i).read())
        #将得到的字符串列表添加到docList
        #日语wordList是字符串列表,就会得到列表,多个框框而已
        docList.append(wordList)
        #将字符串列表中的元素添加到fullList
        #得到的应该是列表
        fullTest.extend(wordList)
        #类列表添加标签1
        classList.append(1)
        #打开并取得另外一个类别为0的文件,然后进行处理
        wordList = textParse(open('email/ham/%d.txt' %i).read())
        docList.append(wordList)
        fullTest.extend(wordList)
        classList.append(0)
    #将所有邮件中出现的字符串构建成字符串列表
    vocabList = createVocabList(docList)
    #构建一个大小为50的整数列表和一个空列表
    trainingSet = list(range(50));testSet = []
    #随机选取1-50中的10个数,作为索引,构建测试集
    for i in range(10):
        #随机选取1-50中的一个整数型
        #random.uniform(0,len(trainingSet))是随机一个浮点数
        randIndex = int(random.uniform(0,len(trainingSet)))
        #将选出的数的列表索引值添加到testSet列表中
        testSet.append(trainingSet[randIndex])
        #从整数列表中删除选出的数,防止下次再次选出
        #同时将剩下的作为训练集
        '''
        python3.x , 出现错误 'range' object doesn't support item deletion
原因:python3.x   range返回的是range对象,不返回数组对象
        解决方法:把 trainingSet = range(50) 改为 trainingSet = list(range(50))
        '''
        del(trainingSet[randIndex])
        #!!!!!!删除后,数组[index]!=index了
    #新建两个列表
    trainMat = [];trainClasses = []
    #遍历训练集中的吗每个字符串列表
    for docIndex in trainingSet:
        #将字符串列表转换为词条向量,然后添加到训练矩阵中
        trainMat.append(setOfWord2Vec(vocabList, docList[docIndex]))
        #将这个邮件的类标签列入训练类标签列表中
        trainClasses.append(classList[docIndex])
    #计算贝叶斯函数需要的概率值并返回
    p0V,p1V,pSpam = trainNB0(array(trainMat), array(trainClasses))
    errorCount = 0
    #遍历测试集中的字符串列表
    for docIndex in testSet:
        #同样将测试机中的字符串列表转为词条向量
        wordVector = setOfWord2Vec(vocabList, docList[docIndex])
        #对于测试集中字符串向量进行预测分类,分类结果不等于实际结果
        if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
            errorCount+=1
        else:
            print('the error rate is : ',float(errorCount)/len(testSet))
            
'''
    实例:朴素贝叶斯从个人广告中获取区域倾向
        本例中,我们从不同的城市的RSS源头中获取同类型广告信息,比较两个城市人们在广告用词上是否不同。如果不同,那么各自的常用词是哪些?而从人们的用词当中,我们能否对不同城市的人所关心的内容有所了解?如果能得到这些信息,分析过后,相信对于广告商而言具有不小的帮助。
    主要是通过用词不同,来推断信息
'''

        
        
        
        
        
        
        
        
        
        
        
        
        
        
        

猜你喜欢

转载自blog.csdn.net/xinjieyuan/article/details/81368454