python机器学习案例系列教程——文档分类器,朴素贝叶斯分类器,费舍尔分类器

全栈工程师开发手册 (作者:栾鹏)

python数据挖掘系列教程

github地址:https://github.com/626626cdllp/data-mining/tree/master/Bayes

贝叶斯分类过程概述:首先有一批已知分类的数据集。对每个输入对象提取特征,根据输入对象的特征属性和输入对象的所属分类,计算分类与特征属性之间的概率关系,以此来实现样本的训练。当对新的输入对象进行预测所属分类时,提取新输入对象的特征,根据训练好的概率,判断输入对象属于每个分类的概率。

先验概率和后验概率

教科书上的解释总是太绕了。其实举个例子大家就明白这两个东西了。

假设我们出门堵车的可能因素有两个(就是假设而已,别当真):车辆太多和交通事故。

堵车的概率就是先验概率 。

那么如果我们出门之前我们听到新闻说今天路上出了个交通事故,那么我们想算一下堵车的概率,这个就叫做条件概率 。也就是P(堵车|交通事故)。这是有因求果。

如果我们已经出了门,然后遇到了堵车,那么我们想算一下堵车时由交通事故引起的概率有多大,

那这个就叫做后验概率 (也是条件概率,但是通常习惯这么说) 。也就是P(交通事故|堵车)。这是有果求因。

下面的定义摘自百度百科:

先验概率是指根据以往经验和分析得到的概率,如全概率公式,它往往作为"由因求果"问题中的"因"出现.

后验概率是指依据得到"结果"信息所计算出的最有可能是那种事件发生,如贝叶斯公式中的,是"执果寻因"问题中的"因".

朴素贝叶斯理论

朴素贝叶斯是贝叶斯决策理论的一部分,所以在讲述朴素贝叶斯之前有必要快速了解一下贝叶斯决策理论。

p ( A ∣ B ) p ( B ) = p ( B ∣ A ) p ( A ) p(A|B)p(B)=p(B|A)p(A) p(AB)p(B)=p(BA)p(A)

p ( A ∣ B ) = p ( B ∣ A ) p ( A ) p ( B ) p(A|B)=\frac{p(B|A)p(A)}{p(B)} p(AB)=p(B)p(BA)p(A)

朴素贝叶斯对条件个概率分布做了条件独立性的假设。 比如下面的公式,假设有n个特征:

p ( a ∣ X ) = p ( X ∣ a ) p ( a ) / p ( X ) = p ( x 1 , x 2 , x 3 . . . x n ∣ a ) p ( a ) / P ( X ) p(a|X)=p(X|a)p(a)/p(X)=p(x_1,x_2,x_3...x_n|a)p(a)/P(X) p(aX)=p(Xa)p(a)/p(X)=p(x1,x2,x3...xna)p(a)/P(X)

由于每个特征都是独立的,我们可以进一步拆分公式

p ( a ∣ X ) = p ( X ∣ a ) p ( a ) / p ( X ) = p ( x 1 ∣ a ) ∗ p ( x 2 ∣ a ) ∗ p ( x 3 ∣ a ) ∗ . . . ∗ p ( x n ∣ a ) ∗ p ( a ) / p ( X ) p(a|X)=p(X|a)p(a)/p(X)=p(x_1|a)*p(x_2|a)*p(x_3|a)*...*p(x_n|a)*p(a)/p(X) p(aX)=p(Xa)p(a)/p(X)=p(x1a)p(x2a)p(x3a)...p(xna)p(a)/p(X)

所以说朴素贝叶斯分类器基于一个简单的假定:给定目标值时属性之间相互条件独立。

朴素贝叶斯推断的一些优点:

生成式模型,通过计算概率来进行分类,可以用来处理多分类问题。
对小规模的数据表现很好,适合多分类任务,适合增量式训练,算法也比较简单。

朴素贝叶斯推断的一些缺点:

对输入数据的表达形式很敏感。
由于朴素贝叶斯的“朴素”特点,所以会带来一些准确率上的损失。
需要计算先验概率,分类决策存在错误率。

费舍尔分类器

费舍尔方法,是前面介绍的朴素贝叶斯方法的一种替代方案,它可以给出非常精确的结果,尤其适合垃圾信息过滤。
与朴素贝叶斯过滤器利用特征概率来计算整篇文档的概率不同,费舍尔方法为文档中的每个特征都求得了分类的概率,然后又将这些概率组合起来,并判断其是否有可能构成一个随机集合。该方法还会返回每个分类的概率,这些概率彼此间可以进行比较。尽管这种方法更为复杂,但是因为它在为分类选择临界值(cutoff)时允许更大的灵活性,所以还是值得一学的。

文档分类过程

爬虫采集数据:提供文本文件。
提取特征数据:将文本文件解析成词条向量。
计算特征概率:为每个特征属性计算属于分类的概率
计算对象概率:根据特征概率计算对象概率
构建分类器:使用对象概率完成分类
分类器效果测试:构建一个完整的程序对一组文档进行分类,将错分的文档输出到屏幕上。

案例

本文以垃圾邮件识别为例进行python代码演示。

首先我们先定义一批简单的已知样本数据,也就是邮件内容和所属分类。当然后面我们会添加特征提取的地方,包含邮件时间,邮件来源,邮件标题,邮件质量等。分类也不仅限于垃圾和非垃圾,可以包含订阅邮件,推广邮件,有毒邮件,购物邮件,学习邮件等等。现在这里我们仅对邮件文本内容进行识别,将邮件区分为垃圾和非垃圾。

在分类器中,你需要重点理清,文章属于某个分类器的概率Pr(category|Document)和在指定分类中文章出现的概率Pr(Document|category)的区别

分类器对输入对象进行分类的原理就是判断输入对象(邮件文档)属于哪个分类的概率更大。
也就是比较在文档已知的情况下,属于每个分类的概率的大小。即Pr( category|Document))概率的大小。

样本数据集

# 邮件数据集,邮件内容和所属分类。这只是一个非常简单的数据集。学会了算法以后再使用复杂数据集。
data=[
    ['Nobody owns the water.','good'],
    ['the quick rabbit jumps fences','good'],
    ['buy pharmaceuticals now','bad'],
    ['make quick money at the online casino','bad'],
    ['the quick brown fox jumps','good'],
]

特征提取函数

要实现对输入对象进行分类,首要要能够对输入对象提取用于分类的特征。在邮件中特征就是单词,我们就是根据单词来进行垃圾邮件识别。
对输入对象进行特征提取。(对邮件文档提取对单词)

中文文档提取参考http://blog.csdn.net/luanpeng825485697/article/details/78857654

# 从输入对象中提取特征(文档中提取不重复单词)
def getwords(doc):
    splitter=re.compile('\\W*')
    print(doc)
    # 根据非字母字符进行单词拆分
    words=[s.lower() for s in splitter.split(doc) if len(s)>2 and len(s)<20]

    # 只返回一组不重复的单词。(特征不重复)
    return dict([(w,1) for w in words])

先验概率(任一样本属于指定分类的概率)

先验概率 P ( Y = C k ) P(Y=C_k) P(Y=Ck)的极大似然估计为所有样本中属于属于分类 C k C_k Ck的比例,也就是任一样本属于分类 C k C_k Ck的概率。

条件概率(指定分类中指定特征出现的概率)

在朴素贝叶斯中,设样本第 j j j个特征可能取值的集合为 a j 1 , a j 2 , a j 3 . . . a j s {a_{j1},a_{j2}},a_{j3}...a_{js} aj1,aj2,aj3...ajs,条件概率 P ( X ( j ) = a j i ∣ Y = C k ) P(X^{(j)}=a_{ji}|Y=C_k) P(X(j)=ajiY=Ck)的极大似然估计为属于分类 C k C_k Ck的所有样本中第 j j j个分量为 a j i a_{ji} aji的样本的比例。

在文档分类器中,条件概率为指定分类中指定特征出现的概率(Pr (word | classification ))=指定分类中指定特征出现的次数/指定分类中所有特征出现的次数。

计算特征概率,我们可以通过样本数据集进行统计。

我们将这个基本功能写在classifier类中,该类将特征存储在sqlite数据库中,并实现对样本数据集和特征的相关统计。后面我们会在classifier类的基础上派生朴素贝叶斯分类器和派生费舍尔分类器。

在classifier类中我们实现将训练集中提取来的特征和分类存储到数据库中,并添加特征与分类之间的概率统计函数。

# 统计基类。主要为了计算特征概率Pr (word | classification )。有多种不同的分类器方式,在此类上派生。定义成类,这样每个实例对象可以训练自己的数据集
class classifier:
    #getfeatures为特征提取函数
    def __init__(self,getfeatures,filename=None):
        # 统计特征/分类组合的数量。(每个特征在不同分类中的数量)
        self.fc={}
        # 统计每个分类中的文档数量。(每个分类中的输入对象的数量)
        self.cc={}
        self.getfeatures=getfeatures
        self.setdb('test.db')    #设定数据库

    # 链接数据库,创建表
    def setdb(self,dbfile):
        self.con=sqlite3.connect(dbfile)
        self.curs = self.con.cursor()
        self.curs.execute('create table if not exists fc(feature,category,count)')
        self.curs.execute('create table if not exists cc(category,count)')

    # 增加对特征feature/分类cat组合的计数值
    def incf(self,feature,cat):
        count=self.fcount(feature,cat)   #计算某一特征在某一分类中出现的次数
        if count==0:
            self.curs.execute("insert into fc values ('%s','%s',1)" % (feature,cat))
        else:
            self.curs.execute("update fc set count=%d where feature='%s' and category='%s'" % (count+1,feature,cat))

    # 查询某一特征出现于某一分类中的次数
    def fcount(self,feature,cat):
        res=self.curs.execute('select count from fc where feature="%s" and category="%s"' %(feature,cat)).fetchall()
        if res==None or len(res)==0: return 0
        else: return float(res[0][0])

    # 增加对某一分类的计数值
    def incc(self,cat):
        count=self.catcount(cat)
        if count==0:
            self.curs.execute("insert into cc values ('%s',1)" % (cat))
        else:
            self.curs.execute("update cc set count=%d where category='%s'" % (count+1,cat))
    # 查询属于某一分类的输入对象(文章)的数量
    def catcount(self,cat):
        res=self.curs.execute('select count from cc where category="%s"' %(cat)).fetchall()
        if res==None or len(res)==0: return 0
        else: return float(res[0][0])
    # 查询所有分类的列表。因为要计算每个属于每个分类的比例
    def categories(self):
        cur=self.curs.execute('select category from cc')
        return [d[0] for d in cur]
    # 所有输入对象(文章)的数量.这是一项无用的计算。
    def totalcount(self):
        res=self.curs.execute('select sum(count) from cc').fetchall()
        if res==None or len(res)==0: return 0
        return res[0][0]

    # 对样本进行统计训练,完善数据库。参数为:输入对象,所属分类
    def train(self,input,cat):
        features=self.getfeatures(input)  #提取特征
        # 针对该分类为提取到的特征增加计数值
        for feature in features:
            self.incf(feature,cat)

        # 增加针对该分类的计数值
        self.incc(cat)
        self.con.commit()

#=================上面是存储特征和分类=================
#=================下面是计算特征概率Pr (word | classification )=================


    # 统计指定分类中某一特征出现的概率(单词在分类中出现的概率,Pr(word/classification))
    def fprob(self,feature,cat):
        if self.catcount(cat)==0: return 0

        # 该特征在分类中出现的次数,除以分类中所有特征的数目
        return self.fcount(feature,cat)/self.catcount(cat)

    # 计算加权概率,为特征设置权重,避免极少特征的强烈震荡。比如money单词只出现了一次在垃圾邮件中。也就是100%是垃圾邮件。所以添加权重概率避免这种事情。
    def weightedprob(self,feature,cat,prf,weight=1.0,ap=0.5):
        # 计算在某一分类中某一特征出现的概率
        basicprob=prf(feature,cat)

        # 统计某一特征在所有分类中出现的次数
        totals=sum([self.fcount(feature,cat) for cat in self.categories()])

        # 计算加权平均
        bp=((weight*ap)+(totals*basicprob))/(weight+totals)
        return bp  #返回加权平均概率

在classifier基类中计算了在指定分类(垃圾非垃圾)中每个特征(单词)出现的概率

Pr (word | classification )

为了计算新输入对象(一批特征)出现时,属于某个分类的概率

Pr ( Document | category )

我们采用两种情况以应对不同的场景。

输入对象概率——朴素贝叶斯分类

朴素贝叶斯分类器的过程。

这里写图片描述

对于特征间相互独立的情况。则指定分类中所有特征都出现的概率等于指定分类中每个特征出现的概率的乘机。即

Pr ( Document | category ) = Pr ( word1| category )*Pr ( word2| category )*Pr ( word3| category )...
# 派生朴素贝叶斯分类器(适用于特征间相互独立的情况)。将单词在目标分类中的概率组合成输入对象(文章)在分类中的概率。
class naivebayes(classifier):
 # 获取输入对象(文章)在指定分类中的概率。Pr(Document|Categrory)
	def docprob(self,input,cat):
	    features=self.getfeatures(input)
	    # 将所有特征的概率相乘
	    p=1
	    for feature in features: p*=self.weightedprob(feature,cat,self.fprob)
	    return p

有了指定分类器文档出现的概率,不能就此计算输入对象属于每个分类器的概率,并比较大小来判断属于哪个分类器。因为每个分类出现的概率本身并不是相等的。

Pr(category|Document)=Pr(Document|category)*Pr(category)/Pr(Document)

我们的目的是为了计算Pr(category|Document),现在我们已经计算了Pr(Document|category),而且对于每一个输入对象Pr(Document)都是相等的,所以要比较大小(而不是计算具体的概率值),只需要再计算Pr(category)。很显然Pr(category)等于指定分类中的特征数目除以总的特征数即可得到。

    # 统计分类的概率,并返回Pr(Document|Category)*Pr(Category)/Pr(Document)。朴素贝叶斯用这个概率代表最终概率进行比较
    def prob(self,input,cat):
        catprob=self.catcount(cat)/self.totalcount()
        docprob=self.docprob(input,cat)   #获取指定分类中,输入对象的概率.Pr(Document|Categrory)
        return docprob*catprob

通过上面的过程,我们就完成输入对象属于每种分类的相对大小。但是很多情况下,并不是输入对象属于一种分类的概率比另一种分类概率高,就把输入对象设置概率高的分类。例如一个邮件属于垃圾邮件的概率为60%,属于正常邮件的概率为40%。但是如果我们的判断有误,会对用户造成巨大的损失,为您情愿不判也不愿判断错误。为解决这一问题,我们可以为每个分类定义一个最小阈值n。输入对象属于该分类的概率至少大于属于其他分类的概率的n倍,才被认为属于该分类。

    def __init__(self,getfeatures):
        classifier.__init__(self,getfeatures)
        self.thresholds={}

    def setthreshold(self,cat,t):
        self.thresholds[cat]=t

    def getthreshold(self,cat):
        if cat not in self.thresholds: return 1.0
        return self.thresholds[cat]

    def classify(self,input,default=None):
        probs={}
        # 寻找概率最大的分类
        max=0.0
        cats = self.categories()
        for cat in cats:
            probs[cat]=self.prob(input,cat)
            if probs[cat]>max:
                max=probs[cat]
                best=cat

        # 确保概率值超过阈值*次大概率值
        for cat in probs:
            if cat==best: continue
            if probs[cat]*self.getthreshold(best)>probs[best]: return default
        return best

使用朴素贝叶斯分类器进行分类的试验

# 简单的样本训练
def sampletrain(cl,data):
    for item in data:
        cl.train(item[0],item[1])


if __name__=="__main__":     #只有在执行当前模块时才会运行此函数
    cl=naivebayes(getwords)   #定义朴素贝叶斯分类器
    sampletrain(cl,data)   #训练样本数据
    best = cl.classify('quick money')  #利用分类器进行分类
    print(best)   #打印分类结果

输入对象概率——费舍尔分类器

对于特征间不相互独立的情况。朴素贝叶斯分类器的效果就会受到影响。这里介绍一种新的分类器——费舍尔分类器。

同样在classifier类的基础上派生出费舍尔分类器。费舍尔分类器先计算某特征属于指定分类的概率Pr(Category|feature),再根据此概率计算费舍尔方法的值(所有概率相乘,取自然对数,再乘以-2)。最后通过输入对象的费舍尔值对输入对象进行分类。

1、计算某特征属于指定分类的概率Pr(Category|feature)

class fisherclassifier(classifier):
    # 计算某特征属于指定分类的概率Pr(Category|feature)
    def cprob(self,f,cat):
        # 特征在该分类中出现的概率
        clf=self.fprob(f,cat)
        if clf==0: return 0

        # 特征在所有分类中出现的频率
        freqsum=sum([self.fprob(f,c) for c in self.categories()])

        # 概率等于特征在该分类中出现的频率除以总体频率
        p=clf/(freqsum)

        return p

2、计算费舍尔值

# 根据输入对象获取属性特征,计算特征属于分类的概率,再计算费舍尔的值作为输入对象(文章)属于指定分类的概率
    def fisherprob(self,input,cat):
        # 将所有概率值相乘
        p=1
        features=self.getfeatures(input)
        for f in features:
            p*=(self.weightedprob(f,cat,self.cprob))

        # 取自然对数,并乘以-2
        fscore=-2*math.log(p)

        # 利用倒置对数卡方函数求得概率
        return self.invchi2(fscore,len(features)*2)
    # 倒置对数卡方函数
    def invchi2(self,chi, df):
        m = chi / 2.0
        sum = term = math.exp(-m)
        for i in range(1, df//2):
            term *= m / i
            sum += term
        return min(sum, 1.0)

3、使用费舍尔值,通过为不同的分类设定各自的费舍尔值类判断是否划分到各自的分类。输入对象的属于不同分类的费舍尔值之间就没有相互比较的意义。他们只需要个每个分类对象的费舍尔门限比较就可以了。

比如,在朴素贝叶斯分类器中,一封邮件要计算出属于正常邮件的概率,还要计算出属于垃圾邮件的概率(在其他应用中还有更多的分类),通过计算至此之间的概率倍数,来决定是否划归到某分类。而在费舍尔分类器中,会先计算邮件属于正常邮件的费舍尔值,如0.7,如果大于正常邮件这个分类的费舍尔门限,如0.6,就直接被划归到正常邮件分类中。如果低于门限,再计算属于其他分类的费舍尔值。

代码实现


    def setminimum(self,cat,min):
        self.minimums[cat]=min
    def getminimum(self,cat):
        if cat not in self.minimums: return 0
        return self.minimums[cat]

    # 使用具体的下限费舍尔值来对输入对象进行分类
    def classify(self,input,default=None):
        # 循环遍历并寻找最佳结果
        best=default
        max=0.0
        for c in self.categories():
            p=self.fisherprob(input,c)   #计算输入对象的费舍尔值
            # 确保其超过下限值
            if p>self.getminimum(c) and p>max:
                best=c
                max=p
        return best

测试代码


if __name__=="__main__":     #只有在执行当前模块时才会运行此函数
    cl = fisherclassifier(getwords)  # 定义费舍尔分类器
    sampletrain(cl, data)  # 训练样本数据(数据清洗、转换、提取)
    best = cl.classify('quick money')  # 利用分类器进行分类
    print(best)  # 打印分类结果

上面的案例我们使用邮件垃圾筛选。我们将全部代码存储为docclass.py文件。全部代码如下。
后面我们使用这个分类器来为博客文章进行分类。

# 文档过滤,垃圾邮件分类。跟聚类(不知道多少分类,也不知道有什么类别)不同,它是一种监督式的学习方法。因此也就在分类前已知道分类(垃圾邮件和非垃圾邮件)。判断对象属于哪种分类。
import sqlite3
import re
import math


# 邮件数据集,,邮件内容和所属分类
data=[
    ['Nobody owns the water.','good'],
    ['the quick rabbit jumps fences','good'],
    ['buy pharmaceuticals now','bad'],
    ['make quick money at the online casino','bad'],
    ['the quick brown fox jumps','good']
]

# 从输入对象中提取特征(文档中提取不重复单词)
def getwords(doc):
    splitter=re.compile('\\W*')
    # print(doc)
    # 根据非字母字符进行单词拆分
    words=[s.lower() for s in splitter.split(doc) if len(s)>2 and len(s)<20]

    # 只返回一组不重复的单词。(特征不重复)
    return dict([(w,1) for w in words])


# 统计基类。主要为了计算特征概率Pr (word | classification )。有多种不同的分类器方式,在此类上派生。定义成类,这样每个实例对象可以训练自己的数据集
class classifier:
    #getfeatures为特征提取函数
    def __init__(self,getfeatures,filename=None):
        # 统计特征/分类组合的数量。(每个特征在不同分类中的数量)
        self.fc={}
        # 统计每个分类中的文档数量。(每个分类中的输入对象的数量)
        self.cc={}
        self.getfeatures=getfeatures
        self.setdb('test.db')    #设定数据库

    # 链接数据库,创建表
    def setdb(self,dbfile):
        self.con=sqlite3.connect(dbfile)
        self.curs = self.con.cursor()
        self.curs.execute('create table if not exists fc(feature,category,count)')
        self.curs.execute('create table if not exists cc(category,count)')

    # 增加对特征feature/分类cat组合的计数值
    def incf(self,feature,cat):
        count=self.fcount(feature,cat)   #计算某一特征在某一分类中出现的次数
        if count==0:
            self.curs.execute("insert into fc values ('%s','%s',1)" % (feature,cat))
        else:
            self.curs.execute("update fc set count=%d where feature='%s' and category='%s'" % (count+1,feature,cat))

    # 查询某一特征出现于某一分类中的次数
    def fcount(self,feature,cat):
        res=self.curs.execute('select count from fc where feature="%s" and category="%s"' %(feature,cat)).fetchall()
        if res==None or len(res)==0: return 0
        else: return float(res[0][0])

    # 增加对某一分类的计数值
    def incc(self,cat):
        count=self.catcount(cat)
        if count==0:
            self.curs.execute("insert into cc values ('%s',1)" % (cat))
        else:
            self.curs.execute("update cc set count=%d where category='%s'" % (count+1,cat))
    # 查询属于某一分类的输入对象(文章)的数量
    def catcount(self,cat):
        res=self.curs.execute('select count from cc where category="%s"' %(cat)).fetchall()
        if res==None or len(res)==0: return 0
        else: return float(res[0][0])
    # 查询所有分类的列表。因为要计算每个属于每个分类的比例
    def categories(self):
        cur=self.curs.execute('select category from cc')
        return [d[0] for d in cur]
    # 所有输入对象(文章)的数量.这是一项无用的计算。
    def totalcount(self):
        res=self.curs.execute('select sum(count) from cc').fetchall()
        if res==None or len(res)==0: return 0
        return res[0][0]

    # 对样本进行统计训练,完善数据库。参数为:输入对象,所属分类
    def train(self,input,cat):
        features=self.getfeatures(input)  #提取特征
        # 针对该分类为提取到的特征增加计数值
        for feature in features:
            self.incf(feature,cat)

        # 增加针对该分类的计数值
        self.incc(cat)
        self.con.commit()

#=================上面是存储特征和分类=================
#=================下面是计算特征概率Pr (word | classification )=================


    # 统计指定分类中某一特征出现的概率(单词在分类中出现的概率,Pr(word/classification))
    def fprob(self,feature,cat):
        if self.catcount(cat)==0: return 0

        # 该特征在分类中出现的次数,除以分类中所有特征的数目
        return self.fcount(feature,cat)/self.catcount(cat)

    # 计算加权概率,为特征设置权重,避免极少特征的强烈震荡。比如money单词只出现了一次在垃圾邮件中。也就是100%是垃圾邮件。所以添加权重概率避免这种事情。
    def weightedprob(self,feature,cat,prf,weight=1.0,ap=0.5):
        # 计算在某一分类中某一特征出现的概率
        basicprob=prf(feature,cat)

        # 统计某一特征在所有分类中出现的次数
        totals=sum([self.fcount(feature,cat) for cat in self.categories()])

        # 计算加权平均
        bp=((weight*ap)+(totals*basicprob))/(weight+totals)
        return bp  #返回加权平均概率



# 派生朴素贝叶斯分类器(适用于特征间相互独立的情况)。将单词在目标分类中出现的概率相乘组成输入对象(文章)出现在分类中的概率。
class naivebayes(classifier):

    def __init__(self,getfeatures):
        classifier.__init__(self,getfeatures)
        self.thresholds={}

    # 获取输入对象(文章)出现在指定分类中的概率。Pr(Document|Categrory)
    def docprob(self,input,cat):
        features=self.getfeatures(input)
        # 将所有特征的概率相乘
        p=1
        for feature in features: p*=self.weightedprob(feature,cat,self.fprob)
        return p

    # 统计分类的概率,并返回Pr(Document|Category)*Pr(Category)/Pr(Document)。朴素贝叶斯用这个概率代表最终概率进行比较
    def prob(self,input,cat):
        catprob=self.catcount(cat)/self.totalcount()
        docprob=self.docprob(input,cat)   #获取指定分类中,输入对象的概率.Pr(Document|Categrory)
        return docprob*catprob

    def setthreshold(self,cat,t):
        self.thresholds[cat]=t

    def getthreshold(self,cat):
        if cat not in self.thresholds: return 1.0
        return self.thresholds[cat]

    def classify(self,input,default=None):
        probs={}
        # 寻找概率最大的分类
        max=0.0
        cats = self.categories()
        for cat in cats:
            probs[cat]=self.prob(input,cat)
            if probs[cat]>max:
                max=probs[cat]
                best=cat

        # 确保概率值超过阈值*次大概率值
        for cat in probs:
            if cat==best: continue
            if probs[cat]*self.getthreshold(best)>probs[best]: return default
        return best


# 派生费舍尔分类器。先计算某特征属于指定分类的概率Pr(Category|feature),再根据此概率计算费舍尔方法的值(所有概率相乘,取自然对数,再乘以-2)。
class fisherclassifier(classifier):
    # 计算某特征属于指定分类的概率Pr(Category|feature)
    def cprob(self,f,cat):
        # 特征在该分类中出现的概率
        clf=self.fprob(f,cat)
        if clf==0: return 0

        # 特征在所有分类中出现的频率
        freqsum=sum([self.fprob(f,c) for c in self.categories()])

        # 概率等于特征在该分类中出现的频率除以总体频率
        p=clf/(freqsum)

        return p

    # 根据输入对象获取属性特征,计算特征属于分类的概率,再计算费舍尔的值作为输入对象(文章)属于指定分类的概率
    def fisherprob(self,input,cat):
        # 将所有概率值相乘
        p=1
        features=self.getfeatures(input)
        for f in features:
            p*=(self.weightedprob(f,cat,self.cprob))

        # 取自然对数,并乘以-2
        fscore=-2*math.log(p)

        # 利用倒置对数卡方函数求得概率
        return self.invchi2(fscore,len(features)*2)
    # 倒置对数卡方函数
    def invchi2(self,chi, df):
        m = chi / 2.0
        sum = term = math.exp(-m)
        for i in range(1, df//2):
            term *= m / i
            sum += term
        return min(sum, 1.0)

    def __init__(self,getfeatures):
        classifier.__init__(self,getfeatures)
        self.minimums={}



    def setminimum(self,cat,min):
        self.minimums[cat]=min
    def getminimum(self,cat):
        if cat not in self.minimums: return 0
        return self.minimums[cat]

    # 使用具体的下限费舍尔值来对输入对象进行分类
    def classify(self,input,default=None):
        # 循环遍历并寻找最佳结果
        best=default
        max=0.0
        for c in self.categories():
            p=self.fisherprob(input,c)   #计算输入对象的费舍尔值
            # 确保其超过下限值
            if p>self.getminimum(c) and p>max:
                best=c
                max=p
        return best


# 简单的样本训练
def sampletrain(cl,data):
    for item in data:
        cl.train(item[0],item[1])


if __name__=="__main__":     #只有在执行当前模块时才会运行此函数
    cl=naivebayes(getwords)   #定义朴素贝叶斯分类器
    sampletrain(cl,data)   #训练样本数据(数据清洗、转换、提取)
    best = cl.classify('quick money')  #利用分类器进行分类
    print(best)   #打印分类结果

    cl = fisherclassifier(getwords)  # 定义费舍尔分类器
    sampletrain(cl, data)  # 训练样本数据(数据清洗、转换、提取)
    best = cl.classify('quick money')  # 利用分类器进行分类
    print(best)  # 打印分类结果


使用文档分类器,对博客文章进行分类。

首先我们要搜集一个数据集。读者可以自己爬取,下面给出一个收集好的数据集python_search.xml

点击下载。

使用前面的分类器对博客文章进行分类。

# 利用分类器,应用到过滤博客订阅源
import feedparser
import re
import docclass

# 接受一个博客订阅源的url文件名并对内容项进行分类
def read(feedfile,classifier):
    # 得到订阅源的内容项并遍历循环
    f=feedparser.parse(feedfile)
    for entry in f['entries']:
        print
        print('-----')
        # 将内容项打印输出
        print('Title:     '+entry['title'])
        print('Publisher: '+entry['publisher'])
        print
        print(entry['summary'])


        # 将所有文本组合在一起,为分类器构建一个内容项
        fulltext='%s\n%s\n%s' % (entry['title'],entry['publisher'],entry['summary'])

        # 将当前分类的最佳推测结果打印输出
        print('Guess: '+str(classifier.classify(fulltext)))

        # 请求用户给出正确分类,并据此进行训练
        if(entry.has_key('cat') and entry['cat']!=None):
            classifier.train(fulltext, entry['cat'])
        else:
            cl=input('Enter category: ')
            classifier.train(fulltext,cl)

if __name__=="__main__":     #只有在执行当前模块时才会运行此函数
    # 对博客文章进行分类和训练
    cl=docclass.fisherclassifier(docclass.getwords)
    cl.setdb('python_feed.db')
    read('python_search.xml',cl)

除了我们可以选择使用不同的分类器来实现对输入对象的分类效果。我们还可以明显的注意到特征的选择对分类也是至关重要的。前面的所有例子,我们均使用文章的所有单词作为输入对象的特征。
但有时能代表一篇文章的可能是标题,作者,摘要等重要信息,而不是全部单词。所以我们还可以对特征提取函数进行改进,以使分类效果更好。

# 利用分类器,应用到过滤博客订阅源
import feedparser
import re
import docclass

# 接受一个博客订阅源的url文件名并对内容项进行分类
def read(feedfile,classifier):
    # 得到订阅源的内容项并遍历循环
    f=feedparser.parse(feedfile)
    for entry in f['entries']:
        print
        print('-----')
        # 将内容项打印输出
        print('Title:     '+entry['title'])
        print('Publisher: '+entry['publisher'])
        print
        print(entry['summary'])


        # 将所有文本组合在一起,为分类器构建一个内容项
        fulltext='%s\n%s\n%s' % (entry['title'],entry['publisher'],entry['summary'])

        # 将当前分类的最佳推测结果打印输出
        print('Guess: '+str(classifier.classify(fulltext)))

        # 请求用户给出正确分类,并据此进行训练
        if(entry.has_key('cat') and entry['cat']!=None):
            classifier.train(fulltext, entry['cat'])
        else:
            cl=input('Enter category: ')
            classifier.train(fulltext,cl)


# 对特征检测的改进。新的特征提取函数。更关注文章的名称、摘要、作者介绍
def entryfeatures(entry):
    splitter=re.compile('\\W*')
    features={}

    # 提取标题中的单词并进行标识
    titlewords=[s.lower() for s in splitter.split(entry['title']) if len(s)>2 and len(s)<20]
    for w in titlewords: features['Title:'+w]=1

    # 提取摘要中单词
    summarywords=[s.lower() for s in splitter.split(entry['summary']) if len(s)>2 and len(s)<20]

    # 统计大写单词
    uc=0
    for i in range(len(summarywords)):
        w=summarywords[i]
        features[w]=1
        if w.isupper(): uc+=1

        # 将从摘要中获得词组作为特征
        if i<len(summarywords)-1:
            twowords=' '.join(summarywords[i:i+1])
            features[twowords]=1

    # 保持文章创建者和发布者名字的完整性
    features['Publisher:'+entry['publisher']]=1

    # UPPERCASE是一个“虚拟”单词,用以指示存在过多的大写内容
    if float(uc)/len(summarywords)>0.3: features['UPPERCASE']=1

    return features


if __name__=="__main__":     #只有在执行当前模块时才会运行此函数

    # 使用改进的特征提取函数对文章分类进行处理
    cl = docclass.fisherclassifier(entryfeatures)
    cl.setdb('python_feed.db')
    read('python_search.xml', cl)


  • 3
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

腾讯AI架构师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值