朴素贝叶斯、费舍尔分类法
案例:有一批图书分类样本数据,用朴素贝叶斯、费舍尔分类法对样本数据学习进行分类。
数据清洗
这里我们不讨论数据的获取和清洗,而是聚焦在使用朴素贝叶斯、费舍尔分类法上。假设我们已经从数据中确定了几列有价值的数据,并做好了数据清洗。清洗后生成样本数据如下:
#第一列Title|第二列Publisher|第三列Summary|第四列图书分类
Handsbook for Python|Oreilly| a book for Python|Python
Handsbook for Linux|Oreilly| a book for Linux|Linux
Handsbook for Hive|Oreilly|a book for Hive|Hive
Handsbook for Spark|Oreilly| a book for Spark|Spark
思路逻辑图:
Python代码
1、定义特征、提取特征 getfeatures.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#@Time: 2019-11-10 22:25
#@Author: gaoll
#特征定义方法1
'''
(1)将Title中出现的单词word当作一个特征,字符长度2-10之间
(2)将Publiser当作一个特征
(3)将Summary中出现的单词word当作特征,字符长度2-20之间
(4)将Summary中出现的两个词的词组当作特征
(5)统计Summary中出现的大写单词,如果出现频次超多30%则作为一个特征,记作UPPERCASE。这是一个虚拟特征。
'''
import re
def getfeature1(item):
arr = item.split('|') #训练样本数据,第一列Title|第二列Publisher|第三列Summary|分类
Title = arr[0]
Publisher = arr[1]
Summary = arr[2]
category = arr[3]
#print(Title)
#print(Publisher)
#print(Summary)
f = {} #建立一个空字典存放特征
splitter = re.compile('\\W*') #按照文中出现的特殊字符,对字符串进行分割
#(1)Title中的出现的单词
titlewords = [s.lower() for s in Title.split(' ') if len(s)>2 and len(s)<20]
#print(titlewords)
for w in titlewords:
f['Title:'+w] = 1 #将Title中出现的单词作为一个特征,它区分于其他位置出现的特征
#(2)Publisher作为一个特征
f['Publisher:'+Publisher] = 1
#(3)Summary中出现的单词作为特征\(4)Summary中出现的两个词的词组作为特征
summarywords = [s for s in Summary.split(' ') if len(s)>2 and len(s)<20]
#print(summarywords)
uc = 0
for i in range(len(summarywords)):
w = summarywords[i]
if w.isupper():
uc += 1
f[w.lower()] = 1 #(3)Summary中出现的单词作为特征
if i < len(summarywords) -1 :
twowords = ' '.join([summarywords[i].lower(),summarywords[i+1].lower()])
f[twowords] = 1 #(4)Summary中出现的两个词的词组作为特征
#(5)统计Summary中出现的大写单词,如果出现频次超多30%则作为一个特征,记作UPPERCASE。这是一个虚拟特征。
#print(uc)
#print(summarywords)
if float(uc)/len(summarywords) > 0.3:
f['UPPERCASE'] = 1
return f,category
2、创建分类器 docclass.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#@Time: 2019-11-10 23:18
#@Author: gaoll
import sqlite3 #使用sqlite操作数据库
import math
#这里定义分类器
#创建一个类
class classifier():
"""docstring for classifier"""
def __init__(self, getfeatures,filename=None):
#super(classifier, self).__init__()
self.getfeatures = getfeatures
def setdb(self,dbfile):
#链接数据库,创建表fc,cc用于存放特征数据
self.con = sqlite3.connect(dbfile)
self.con.execute('create table if not exists fc(feature,category,count)')
self.con.execute('create table if not exists cc(category,count)')
def incf(self,f,cat):
#将[f,cat,num]数据写入 fc 表中
count = self.fcount(f,cat)
if count == 0 :
self.con.execute("insert into fc values(%s,%s,1)"%(f,cat))
else:
self.con.execute("update fs set count=%d where feature='%s' and category='%s'"%(count+1,f,cat))
def fcount(self,f,cat):
#从表 fc 中读取当前特征[f,cat,num]
res = self.con.execute("select count from fc where feature='%s' and category='%s'"%(f,cat)).fetchone()
if res == None:
return 0
else:
return float(res[0])
def incc(self,cat):
#将[cat,num] 数据写入cc 表中
count = self.catcount(cat)
if count == 0 :
self.con.execute("insert into cc values('%s',1)"%cat)
else:
self.con.execute("update cc set count=%d where cat='%s'"%(count+1,cat))
def catcount(self,cat):
#读取当前[cat,num]数据
res = self.con.execute("select count from cc where category='%s'"%cat).fetchone()
if res == None:
return 0
else:
return float(res[0])
def categories(self):
#全部分类的列表
cur = self.con.execute('select category form cc').fetcheall()
category_list = [d[0] for d in cur]
return category_list
def totalcount(self):
#总的样本量
res = self.con.execute('select sum(count) from cc').fetchone()
if res == None:
return 0
else:
return res[0]
def train(self,item):
features,cat = self.getfeatures(item) #特征提取时返回的是特征列表和分类两个变量
self.incc(cat) #[cat,num]数据写入
for f in features:
self.incf(f,cat) #[f,cat,num]数据写入
self.con.commit() #提交数据库命令操作
def basic_prob(self,f,cat):
#类方法,基本概率P(f,cat),朴素贝叶斯和费舍尔分类方法都有用到
#定义基本概率P(f,cat)= 特征f出现在cat中计数值/分类cat计数值。
if self.catcount(cat) == 0:
return 0
else:
return self.fcount(f,cat)/self.catcount(cat)
def weightedprob(self,f,cat,prob_func,weight=1.0,assumedprob=0.5):
#从初始值assumedprob=0.5开始,取其与新计算的概率值得加权概率值作为概率
basicprob = prob_func(f,cat) #计算当前的概率值
totals = 0
for c in self.categories():
totals += self.fcount(f,cat) #计算f特征在所有分类中出现的次数,把它当作新概率的权重系数
bp = (weight*assumedprob + totals*basicprob) / (weight + totals) #计算权重概率
return bp
#创建朴素贝叶斯分类器
'''
1、根据贝叶斯公式:P(cat|item) = P(item|cat) * (P(cat) / P(item))
2、其中概率P(item|cat) *= P(f|cat),f为item中提取的特征
3、其中概率P(f|cat)=fc(f,cat)/cc(cat) #定义成了类方法
4、其中概率P(cat)=cc(cat)/total分类数
5、其中P(item)对于本次计算概率都一样,所以忽略此项
6、综上,对于给定的一个item属于分类cat的概率值P(cat|item)
'''
class naivebayes(classifier):
"""新建一个classifier的子类,命名navibayes,朴素贝叶斯"""
def __init__(self, getfeatures):
super().__init__(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
else:
return self.thresholds[cat]
def naivebayes_prob(self,item,cat):
#根据公式,计算贝叶斯分类概率
cat_prob = self.catcount(cat) / self.totalcount() #分类cat的概率
item_cat_prob = 1
features = self.getfeatures(item) #提取特征
for f in features:
item_cat_prob *= self.weightedprob(f,cat,self.basic_prob) #计算item属于cat分类的概率,也可以用最简单的概率计算公式basic_prob
bayesprob = item_cat_prob * cat_prob
return bayesprob
def classify(self,item,default=None):
#比较分类概率,及阈值,得出最后的分类
maxprob = 0.0
best_cat = None
for cat in self.categorys():
bayesprob = self.naivebayes_prob(item,cat)
if bayesprob > maxprob * self.getthreshold(cat):
maxprob = bayesprob
best_cat = cat
return best_cat
#创建费舍尔分类器
'''
1、根据F检验,计算概率公式P(cat|item)
=invchi2(fscore,df),其中invchi2为倒置对数卡方函数,自由度df = item中特征量*2。
2、其中值fscore= -2 * math.log(P(item|cat))
3、其中概率P(item|cat) *= P(f|cat),f为item中提取的特征
4、其中P(f|cat)=P1(f|cat)/sum(P1(f|cat),cat)
5、其中概率值P1(f|cat)=fc(f,cat)/cc(cat)
6、综上,对于给定的一个item属于分类cat的概率值P(cat|item)
'''
class fisherclassfier(classifier):
"""新建一个classifier的子类,命名fisherclassfier,费舍尔分类器"""
def __init__(self, getfeatures):
super().__init__(getfeatures) #获取父类中定义的特征提取方法
self.minimuns = {} #存储每个分类的概率下限
def setminimum(self,cat,min):
self.minimuns[cat] = min
def getminmun(self,cat):
if cat not in self.minimuns:
return 0
else:
return self.minimuns[cat]
def cprob(self,f,cat):
#特征在该分类中出现的频率,即基本概率
clf = self.basic_prob(f,cat)
if clf == 0:
return 0
#特征在所有分类中出现的频率
freqsum = 0
for c in self.categories():
freqsum += self.basic_prob(f,c)
#概率等于特征在该分类中出现的频率除以总体频率
p = clf/freqsum
return p
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 fisher_prob(self,item,cat):
item_cat_prob = 1
features = self.getfeatures(item)
for f in features:
item_cat_prob *= self.weightedprob(f,cat,self.cprob)
#计算F值
fscore = -2 * math.log(item_cat_prob)
#利用倒置对数卡方函数求得概率
return self.invchi2(fscore,len(features)*2)
def classify(self,item,default=None):
#比较各分类概率,及下限,得出最终概率
maxprob = 0.0
best_cat = None
for c in self.categories():
fisherprob = self.fisher_prob(item,cat)
if fisher_prob > self.getminmun(c) anf fisher_prob > maxprob:
maxprob = fisher_prob
best_cat = c
return best_cat
3、训练和预测 train_predict.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#@Time: 2019-11-11 01:18
#@Author: gaoll
def sampletrain(samplefile,cf,database):
#对样本文件进行训练
cf.setdb(database) #输入sql使用的database
f = open(samplefile,'r')
for line in f:
cf.train(line) #对样本进行训练
def predict(testfile,cf):
#对测试文件进行预测
#cf.setdb('databasename.db') #输入sql使用的database
f = open(testfile,'r')
for line in f:
cat=cf.classify(line) #对样本进行训练
print('|'.join([line,cat]))
4、运行 run.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#@Time: 2019-11-11 02:53
#@Author: gaoll
import os,sys,datetime
import getfeatures
import docclass
import train_predict
from getfeatures import getfeature1
from getfeatures import getfeature2
from train_predict import sampletrain
from train_predict import predict
if __name__ == '__main__':
train_file = 'train_sample_file.txt'
test_file = 'test_sample_file.txt'
cf = docclass.fisherclassfier(getfeatures.getfeature1) #使用费舍尔方法 + 第一种特征定义
database = 'gaoll' #数据库名
train_predict.sampletrain(train_file,cf,database) #开始训练
train_predict.predict(test_file,cf) #预测
Done