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

  • 相关阅读:
    94. Binary Tree Inorder Traversal
    101. Symmetric Tree
    38. Count and Say
    28. Implement strStr()
    实训团队心得(1)
    探索性测试入门
    LC.278. First Bad Version
    Search in Unknown Sized Sorted Array
    LC.88. Merge Sorted Array
    LC.283.Move Zeroes
  • 原文地址:https://www.cnblogs.com/jhcelue/p/7020237.html
Copyright © 2011-2022 走看看