孙宇的技术专栏 大数据/机器学习

朴素贝叶斯

2014-12-12

阅读:


朴素贝叶斯

k近邻算法是根据与他接近的点进行比较,将最接近的结果返回做为推算结果。 决策树是总结出规律,并进行计算。

现在可以看出,k近邻算法通常是对一些数字类型的值进行计算距离(通常要进行归一处理),数据量越大,推测越准确。

决策树则可以从较小的数据中发现规律,总结一套流程的顺序。

有时候,我们得到了几个可能的结果;需要知道各个结果的概率是多少以此来判断。这就是贝叶斯。

优点:在数据较小的时候也可以用,可以处理的问题非常多。 缺点:对输入的数据格式要求较高。

案例:某条评论是否是侮辱性的

网站上经常会有评论功能,而评论需要屏蔽一些侮辱性质的语言。如何判断一个评论是否是有侮辱性?我们可以事先整理好一个词库,把一些词填充进去,如果评论中出现该词就认为它是侮辱性的。但这样不够智能,假如我们已经通过这种方式积累了一批数据,我们可以通过概率,让机器自己去找出哪个词最有可能是侮辱性的,然后再判断用户提交的评论里是否包含这些词,从而实现自动扩充词库并判定。

所以,我们需要做的是:计算出某段话是侮辱性的概率,即计算 p(c1|w) c1 表示是侮辱性;w 表示文本。由于文本是由多个单词组成的,于是上面的公式又可以转化为:p(c1|w0)p(c1|w1)...p(c1|wn)

单独拿出一个来观察:p(c1|w0) 表示文本中第一个单词是侮辱性的概率。它的计算方式是:

p(c1|w0) = p(w0|c1)p(c1) / p(w0)

p(c1) 表示侮辱性文本的概率,可以用现有数据中是侮辱性的文档除以文档总数。
p(w0) 表示某单词出现的概率,可以用 w0 出现的次数除以总的单词数。
p(w0|c1) 表示在侮辱性的文档中,w0 出现的概率。那就用侮辱性文本中 w0 出现的次数除以侮辱性文本单词总数。

思路:遍历现有所有数据,得到侮辱性和非侮辱性的文本总数以及单词总数。以及在两个类型数据中单词 w0 出现的次数。根据公式,分别计算出 w0 在两个类型中的概率。较大概率者即为该词所属的类型。

代码:

# -*- coding: utf-8 -*-
from numpy import *

def loadDataSet():
    # 构建几个测试用的语句。每个语句都按单词进行了分割。并且对每个语句都进行了是否侮辱性质的设置。为了方便后面的计算。
    postingList = [['我', '的', '天', '啊', '你', '真', '胖'],
                   ['今', '真', '是', '日', '了', '狗', '了', '啊'],
                   ['我', '非', '常', '喜', '欢', '这', '样', '的', '你'],
                   ['你', '是', '不', '是', '傻'],
                   ['真', '不', '知', '道', '怎', '么', '说', '你', '了'],
                   ['你', '傻', 'B', '了', '吧', '这', '么', '怂']]
    # 1 是侮辱性的;0不是
    classVec = [0, 1, 0, 1, 0, 1]  # 1 is abusive, 0 not
    return postingList, classVec


# 构建词汇表.把所有词去重
def createVocabList(dataSet):
    # 创建空集合
    vocabSet = set([])
    # 遍历数据集的每一行.把整个文档的进行去重
    for document in dataSet:
        vocabSet = vocabSet | set(document)
    return list(vocabSet)


# 把文本转换为向量.向量长度是词汇表的长度。输入文本中有哪些单词,就把词汇表中对应单词的位置标为 1
def setOfWords2Vec(vocabList, inputSet):
    # 创建空的新向量.
    returnVec = [0] * len(vocabList)
    # 遍历输入文本(是个单词向量).如果文本向量中的单词
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else:
            print "单词: %s 不在词库中!" % word
    return returnVec


# 处理原始测试数据
def trainNB0(trainMatrix, trainCategory):
    # 文本数(即向量数)
    numTrainDocs = len(trainMatrix)
    # 去重后,向量的长度,即单词总数
    numWords = len(trainMatrix[0])
    # 侮辱性的文本数除以总文本数。即侮辱性文本的概率。
    # 因为侮辱性的值是 1,所以 sub(trainCategory) 即侮辱性文本的个数
    pAbusive = sum(trainCategory) / float(numTrainDocs)
    # 初始化一个正常文本的向量
    p0Num = zeros(numWords)
    # 初始化一个侮辱文本的向量
    p1Num = zeros(numWords)
    # 正常属性的单词数
    p0Denom = 0.0
    # 侮辱性的单词数
    p1Denom = 0.0
    # 遍历每个文本.如果文本是侮辱性的,就把当前文本向量加到侮辱性向量上。最终得到的侮辱性向量是所有侮辱性文本的累加
    # 通过遍历该向量就知道哪个词出现的频率最高
    # 同理,如果文本不是侮辱性的,就把文本加到正常向量上
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])

    p1Vect = p1Num / p1Denom
    p0Vect = p0Num / p0Denom
    return p0Vect, p1Vect, pAbusive


# 计算概率并判断是否是侮辱性的
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    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()
    myVocabList = createVocabList(listOPosts)
    trainMat = []
    # 把测试数据的每一行都转换成向量.并合并在一起
    for postinDoc in listOPosts:
        trainMat.append(setOfWords2Vec(myVocabList, postinDoc))
    # 训练算法
    p0V, p1V, pAb = trainNB0(array(trainMat), array(listClasses))
    # 输出词库
    print "输出词库: "
    for loopWord in myVocabList:
        print loopWord
    # 正常内容的向量
    print "正常内容的向量: "
    print p0V
    # 侮辱性内容的向量
    print "侮辱性内容的向量"
    print p1V

    testEntry = ['爱', '你', '哟']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print "".join(testEntry), '的类型是: ', classifyNB(thisDoc, p0V, p1V, pAb)
    testEntry = ['狗', '日']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print "".join(testEntry), '的类型是: ', classifyNB(thisDoc, p0V, p1V, pAb)

testingNB()



代码执行的输出:

输出词库:
样B了日的天今这你是怂知吧怎胖真啊不狗非说喜常么欢我道傻
正常内容的向量: 
[ 0.04  0.    0.04  0.    0.08  0.04  0.    0.04  0.12  0.    0.    0.04
  0.    0.04  0.04  0.08  0.04  0.04  0.    0.04  0.04  0.04  0.04  0.04
  0.04  0.08  0.04  0.  ]
侮辱性内容的向量:
[ 0.          0.05263158  0.10526316  0.05263158  0.          0.
  0.05263158  0.05263158  0.10526316  0.10526316  0.05263158  0.
  0.05263158  0.          0.          0.05263158  0.05263158  0.05263158
  0.05263158  0.          0.          0.          0.          0.05263158
  0.          0.          0.          0.10526316]
单词: 爱 不在词库中!
单词: 哟 不在词库中!
爱你哟 的类型是:  0
狗日 的类型是:  1

词库,正常向量,侮辱性内容向量,是三个等长的向量。其中内容向量的各值表示当前值出现的频率。如:正常内容向量的第一个值是 0.04,表示 “样” 这个字在正常的文本中出现的频率是 4%。第二个值是 0,而它对应的词是 “B”,表示这个词在正常文本中没有出现过。

同理,侮辱性向量中的第一个值是 0,说明 “样” 在侮辱性内容中未出现过。

在两个向量中,找到频率最大的词,是正常内容向量中第 9 (index 是 8)的值,频率是 0.12,对应的词是 “你”,我们对照一下测试时的文本发现确实 “你” 这个字出现的次数最多。

另外下面对 “爱你哟” 和“狗日” 进行了判断。结果表示前者是正常,后者是侮辱性的。

注意:这种概率计算法需要测试的内容尽量多,越多越准确。比如上面的示例中可以看到:侮辱性向量中频率较高,正常向量中较低的值还有“了”,“你”。我们可以用 “服你了” 这一段话进行测试,发现它也会被判断为侮辱性。

案例:过滤垃圾邮件

有了判断侮辱词的案例。就可以扩展出判断垃圾邮件的功能。它们都是对文本内容进行判断。

思路:已经有一些邮件,我们已经知道它是正常邮件还是垃圾邮件。我们可以把所有邮件的内容合并,组成一个词库。然后对一部分邮件进行训练,对垃圾邮件内容的词进行标识。当训练完成后,我们就知道哪些词最能代码垃圾邮件的内容。以此为标准对其它邮件进行判断。

代码如下:

# -*- coding: utf-8 -*-
from numpy import *
import sys

reload(sys)
sys.setdefaultencoding('utf8')

# 构建词汇表.把所有词去重
def createVocabList(dataSet):
    # 创建空集合
    vocabSet = set([])
    # 遍历数据集的每一行.把整个文档的进行去重
    for document in dataSet:
        vocabSet = vocabSet | set(document)
    return list(vocabSet)

# 处理原始测试数据
def trainNB0(trainMatrix, trainCategory):
    # 文本数(即向量数)
    numTrainDocs = len(trainMatrix)
    # 去重后,向量的长度,即单词总数
    numWords = len(trainMatrix[0])
    # 侮辱性的文本数除以总文本数。即侮辱性文本的概率。
    # 因为侮辱性的值是 1,所以 sub(trainCategory) 即侮辱性文本的个数
    pAbusive = sum(trainCategory) / float(numTrainDocs)
    # 初始化一个正常文本的向量
    p0Num = zeros(numWords)
    # 初始化一个侮辱文本的向量
    p1Num = zeros(numWords)
    # 正常属性的单词数
    p0Denom = 0.0
    # 侮辱性的单词数
    p1Denom = 0.0
    # 遍历每个文本.如果文本是侮辱性的,就把当前文本向量加到侮辱性向量上。最终得到的侮辱性向量是所有侮辱性文本的累加
    # 通过遍历该向量就知道哪个词出现的频率最高
    # 同理,如果文本不是侮辱性的,就把文本加到正常向量上
    for i in range(numTrainDocs):
        if trainCategory[i] == 1:
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        else:
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])

    p1Vect = p1Num / p1Denom
    p0Vect = p0Num / p0Denom
    return p0Vect, p1Vect, pAbusive


# 计算概率并判断是否是侮辱性的
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
    p1 = sum(vec2Classify * p1Vec) + log(pClass1)
    p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
    if p1 > p0:
        return 1
    else:
        return 0

# 在新的向量(和词库等长)上把输入向量对位的位置累加1,
def bagOfWords2VecMN(vocabList, inputSet):
    returnVec = [0] * len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] += 1
    return returnVec

# 将文本切割成单词数组,并将单词都小写
def textParse(bigString):
    import re
    listOfTokens = re.split(r'\W*', bigString)
    return [tok.lower() for tok in listOfTokens if len(tok) > 2]

def spamTest():
    docList = [];
    classList = [];
    fullText = [];
    fileName = []
    # 将 spam 和 ham 目录下各个文件的内容都读取出来.分别添加到各个向量中. spam 表示是垃圾邮件
    # docList 中,每个文件的内容做为一个向量值
    # fullText 中存在着所有文本的内容
    # classList 存着各个文档的类型.1 表示是 span,0 表示是 ham
    # 执行完后,classList 和 docList 的长度都是 50
    for i in range(1, 26):
        loopName = 'email/spam/%d.txt' % i
        wordList = textParse(open(loopName).read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(1)
        fileName.append(loopName)

        loopName = 'email/spam/%d.txt' % i
        wordList = textParse(open(loopName).read())
        docList.append(wordList)
        fullText.extend(wordList)
        classList.append(0)
        fileName.append(loopName)
    # 创建词库
    vocabList = createVocabList(docList)
    # 总共有 50 个测试文档.所以这里随机取值范围要小于 50
    trainingSet = range(50)
    testSet = []
    # 构建一个训练用的 set,长度是 10. 里面的值都是随机取。范围是 (0-50)
    for i in range(10):
        randIndex = int(random.uniform(0, len(trainingSet)))
        testSet.append(trainingSet[randIndex])
        del (trainingSet[randIndex])
    trainMat = []
    trainClasses = []
    # 准备训练数据
    for docIndex in trainingSet:
        trainMat.append(bagOfWords2VecMN(vocabList, docList[docIndex]))
        trainClasses.append(classList[docIndex])
    p0V, p1V, pSpam = trainNB0(array(trainMat), array(trainClasses))
    errorCount = 0
    for docIndex in testSet:
        wordVector = bagOfWords2VecMN(vocabList, docList[docIndex])
        # 计算算到的值和它真实的值进行比较.如果对不上表示程序判断错误
        if classifyNB(array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
            errorCount += 1
            print "判断出错: 文档 %s" % fileName[docIndex], " ".join(docList[docIndex])
    print '错误率: ', float(errorCount) / len(testSet)
    # return vocabList,fullText

spamTest()


运行输出:

判断出错: 文档 email/spam/20.txt get off online watchesstore discount watches for all famous brands watches arolexbvlgari dior hermes oris cartier and more brands louis vuitton bags wallets gucs tiffany jewerly enjoy full year warranty shipment via reputable courier fedex ups dhl and ems speedpost you will 100 recieve your order
判断出错: 文档 email/spam/17.txt home based business opportunity knocking your door don rude and let this chance you can earn great income and find your financial life transformed learn more her success work from home finder experts
错误率:  0.7


上一篇 回归

下一篇 概率基础

评论

文章