徒手现实一个朴素贝叶斯分类器

和自甴很熟 提交于 2020-01-22 13:21:35

徒手实现一个贝叶斯分类器

引子

朴素贝叶斯分类器顾名思义是以贝叶斯公式为基础的分类器,其将后验概率转换为先验概率和不同类的条件概率的乘积,再通过比较不同的类别下该乘积的大小实现分类。不同于其他的分类器,朴素贝叶斯分类器严格意义上没有训练过程,只需计算相关概率即可。贝叶斯分类器比较适合对自然语言分类的模型,下面以对英文评文本类为例,详细描述如何实现一个贝叶斯分类器。

代码

首先引入语料库,其内容为英文网站评论,如果需要处理中文,还需要对中文进行分词,获得单词列表。

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”表示负面评价。在实际运用中,睡着语料库的丰富,还可以进一步细化分类标准,例如正面评价分为5级,负面评价也分为5级。
由于计算机无法直接对文本进行处理,首先需要对文本进行编码,这里我们采用简单的词袋模型,即建立一个单词表,将所有在语料库中出现的单词包含其中。具体实现方法可以采用python中的集合数据类型,集合中不能包含重复元素,具有自动去重功能。

dataSet = postingList
vocabSet = set([])   #初始化一个空集合
for document in dataSet:   
    # | 表示集合的并操作,集合中不能包含重复元素,并集操作时会自动去除两个集合中相同的元素
    vocabSet = vocabSet | set(document)
vocabSet = list(vocabSet)    #list的操作比集合set更方便 

为了便于计算机进行处理,需要对训练样本进行编码。在已获得单词列表的基础上,将每一条训练样本转换成固定长度的特征向量,其长度等于单词列表的长度。

def bagOfWords2VecMN(vocabList, inputSet):    
    #特征向量的长度等于词汇表的长度    
    returnVec = [0]*len(vocabList)    
    for word in inputSet:        
        if word in vocabList:            
            #出现一次相对应位置计数加一            
            returnVec[vocabList.index(word)] += 1
    return returnVec
    
#将数据集转换为词袋模型下的特征向量,编码
trainMat = []
for postinDoc in postingList:   
    res = bagOfWords2VecMN(vocabSet,postinDoc)    
    trainMat.append(res)
#将list转换为数组,便于训练
trainMatrix = np.array(trainMat)
trainCategory = np.array(classVec)
print(trainMatrix)

通过以上操作,将训练样本转换为如下形式,其中特征向量长度为词汇表长度,每个数字对应词汇表中每个单词的出现次数。

[[0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 1 0 0 0 1 0 0 1]
 [1 0 0 1 1 0 0 0 1 0 0 0 0 0 1 0 1 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0]
 [0 1 0 0 0 1 1 1 0 0 0 0 1 0 1 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 1 0 0 0 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0]
 [1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 1 1 0 0 0 0 1 0 1 1 0 0 1 0 0]
 [0 0 0 0 1 0 0 0 0 0 1 1 0 1 0 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]]

完成上述步骤后,即可计算各个概率值。首先求解先验概率。对于离散变量,可以将先验概率的求解简化为频率值的统计。

numTrainDocs = len(trainMatrix)     #样本数量
numWords = len(trainMatrix[0])     #样本特征数量
pC1 = sum(trainCategory) / float(numTrainDocs)    #P(1)的比例,即先验概率

接下来需要做的是求解每个类别下的条件概率。针对C1这个类别,需要求出p(F1|C1)p(F2|C1)…p(F32|C1)这32个值,特征对应条件概率则等于该单词出现次数/给类别下出现单词的总数。如果某些类别中,某些单词的出现次数为0,会导致连乘结果直接为0。此外,由于p(F1|C1)p(F2|C1)…p(F32|C1)中每一个概率值都可能较小,连乘之后可能导致计算机下溢。解决办法是将单词列表和每个类别单词总数分别初始化为1和2以及对概率连乘值取ln,避免出现0值,并将概率值控制在一定范围,同时还将连乘运算转换为连加,避免出现过小值。

#获取不同类别的单词总数
numWordC0 = 2    #单词总数初始化为2,避免极小值
numWordC1 = 2
for i in range(numTrainDocs):
    if trainCategory[i] == 0:
        for num in trainMatrix[i]:            
            numWordC0 += num
    else:
        for num in trainMatrix[i]:
            numWordC1 += num

#获取不同类别条件下,每次单词出现的次数
numWordFsC0 = np.ones(numWords)    #单词出现次数初始化为1,避免出现0值
numWordFsC1 = np.ones(numWords)
for j in range(numTrainDocs):    
    for k in range(numWords):        
        if trainCategory[j] == 0:
            numWordFsC0[k] += trainMatrix[j][k]
        else:            
            numWordFsC1[k] += trainMatrix[j][k]
                  
#计算每个单词不同类的条件概率值
psOfFsC0 = numWordFsC0/numWordC0
psOfFsC1 = numWordFsC1/numWordC1

获得了先验概率和条件概率之后,我们就可以对测试目标进行分类,分类思路也很简单,将测试目标中所包含单词的不同类的条件概率的自然对数值进行累加,再与不同类的先验概率的对数值相加,较大的一类即分类结果。

testWords = ['stupid','garbage']
testValueC0 = 0
testValueC1 = 0
# 寻找测试目标中每个单词所对应的条件概率值,取对数并累加
for  word in testWords:
    testValueC0 += math.log(psOfFsC0[vocabSet.index(word)])
    testValueC1 += math.log(psOfFsC1[vocabSet.index(word)])
# 与类对应的先验概率值累加
testValueC0 += math.log(1-pC1)
testValueC1 += math.log(pC1)

print('C0: ',testValueC0)
print('C1: ',testValueC1)
# 对值进行比较,较大者则是所属的类
if(testValueC0 > testValueC1):
    print("belone to C0")
else:
    print("belone to C1")

打印结果如下:

C0:  -7.20934025660291
C1:  -4.702750514326955
belone to C1

其中C1值明显大于C0值,故判断该测试目标属于C1类。同时我们可以通过常识判断’stupid’,'garbage’明显属于负面评价,与测试结果相同。
若将测试目标改为明显的正面评价:[‘love’,‘my’,‘dalmation’],运行结果如下。

C0:  -7.694848072384611
C1:  -9.826714493730215
belone to C0

打印结果显示C0值明显大于C1值,属于C0类,与我们的常识判断相同。

小结

朴素贝叶斯分类器原理清晰,结果简单,效果明显,能够有效的实现自然语言处理分类。后续将探索在更大的语料库基础上、采用不同的词袋模型的朴素贝叶斯分类的有效性、准确性。

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