zoukankan      html  css  js  c++  java
  • 机器学习之基于朴素贝叶斯文本分类算法

    原理

    在分类(classification)问题中,经常须要把一个事物分到某个类别。一个事物具有非常多属性。把它的众多属性看做一个向量,即x=(x1,x2,x3,,xn)。用x这个向量来代表这个事物。类别也是有非常多种,用集合Y=y1,y2,ym表示。假设x属于y1类别,就能够给x打上y1标签,意思是说x属于y1类别。

    这就是所谓的分类(Classification)

    x的集合记为X。称为属性集。一般X和Y的关系是不确定的,你仅仅能在某种程度上说x有多大可能性属于类y1,比方说x有80%的可能性属于类y1,这时能够把X和Y看做是随机变量,P(Y|X)称为Y的后验概率(posterior probability),与之相对的,P(Y)称为Y的先验概率(prior probability)1

    在训练阶段。我们要依据从训练数据中收集的信息,对X和Y的每一种组合学习后验概率P(Y|X)分类时,来了一个实例x,在刚才训练得到的一堆后验概率中找出全部的P(Y|x), 当中最大的那个y,即为x所属分类。依据贝叶斯公式,后验概率为P(Y|X)=P(X|Y)P(Y)P(X)

    在比較不同Y值的后验概率时。分母P(X)总是常数,因此能够忽略。先验概率P(Y)能够通过计算训练集中属于每个类的训练样本所占的比例easy地预计。这里直接计算P(Y|X)比較麻烦,而朴素贝叶斯分类提出了一个独立性如果:x1,x2,...,xn相互独立,这也是被称之为“朴素”的原因之中的一个。于是P(X|Y) = P(x1|Y)P(x2|Y)...P(xn|Y)。而P(xi|Y)非常好求了。

    对于二元分类,要比較P(Y=1|X)和P(Y=0|X)的大小,仅仅需比較分子P(X|Y)P(Y)部分。

    因此仅仅需计算n个条件概率和先验概率。

    文本分类

    在文本分类中,如果我们有一个文档d∈X,X是文档向量空间(document space),和一个固定的类集合C={c1,c2,…,cj},类别又称为标签。显然,文档向量空间是一个高维度空间。我们把一堆打了标签的文档集合<d,c>作为训练样本,<d,c>∈X×C。

    比如:

    <d,c>={Beijing joins the World Trade Organization, China}

    对于这个仅仅有一句话的文档,我们把它归类到 China,即打上china标签。

    我们期望用某种训练算法。训练出一个函数γ,可以将文档映射到某一个类别:

    γ:X→C

    这样的类型的学习方法叫做有监督学习,由于事先有一个监督者(我们事先给出了一堆打好标签的文档)像个老师一样监督着整个学习过程。

    朴素贝叶斯分类器是一种有监督学习,常见有两种模型,多项式模型(multinomial model)和伯努利模型(Bernoulli model)。


    多项式模型

    在多项式模型中。 设某文档d=(t1,t2,…,tk)。tk是该文档中出现过的单词。同意反复。则

    先验概率P(c)= 类c下单词总数/整个训练样本的单词总数

    类条件概率P(tk|c)=(类c下单词tk在各个文档中出现过的次数之和+1)/(类c下单词总数+|V|)

    V是训练样本的单词表(即抽取单词集合。单词出现多次,仅仅算一个),|V|则表示训练样本包括多少种单词。在这里,m=|V|, p=1/|V|。

    P(tk|c)能够看作是单词tk在证明d属于类c上提供了多大的证据。而P(c)则能够觉得是类别c在总体上占多大比例(有多大可能性)。

    伯努利模型

    P(c)= 类c下文件总数/整个训练样本的文档总数

    P(tk|c)=(类c下包括单词tk的文件数+1)/(类c下文档总数+2)     在这里,m=2, p=1/2。

     #这里一定要注意:伯努利是以文档为粒度的,所以分母是文档总数,而不是网上以讹传讹类c下单词总数

    这里贴几个以讹传讹的链接:

    http://blog.csdn.net/kongying168/article/details/7026389

    http://cn.soulmachine.me/blog/20100528/

    还有好多其它的就不一一列举了。


    上面两个模型中的分子分母都加上了一些数,这是为了防止某个条件概率为0,从而整个P(X|Y) = P(x1|Y)P(x2|Y)...P(xn|Y)乘积为0。


    实例演示

    对一个邮件进行垃圾分类。数据集来自加州大学欧文分校的http://archive.ics.uci.edu/ml/datasets/Spambase spambase
    数据格式说明例如以下:

    英文的。我就不解释了(主要是解释起来费劲,-_-。

    sorry。)


    为了判别分类模型的好坏,能够计算AUC值。

    本次实验採用sklearn包中的metrics计算ROC曲线和AUC值(ROC和AUC详见http://blog.csdn.net/chjjunking/article/details/5933105);而这个是须要每一个样本的后验概率的。

    因此分子P(X)的值也要求出来,将整个式子转换下:

    P(Y|X)=P(X|Y)P(Y)P(X) = P(x1|Y=1)P(x2|Y=1)...P(xn|Y=1)P(Y=1) (P(x1|Y=1)P(x2|Y=1)...P(xn|Y=1) + P(x1|Y=0)P(x2|Y=0)...P(xn|Y=0))
    分子分母上下同一时候除以P(x1|Y=0)P(x2|Y=0)...P(xn|Y=0)得
    公式不好打啊,过几天用手写。然后传图片吧,-_-。

    sorry!


    好了。废话不多说,上代码:
    NaiveBayes.py
    #!/usr/bin/python
    #  NaiveBayes classification
    #  lming_08 2014.07.06
    import math
    import numpy as np
    from sklearn import metrics
    
    class NaiveBayes:
        def __init__(self, trainFile):
            self.trainingFile = trainFile
            self.trainingData = self.read_data(trainFile)
    
        # Read training or testing data
        def read_data(self, file):
            data = []
            fd = open(file)
            for line in fd:
                arr = line.rstrip().split(',')
                # Turn an instance's last column(y column) as integer
                arr[-1] = int(arr[-1])
                # Append the instance to trainingData
                data.append(tuple(arr))
            fd.close()
            return data
    
        def train_model_with_Bernoulli(self):
            self.sumPosInstance = 0.
            self.sumNegInstance = 0.
            self.termfreq = {}
            for instance in self.trainingData:
                    if int(instance[-1]) == 1:
                        self.sumPosInstance += 1
                    else:
                        self.sumNegInstance += 1
    
                    for i in range(len(instance) - 1):
                        key = str()
                        if i < 55:                        
                            if float(instance[i]) > 0:
                                key = "freq" + "|" + str(i + 1) + "|" + "1"
                            else:
                                key = "freq" + "|" + str(i + 1) + "|" + "0"
                        else:
                            key = "length" + "|" + str(i + 1) + "|" + instance[i]
    
                        if key not in self.termfreq:
                            self.termfreq[key] = [0, 0]
                        
                        if int(instance[-1]) == 1:
                            self.termfreq[key][1] += 1
                        else:
                            self.termfreq[key][0] += 1
            # prior_prob = p(y = 1)
            self.prior_prob = self.sumPosInstance / (self.sumPosInstance + self.sumNegInstance)
            # prior_ratio = p(y=1) / p(y=0)
            self.prior_ratio = self.sumPosInstance / self.sumNegInstance
    
        # the function should be called before predict()
        def set_testfile(self, testFile):
            self.testing_data = self.read_data(testFile)
    
        def predict(self):
            self.testingY = []
            self.predict_result = []
    
            for instance in self.testing_data:
                self.predict_result.append(self.predict_instance_with_Bernoulli(instance))
                self.testingY.append(instance[-1])
        
        def get_statistics(self):
            true_classify_count = 0.
            false_classify_count = 0.
            for instance in self.testing_data:
                post_prob = self.predict_instance_with_Bernoulli(instance)
                if post_prob >= 0.5 and instance[-1] == 1:
                    true_classify_count += 1
                elif post_prob >= 0.5 and instance[-1] == 0:
                    false_classify_count += 1
                elif post_prob < 0.5 and instance[-1] == 1:
                    false_classify_count += 1
                elif post_prob < 0.5 and instance[-1] == 0:
                    true_classify_count += 1
            return true_classify_count, false_classify_count
    	
        def predict_instance_with_Bernoulli(self, instance):
            f = 0.
            for i in range(len(instance) - 1):
                key = str()
                if i < 55:                        
                    if float(instance[i]) > 0:
                        key = "freq" + "|" + str(i + 1) + "|" + "1"
                    else:
                        key = "freq" + "|" + str(i + 1) + "|" + "0"
                else:
                    key = "length" + "|" + str(i + 1) + "|" + instance[i]
    
                if key in self.termfreq:
                    f += math.log( (self.termfreq[key][1] + 1) / ((self.termfreq[key][0] + 2) * self.prior_ratio) )
    
            
            posterior_ratio = self.prior_ratio * math.exp(f)
            # posterior probability
            prob = posterior_ratio / (1. + posterior_ratio)
            return prob
    
        def getAUC(self):
            y = np.array(self.testingY)
            pred = np.array(self.predict_result)
            fpr, tpr, thresholds = metrics.roc_curve(y, pred) # metrics.roc_curve(y, pred, pos_label=1)
            auc = metrics.auc(fpr, tpr)
            return auc
    
    def main(trainFile, testFile):
        nb = NaiveBayes(trainFile)
        nb.train_model_with_Bernoulli()
        nb.set_testfile(testFile)
        nb.predict()
        print("the value of AUC: " % nb.getAUC())
        print("the value of priori probability:" % nb.priorProb)
    
    if __name__ == "__main__":
        if len(argv) != 3:
            print "Usage: python %s trainFile(in) testFile(out)" % __file__
            sys.exit(0)
    
        main(argv[1], argv[2])

    PartitionFile.py    用于将数据文件切割为训练文件(80%)和測试文件(20%)
    #!/usr/bin/python
    #  Partitioning a file into training(80%) and testing file(20%)
    #  lming_08 2014.07.06
    from random import randint
    
    def partition_file(file, train_file, test_file):
        ltest = []
        ltrain = []
        fd = open(file, "r")
        train_fd = open(train_file, "w")
        test_fd = open(test_file, "w")
        test_index = 0
        train_index = 0
        for line in fd:
            rnum = randint(1, 10)
            if rnum == 5 or rnum == 6:
                test_index += 1
                ltest.extend(line)
                if test_index == 100:
                    test_fd.writelines(ltest)
                    ltest = []
                    test_index = 0
            else:
                train_index += 1
                ltrain.extend(line)
                if train_index == 100:
                    train_fd.writelines(ltrain)
                    ltrain = []
                    train_index = 0
        if len(ltest) > 0:
            test_fd.writelines(ltest)
        if len(ltrain) > 0:
            train_fd.writelines(ltrain)
        train_fd.close()
        test_fd.close()
        fd.close()
    

    Classify.py    运行文件
    #!/usr/bin/python%
    #  main execute file
    #  lming_08 2014.07.06
    import sys
    from sys import argv
    from NaiveBayes import NaiveBayes
    from PartitionFile import partition_file
    
    def main(srcFile, trainFile, testFile):
        partition_file(srcFile, trainFile, testFile)
        nb = NaiveBayes(trainFile)
        nb.train_model_with_Bernoulli()
        nb.set_testfile(testFile)
        nb.predict()
        print("the value of AUC: %f" % nb.getAUC())
        print("the value of priori probability: %f" % nb.prior_prob)
        true_classify_count, false_classify_count = nb.get_statistics()
        error_rate = false_classify_count/(true_classify_count + false_classify_count)
        print("the error rate : %f" % error_rate)
    	
    if __name__ == "__main__":
        if len(argv) != 4:
            print "Usage: python %s srcFile(in) trainFile(out) testFile(out)" % __file__
            sys.exit(0)
    
        main(argv[1], argv[2], argv[3])

    运行结果为:


    能够看到AUC值还是比較高的。分类错误率为9.87%,还是能够接受的。
    发火最后再吐槽下。上文中指出的以讹传讹的算法居然在《机器学习实战》这本书中出现了,而网上好多同学也学习过书中的代码。但是并没有人怀疑并指出这个。

    參考文献

    http://blog.csdn.net/kongying168/article/details/7026389

    http://cn.soulmachine.me/blog/20100528/

    http://www.chepoo.com/naive-bayesian-text-classification-algorithm-to-learn.html

    http://blog.csdn.net/chjjunking/article/details/5933105

  • 相关阅读:
    python ascii codec can't decode
    python文件编码说明 coding=utf-8
    windbg的使用
    在后台运行Python脚本服务
    ubuntu下更改分辨率
    【转】VC调试的时候 “没有调试信息,未加载符号”
    常用正则表达式——中文匹配、拉丁匹配
    SQL的经典操作——批量复制同行的其它列数据到其它列数据
    关于C++对汉字拼音的处理(3)
    关于环境变量设置是否需要重启的问题
  • 原文地址:https://www.cnblogs.com/jhcelue/p/7020237.html
Copyright © 2011-2022 走看看