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

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(A∣B)p(B)=p(B∣A)p(A)

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

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

p(a∣X)=p(X∣a)p(a)/p(X)=p(x1,x2,x3...xn∣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(a∣X)=p(X∣a)p(a)/p(X)=p(x1​,x2​,x3​...xn​∣a)p(a)/P(X)

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

p(a∣X)=p(X∣a)p(a)/p(X)=p(x1∣a)∗p(x2∣a)∗p(x3∣a)∗...∗p(xn∣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(a∣X)=p(X∣a)p(a)/p(X)=p(x1​∣a)∗p(x2​∣a)∗p(x3​∣a)∗...∗p(xn​∣a)∗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=Ck)P(Y=C_k)P(Y=Ck​)的极大似然估计为所有样本中属于属于分类CkC_kCk​的比例,也就是任一样本属于分类CkC_kCk​的概率。

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

在朴素贝叶斯中,设样本第jjj个特征可能取值的集合为aj1,aj2,aj3...ajs{a_{j1},a_{j2}},a_{j3}...a_{js}aj1​,aj2​,aj3​...ajs​,条件概率P(X(j)=aji∣Y=Ck)P(X^{(j)}=a_{ji}|Y=C_k)P(X(j)=aji​∣Y=Ck​)的极大似然估计为属于分类CkC_kCk​的所有样本中第jjj个分量为ajia_{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=getfeaturesself.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 0else: 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 0else: 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 0return 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=1for 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]=tdef getthreshold(self,cat):if cat not in self.thresholds: return 1.0return self.thresholds[cat]def classify(self,input,default=None):probs={}# 寻找概率最大的分类max=0.0cats = 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: continueif probs[cat]*self.getthreshold(best)>probs[best]: return defaultreturn 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=1features=self.getfeatures(input)for f in features:p*=(self.weightedprob(f,cat,self.cprob))# 取自然对数,并乘以-2fscore=-2*math.log(p)# 利用倒置对数卡方函数求得概率return self.invchi2(fscore,len(features)*2)# 倒置对数卡方函数def invchi2(self,chi, df):m = chi / 2.0sum = term = math.exp(-m)for i in range(1, df//2):term *= m / isum += termreturn min(sum, 1.0)

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

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

代码实现

def setminimum(self,cat,min):self.minimums[cat]=mindef getminimum(self,cat):if cat not in self.minimums: return 0return self.minimums[cat]# 使用具体的下限费舍尔值来对输入对象进行分类def classify(self,input,default=None):# 循环遍历并寻找最佳结果best=defaultmax=0.0for c in self.categories():p=self.fisherprob(input,c)   #计算输入对象的费舍尔值# 确保其超过下限值if p>self.getminimum(c) and p>max:best=cmax=preturn 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=getfeaturesself.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 0else: 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 0else: 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 0return 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=1for 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*catprobdef setthreshold(self,cat,t):self.thresholds[cat]=tdef getthreshold(self,cat):if cat not in self.thresholds: return 1.0return self.thresholds[cat]def classify(self,input,default=None):probs={}# 寻找概率最大的分类max=0.0cats = 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: continueif probs[cat]*self.getthreshold(best)>probs[best]: return defaultreturn 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=1features=self.getfeatures(input)for f in features:p*=(self.weightedprob(f,cat,self.cprob))# 取自然对数,并乘以-2fscore=-2*math.log(p)# 利用倒置对数卡方函数求得概率return self.invchi2(fscore,len(features)*2)# 倒置对数卡方函数def invchi2(self,chi, df):m = chi / 2.0sum = term = math.exp(-m)for i in range(1, df//2):term *= m / isum += termreturn min(sum, 1.0)def __init__(self,getfeatures):classifier.__init__(self,getfeatures)self.minimums={}def setminimum(self,cat,min):self.minimums[cat]=mindef getminimum(self,cat):if cat not in self.minimums: return 0return self.minimums[cat]# 使用具体的下限费舍尔值来对输入对象进行分类def classify(self,input,default=None):# 循环遍历并寻找最佳结果best=defaultmax=0.0for c in self.categories():p=self.fisherprob(input,c)   #计算输入对象的费舍尔值# 确保其超过下限值if p>self.getminimum(c) and p>max:best=cmax=preturn 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']:printprint('-----')# 将内容项打印输出print('Title:     '+entry['title'])print('Publisher: '+entry['publisher'])printprint(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']:printprint('-----')# 将内容项打印输出print('Title:     '+entry['title'])print('Publisher: '+entry['publisher'])printprint(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=0for i in range(len(summarywords)):w=summarywords[i]features[w]=1if 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']=1return featuresif __name__=="__main__":     #只有在执行当前模块时才会运行此函数# 使用改进的特征提取函数对文章分类进行处理cl = docclass.fisherclassifier(entryfeatures)cl.setdb('python_feed.db')read('python_search.xml', cl)

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

  1. python机器学习案例系列教程——算法总结

    机器学习算法太多了,分类.回归.聚类.推荐.图像识别领域等等,要想找到一个合适算法真的不容易,所以在实际应用中,我们一般都是采用启发式学习方式来实验.通常最开始我们都会选择大家普遍认同的算法,诸如SV ...

  2. python机器学习案例系列教程——层次聚类(文档聚类)

    分享一个朋友的人工智能教程.零基础!通俗易懂!风趣幽默!还带黄段子!大家可以看看是否对自己有帮助:点击打开 docker/kubernetes入门视频教程 全栈工程师开发手册 (作者:栾鹏) pyth ...

  3. python机器学习案例系列教程——推荐系统

    全栈工程师开发手册 (作者:栾鹏) python数据挖掘系列教程 主流的推荐系统算法大致分为两类: 基于用户行为数据的协同过滤算法 基于内容数据的过滤算法 大致而言,基于内容数据的算法适用于cold ...

  4. python机器学习案例系列教程——集成学习(Bagging、Boosting、随机森林RF、AdaBoost、GBDT、xgboost)

    全栈工程师开发手册 (作者:栾鹏) python数据挖掘系列教程 可以通过聚集多个分类器的预测结果提高分类器的分类准确率,这一方法称为集成(Ensemble)学习或分类器组合(Classifier C ...

  5. python机器学习案例系列教程——k均值聚类、k中心点聚类

    全栈工程师开发手册 (作者:栾鹏) python数据挖掘系列教程 上一篇我们学习了层次聚类.层次聚类只是迭代的把最相近的两个聚类匹配起来.并没有给出能给出多少的分组.今天我们来研究一个K均值聚类.就是 ...

  6. python机器学习案例系列教程——决策树(ID3、C4.5、CART)

    全栈工程师开发手册 (作者:栾鹏) python数据挖掘系列教程 决策树简介 决策树算是最好理解的分类器了.决策树就是一个多层if-else函数,就是对对象属性进行多层if-else判断,获取目标属性 ...

  7. python机器学习案例系列教程——GBDT构建新特征

    全栈工程师开发手册 (作者:栾鹏) python数据挖掘系列教程 GBDT的算法参考:https://blog.csdn.net/luanpeng825485697/article/details/7 ...

  8. python机器学习案例系列教程——聚类算法总结

    全栈工程师开发手册 (作者:栾鹏) python教程全解 一.什么是聚类? 聚类(Clustering):聚类是一个人们日常生活的常见行为,即所谓"物以类聚,人以群分",核心的思想 ...

  9. python机器学习案例系列教程——GBDT算法、XGBOOST算法

    全栈工程师开发手册 (作者:栾鹏) python数据挖掘系列教程 GBDT概述 GBDT也是集成学习Boosting家族的成员,但是却和传统的Adaboost有很大的不同.回顾下Adaboost,我们 ...

最新文章

  1. python readlines慢_为什么readline()比Python中的readlines()慢得多?
  2. Spring MVC上下文父子容器
  3. 声明为指针,定义为数组,声明为数组,定义为指针
  4. python中的pygame模块使用方法_Pygame的基本使用
  5. SSL请求trustStore的两种注册方式
  6. linux常见故障一:linux 文件系统变只读
  7. nginx的目录结构和配置文件
  8. python3.8自带matlop和numpy吗_python3.8自带matlop和numpy吗_python之matloplib可视化
  9. 寄存器间接寻址缺点_详解西门子间接寻址之地址寄存器间接寻址
  10. Windows Terminal 窗口/控制台切换快捷键总结
  11. matlab练习程序(模拟退火SA)
  12. Rust: Rust 异步入门 (作者洋芋,来自Rust语言中文社区)
  13. 物联网终端有哪些应用
  14. android 2k屏分辨率是多少,手机2k屏幕是什么意思 2k屏幕几大问题
  15. linux清华源地址,pip清华镜像源使用方法总结
  16. 实话实说!猿如意大测评!助力coding!
  17. 企业运维容器之 docker 安全
  18. orCAD中的文本编辑框如何换行?
  19. 题目:python 打印出如下图案(菱形):
  20. 什么是大数据4v 指的是哪四个

热门文章

  1. (转)【自动语音识别课程】
  2. 语音识别(LSTM+CTC)
  3. python3 x默认使用的编码_Python3编码问题(Python2请忽略)
  4. 【数组】牛客网:调整数组顺序使奇数位于偶数前面(一)
  5. 【动态规划笔记】完全背包问题及优化
  6. 【离散期末复习】:集合论
  7. FFmpeg源代码简单分析:avcodec_open2()
  8. 安卓 mysql读取图片路径_android开发之数据库存取图片
  9. 坚果pro2 android 8,手机 篇一:坚果Pro2特别版使用感受
  10. php可以在dw中吗,php新手求助,为什么在DW中设计里能看到运行php程序的结果,但…...