朴素贝叶斯

1、理论部分

1.1、贝叶斯公式

$$P(c|x)=\frac{P(c)P(x|c)}{P(x)}\qquad\dots(1)$$
其中,$P(c)$是类“先验概率”;$P(x|c)$是样本$x$相对于类标记$c$的类条件概率,或称为“似然”;$P(x)$是用于归一化的“证据因子”。对给定样本$x$,证据因子$P(x)$与类标记无关,因此估计$P(c|x)$的问题就转化为如何基于训练数据$D$来估计先验$P(c)$和似然$P(x|c)$
类先验概率$P(c)$表达了样本空间中各类样本所占的比例,根据大数定律,当训练集包含充足的独立同分布样本时,$P(c)$可通过各类样本出现的频率来进行估计。
对类条件概率$(P(x|c))$来说,由于它涉及关于$x$所有属性的联合概率,直接根据样本出现的频率来估计将会遇到严重的困难。为避开这个障碍,朴素贝叶斯分类器采用了“属性条件独立性假设”;对已知类别,假设所有属性相互独立。换言之,假设每个属性独立的对分类结果产生影响。
基于属性条件独立性假设,贝叶斯公式可重写为: $$P(c|x)=\frac{P(c)P(x|c)}{P(x)}\qquad=\frac{P(c)}{P(x)}\prod_{i=1}^d{P(x_i|c)}\dots(2)$$ 其中$d$为属性数目,$x_i$为$x$在第i个属性上的取值
由于对于所有类别来说$P(x)$相同,因此贝叶斯判定准则:$$h_{nb}(x)=arg max_{c\in y}P(c)\prod_{i=1}^d{P(x_i|c)}\dots(3)$$
显然,朴素贝叶斯分类器的训练过程就是基于训练集$D$来估计类先验概率$P(c)$,并为每个属性估计条件概率$P(x_i|c)$
令$D_c$表示训练集$D$中第$c$类样本组成的集合,若有充足的独立同分布样本,则可容易的估计出先验概率:$$P(c)=\frac{|D_c|}{|D|}\dots(4)$$
对离散属性而言,令$D_{c,x_i}$表示$D_c$中在第$i$个属性上取值为$x_i$的样本组成的集合,则条件概率$P(x_i|c)$可估计为$$P(x_i|c)=\frac{|D_{c,x_i}|}{|D_c|}\qquad\dots(5)$$ 为了避免其他属性携带的信息被训练集中未出现的属性值抹去,在估计概率值时通常要进行“平滑”,常用“拉普拉斯修正”。具体来说,令$N$表示训练集$D$中可能的类别数,$N_i$表示第$i$个属性可能的取值数,则(4)(5)两式分别修正为:$$\hat{P}(c)=\frac{D_c+1}{|D|+N}\qquad\dots(6)$$ $$\hat{P}(x_i|c)=\frac{D_{c,x_i}+1}{|D|+N}\qquad\dots(7)$$

2、实战演练

2.1、加载数据集

import numpy as np
def loadDataSet():
    """
    导入数据, 1代表脏话
    @ return postingList: 数据集
    @ return classVec: 分类向量
    """
    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

导入训练集及其分类,1代表是脏话,0代表不是

loadDataSet()
([['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])

2.2、创建单词表

将训练数据中的每一个词都存储到词库中。

def createVocabList(dataSet):
    """
    创建词库
    @ param dataSet: 数据集
    @ return vocabSet: 词库
    """
    vocabSet = set([])
    for document in dataSet:
        # 求并集
        vocabSet = vocabSet | set(document)
    return list(vocabSet)
listOPosts, listClasses = loadDataSet()
myVocabList = createVocabList(listOPosts)
myVocabList
['quit',
 'how',
 'problems',
 'mr',
 'dalmation',
 'garbage',
 'love',
 'stop',
 'maybe',
 'him',
 'take',
 'to',
 'stupid',
 'food',
 'not',
 'cute',
 'buying',
 'flea',
 'park',
 'help',
 'ate',
 'dog',
 'licks',
 'please',
 'so',
 'has',
 'my',
 'is',
 'posting',
 'I',
 'steak',
 'worthless']

2.3、生成词向量

def setOfWords2Vec(vocabList, inputSet):
    """
    文本词向量.词库中每个词当作一个特征,文本中有该词,该词特征就是1,没有就是0
    @ param vocabList: 词表
    @ param inputSet: 输入的数据集
    @ return returnVec: 返回的向量
    """
    returnVec = [0] * len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else:
            print("单词: %s 不在词库中!" % word)
    return returnVec
testEntry = ['love', 'my', 'dalmation']
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
thisDoc
array([0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 1, 0, 0, 0, 0, 0])

2.4、训练分类器

def trainNB0(trainMatrix, trainCategory):
    """
    训练
    @ param trainMatrix: 训练集
    @ param trainCategory: 分类
    """
    numTrainDocs = len(trainMatrix)   # 训练数据的长度
    numWords = len(trainMatrix[0])    # 训练数据的词汇量
    pAbusive = sum(trainCategory) / float(numTrainDocs)
    # 防止某个类别计算出的概率为0,导致最后相乘都为0,所以初始词都赋值1,分母赋值为2.  拉普拉斯修正
    p0Num = np.ones(numWords)   # 分子
    p1Num = np.ones(numWords)
    p0Denom = 2   # 分母
    p1Denom = 2
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    # 这里使用log函数,方便计算,因为最后是比较大小,所有对结果没有影响。
    p1Vect = np.log(p1Num / p1Denom)   # P^(x_1|c)
    p0Vect = np.log(p0Num / p0Denom)   # P^(x_2|c)
    return p0Vect, p1Vect, pAbusive

2.5、 进行分类

def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    """
    判断大小
    """
    p1 = sum(vec2Classify * p1Vec)  # P(c)*P(x_1|c)
    p0 = sum(vec2Classify * p0Vec)  # P(c)*P(x_2|c)
    if p1 > p0:
        return 1
    else:
        return 0

2.6、测试

def testingNB():
    listOPosts, listClasses = loadDataSet()
    myVocabList = createVocabList(listOPosts)
    trainMat = []
    for postinDoc in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    print(trainMat)   # 查看训练集矩阵
    p0V, p1V, pAb = trainNB0(np.array(trainMat), np.array(listClasses))
    testEntry = ['love', 'my', 'dalmation']
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
    testEntry = ['stupid', 'garbage']
    thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))
testingNB()
[[0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 0, 0], [0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1], [0, 1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0], [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]]
['love', 'my', 'dalmation'] classified as:  0
['stupid', 'garbage'] classified as:  1