标签

2015年2月24日星期二

最好理解的贝叶斯分类——朴素贝叶斯分类器

《机器学习》里说的是贝叶斯学习。不过其实数据挖掘的Classification本质就是一种机器学习。这里参考《数据挖掘》一书居多。
贝叶斯分类基于概率论中与条件概率相关的贝叶斯定理,根据条件概率的计算方法:
贝叶斯定理的公式通过两个事件相互间的条件概率消去了依赖于时间独立性的难以计算的
 

这样一来,如果我们定义B是我们已知的一系列属性,视为“证据”。我们要计算带有这系列属性的记录的分类结果,可以先算出每个可能分类在已知条件(即这些证据)下的后验概率(Posterior Probability)。
贝叶斯定理提供了根据一系列已知概率来计算后验概率的方法。一系列已知概率,根据大数定理,我们可以用已有的统计样本(训练集)的频率来估计。因此准确性其实有赖于训练集的规模和代表性。

朴素之处
假设每条记录(元组)用一个n维向量表示其属性,假设所有可能分类为,则我们需要对所有分类,为这个元组计算其属于某一分类的概率:
最后我们选择有最高后验概率的类,作为这个元组的分类预测。也就是其概率比其他分类都高的分类。
朴素贝叶斯分类之所以被称为朴素(Naive,幼稚的),是因为它对于很难计算的后验概率,它作了以下假设:所有属性之间都是条件独立的,也就是说,一个议员在财政议题上的态度,与他在外交政策上的态度(没错还是用美国国会数据的例子)是无关的。这个假设简化了该后验概率的计算。之所以说后验概率难算,是因为我们很难在训练集中找到和待分类元组的属性完全吻合的记录,特别是当训练集规模不够大的时候(很难找到分类为,且属性向量为的记录)。当我们假设属性间条件独立的时候,便有:
一个属性向量的后验概率是其各维的乘积。
这种假设有其局限性,因为属性之间条件独立是很不可能的,比如议员在议题A上的取态不太可能和议题B的无关(当然其中往往没有直接的逻辑关系,但数学上定义的条件独立要求两个事件共同发生的概率等于两者概率的乘积)。因此这个假设虽然方便,但带来误差,但这不影响这种“幼稚”的方法的实用性。

先验概率估算
如果不存在可用的训练集,最好的方法是遵循信息论中的熵最小原则,假设所有的类是等概率的。但其实我们往往都有办法得到训练元组,前面说过,离散属性的概率可以通过其频率获得,而对于连续属性,我们进行一个比较依靠经验法则的假设,我们假设连续属性都满足正态分布,根据训练集作为统计样本得到均值和标准差,定义出该属性的分布公式:
那么我们只需将待分类元组的连续属性值根据公式算出其概率。
只要掌握了上述的思想,我们可以发现贝叶斯分类的数学模型是非常简洁好懂的。

拉普拉斯估计
根据类条件独立的假设,某属性向量的概率由其各维乘积得到,但如果遇到其中一个维的概率为0,那么估算出来的后验概率都会为0。如此一来该类的估算概率便为零。但这很可能只是因为训练集的局限性所致(因为规模不够大,或是不够全面)。这时我们引入另外一个假设,假设训练集很大,以致对每个计数结果加上1不会对估计概率产生不可忽略的影响,我们就可以用这种方法来避免零概率值的出现。
当然,在对k个技术结果都加一后,计算频率的分母应该加上k。
另,我们如果只是需要分类结果,因为我们是找具有最大后验概率的分类,而对于某个特定的属性向量X,其概率P(X)只是起到了归一化因子的作用,因此计算时只需要计算分子然后进行排序即可。(不对浮点数归一化同时可以避免精度丧失)

测试
以下是对美国国会数据集进行朴素贝叶斯分类的代码:
import random
class instance():
    def __init__(self, party, votes):
        self.party = party
        self.votes = votes
dataFile = open('dataset.txt', 'r')
datas = dataFile.readlines()
database = []
for line in datas:
    line = line[:-2]
    attrs = line.split(',')
    member = instance(attrs[0], attrs[1:])
    database.append(member)
partyName = ['democrat', 'republican']
trainset = []
checkset = []
for i in range(len(database)):
    if random.random() > 0.9:
        checkset.append(database[i])
    else:
        trainset.append(database[i])
stats = {}
for name in partyName:
    stats[name] = {"number" : 0, "votes" : []}
for k in stats:
    for i in range(len(database[0].votes)):
        stats[k]['votes'].append({"y" : 0, "?" : 0, "n" : 0})
for record in trainset:
    stats[record.party]['number'] += 1
    for i in range(len(record.votes)):
        stats[record.party]['votes'][i][record.votes[i]] += 1
wrong = 0.0
for record in checkset:
    probability = {}
    for name in partyName:
        probability[name] = 0.0
    for k in probability:
        p = 1.0
        for i in range(len(record.votes)):
            p *= (float(stats[k]['votes'][i][record.votes[i]]) / float(stats[k]['number']))
        probability[k] = p    
    if probability['democrat'] > probability['republican']:
        predict = "democrat"
    else:
        predict = "republican"
    print record.party + " , " + predict
    if record.party != predict:
        wrong += 1.0
print "correct rate: " + str(1 - wrong / len(checkset))
每条记录有90%的可能选作训练集,剩余用于检验分类正确性,最后打印出正确率
无耻地选了难得的全对结果,实际的正确率大约稳定在85%以上。
以下观点摘自《数据挖掘》:
理论上贝叶斯分类具有最小的错误率,但实践中容易因缺乏可用的概率数据和其假定的不确定性影响。

没有评论 :

发表评论