朴素贝叶斯-学习笔记
朴素贝叶斯
标签(空格分隔): 机器学习
朴素贝叶斯算法适合给文档分类。
词向量
先对文档分词,所有文档中所有不同的词构成词汇表。每个文档根据词汇表能形成一个词向量,1表示对应维度的词条出现在文档中,0表示词条未出现。
这种想法推广开来,发现向量是新时代的数字。
比如一个可能发生的事件集合,可以表示为一个向量,每个维度对应一个事件发生的概率。
问题描述
任给一个文档,怎样确定其分类?
每个文档对应一个词向量\(\vec w\),需要确定\(p(c_i|\vec w)\)的最大值,即:已知这个文档的条件下,把这个文档归为哪个类的概率最大?
朴素贝叶斯
其中\(\vec w\)表示词向量。对于任意一个分类\(c_i\),\(p(\vec w)\)是相同的,因而只需要确定\(p(\vec w|c_i)\)和\(p(c_i)\)。
\(p(c_i)\):从训练数据中,用简单除法得到。
\(p(\vec w|c_i)\):根据朴素贝叶斯假设,特征向量\(\vec w\)在\(c_i\)条件下相互独立,也就是:给定目标值时属性之间相互条件独立:
而对于\(p(w_j|c_i)\),只需要在扫描训练数据时,依据\(c_i\)过滤数据项,在这些被过滤出的数据中(都是\(c_i\)类的),用简单统计的结果相除,就得到在\(j\)维上的概率权值\(e(w_j|c_i)\)。为什么不是\(p(w_j|c_i)\)?因为最终要计算的是一个未知分类的向量\(\vec x\)的分类,需要将\(\vec x\)与概率权值向量\(\vec e\)对应位相乘,这才得到\(p(w_j|c_i)\)序列。\(\vec x\)中的元素非1即0,如果元素为1,那么\(p(w_j|c_i)\)就取\(e(w_j|c_i)\),否则取0。
简单优化
条件概率乘积为0
\(p(\vec w|c_i)=\Pi p(w_j|c_i)\)这个公式中,一旦\(p(w_j|c_i)\)等于0,整个结果就是0。但是对于\(\vec w\),你怎样判断\(\Pi p(w_j|c_i)=0\)的所有\(c_i\),该如何判断哪个\(c_i\)更好?万一所有的\(c_i\)对应的\(p(\vec w|c_i)=\Pi p(w_j|c_i)\)都等于0,该怎样确定最终的分类?因此,要避免某个\(p(w_j|c_i)\)等于0。一个办法是使用拉普拉斯平滑:将所有词的出现数初始化为1,并将分母初始化为2。对应《机器学习实战》中文版P62的代码:
p0Num = onew(numWords)
p1Num = ones(numWords)
p0Denom = 2.0
p1Denom = 2.0
条件概率乘积下溢
相乘的都是概率,都是小于1的数,乘不了几次就得到非常小的数字,产生下溢,干扰比较。一个方法是用取对数。没错,就是这么老套的办法,但是管用,因为\(\forall x \in (0,1)\),x越小,\(\log x\)的绝对值越大,或者说负的程度越大。对于\(p(\vec w|c_i)=\Pi p(w_j|c_i)\)取对数,就是一堆负数相加。这种情形下对于任意的\(\vec x\)是很方便的。
当然,公式中还有一个\(p(c_i)\),也需要对数处理后相加。
代码
使用分类器
在训练阶段我们只计算\(e(\vec w|c_i)\),这其实就是训练出来的分类器。一旦完成训练,就可以使用分类器:
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
"""
@param vec2Classify: 需要被分类的词向量
@param p0Vec: 第0类对应的词概率向量
@param p1Vec: 第1类对应的词概率向量
@param
"""
p1=sum(vec2Classify*p1Vec) + np.log(pClass1)
p0=sum(vec2Classify*p0Vec) + np.log(1.0-pClass1)
if p1>p0:
return 1
else:
return 0
训练模型
使用训练数据,训练出分类器,也就是获取\(e(\vec w|c_i)\)和\(e(c_i)\)。同时修正了《机器学习实战》P61的一个bug,即对于\(p1Vect和p0Vect的计算,分母应当是训练数据对应类的向量总数,而不是元素总和\):
def trainNB0(trainMatrix, trainCategory):
"""
朴素贝叶斯分类器训练函数:计算P(\vec w|c_1)和P(\vec w|c_2)
@param trainMatrix:训练矩阵
@param trainCategory: 和trainMatrix配套使用的向量,表示每个trainMatrix[i]所属类别
"""
numTrainDocs = len(trainMatrix) #文档总数
numWords=len(trainMatrix[0]) #向量维数:不重复的单词数量
pAbusive=sum(trainCategory)/float(numTrainDocs)
p0Num=np.ones(numWords)
p1Num=np.ones(numWords)
p0Denom=2.0
p1Denom=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 = p1Num/p1Denom ##书上这句是错的
#p0Vect = p0Num/p0Denom ##这句也是错的
num_c1 = sum(trainCategory)
num_c0 = len(trainCategory) - num_c1
p1Vect = log(p1Num / num_c1) #这才正确的是p(\vec w|c_1)
p0Vect = log(p0Num / num_c0) #这才正确的是p(\vec w|c_0)
return p0Vect, p1Vect, pAbusive #pAbusive就是p(c_1)
理论上到这里就算出了基本的表达式,可以用来比较了。
总结
感觉《机器学习实战》第四章朴素贝叶斯
对于取对数的说明不够清楚,使用分类器时的向量相乘更是没有任何说明,让人一头雾水。不过章节后面的垃圾邮件分类和词语倾向性的例子还不错~