朴素贝叶斯分类(Python)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/u012369535/article/details/88389587

一、贝叶斯公式及分类

贝叶斯公式是在条件概率和全概率公式的基础上得来的,详细请参考:
https://blog.csdn.net/Hearthougan/article/details/75174210
贝叶斯公式p(yi|X) = p(X|yi) p(yi) / p(X) = p(yi) p(x1|yi) p(x2|yi) … p(xj|yi) / p(X)
P(X):待分类对象自身的概率,可忽略
P(yi):每个类别的先验概率,如P(军事)
P(X|yi):每个类别产生该对象的概率
P(xi|yi):每个类别产生该特征的概率,如P(苹果|科技)

那么我们知道了朴素贝叶斯公式,我们怎么来进行分类呢,也就是说我们要怎么求这个概率,这个过程中,我们首先要知道先验概率和条件概率即知道分子p(X|yi)和p(yi),为什么这里不说p(x)呢,因为这个概率始终是个固定值,可以约去,那么我们来说一下p(X|yi)怎么理解:

例如1:

• 总共训练数据1000篇,其中军事类300篇,科技类240篇,生活类140篇,…

• 军事类新闻中,单词"谷歌"出现在15篇中,单词“投资”出现9篇,上涨出现36篇

P(yi)

– p(军事)=0.3, p(科技)=0.24, p(生活)=0.14,…

P(xj|yi)

– P(谷歌|军事)=0.05, P(投资|军事)=0.03, P(上涨|军事)=0.12,…

– P(谷歌|科技)=0.15, P(投资|科技)=0.10, P(上涨|科技)=0.04,…

– P(谷歌|生活)=0.08, P(投资|生活)=0.13, P(上涨|生活)=0.18,…

例如2:

给大家100篇文章,其中50篇是军事、30篇财经、20篇体育

P(y=军事) = 50/100

P(y=财经) = 30/100

P(y=体育) = 20/100

P(X):这篇文章的概率=是一个固定值,可以忽略掉

P(yi|X)≈ P(yi)P(X|yi)

P(X|yi) :对于y指定的类别中,出现X的概率,X={x1,x2,x3,…},xi是文章X包含的所有单词

P(xi|yi):对于y指定的类别中,出现xi这个词的概率

y=军事,x1=军舰

X={军舰、大炮、航母}

P(X|y=军事) = P(x1=军舰|y=军事)*P(x2=大炮|y=军事)*P(x3=航母|y=军事)

前提:独立同分布=》朴素贝叶斯

P(yi|X)≈ P(yi)P(X|yi)

对每一个标签都求对应概率,最大者为该分类

为了完成NB分类问题,我们需要2类参数来支持

1、先验概率P(yi)

2、条件概率P(X|yi)

其实这里所谓的求这类的参数也就是求模型的过程,即参数就是模型。

在这里说明一下,我们在平时代码实现的过程中有两种求解条件概率的计算方法,这两类都可以计算条件概率:

第一种:

分子:军事类文章中包含“谷歌”这个词的文章个数

分母:军事类文章个数

p(x="谷歌"|y="军事"):分子 / 分母

第二种:

分子:军事类文章中包含“谷歌”这个词的个数

分母:军事类文章中所有词的个数

p(x="谷歌"|y="军事"):分子 / 分母

朴素贝叶斯说了这么多,有什么优缺点呢:

优点:简单有效,结果是概率,对二值和多值同样适用

二、Python代码实现

代码注释参考:https://blog.csdn.net/Jameslvt/article/details/81321145
数据准备:总共3116篇已经分好词的文件(文章),词与词之间用空格分开,每篇文章属于体育、财经和汽车三类中的一类,并且类别包含在文件名中。

数据转换:目的是把所有数据文件(每篇文章的类别和单词均转换成数字token)放到一个文件中,该文件的每一行表示一篇文章的内容,第一列为类别,最后一列是#号+原文件名称。
在这里插入图片描述

import sys
import os
import random
#列表,保存所有文章的不重复的词
WordList = []
#字典,保存所有文章的不重复的词的id值
WordIDDic = {}

#训练阈值
TrainingPercent = 0.8

#输入文件夹
inpath = sys.argv[1]
#输出的文件
OutFileName = sys.argv[2]
#输出的文件训练集是OutFileName.train,测试集是:OutFileName.test
trainOutFile = file(OutFileName+".train", "w")
testOutFile = file(OutFileName+".test", "w")

def ConvertData():
    i = 0
    tag = 0
    for filename in os.listdir(inpath):
    #只分析三大类的数据,财经,汽车,体育,并且给每个类别打个标示,财经是1,汽车2,体育3
        if filename.find("business") != -1:
            tag = 1
        elif filename.find("auto") != -1:
            tag = 2
        elif filename.find("sport") != -1:
            tag = 3
        #统计一共读了多少篇文章
        i += 1
        #设置一个随机数,为了下面能够把数据按照二八原则分为训练集和测试集
        rd = random.random()
        #把测试集的文件对象赋给outfile变量
        outfile = testOutFile
        #若随机数小于0.8则把训练集文件对象赋值给outfile变量,这种操作是为了下面写入数据做准备
        if rd < TrainingPercent:
            outfile = trainOutFile

        if i % 100 == 0:
            print i,"files processed!\r",
        #读入目录下的文章内容,inpath是目录地址,filename是目录下的文件名称
        infile = file(inpath+'/'+filename, 'r')
        #首先把三类文章的标签写入输出文件开头,后面加空格
        outfile.write(str(tag)+" ")
        #一次性全部读入,python中read,readline 不同的读的方式不一样
        content = infile.read().strip()
        #进行编码转义
        content = content.decode("utf-8", 'ignore')
        #因为一次性读入,所以会有换行的问题,这里直接把换行替换成空格,并且以空格切分开,words是个列表
        words = content.replace('\n', ' ').split(' ')
        for word in words:
            if len(word.strip()) < 1:
                continue
            '''这里的代码是我觉得写的最好的一段代码,简单又复杂。
               首先判断这个单词在不在token2Id的字典里面,若不在,将该单词添加到一个wordList列表中
               然后将该单词存入token2Id的字典里面,key是word,value是wordList列表的长度
               这个if判断的精华在于利用判断word是否在token2id的字典来进行去重复
            '''
            if word not in WordIDDic:
                WordList.append(word)
                WordIDDic[word] = len(WordList) #向字典添加新的键值对
            #将word对应的value写入输出文件中
            outfile.write(str(WordIDDic[word])+" ")
        #最后一个单词转换完成后,用#号隔开文章内容和名称
        outfile.write("#"+filename+"\n")
        infile.close()

    print i, "files loaded!"
    print len(WordList), "unique words found!"

#首先调用ConvertData()函数
ConvertData()
trainOutFile.close()
testOutFile.close()

加载数据:目的是在所有训练文章中,
初始化ClassFeaProb字典,其表示各单词在各类别中的出现概率;
计算完成ClassFeaDic字典,其表示各单词在各类别中出现的频次;
计算完成ClassFreq字典,其表示各类别的文章数;
计算完成WordDic字典,其表示所有训练文章中出现的单词集合。
在这里插入图片描述

import sys
import os
import math

DefaultFreq = 0.1
TrainingDataFile = "nb_data.train"
ModelFile = "nb_data.model"
TestDataFile = "nb_data.test"
TestOutFile = "nb_data.out"
ClassFeaDic = {} #{classid :{自增长id:计数器}}
ClassFreq = {} #频次 classid下token的总数
WordDic = {} #token字典
ClassFeaProb = {} #条件概率字典
ClassDefaultProb = {}
ClassProb = {}

def Dedup(items):
    tempDic = {}
    for item in items:
        if item not in tempDic:
            tempDic[item] = True
    return tempDic.keys()

#加载数据,初始化,记录每个类型文章的次数、每个文章的词语出现在每个类型下的次数
def LoadData():
    i =0
    #读入训练集
    infile = file(TrainingDataFile, 'r')
    #以行的方式读入,在上次的Data处理中已经转换成每一行的内容,首先读入一行
    sline = infile.readline().strip()
    #判断该文件是否有内容,循环所有的训练集
    while len(sline) > 0:
        #找到每行的#号和文章名,输出是个索引index数字
        pos = sline.find("#")
        if pos > 0:
            #取出从该篇文章开始到#号的位置
            sline = sline[:pos].strip()
        #将取出的文章内容以空格分割开    
        words = sline.split(' ')
        #判断文章有么有问题
        if len(words) < 1:
            print "Format error!"
            break
        #得到刚刚那三类文章的tag即财经是1,汽车2,体育3 赋给classid
        classid = int(words[0])
        #判断这类型文章在不在文章字典里面,不在进入if,在进行频次+1,为了求先验概率
        if classid not in ClassFeaDic:
            #记录每个类中的每个token的计数
            ClassFeaDic[classid] = {}
            #记录每个token在各自类中的概率
            ClassFeaProb[classid] = {}
            #记录每个类的文章个数
            ClassFreq[classid]  = 0
        ClassFreq[classid] += 1
        #获取除了文章标签的真正内容
        words = words[1:]
        #remove duplicate words, binary distribution
        #words = Dedup(words)
        for word in words:
            if len(word) < 1:
                continue
            #获取每个词,这里词是我们转置的数字    
            wid = int(word)
            #判断当前的词在不在词语字典里面,不在初始化为1
            if wid not in WordDic:
                WordDic[wid] = 1
            #若当前词在该词的字典里面,紧接着判断当前词在不在当前classid文章中的字典里面,不在初始化为1,在的话记录频次
            if wid not in ClassFeaDic[classid]:
                ClassFeaDic[classid][wid] = 1
            else:
                ClassFeaDic[classid][wid] += 1
        #记录读的总行数
        i += 1
        #接着再读入一行直到结束
        sline = infile.readline().strip()
    infile.close()
    print i, "instances loaded!"
    print len(ClassFreq), "classes!", len(WordDic), "words!"

计算模型:目的是
计算出各类别的先验概率ClassProb字典,即p(yi);
计算出各类别中出现各单词的条件概率ClassFeaProb字典,即p(xi|yi);
计算出各类别的默认概率ClassDefaultProb字典
在这里插入图片描述

#计算模型,求先验概率p(yi)和条件概率p(x|yi)
def ComputeModel():
    sum = 0.0
    #循环遍历不同类文章记录的字典的value值,key是classid value是该类对应的频次
    for freq in ClassFreq.values():
        #sum将所有的value相加即得到三类文章的总的篇幅数
        sum += freq
    #循环遍历不同类文章记录的字典的key值,key是classid value是该类对应的频次
    for classid in ClassFreq.keys():
        #当前classid对应的先验概率,用体育举例子,体育类文章篇幅数除以总的篇幅数,循环计算三类文章各个先验概率
        ClassProb[classid] = (float)(ClassFreq[classid])/(float)(sum)
    #循环遍历每类文章中每个词频的字典中的key,key是classid,value是个字典{词:词频}
    for classid in ClassFeaDic.keys():
        #Multinomial Distribution
        sum = 0.0
        #循环遍历当前classid对应的value字典{词:词频},中的key即词
        for wid in ClassFeaDic[classid].keys():
            #统计当前类文章的词频次数
            sum += ClassFeaDic[classid][wid]
        #newsum = (float)(sum+len(WordDic)*DefaultFreq)
        #为了使程序健壮,防止向下溢出,这里可以把sum+1
        newsum = (float)(sum + 1)
        #Binary Distribution
        #newsum = (float)(ClassFreq[classid]+2*DefaultFreq)
        #循环遍历当前类文章的的key即词
        for wid in ClassFeaDic[classid].keys():
            #存入条件概率值,用体育举例子,体育文章中铅球的条件概率=铅球在体育文章中的总数/体育文章的总词数
            ClassFeaProb[classid][wid] = (float)(ClassFeaDic[classid][wid]+DefaultFreq)/newsum
        #每一类文章设置一个默认的条件概率,防止在测试集时候一个词在当前类文章没有,就用该值
        ClassDefaultProb[classid] = (float)(DefaultFreq) / newsum
    return

存储模型:向文件中写入模型结果,即对所有训练文章来说,求得的每个类别的先验概率,以及每个类别下出现的单词的条件概率。其格式如下:其中,第一行的classid是按照ClassFreq.keys而来,第二到四行的classid是按照ClassFeaDic.keys而来。注:在计算模型阶段的所有字典中的key的顺序即classid的顺序都是一致的。
第一行: classid1 先验概率 默认概率 classid2 先验概率 默认概率 classid3 先验概率 默认概率
第二行:(classid1) word1_token 条件概率 word2_token 条件概率 word3_token 条件概率
第三行:(classid2) word1_token 条件概率 word2_token 条件概率 word3_token 条件概率
第四行:(classid3) word1_token 条件概率 word2_token 条件概率 word3_token 条件概率

在这里插入图片描述

def SaveModel():
    #以写的方式打开该类文件
    outfile = file(ModelFile, 'w')
    #循环遍历类别频次字典,获取三大类别的classid
    for classid in ClassFreq.keys():
        #将classid写入文件
        outfile.write(str(classid))
        outfile.write(' ')
        #将获取的先验概率写入文件
        outfile.write(str(ClassProb[classid]))
        outfile.write(' ')
        #将默认的每类别条件概率写入文件
        outfile.write(str(ClassDefaultProb[classid]))
        outfile.write(' ' )
    #第一行遍历3类文章对应的不同概率,然后换行
    outfile.write('\n')
    #循环遍历每个类别对应的词和词频的字典,key是classid,value是{词:词频}
    for classid in ClassFeaDic.keys():
        #循环value的key即词
        for wid in ClassFeaDic[classid].keys():
            #将获得的词用来获取该词的条件概率
            outfile.write(str(wid)+' '+str(ClassFeaProb[classid][wid]))
            outfile.write(' ')
        #每一类的文章模型为一行
        outfile.write('\n')
    outfile.close()

加载模型:目的是从已经计算好的模型文件中读取各字典,为预测阶段作数据准备
条件概率字典ClassFeaProb
先验概率字典ClassProb
默认概率字典ClassDefaultProb
单词字典WordDic
在这里插入图片描述

def LoadModel():
    global WordDic
    WordDic = {}
    global ClassFeaProb
    ClassFeaProb = {}
    global ClassDefaultProb
    ClassDefaultProb = {}
    global ClassProb
    ClassProb = {}
    #读入模型
    infile = file(ModelFile, 'r')
    sline = infile.readline().strip()
    #每个文章类型以空格分割开
    items = sline.split(' ')
    if len(items) < 6:
        print "Model format error!"
        return
    i = 0
    while i < len(items):
        #获取每类文章的tag
        classid = int(items[i])
        #定义一个字典
        ClassFeaProb[classid] = {}
        i += 1
        if i >= len(items):
            print "Model format error!" 
            return
        #存放先验概率
        ClassProb[classid] = float(items[i])
        #i+1接下来是默认的条件概率
        i += 1
        if i >= len(items):
            print "Model format error!" 
            return
        ClassDefaultProb[classid] = float(items[i])
        i += 1
    #循环遍历条件概率的key,key是不同类别的文章classid
    for classid in ClassProb.keys():
        #接下来读入第二行数据,就是某一类文章的某个词的条件概率
        sline = infile.readline().strip()
        items = sline.split(' ')
        i = 0
        while i < len(items):
            #获取该词
            wid  = int(items[i])
            #判断在不在词字典里,不在初始化
            if wid not in WordDic:
                WordDic[wid] = 1
            i += 1
            if i >= len(items):
                print "Model format error!"
                return
            #并且给当前文章类的该词字典中赋上条件概率
            ClassFeaProb[classid][wid] = float(items[i])
            #接下来循环第二个词,以此类推
            i += 1
    infile.close()
    print len(ClassProb), "classes!", len(WordDic), "words!"

预测测试集:目的是利用加载后的模型(各类别先验概率、各类别中各单词的条件概率)
若测试文章X={x1,x2,x3,…},则预测其属于哪一类别:yi = {y1,y2,y3}的概率如下
p(yi|X) = p(X|yi)p(yi) = p(x1|yi)p(x2|yi)…p(xn|yi)p(yi),其中p(xi|yi)和p(yi)均是从训练集中计算得来的,具体是判断xi在不在yi对应的单词条件概率字典里,在的话就利用已求得的概率值,不在的话就用默认概率值。预测阶段输出两个列表如下:
获取测试集每篇文章的真实类别集合
TrueLabelList = [classid, classid, classid, …]
获取测试集每篇文章的预测类别集合
PredLabelList = [classid, classid, classid, …]

在这里插入图片描述

def Predict():
    global WordDic
    global ClassFeaProb
    global ClassDefaultProb
    global ClassProb

    TrueLabelList = []
    PredLabelList = []
    i =0
    #读入测试集
    infile = file(TestDataFile, 'r')
    outfile = file(TestOutFile, 'w')
    #以每行读入,这里是每篇文章以及文章中的词
    sline = infile.readline().strip()
    scoreDic = {}
    iline = 0
    while len(sline) > 0:
        iline += 1
        if iline % 10 == 0:
            print iline," lines finished!\r",
        #这块和前面的一样,掠过#后面的文件名称
        pos = sline.find("#")
        if pos > 0:
            sline = sline[:pos].strip()
        words = sline.split(' ')
        if len(words) < 1:
            print "Format error!"
            break
        classid = int(words[0])
        TrueLabelList.append(classid)
        #内容从1开始直到最后
        words = words[1:]
        #remove duplicate words, binary distribution
        #words = Dedup(words)
        #循环遍历每个类别的先验概率,放入scoreDic字典中
        for classid in ClassProb.keys():
            scoreDic[classid] = math.log(ClassProb[classid])
        for word in words:
            if len(word) < 1:
                continue
            wid = int(word)
            #过滤掉一些没有的词
            if wid not in WordDic:
                #print "OOV word:",wid
                continue
            for classid in ClassProb.keys():
                #判断当前词存不存在条件概率的字典中,若不存在,直接取默认的条件概率,否则取出条件概率
                if wid not in ClassFeaProb[classid]:
                    # 如果当前分类中不包含这个分词,就算出一个默认概率 p(x1|y) +p(x2|y) +p(x3|y) == p(军舰|军事) +p(大炮|军事)
                    scoreDic[classid] += math.log(ClassDefaultProb[classid])
                else:
                    scoreDic[classid] += math.log(ClassFeaProb[classid][wid])
        #binary distribution
        #wid = 1
        #while wid < len(WordDic)+1:
        #   if str(wid) in words:
        #       wid += 1
        #       continue
        #   for classid in ClassProb.keys():
        #       if wid not in ClassFeaProb[classid]:
        #           scoreDic[classid] += math.log(1-ClassDefaultProb[classid])
        #       else:
        #           scoreDic[classid] += math.log(1-ClassFeaProb[classid][wid])
        #   wid += 1
        i += 1
        maxProb = max(scoreDic.values())
        for classid in scoreDic.keys():
            if scoreDic[classid] == maxProb:
                PredLabelList.append(classid)
        sline = infile.readline().strip()
    infile.close()
    outfile.close()
    print len(PredLabelList),len(TrueLabelList)
    return TrueLabelList,PredLabelList

猜你喜欢

转载自blog.csdn.net/u012369535/article/details/88389587