正文
Part 4.2.1 一个小小的问题
在训练的样本中,由于单词‘money’只出现了一次,并且是一个赌博类的广告,因此被分类‘bad’类,那我们计算p('money' | 'good') = 0,这是非常危险和不公平的,由于我们训练样本的缺失,导致所有含有‘money’这个单词的文档都被判断为‘bad’类文档。显然这种结果是我们不愿意接受的,因此我们对概率进行一些加权,使一些即使在训练样本中没有出现的单词,在求条件概率的时候,不至于为0。具体做法如下:
def weightedprob(self, f, cat, prf, weight=1, ap=0.5):
# 使用fprob函数计算原始的条件概率
basicprob = prf(f, cat)
totals = sum([self.fcount(f, c) for c in self.categories()])
bp = ((weight*ap)+(totals*basicprob))/(weight+totals)
return bp
这个函数就是经过加权以后的条件概率,我们来对比一下加权前后的条件概率:
cl = classifier(getwords)
sampletrain(cl)
cl.fprob('money','good')
out:0
cl.weightedprob('money','good')
out:0.25
Part 4.3 朴素分类器
之所以称为朴素贝叶斯分类器的前提是被组合的各个概率之间是独立的,在我们的例子中,可以这样理解:一个单词在属于某个分类文档中概率,与其他单词出现在该分类的概率是不相关的。事实上,这个假设并不成立,因为很多词都是结伴出现的,但是我们可以忽略,实践显示,在假设各单词互相独立的基础上,使用朴素贝叶斯对文本分类可以达到比较好的效果
Part 4.3.1 计算整篇文档属于某个分类的概率
假设我们已经注意到,有20%的‘bad’文档出现了‘python’单词- P('python'| 'bad') = 0.2,同时有80%的文档出现了单词‘casino’-P('casino'| 'bad')=0.8,那么当‘python’和‘casino’同时出现在一篇‘bad’文档的概率是P('casino' & 'python' | 'bad') = 0.8 * 0.2 = 0.16。
我们新建一个子类,继承自classifier,取名naivebayes,并添加一个docprob函数
class naivebayes(classifier):
def __init__(self, getfeatures):
classifier.__init__(self, getfeatures)
def docprob(self, item, cat):
features = self.getfeatures(item)
# Multiply the probabilities of all the features together
p = 1
for f in features:
p *= self.weightedprob(f, cat, self.fprob)
return p
现在我们已经知道了如何计算P(Document|category),但是我们需要知道的是,最终我们需要的结果是P(category|Document),换而言之,对于一篇给定的文档,我们需要找出它属于各个分类的概率,我们感到欣慰的是,这就是贝叶斯需要解决的事情
在本例中:
P(category|Document) = P(Document|category) * P(category) / P(Document)
P(Document|category) 已经被我们用 docprob 函数计算出来了,P(category)也很好理解和计算:代表我们随你选择一篇文档,它属于某个分类的概率。P(Document)对于所有的文档来说,都是一样的,我们直接选择忽略掉他
我们在naivebayes中新添加一个prob函数,计算一篇文档属于某个分类的概率(P(Document|category) * P(category) )
def prob(self, item, cat):
catprob = self.catcount(cat)/self.totalcount()
docprob = self.docprob(item, cat)
return docprob * catprob
到现在为止,我们的朴素贝叶斯分类器编写基本完成。我们看看针对不同的文档(字符串),概率值是如何变化的:
cl = naivebayes(getwords)
sampletrain(cl)
cl.prob('quick rabbit', 'good')
out: 0.156
cl.prob('quick rabbit', 'bad')
out: 0.05
根据训练的数据,我们认为相对于‘bad’分类而言,我们认为‘quick rabbit’更适合于'good'分类.
最后我们完善一下我们的分类器,我们只需要给出文档,分类器会自动给我们找出概率最大的哪一个分类。
我们为naivebayes新添加一个方法 :classify
def classify(self, item):
max = 0.0
for cat in self.categories():
probs[cat] = self.prob(item, cat)
if probs[cat] > max:
max = probs[cat]
best = cat
return best
继续测试:
cl = naivebayes(getwords)
sampletrain(cl)
cl.classify('quick rabbit')
out:good
但是到目前为止,我们所使用的训练数据,或者测试数据,都是简单的字符串,同时也是我们人为制造的,但是在真实的生产环境中,这几乎是不可能的,数据要更为复杂,更为庞大。回到开头,我这里使用在康奈尔大学下载的2M影评作为训练数据和测试数据,里面共同、共有1400条,好评和差评各自700条,我选择总数的70%作为训练数据,30%作为测试数据,来检测我们手写的朴素贝叶斯分类器的效果
首先我们稍微修改一下:我们的训练函数:sampletrain,以便能够训练大规模数据
def sampletrain(cl, traindata, traintarget):
for left, right in zip(traindata, traintarget):.
cl.train(left, right)
我们可以把需要训练的数据放在一个list里面或者迭代器里面,其对应的分类也是如此,在函数中,我们使用traindata, traintarget分别替代我们的训练数据和其对应的分类。
我们定义一个函数 get_dataset获得打乱后的数据
def get_dataset():
data = []
for root, dirs, files in os.walk(r'E:\研究生阶段课程作业\python\好玩的数据分析\朴素贝叶斯文本分类\tokens\neg'):
for file in files:
realpath = os.path.join(root, file)
with open(realpath, errors='ignore') as f:
data.append((f.read(), 'bad'))
for root, dirs, files in os.walk(r'E:\研究生阶段课程作业\python\好玩的数据分析\朴素贝叶斯文本分类\tokens\pos'):
for file in files:
realpath = os.path.join(root, file)
with open(realpath, errors='ignore') as f:
data.append((f.read(), 'good'))
random.shuffle(data)
return data
在定义一个函数,对我们的数据集进行划分,训练集和测试集分别占07和0.3
def train_and_test_data(data_):
filesize = int(0.7 * len(data_))
# 训练集和测试集的比例为7:3
train_data_ = [each[0] for each in data_[:filesize]]
train_target_ = [each[1] for each in data_[:filesize]]
test_data_ = [each[0] for each in data_[filesize:]]
test_target_ = [each[1] for each in data_[filesize:]]
return train_data_, train_target_, test_data_, test_target_
计算我们的分类器在真实数据上的表现:
if __name__ == '__main__':
cl = naivebayes(getwords)
data = dataset()
train_data, train_target, test_data, test_target = train_and_test_data(data)
sampletrain(cl, train_data, train_target) #对训练我们的分类器进行训练
predict = []
for each in test_data:
predict.append(cl.classify(each))
count = 0
for left,right in zip(predict,test_target ):
if left == right:
count += 1
print(count/len(test_target))
out :0.694
对于我们的测试集,大约有420个影评,我们使用简单的、完全手写的贝叶斯分类器达到了将近70%的预测准确率,效果还算可以,从头到尾,你是不是被贝叶斯的神奇应用折服了呢。如果你是初学者,可以按照本片博客,一步一步完成朴素贝叶斯分类器的编写,如果你嫌麻烦,可以直接向我要源码。(其实把本文所有的代码加起来就是完整的源码啦)