机器学习:朴素贝叶斯

纵然是瞬间 提交于 2020-02-15 00:20:45

朴素贝叶斯(Naive Bayesian)分类器可以给出一个最优的类别猜测结果,同时给出这个猜测的概率估计值

优点:在数据较少的情况下仍然有效,可以处理多类别问题
缺点:对于输入数据的准备方式较为敏感

贝叶斯准则

公式
  \(\small P(C|X)=\frac{P(X|C)}{P(X)}P(C)\)
以文本分类为例
  X 是一个词组 \(\small (x_{0}, x_{1}, x_{2}, ..., x_{n})\)
  P(X)X 组词出现的概率
  P(C) 是标签 C 出现的概率
  P(X|C) 是标签 C 出现 X 词组的概率
  P(C|X)X 词组是分类 C 的概率
又有
  \(\small P(X) = P(x_{0})*P(x_{1})*P(x_{2})*...*P(x_{n})\)
  \(\small P(X|C) = P(x_{0}|C)*P(x_{1}|C)*P(x_{2}|C)*...*P(x_{n}|C)\)
  
对于特定的 X,由于 P(X) 是一样的
只需要比较 \(\small P(X|C_{i})P(C_{i})\)\(\small P(X|C_{j})P(C_{j})\)
就可以知道是 X 是属于分类 i 的可能性大还是属于分类 j 的可能性大
并且可以求出 X 属于不同分类的概率

朴素

假定特征之间相互独立,并且同等重要,这样的假设虽然不完全正确(比如单词的含义不是独立而是和上下文有关,有些单词比如邮件开头的 Hi 并不重要),但使得算法更简单,同时也有效果

代码

# coding=utf-8

"""
以留言分类为例子
一个一维数组代表一条留言,每个元素代表一个单词
判断留言是否不当内容
"""

import numpy as np


# 从样本数据集创建词典
def createVocabList(dataSet):
    """
    dataSet - 样本数据集,二维数组,每一行是一条留言数据,每个元素是留言的一个单词
    """
    vocabSet = set()

    for document in dataSet:
        # 将样本集中出现过的单词存到一个集合并去重
        vocabSet = vocabSet | set(document)

    # 将结果转成列表,返回作为词典
    return list(vocabSet)


# 将一条留言转换成一个特征向量
def convertDoc2Vec(vocabList, inputDoc):
    """
    vocabList - 词典
    inputDoc  - 留言
    """

    # 创建一个长度和词典一样的特征向量
    # 特征向量的每个值取 1 或 0,代表词典中的这个词在留言中存在,0 代表不存在,全部初始化为 0
    returnVec = [0]*len(vocabList)

    # 遍历留言的每个单词
    for word in inputDoc:
        if word in vocabList:
            # 单词存在词典中,特征向量的对应位置设为 1
            returnVec[vocabList.index(word)] = 1
        else:
            # 不存在与词典中,忽略
            print "the word: %s is not in my Vocabulary!" % word

    return returnVec


# 朴素贝叶斯训练
def trainNB(trainMatrix, trainCategory):
    """
    trainMatrix   - 用于训练的样本,二维 numpy 数组,行数代表留言数,列数是词典收录的词量
                    如果词典的第三个词在第二个样本出现过,则 trainMatrix[1][2] = 1,否则 trainMatrix[1][2] = 0
    trainCategory - 样本数据的分类,一维 numpy 数组,1 代表不当言论,0 代表普通言论
    """

    # 样本数
    numTrainDocs = len(trainMatrix)

    # 词典大小,即特征向量的长度
    numWords = len(trainMatrix[0])

    # 不当留言在所有留言中的概率,即 P(C1)
    # sum 对一维数组求和,由于只有 1 和 0 两种值,求和结果就是不当言论的总数
    p1 = np.sum(trainCategory)/float(numTrainDocs)

    # 每个单词出现在分类 0 的次数,和出现在分类 1 的次数,以及分类 0 的总词数,分类 1 的总词数
    # 目的是为了求出 P(Xn|C0),P(Xn|C1)
    # 初始化为 1 和 2 是为了防止出现统计为 0 的情况,不然 P(Xn|C0),P(Xn|C1) 会导致最终结果为 0
    # 选 1 和 2 这样如果完全统计不到,那就默认 50% 的概率
    p0WordNum = np.ones(numWords)
    p1WordNum = np.ones(numWords)
    p0TotalNum = 2.0
    p1TotalNum = 2.0

    # 遍历每个留言
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            # 统计每个单词在类别 1 (既不当言论) 中出现的次数
            p1WordNum += trainMatrix[i]
            # 统计类别 1 的总单词数
            p1TotalNum += np.sum(trainMatrix[i])
        else:
            # 统计类别 0
            p0WordNum += trainMatrix[i]
            p0TotalNum += np.sum(trainMatrix[i])

    # 统计每个单词在类别 0 和 类别 1 中出现的概率,即 P(Xn|C0) 和 P(Xn|C1)
    # 取对数是为了防止下溢出,概率太小有可能被当成 0 处理
    p1Vec = np.log(p1WordNum/p1TotalNum)
    p0Vec = np.log(p0WordNum/p0TotalNum)

    p0 = np.log(1.0 - p1)
    p1 = np.log(p1)

    # 返回 log(P(Xn|C0)),log(P(Xn|C1)),log(P(C0)),log(P(C1))
    # 对于新留言 X,只要比较 log(P(X|C0)*P(C0)) 和 log(P(X|C1)*P(C1))
    # 相当于比较 log(P(X|C0)) + log(P(C0)) 和 log(P(X|C1)) + log(P(C1))
    # 就可以判断属于哪种分类的概率大
    return p0Vec, p1Vec, p0, p1


# 朴素贝叶斯分类
def classifyNB(featureVec, p0Vec, p1Vec, p0, p1):
    """
    featureVec - 要分类的留言,以词典的特征向量表示
    p0Vec - log(P(X|C0))
    p1Vec - log(P(X|C1))
    p0 - log(P(C0))
    p1 - log(P(C1))
    """

    # 比较 log(P(X|C0)) + log(P(C0)) 和 log(P(X|C1)) + log(P(C1))
    # 其中
    #      log(P(X|C0)) = log(P(X1|C0)) + log(P(X2|C0)) + ... + log(P(Xn|C0))
    #      log(P(X|C1)) = log(P(X1|C1)) + log(P(X2|C1)) + ... + log(P(Xn|C1))
    p1 = sum(featureVec * p1Vec) + p1
    p0 = sum(featureVec * p0Vec) + p0

    # 哪个值大,就认为文档属于哪个分类,如果要给出具体概率还要进一步计算
    if p1 > p0:
        return 1
    else:
        return 0


# 测试
def testNB(dataSet, labelSet, testData):
    """
    dataSet  - 样本数据集,二维数组,每一行是一条留言数据,每个元素是留言的一个单词
    labelSet - 样本数据集的分类标签,一维数组
    testData - 要分类的数据,一位数组,每个元素是一个单词
    """
    # 创建词典
    vocabList = createVocabList(dataSet)

    # 转为词典特征向量数组
    trainMat = []
    for doc in dataSet:
        # 将每个文档转换为词典特征向量
        trainMat.append(convertDoc2Vec(vocabList, doc))

    # 训练
    p0Vec, p1Vec, p0, p1 = trainNB(np.array(trainMat), np.array(labelSet))

    # 将要预测的数据转为词典特征向量
    testVec = np.array(convertDoc2Vec(vocabList, testData))

    # 预测
    print classifyNB(testVec, p0Vec, p1Vec, p0, p1)




易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!