机器学习实战--朴素贝叶斯分类器--学习笔记

import numpy as np
from functools import reduce

"""
函数说明:创建实验样本
Parameters:无
Returns:
    postingList - 实验样本切分的词条
    classVec - 类别标签向量
Modify:
    2019-03-21
"""


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]  # 标签向量 0:非侮辱性 1:侮辱性
    return postingList, classVec


"""
函数说明:将切分的实验样本整理成不重复的词条列表,即词汇表
         (把出现过的词汇记录下来)
Parameters:
    dataSet - 实验样本划分的词条
Returns:
    vocabSet - 词汇表
Modify:
    2019-03-21
"""


def createVocabList(dataSet):
    vocabSet = set([])  # 创建集合,存放出现过的词汇。
    for document in dataSet:
        vocabSet = vocabSet | set(document)  # 集合的或操作即两个集合取并集
    # 集合的性质:“无序非重”
    # 将无序的集合变成有序的列表,方便使用
    return list(vocabSet)


"""
函数说明:根据词汇表,将词条向量化
Parameters:
    vocabList - 词汇表(记录了所有词汇)
    inputSet - 实验样本划分的词条
Returns:
    returnVec - 向量化后的词条
Modify:
    2019-03-21
"""


def setOfWords2Vec(vocabList, inputSet):
    print("\n词汇表=", vocabList, "\n词条=", inputSet)
    returnVec = [0] * len(vocabList)  # 长度与词汇表相同,用0填充的向量
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else:
            print("警告:词汇表中没有词汇", word)  # 正常情况下词汇表中应该会包含全部词汇
    print("向量化后的词条:\n", returnVec)
    return returnVec
    # 假设词汇表:                        [a,b,c,d,e,f,g,h]
    # 假设将进行向量化的词条:              [d,e,f,b]
    # 词条向量化:
    # 1:创建与词汇表等成的以0填充的向量: [0,0,0,0,0,0,0,0]
    # 2: 词条向量化:                     [0,1,0,1,1,1,0,0]


"""
函数说明:朴素贝叶斯分类器训练函数
Parameters:
    trainMatrix - 词条向量构成的矩阵
    trainCategory - 标签向量
Returns:
    p0Vect - 非侮辱性条件概率数组
    p1Vect - 侮辱性条件概率数组
    pAbusive - 文档属于侮辱类的概率
Modify:
    2019-03-21
"""
"""
P(侮辱性|(词汇1 词汇2 词汇3 ...词汇n))   词条中出现了这些词汇的条件下,词条具有侮辱性的概率
    = P(侮辱性)*P((词汇1 词汇2 词汇3 ...词汇n)|侮辱性)/P(词汇1 词汇2 词汇3 ...词汇n)
    = P(侮辱性)*P(词汇1|侮辱性)*P(词汇2|侮辱性)*P(词汇3|侮辱性)*......P(词汇n|侮辱性)/P(词汇1 词汇2 词汇3 ...词汇n)   假设条件独立
    
向量化后的词条集合:                                                       标签向量:
  [[1 0 1 0 1 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 1 0 0]             [0, 1, 0, 1, 0, 1]
   [0 0 0 1 0 0 0 0 0 0 1 1 0 0 1 0 0 0 0 0 0 0 1 1 0 0 0 0 1 0 1 0]
   [1 1 0 0 0 1 0 1 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0 1 1 0 0 0 0 0 0 0]
   [0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 1 0 0 0 0 1]
   [1 0 0 1 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 1 1 0 0 1 0 1 0 1 0 0 0 0]
   [0 0 0 0 0 0 0 0 1 1 1 1 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0]]
"""


def trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix)  # 词条总数
    numWords = len(trainMatrix[0])  # 每个词条中的词汇数
    pAbusive = sum(trainCategory) / float(numTrainDocs)  # P(侮辱性词条)= 侮辱性词条数/词条总数
    p0Num = np.zeros(numWords)  # 长度为词条长度,以0填充的矩阵
    p1Num = np.zeros(numWords)
    p0Demon = 0.0
    p1Demon = 0.0
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i]  # [词汇i出现在侮辱性词条中的次数]
            p1Demon += sum(trainMatrix[i])
        else:
            p0Num += trainMatrix[i]  # [词汇i出现在非侮辱性词条中的次数]
            p0Demon += sum(trainMatrix[i])
    p1Vect = p1Num / p1Demon  # [ P(词汇1|侮辱性) P(词汇2|侮辱性) P(词汇3|侮辱性)......P(词汇n|侮辱性)]           每个词汇属于侮辱词汇的概率
    p0Vect = p0Num / p0Demon  # [ P(词汇1|非侮辱性) P(词汇2|非侮辱性) P(词汇3|非侮辱性)......P(词汇n|非侮辱性)]   每个词汇属于非侮辱词汇的概率
    return p0Vect, p1Vect, pAbusive


"""
在使用trainNB0函数的时候发现一些问题:
  如果新实例文本,包含这种概率为0的分词,那么最终的文本属于某个类别的概率也就是0了。
  显然,这样是不合理的,为了降低这种影响,可以将所有词的出现数初始化为1,并将分母初始化为2。
  这种做法就叫做拉普拉斯平滑(Laplace Smoothing)又被称为加1平滑,是比较常用的平滑方法,它就是为了解决0概率问题。
  
除此之外,另外一个遇到的问题就是下溢出:
  这是由于太多很小的数相乘造成的。学过数学的人都知道,两个小数相乘,越乘越小,这样就造成了下溢出。
  在程序中,在相应小数位置进行四舍五入,计算结果可能就变成0了。为了解决这个问题,对乘积结果取自然对数。
  通过求对数可以避免下溢出或者浮点数舍入导致的错误。同时,采用自然对数进行处理不会有任何损失。
"""


def trainNB0_Plus(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix)  # 词条总数
    numWords = len(trainMatrix[0])  # 每个词条中的词汇数
    pAbusive = sum(trainCategory) / float(numTrainDocs)  # P(侮辱性词条)= 侮辱性词条数/词条总数
    p0Num = np.ones(numWords)  # 长度为词条长度,以0填充的矩阵
    p1Num = np.ones(numWords)
    p0Demon = 2.0
    p1Demon = 2.0
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i]  # [词汇i出现在侮辱性词条中的次数]
            p1Demon += sum(trainMatrix[i])
        else:
            p0Num += trainMatrix[i]  # [词汇i出现在非侮辱性词条中的次数]
            p0Demon += sum(trainMatrix[i])
    p1Vect = np.log(p1Num / p1Demon)  # log [ P(词汇1|侮辱性) P(词汇2|侮辱性) P(词汇3|侮辱性)......P(词汇n|侮辱性)]           每个词汇属于侮辱词汇的概率
    p0Vect = np.log(p0Num / p0Demon)  # log [ P(词汇1|非侮辱性) P(词汇2|非侮辱性) P(词汇3|非侮辱性)......P(词汇n|非侮辱性)]   每个词汇属于非侮辱词汇的概率
    return p0Vect, p1Vect, pAbusive


"""
函数说明:使用朴素贝叶斯分类器进行分类
Parameters:
    vec2Classify - 待分类的词条数组
    p0Vec - 每个词汇属于非侮辱词汇的概率    [ P(词汇1|非侮辱性) P(词汇2|非侮辱性) P(词汇3|非侮辱性)......P(词汇n|非侮辱性)]   
    p1Vec - 每个词汇属于侮辱词汇的概率      [ P(词汇1|侮辱性) P(词汇2|侮辱性) P(词汇3|侮辱性)......P(词汇n|侮辱性)]           
    pClass1 - 文档属于侮辱类的概率          P(侮辱类) 
Returns:
    0 - 属于非侮辱类
    1 - 属于侮辱类
Modify:
    2019-03-22
"""


def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = reduce(lambda x, y: x * y, vec2Classify * p1Vec) * pClass1
    p0 = reduce(lambda x, y: x * y, vec2Classify * p0Vec) * (1.0 - pClass1)
    """
    reduce(function,可迭代序列)  
              函数将一个数据集合(链表,元组等)中的所有数据进行下列操作:
                用传给 reduce 中的函数 function(有两个参数)先对集合中的第 1、2 个元素进行操作,
                得到的结果再与第三个数据用 function 函数运算,最后得到一个结果。
    lambda:python中生成匿名函数的方法
    
    例子:reduce(lambda x.y:x+y,[1,2,3,4])
    相当于  1+2  -> (1+2)+3  -> ((1+2)+3)+4
    
    P(侮辱|词条) = P(侮辱)*P(词条|侮辱)/P(词条)
    P(正常|词条) = P(正常)*P(词条|正常)/P(词条)
                        因为只需要比较词条属于侮辱类和非侮辱类的概率,所以:
    P(侮辱|词条) = P(侮辱)*P(词条|侮辱)
    P(正常|词条) = P(正常)*P(词条|正常)
    
                        因为朴素贝叶斯算法假设各条件相互独立:
    P(侮辱|词条) = P(侮辱)*P(词条|侮辱) = P(侮辱)*P(词汇1|侮辱)*P(词汇2|侮辱)*......P(词汇n|侮辱)
    P(正常|词条) = P(正常)*P(词条|正常) = P(正常)*P(词汇1|正常)*P(词汇2|正常)*......P(词汇n|正常)
    
    vec2Classify * p1Vec = [词汇i是否存在]*[P(词汇i|侮辱)]      
    例:[1,0,1,1,0]*[0.01,0.02,0.00,0.03,0.00]=[0.01,0.00,0.00,0.03,0.00]
    
    reduce(lambda x, y: x * y, vec2Classify * p1Vec) = P(词汇1|侮辱)*P(词汇2|侮辱)*......P(词汇n|侮辱)
    例:0.01*0.00*0.00*0.03*0.00    由此看出本算法存在缺陷,需要改进
    
    p1 = reduce(lambda x, y: x * y, vec2Classify * p1Vec) * pClass1
       = P(词条|侮辱) = P(侮辱)*P(词汇1|侮辱)*P(词汇2|侮辱)*......P(词汇n|侮辱)*P(侮辱)
       = P(侮辱|词条)
    """
    print("侮辱类概率:", p1, " 非侮辱类概率:", p0)
    if p1 > p0:
        return 1
    else:
        return 0


"""
由于想要通过取对数的方式解决下溢出
"""


def classifyNB_plus(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = sum(vec2Classify * p1Vec) + np.log(pClass1)  # log(ABCDE)=log(A)+log(B)+log(C)+log(D)+log(E)
    p0 = sum(vec2Classify * p0Vec) + np.log(1.0 - pClass1)
    print("侮辱类概率:", p1, " 非侮辱类概率:", p0)
    if p1 > p0:
        return 1
    else:
        return 0


"""
函数说明:测试朴素贝叶斯分类器
Parameters:
    无
Returns:
    无
Modify:
    2019-03-22
"""


def testingNB():
    list0Posts, listClasses = loadDataSet()  # 词条集合,词条类别标签
    myVocabList = createVocabList(list0Posts)  # 词汇表
    print("词汇表:\n", myVocabList)
    print("标签向量:\n", listClasses)
    trainMat = []
    for postinDoc in list0Posts:  # 逐个词条进行向量化
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    print("向量化后的词条集合:\n", np.array(trainMat))  # 为了方便展示,使用了numpy数组
    p0V, p1V, pAb = trainNB0_Plus(trainMat, listClasses)
    print("[P(词汇i|非侮辱性)]:\n", p0V)
    print("[P(词汇i|侮辱性)]:\n", p1V)
    print("P(侮辱性):\n", pAb)
    testEntry = ['love', 'my', 'dalmation']  # 测试样本1
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))  # 测试样本向量化
    if classifyNB_plus(thisDoc, p0V, p1V, pAb):
        print(testEntry, "具有侮辱性")
    else:
        print(testEntry, "不具侮辱性")


if __name__ == '__main__':
    testingNB()

猜你喜欢

转载自blog.csdn.net/qq_39514033/article/details/88741542