机器学习实战-朴素贝叶斯

2019-06-03  本文已影响0人  又双叒叕苟了一天

对于特征向量\boldsymbol w_i,拥有标签c_i,c_i\in\{c_1, c_2,...\},即分类个数。我们对每一类标签计算概率:
p(c_i|\boldsymbol w)=\frac{p(\boldsymbol w|c_i)p(c_i)}{p(\boldsymbol w)}
然后取概率最大的那个标签作为我们的分类。

为了实现这个思路,我们首先需要构造特征向量\boldsymbol w_i

拿文本分类的任务举例,我们构造一个数据集:

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]  # 1代表侮辱性文字,0代表正常言论
    return postingList, classVec

postingList, classVec = loadDataSet()

一共有6条文本,classVec对每条文本的分类进行了标注。

接着我们通过词袋的方式把这些文本转化成向量的形式,先统计所有文本中出现过的词汇,构成词汇表:

def createVocabList(dataSet):
    vocabSet = set([])
    for document in dataSet:
        vocabSet = vocabSet | set(document)
    return list(vocabSet)

vocabList = createVocabList(postingList)
print(vocabList)
'''
['worthless', 'is', 'I', 'not', 'flea', 'maybe', 'steak', 'help', 'quit', 'stop', 'so', 'garbage', 'stupid', 'mr', 'dalmation', 'dog', 'park', 'please', 'to', 'cute', 'him', 'my', 'take', 'licks', 'buying', 'food', 'posting', 'how', 'love', 'ate', 'has', 'problems']
'''

之后统计每个文本的每个单词的出现次数,构成一个稀疏表示的向量:

def setOfWords2Vec(vocabList, inputSet):
    returnVec = [0] * len(vocabList)
    for word in inputSet:
        if word in vocabList:
            returnVec[vocabList.index(word)] = 1
        else:
            print("the word: %s is not in my Vocabulary!" % word)
    return returnVec

vec = setOfWords2Vec(vocabList, postingList[0])
print(vec)  
# [0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1]

上面是第一个文本的向量表示,0就表示词汇表中该位置的单词在这个文本中出现了1次,0表示没有出现过,当然也可能出现多次,那个位置就会使出现的次数n。

然后我们计算p(\boldsymbol w|c_i)p(c_i),其中p(c_i)是这类文本占所有样本的比例。p(\boldsymbol w|c_i)=p(w_1,w_2,...,w_n|c_i)。假设所有单词的出现都独立,则p(\boldsymbol w|c_i)=p(w_0|c_i)p(w_1|c_i)...p(w_n|c_i)。而每个p(w|c_i)只需要计算这个wc_i这类文本中出现的次数除以c_i这类文本单词的总数就可以得到。我们通过以下代码得到p(w|c_i)p(c_i)

def trainNB0(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix)  # post个数
    numWords = len(trainMatrix[0])  # 词汇表大小
    pAbusive = sum(trainCategory) / float(numTrainDocs)  # 侮辱性的post占所有post的比例
    p0Num, p1Num = zeros(numWords), zeros(numWords)  # 初始化词汇表大小的两个0向量
    p0Denom, p1Denom = 0.0, 0.0
    for i in range(numTrainDocs):
        # 统计所有侮辱性post中每个单词的出现次数和侮辱性post的总长度
        if trainCategory[i] == 1:  # 侮辱性post
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        # 统计所有非侮辱性post中每个单词的出现次数和非侮辱性post的总长度
        else:  # 非侮辱性post
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect = p1Num / p1Denom  # 词汇表中每个单词在侮辱性post中出现的概率
    p0Vect = p0Num / p0Denom  # 词汇表中每个单词在非侮辱性post中出现的概率
    return p0Vect, p1Vect, pAbusive

trainMat = []
for postinDoc in postingList:
    trainMat.append(setOfWords2Vec(vocabList, postinDoc))
p0V, p1V, pAb = trainNB0(trainMat, classVec)
print(p0V)
# [0.         0.04166667 0.04166667 0.         0.04166667 0.
#  0.04166667 0.04166667 0.         0.04166667 0.04166667 0.
#  0.         0.04166667 0.04166667 0.04166667 0.         0.04166667
#  0.04166667 0.04166667 0.08333333 0.125      0.         0.04166667
#  0.         0.         0.         0.04166667 0.04166667 0.04166667
#  0.04166667 0.04166667]
print(p1V)
# [0.10526316 0.         0.         0.05263158 0.         0.05263158
#  0.         0.         0.05263158 0.05263158 0.         0.05263158
#  0.15789474 0.         0.         0.10526316 0.05263158 0.
#  0.05263158 0.         0.05263158 0.         0.05263158 0.
#  0.05263158 0.05263158 0.05263158 0.         0.         0.
#  0.         0.        ]
print(pAb)  # 0.5

我们可以看到在三个侮辱性文本中都出现的单词stupid,当文本是侮辱性的时候他出现的概率也最高,有0.157,即p1V下标12的单词。

有了这些数据,我们就可以计算p(\boldsymbol w|c_i)p(c_i)=p(w_0|c_i)p(w_1|c_i)...p(w_n|c_i)p(c_i)了。但是这样还会存在问题,p(w|c_i)中只要存在一个为0,那么乘积就会等于0。所以我们初始化时假设每个单词已经都出现过了一次,就避免了这个问题。并且,因为每个p(w|c_i)的数值可能非常的小,他们之间的乘积p(w_0|c_i)p(w_1|c_i)...p(w_n|c_i)p(c_i)就也会非常的小,几个非常的小的结果就不太好比较大小,并且数值上也容易出错。所以就通过连续单调递增函数log计算\log p(\boldsymbol w|c_i)p(c_i)=\log p(w_0|c_i)+\log p(w_1|c_i)+...+\log p(w_n|c_i)+ \log p(c_i),比较不同类别之间\log p(\boldsymbol w|c_i)p(c_i)的大小。分母p(\boldsymbol w)对于不同类别的计算来说都相等,我们就不对它进行统计,只需比较分子的大小。

改进后的代码如下

def trainNB(trainMatrix, trainCategory):
    numTrainDocs = len(trainMatrix)  # post个数
    numWords = len(trainMatrix[0])  # 词汇表大小
    pAbusive = sum(trainCategory) / float(numTrainDocs)  # 侮辱性的post占所有post的比例
    p0Num, p1Num = ones(numWords), ones(numWords)  # 初始化词汇表大小的两个0向量
    p0Denom, p1Denom = 2.0, 2.0
    for i in range(numTrainDocs):
        # 统计所有侮辱性post中每个单词的出现次数和侮辱性post的总长度
        if trainCategory[i] == 1:  # 侮辱性post
            p1Num += trainMatrix[i]
            p1Denom += sum(trainMatrix[i])
        # 统计所有非侮辱性post中每个单词的出现次数和非侮辱性post的总长度
        else:  # 非侮辱性post
            p0Num += trainMatrix[i]
            p0Denom += sum(trainMatrix[i])
    p1Vect = log(p1Num / p1Denom)  # 词汇表中每个单词在侮辱性post中出现的概率
    p0Vect = log(p0Num / p0Denom)  # 词汇表中每个单词在非侮辱性post中出现的概率
    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 = trainNB(array(trainMat), array(listClasses))
    testEntry = ['love', 'my', 'dalmation']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))  
    testEntry = ['stupid', 'garbage']
    thisDoc = array(setOfWords2Vec(myVocabList, testEntry))
    print(testEntry, 'classified as: ', classifyNB(thisDoc, p0V, p1V, pAb))

testingNB()
# ['love', 'my', 'dalmation'] classified as:  0
# ['stupid', 'garbage'] classified as:  1
上一篇下一篇

猜你喜欢

热点阅读