#朴素贝叶斯学习#

朴素贝叶斯学习

按照学习计划,开始学习贝叶斯在机器学习上的应用,主要以多项式朴素贝叶斯作为学习重点学习(在学习过程发现,自己被高斯贝叶斯分类器同样吸引)。

这里主要以文档分类作为学习目的,二元分类以垃圾邮件或者垃圾文档做例子,扩展到多元分类发现也挺简单的。由于对python的异常喜爱,就以python作为实现工具。

预处理

拿到原始的数据文件后,先将停用词给去掉,因为这些词对于分类的作用贡献特别小,所以可以剔除。

nltk库里面有现成的,直接导出来用:

from nltk.corpus import stopwords
def remove_stop_word(text):
    stop_word=stopwords.words('english')
    return [word for word in text.split() if word not in stop_word]

常见的英文停用词大概有127个

因为有些词在句子开头有大小写问题,所以将所有的词都转换成小写。

def all_to_lower(text):
    return text.lower()

由于原始的每份数据看作是一个字符串,所以里面可能包含回车,多个空白,换行符,这些可以split()来解决,然后则需要将标点符号做删除处理下:

def remove_punctuation(text):
    reStr=''
    for x in text:
        if x not in string.punctuation:
            reStr+=x
    return reStr

对于整体样本来说,包含spam和ham的,建立一个全局的字典,用来统计单词出现的次数,计算频率。

将原始的数据经行统计后,可以开始用朴素贝叶斯(naive bayesian)模型训练了。这里的朴素指的是,将词与词之间当作独立的,也就说每个词的出现都是相互独立了,这是一个理想化的假设。

基本原理

给定一个文档D,来计算估计文档D是S(spam)的概率有多大,D是H(ham)的概率有多大。

根据贝叶斯理论,给出以下公式:

P(C|D)=P(DC)P(D)

其中:
C代表文档的类别,也就是class
D代表文档

这里的话,分类就是分两类问题,也就是spam和ham

P(C|D)=P(S)P(D|S)P(D)

P(H|D)=P(H)P(D|H)P(D)

其中:
P(S|D):代表的是给定一篇文档,或者说一段信息,这段文档或者信息属于spam的概率。
P(S):代表已有的数据记录中,属于spam的概率,也就是先验概率,比如有100篇文档,其中20篇文档是spam。
P(D|S):代表的是,已知是spam,单个word的概率。
P(H)代表的是非spam的概率
P(W|H)代表的是单词出现在非spam中的概率。

可以将某一类的文档描述成一些相互独立词的概率,比如一个文档是属于C类的,那么用
概率来描述就是:

P(wi|C)

其中 wi 表示第 i 个词出现情况。
按照这样的处理,将一个文档中,词的出现都看作是随机分布的,也就是说,单词与文档长度、单词出现的
位置,甚至其他上下文含义无关。
那么给出一个包含wi个词的文档D,所属类别C的概率,可以如下描述:
P(D|C)=p(wi|C)

那么上面的式子写作

P(S|D)=P(S)p(wi|S)/P(D)

P(H|D)=P(H)p(wi|H)/P(D)

将二者做除法,

P(S|D)P(H|D)=P(S)p(wi|S)P(H)p(wi|H)

在文本分类中,常见的计算概率模型由两种,一种是伯努利朴素贝叶斯(Bernoulli naive Bayes),另外一个是多项式朴素贝叶斯(Multinomial naive Bayes)这里主要讨论的是多项式朴素贝叶斯(不同的问题,适当调整用不同的模型,关于模型的选择,等这篇弄完了,再去深入研究点,感觉模型选择也挺有意思的)。
P(S) P(H) 都比较容易计算,主要计算就是在 p(Wi|S) p(wi|H)

帮助理解计算,用以下例子说明。来自
http://nlp.stanford.edu/IR-book/html/htmledition/naive-bayes-text-classification-1.html

docIDwords in documentin c = China?
training set1Chinese Beijing Chineseyes
2Chinese Chinese Shanghaiyes
3Chinese Macaoyes
4Tokyo Japan Chineseno
test set5Chinese Chinese Chinese Tokyo Japan?

前面四个句子标记为属于China,最后一个标记为 非China

P(t|C)=TctTct

这里t指的是单词t出现在类别C中, Tct 指的是单词t在类别C中出现的次数, Tct 指的是类别C中单词数量总数。
比如 P(Chinese|C)=5/8
P(Chinese|C¯¯¯)=1/3
这些看着都没问题,但是如果一个词只出现测试样本中某一类时,而没有出现在其他类中,
那么这个词的条件概率计算出来就是 Pword|C=0/8 或者 P(word|C¯¯¯)=0/3
最终根据多项式乘法,得到的后验概率结果是0。无法分类,比如 P(Tokyo|C)=0/8=0
为了避免或者解决这个问题,可以在分子分母上同时加一个1,或者引入Laplace smoothing(we use add-one or Laplace smoothing, which simply adds one to each count)
上面式子修正为:
P(t|c)=Tct+1Tct+|V|

其中 |V| 为词汇表的长度。
同理上面的计算修正为:
P(Chinese|C)=(5+1)/(8+6)=3/7
P(Chinese|Cfei)=(1+1)/(3+6)=2/9
P(Tokyo|C)=(0+1)/(8+6)=1/14

根据多项式模型。
P(x1=c1,x2=c2,...,xn=cn)=ci!ci!pci

所以

P(Chinese=3,Tokyo=1,Japan=1|C)=P(C)P(Chiese|C)3P(Tokyo|C)P(Japan|C)=3/4(3/7)3(1/14)(1/14)5!/3!=0.006024275599452608
P(Chinese=3,Tokyo=1,Japan=1|C¯¯¯)=P(C¯¯¯)P(Chiese|C¯¯¯)3P(Tokyo|C¯¯¯)P(Japan|C¯¯¯)=1/4(2/9)3(2/9)(2/9)5!/3!=0.0027096140493488444
计算得到
P(Chinese=3,Tokyo=1,Japan=1|C)>P(Chinese=3,Tokyo=1,Japan=1C¯¯¯)
所以认为该文档属于China类。

明白之后,可以将此应用到恶意文档分类上。

代码细节

原始文档来源自python in action这本书:
书中一共有25篇spam文档和25篇ham文档。
在原始文档划分中,我选用随机划分。
定义俩个函数read_sampleshuffle_samples,一起配合,用来处理这个事情。
read_sample负责读取所有数据到内存,同时用0和1对数据做标签,0表示ham,1表示spam,如果涉及到多类的话,可以增加标签,比如2、3、4等等,其中函数内部主要用列表来处理,最终read_sample返回四个参数,分别为测试样本,已经处理好的长spam数据,已经处理好的长ham数据,以及spam的先验概率(只要有spam的先验概率,由于是二元分类问题,非此即彼,所以用1减去spam的先验概率就可以得到ham的先验概率)
shuffle_samples负责对已经读到内存的数据做随机划分处理,划分出训练样本和测试样本,返回测试样本和训练样本供后面使用。
再获得训练和测试样本后,开始计算多项式朴素贝叶斯中的各项参数,定义一个函数model_compute来做这个事情,最终,将就算出来的数据以元组或者其他结构传递出来。
最后做预测,pridict_class读取测试数据和model_compute中计算出来的模型参数,完成预测。

def main():
    test,spam_long_sentence,ham_doc_long_sentence,P_S=read_sample()#read_sample 返回四个值,如上文提到的
    model_para=model_compute(spam_long_sentence,ham_doc_long_sentence,P_S)#利用read_sample返回参数,经行参数计算,这里面用到的是多项式朴素贝叶斯
    pridict_class(test,model_para)#预测文档分类

read_sample:

def read_sample():

    spam_long_sentence=''
    ham_doc_long_sentence=''

    raw_spam=[]
    raw_ham=[]
    global_list=[]

    for i in range(1,26):#读取原始文档数据
        f1= open('./spam/%d.txt' % i)
        spam_doc=f1.read()
        f1.close()
        raw_spam.append([spam_doc,1])
        f2= open('./ham/%d.txt' % i)
        ham_doc=f2.read()
        f2.close()
        raw_ham.append([ham_doc,0])

    global_list.extend(raw_spam)
    global_list.extend(raw_ham)
    data=np.array(global_list)
    test,train=shuffle_samples(data)

    doc_spam=0
    doc_ham=0
    for row in train:
        if int(row[1])==1:
            doc_spam+=1
            spam_long_sentence+=deal_with_text(row[0])#deal_with_text()负责对数据经行预处理,比如去除停用词,数字和标点符号的处理等等
        else:
            doc_ham+=1
            ham_doc_long_sentence+=deal_with_text(row[0])

    return test,spam_long_sentence,ham_doc_long_sentence,float(doc_spam)/(doc_spam+doc_ham)

deal_with_text:

def deal_with_text(sentence):
    # print sentence
    step1=remove_stop_word(sentence) #将原始的文档当作一个长字符串,然后分布处理,第一步,去除停用词
    # print step1
    step2=remove_punctuation_number(step1)#第二步,去除标点
    step3=remove_blank(step2)#第三步,对空格的处理
    return step3+' '
def remove_stop_word(text):#第一步的过程,停用词可以自己收集也可以用已有的别人的总结,我这里用的是nltk中的数据
    stop_word=stopwords.words('english')
    return ' '.join([word for word in text.split() if word not in stop_word ])   
def remove_punctuation_number(text):
#第二步,去除符号和数字,这里,我将所有的数字全部都剔除了。可以有其他测量,比如包含邮箱地址的,可以整体替换成其他表示等等,不同的策略,不同的处理。
    reStr=''
    for x in text:
        if x not in string.punctuation and not x.isdigit():
            reStr+=x
        else:
            reStr+=' '
    return reStr
def remove_blank(text):#第三部处理
    return ' '.join(text.split()).lower()

model_compute:

def model_compute(trian_spam_data,trian_ham_data,P_S):#根据多项式朴素贝叶斯原理经行模型参数计算,最终返回一堆计算好的参数,这里用元祖返回,用列表返回也可以。

    P_word_H={}#用来统计在不同类别中,不同词的频率,H代表在条件是Ham下,S代表是Spam条件下
    P_word_S={}
    P_S=P_S #在训练样本中的先验概率,P_S代表Spam的,P_H代表Ham的
    P_H=1-P_S
    n_of_vocabulary=0 #总去重词数
    # print P_S,P_H

    len_of_spam_word=len(trian_spam_data.split())
    len_of_ham_word=len(trian_ham_data.split())
    n_of_vocabulary= len_of_spam_word
    ham_count=count_words(trian_ham_data)#count_words()用来计算目标数据中的各个词的频率。
    spam_count=count_words(trian_spam_data)

    #用来计算训练样本中一共有多少个不重复的单词
    vocabulary= ham_count.keys()
    vocabulary.extend(spam_count.keys())
    n_of_vocabulary=len(set(vocabulary))

    for word in ham_count:
        P_word_H[word]=(float(ham_count[word])+1)/(len_of_ham_word+n_of_vocabulary)
    for word in spam_count:
        P_word_S[word]=(float(spam_count[word])+1)/(len_of_spam_word+n_of_vocabulary)
    return (P_word_H,P_H,P_word_S,P_S,len_of_ham_word,len_of_spam_word,n_of_vocabulary)

count_words:

def count_words(text):#统计给定的text中的单词频数
    text_count={}
    word_list=text.split()
    for x in word_list:
        if text_count.has_key(x):
            text_count[x]+=1
        else:
            text_count[x]=1
    return text_count

pridict_class:

def pridict_class(test,model_para):#将read_sample中返回的test和model_compute返回的模型参数元祖,当作参数参入

    P_word_H=model_para[0]#模型参数分别赋值
    P_H=model_para[1]
    P_word_S=model_para[2]
    P_S=model_para[3]
    len_of_H=model_para[4]
    len_of_S=model_para[5]
    V=model_para[6]

    for data_line in test:
        orign_label=data_line[1]
        predict_class=None
        sentence_dict=count_words(deal_with_text(data_line[0]))
        # print sentence_dict
        H_sig_score=[]
        S_sig_score=[]
        for word in sentence_dict:
            if word in P_word_H:#ham条件概率中,存在这个word
                H_sig_score.append(P_word_H[word])
            else:
                H_sig_score.append(1.0/(len_of_H+V))
            if word in P_word_S:
                S_sig_score.append(P_word_S[word])
            else:
                S_sig_score.append(1.0/(len_of_S+V))
        H_sig_score.append(P_H)
        S_sig_score.append(P_S)
        #print data_line
        #print H_sig_score
        #print S_sig_score
        calculate_H=sum(map(math.log,H_sig_score))#对结果取对数累计,减少精度损失
        calculate_S=sum(map(math.log,S_sig_score))
        if calculate_H>calculate_S:
            predict_class=0
        else:
            predict_class=1
        print "predict_class:",predict_class
        print "orign_label:",orign_label

为了评估这个模型分类效果的好坏,可以添加一段代码,用来记录。
全局定义四个变量,TP,TN,FP,FN

        orign_label=int(orign_label)
        if orign_label==1 and predict_class==1:
            TP+=1
        if orign_label==1 and predict_class==0:
            FP+=1
        if orign_label==0 and predict_class==1:
            FN+=1
        if orign_label==0 and predict_class==0:
            TN+=1

具体的含义可以在网上查查资料,主要就是评估模型的好坏,评估的方法不止这一个,但是这个方法是比较常见的。

    recall=float(TP)/(TP+FN)
    precision =float(TP) / (TP + FP)
    f1=float(2*precision*recall)/(precision+recall)

同时将main函数连续跑1000次,统计全局。

if __name__=="__main__":
    for x in range(1000):
        main()

最终得到以下数据,总数据是50个文档,选取不同数量的测试文档(随机选择)得到的数据

train_numbertest_numberTPTNFPFNrecallprecisionf1
4731495143236370.9758485639690.9764859568910.976167156383
4731452145845450.969939879760.969939879760.96993987976
4731487143746300.9802241265660.9699934768430.975081967213
4552441240273840.9667326732670.9709626093870.96884302441
4552430243268700.9720.9727782225780.972388955582
4552388244279910.9632916498590.9679773003650.965628790942
437341833451041330.9625457617570.9704713231120.966492294642
43733253455941260.9634888438130.9725065808720.967976710335
437334234221081280.9631123919310.9686956521740.965895953757
4010480848621351950.9610233859680.9726886506170.966820832495
4010479648291622130.9574765422240.967325534490.962375840273
4010478848371612140.9572171131550.9674681753890.962315345191
3515729271292093700.9517097363610.9721370483940.961814944272
3515726871332353640.9523060796650.9686791949890.960422860918
3515736570422303630.9530279503110.9697169190260.961300006526
2030140111326986818520.8832503309590.9416627461520.911521696702
2030140461317184019430.8784789542810.9435711406690.909862348178
2030141471312684218850.8824226546910.9438254720130.912091808775
10401684314075319058920.7408401143610.8407627414770.787644968201
10401660414379331357040.7443069750760.8336596877040.786453522795
10401661014724332753390.7567542940450.8331243416760.793105094781

画个简要的图便于观察
折线图

一些参考资料:

https://solvethat.wordpress.com/2014/03/30/spam-identifcation-in-social-networks/
http://en.wikipedia.org/wiki/Naive_Bayes_classifier
http://openclassroom.stanford.edu/MainFolder/DocumentPage.php?course=MachineLearning&doc=exercises/ex6/ex6.html
http://nlp.stanford.edu/IR-book/html/htmledition/naive-bayes-text-classification-1.html
http://www.amplifypartners.com/interviews/on-the-evolution-of-machine-learning-from-linear-models-to-neural-networks/

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值