机器学习之基于朴素贝叶斯文本分类算法

原理

在分类(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)可以通过计算训练集中属于每一个类的训练样本所占的比例容易地估计。这里直接计算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/tbkken/article/details/8062358

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

机器学习之基于朴素贝叶斯文本分类算法,布布扣,bubuko.com

时间: 2024-10-15 15:48:18

机器学习之基于朴素贝叶斯文本分类算法的相关文章

《机器学习实战》学习笔记:基于朴素贝叶斯的分类方法

概率是许多机器学习算法的基础,在前面生成决策树的过程中使用了一小部分关于概率的知识,即统计特征在数据集中取某个特定值的次数,然后除以数据集的实例总数,得到特征取该值的概率. 目录: 一.基于贝叶斯理论的分类方法 二.关于朴素贝叶斯的应用场景 三.基于Python和朴素贝叶斯的文本分类 1.准备数据 2.训练算法 3.测试算法 四.小结 以下进入正文: 一.基于贝叶斯理论的分类方法 假设有两类数据组成的数据集如下: 其中,假设两个概率分布的参数已知,并用p1(x,y)表示当前数据点(x,y)属于类

基于朴素贝叶斯的定位算法

1 定位背景介绍 一说到定位大家都会想到gps,然而gps定位有首次定位缓慢(具体可以参考之前的博文<LBS定位技术>).室内不能使用.耗电等缺陷,这些缺陷大大限制了gps的使用.在大多数移动互联网应用例如google地图.百度地图等,往往基于wifi.基站来进行定位. 一般APP在请求定位的时候会上报探测到的wifi信号.基站信号.以wifi为例,手机会探测到周围各个wifi(mac地址)对应的信号强度(RSSI),即收集到信号向量(<WF1, RSSI1> <WF2, R

详解使用EM算法的半监督学习方法应用于朴素贝叶斯文本分类

1.前言 对大量需要分类的文本数据进行标记是一项繁琐.耗时的任务,而真实世界中,如互联网上存在大量的未标注的数据,获取这些是容易和廉价的.在下面的内容中,我们介绍使用半监督学习和EM算法,充分结合大量未标记的样本,以期获得文本分类更高的准确率.本文使用的是多项式朴素贝叶斯作为分类器,通过EM算法进行训练,使用有标记数据以及未标记的数据.研究了多类分类准确率与训练集中未标记数据的比例之间的关系.并探索方法来降低EM过程的计算代价来加速训练.结果显示,半监督EM-NB分类器可以在只给2%标记数据情况

分类(一):朴素贝叶斯文本分类

1.朴素贝叶斯假设 为了处理这种维数过高的情况,我们做一个假设:X的每一维特征之间都是独立的.这也就是朴素贝叶斯假设. 根据独立分布的条件,我们就能够容易地写出P(d|C),如下: P(d/C) = ∏ P(ti / C) d代表文档,ti代表文档中的每个词,C代表类. 2.朴素贝叶斯分类器 朴素贝叶斯分类器是一种有监督学习,常见有两种模型,多项式模型(multinomial model)和伯努利模型(Bernoulli model). 先验概率在<信息检索导论>里面都是以类c下的文档数占比来

朴素贝叶斯文本分类(详解)

from numpy import zeros,array from math import log def loadDataSet(): #词条切分后的文档集合,列表每一行代表一个email postingList=[['your','mobile','number','is','award','bonus','prize'], ['new','car','and','house','for','my','parents'], ['my','dalmation','is','so','cute

基于朴素贝叶斯的文本分类

前言:学习机器学习的过程意识到,数学是工科的基石.很多数学公式似懂非懂,因此有了这篇博客,想在学习每个模型的过程中搞懂其中的数学理论. 贝叶斯决策论 1.前置知识:先验概率与后验概率 先验概率P(B):根据以往经验和分析得到的概率 先验概率是一种常识性.经验性认知,比如抛硬币正反面的概率是1/2. 后验概率P(A|B):某个因素的到来影响了对某个结果发生可能性的判断 后验概率是基于已知,对结果发生的可能性一种推测. 比如:文本分类中,假设文章类别为3类,没有数据时,观测到类别c的概率是先验概率P

详解基于朴素贝叶斯的情感分析及 Python 实现

相对于「 基于词典的分析 」,「 基于机器学习 」的就不需要大量标注的词典,但是需要大量标记的数据,比如: 还是下面这句话,如果它的标签是: 服务质量 - 中 (共有三个级别,好.中.差) ╮(╯-╰)╭,其是机器学习,通过大量已经标签的数据训练出一个模型, 然后你在输入一条评论,来判断标签级别 宁馨的点评 国庆活动,用62开头的信用卡可以6.2元买一个印有银联卡标记的冰淇淋, 有香草,巧克力和抹茶三种口味可选,我选的是香草口味,味道很浓郁. 另外任意消费都可以10元买两个马卡龙,个头虽不是很大

(数据挖掘-入门-8)基于朴素贝叶斯的文本分类器

主要内容: 1.动机 2.基于朴素贝叶斯的文本分类器 3.python实现 一.动机 之前介绍的朴素贝叶斯分类器所使用的都是结构化的数据集,即每行代表一个样本,每列代表一个特征属性. 但在实际中,尤其是网页中,爬虫所采集到的数据都是非结构化的,如新闻.微博.帖子等,如果要对对这一类数据进行分类,应该怎么办呢?例如,新闻分类,微博情感分析等. 本文就介绍一种基于朴素贝叶斯的文本分类器. 二.基于朴素贝叶斯的文本分类器 目标:对非结构化的文本进行分类 首先,回顾一下朴素贝叶斯公式: 特征.特征处理:

基于朴素贝叶斯的内容推荐算法

论文出处: http://www.cs.utexas.edu/~ml/papers/libra-sigir-wkshp-99.pdf 引言 这篇文章里面将会详细介绍基于多项式贝叶斯的内容推荐算法的符号以及术语,公式推导以及核心思想,学习如何从文本分类的角度来实现物品推荐.详细了解算法过程后,你应该可以利用里面的公式来计算出某个用户对于单词级别的喜好强度列表(profile),根据这个强度大小来对其他物品(需先用该强度来对该物品做加权算出该物品的喜好强度)做一个推荐的排序,从而得到用户可能最喜欢的