概率模型的训练过程就是参数估计。贝叶斯学派认为参数是未观测到的随机变量,其本身可能也有分布,因此可以假定参数服从一个先验分布,然后基于观测到的数据来计算后验分布。频率主义学派则认为参数虽然未知,但是却有客观存在的固定值,因此可以通过优化似然函数等来确定参数。极大似然估计(Maximum Likelihood Estimation)就是亘古数据采样来估计概率分布参数的经典方法。
令 Dc 表示训练集D中第c类样本组成的集合,假设这些样本是独立同分布的,则参数
θc 对于数据集 Dc 的似然是:
因为很小的概率连乘会造成下溢,通常使用对数
此时参数 θc 的极大似然估计为: argmaxLL(θc) 。这种参数化方法虽然能使条件概率估计变得相对简单,但是估计结果的准确性严重依赖于所假设的概率分布形式是否符合潜在的真是数据分布。在现实应用中若要取得比较好的效果,需要利用一些经验知识。
之所以叫朴素贝叶斯,是因为采用了属性条件独立性假设,就是假设每个属性独立地对分类结果产生影响。即有下面的公式:
后面连乘的地方要注意的是,如果有一向概率值为0会影响后面估计,所以我们对未出现的属性概率设置一个很小的值,并不为0,这就是拉普拉斯修正(Laplacian correction)。
因为属性条件独立性的假设现实生活中很难得到,所以尝试对这个条件有所放松,得到半朴素贝叶斯分类器(semi-naive Bayes classifiers)。
其基本想法是适当考虑一部分属性间的相互依赖关系。独立依赖估计(One-Dependent Estimator)是半朴素贝叶斯分类器常用策略,假设每个属性在类别之外最多仅依赖一个其他属性。
最直接是生成一个超父没然后通过交叉验证等模型选择方法来确定超父属性,这就是SPODE(Super-Parent ODE)方法。TAN(Tree Augumented naive Bayes)则是在最大带权生成树(maximum weighted spanning tree)算法的处处上,将属性关系简约化。
也称为信念网,借助有向无环图(Directed Acyclic Graph)来刻画属性之间的依赖关系,并使用条件概率表(Conditional Probability Table)来描述属性的联合概率分布。
如果网络结构已知,即属性间的依赖关系比较明确,那贝叶斯网络学习过程相对简单,如果并不知道网络结构,“评分搜索”是一个常用办法。就是我们先定义一个评分函数,用来估计网络和数据的契合程度,然后基于评分函数来寻找最优的网络。
常用评分函数通常基于信息论,将学习问题看作一个数据压缩任务,学习目标是找到一个能以最短编码长度描述训练数据的模型,这就是最小描述长度(Minimal Description Length,MDL)准则。
贝叶斯网络的推断需要精确计算后验概率,这是个NP难的问题,所以一般我们用近似推断,采用一种随机采样的方式,吉布斯采样(Gibbs sampling)。实际上吉布斯采样是一个马尔科夫链,故其收敛很慢。
EM(Expectation-Maximization)是估计参数隐藏量的利器,它是一种迭代方法。简要来说,EM算法使用两个步骤交替计算,第一步计算期望(E步),利用当前估计的参数值来计算对数似然的期望值;第二步是最大化(M步),寻找能值得E步产生的似然期望最大化的参数值。然后新的参数值重新用于E步,直到收敛到全局最优解。
隐变量估计问题也可以通过梯度下降等优化算法求解,但由于求和的项数将随着隐变量的数目以指数级上升,会给梯度计算带来麻烦;EM可以看作一种非梯度优化方法,事实上,可以看作用坐标下降(coordinate descent)法来最大化对数似然下界的过程。
朴素贝叶斯在很多情况下都能获得相当好的效果,在信息检索领域尤其常用。我们先来个简单的文本分类,首先需要把文本表示成一个向量。力求简单我们用该词是否出现过表示,出现过则为1,未出现则为0。
def setOfWords2Vec(vocabList, inputSet):
returnVec = [0]*len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] = 1
else: print "the word: %s is not in my Vocabulary!" % word
return returnVec
转换成向量之后就是用贝叶斯公式计算概率了。又因为朴素贝叶斯是属性独立的,所以可用如下公式计算:
def trainNB0(trainMatrix,trainCategory):
numTrainDocs = len(trainMatrix)
numWords = len(trainMatrix[0])
pAbusive = sum(trainCategory)/float(numTrainDocs)
p0Num = ones(numWords); p1Num = ones(numWords) #change to ones()
p0Denom = 2.0; p1Denom = 2.0 #change to 2.0
for i in range(numTrainDocs):
if trainCategory[i] == 1:
p1Num += trainMatrix[i]
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
p1Vect = log(p1Num/p1Denom) #change to log()
p0Vect = log(p0Num/p0Denom) #change to log()
return p0Vect,p1Vect,pAbusive
为了避免概率为0的情况(下溢问题),我们在这里把p0Denom和p1Denomd都设置成2。这个函数我们通过接下来就可以分类啦。
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
p1 = sum(vec2Classify * p1Vec) + log(pClass1)
p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1)
if p1 > p0:
return 1
else:
return 0
在哪个类的概率大就分到哪类,是不是很简单呢。我们现在要升级一下了。仅用单词是否出现过来表示这个叫做词集模型(set-of-words),这样不能表示文中多次出现的单词,认为每个单词权重都是一样的。下面我们改进成词袋模型(bag-of-words model),这样就可以表示多次出现的词了。但是文档中有很多没有意义的虚词被统计了,这些权重很高又没什么意义的词我们可以过滤掉吗?当然可以!这个就是所谓的停用词表,目前有很多不错的停用词表。词袋模型的一个大的缺陷是无法表示词语的位置和逻辑关系。但是对于常见应用,比如最著名的垃圾邮件分类,朴素贝叶斯方法已经取得了很好的效果了。
这里有一个用朴素贝叶斯分类实现的垃圾邮件分类的例子,
完整的代码以及测试集数据请从这里下载。