diff --git a/docs/4.朴素贝叶斯.md b/docs/4.朴素贝叶斯.md index c109c8ff..8e60bc41 100644 --- a/docs/4.朴素贝叶斯.md +++ b/docs/4.朴素贝叶斯.md @@ -215,7 +215,7 @@ def setOfWords2Vec(vocabList, inputSet): 我们使用上述公式,对每个类计算该值,然后比较这两个概率值的大小。 -首先可以通过类别 i (侮辱性留言或者非侮辱性留言)中文档数除以总的文档数来计算概率 p(ci) 。接下来计算 p(w | ci) ,这里就要用到朴素贝叶斯假设。如果将 w 展开为一个个独立特征,那么就可以将上述概率写作 p(w0, w1, w2...wn | ci) 。这里假设所有词都互相独立,该假设也称作条件独立性假设,它意味着可以使用 p(w0 | ci)p(w1 | ci)p(w2 | ci)...p(wn | ci) 来计算上述概率,这样就极大地简化了计算的过程。 +首先可以通过类别 i (侮辱性留言或者非侮辱性留言)中的文档数除以总的文档数来计算概率 p(ci) 。接下来计算 p(w | ci) ,这里就要用到朴素贝叶斯假设。如果将 w 展开为一个个独立特征,那么就可以将上述概率写作 p(w0, w1, w2...wn | ci) 。这里假设所有词都互相独立,该假设也称作条件独立性假设(例如 A 和 B 两个人抛骰子,概率是互不影响的,也就是相互独立的,A 抛 2点的同时 B 抛 3 点的概率就是 1/6 * 1/6),它意味着可以使用 p(w0 | ci)p(w1 | ci)p(w2 | ci)...p(wn | ci) 来计算上述概率,这样就极大地简化了计算的过程。 朴素贝叶斯分类器训练函数 @@ -242,22 +242,35 @@ def _trainNB0(trainMatrix, trainCategory): p0Denom = 0.0 p1Denom = 0.0 for i in range(numTrainDocs): - # 遍历所有的文件,如果是侮辱性文件,就计算此侮辱性文件中出现的侮辱性单词的个数 + # 是否是侮辱性文件 if trainCategory[i] == 1: - p1Num += trainMatrix[i] #[0,1,1,....]->[0,1,1,...] + # 如果是侮辱性文件,对侮辱性文件的向量进行加和 + p1Num += trainMatrix[i] #[0,1,1,....] + [0,1,1,....]->[0,2,2,...] + # 对向量中的所有元素进行求和,也就是计算所有侮辱性文件中出现的单词总数 p1Denom += sum(trainMatrix[i]) else: - # 如果不是侮辱性文件,则计算非侮辱性文件中出现的侮辱性单词的个数 p0Num += trainMatrix[i] p0Denom += sum(trainMatrix[i]) # 类别1,即侮辱性文档的[P(F1|C1),P(F2|C1),P(F3|C1),P(F4|C1),P(F5|C1)....]列表 - # 即 在1类别下,每个单词出现次数的占比 + # 即 在1类别下,每个单词出现的概率 p1Vect = p1Num / p1Denom# [1,2,3,5]/90->[1/90,...] # 类别0,即正常文档的[P(F1|C0),P(F2|C0),P(F3|C0),P(F4|C0),P(F5|C0)....]列表 - # 即 在0类别下,每个单词出现次数的占比 + # 即 在0类别下,每个单词出现的概率 p0Vect = p0Num / p0Denom return p0Vect, p1Vect, pAbusive +``` +> 测试算法: 根据现实情况修改分类器 + +在利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,即计算 p(w0|1) * p(w1|1) * p(w2|1)。如果其中一个概率值为 0,那么最后的乘积也为 0。为降低这种影响,可以将所有词的出现数初始化为 1,并将分母初始化为 2 (取1 或 2 的目的主要是为了保证分子和分母不为0,大家可以根据业务需求进行更改)。 + +另一个遇到的问题是下溢出,这是由于太多很小的数相乘造成的。当计算乘积 p(w0|ci) * p(w1|ci) * p(w2|ci)... p(wn|ci) 时,由于大部分因子都非常小,所以程序会下溢出或者得到不正确的答案。(用 Python 尝试相乘许多很小的数,最后四舍五入后会得到 0)。一种解决办法是对乘积取自然对数。在代数中有 ln(a * b) = ln(a) + ln(b), 于是通过求对数可以避免下溢出或者浮点数舍入导致的错误。同时,采用自然对数进行处理不会有任何损失。 + +下图给出了函数 f(x) 与 ln(f(x)) 的曲线。可以看出,它们在相同区域内同时增加或者减少,并且在相同点上取到极值。它们的取值虽然不同,但不影响最终结果。 + +![函数图像](/images/4.NaiveBayesian/NB_7.png ) + +```python def trainNB0(trainMatrix, trainCategory): """ 训练数据优化版本 @@ -296,22 +309,9 @@ def trainNB0(trainMatrix, trainCategory): # 类别0,即正常文档的[log(P(F1|C0)),log(P(F2|C0)),log(P(F3|C0)),log(P(F4|C0)),log(P(F5|C0))....]列表 p0Vect = log(p0Num / p0Denom) return p0Vect, p1Vect, pAbusive + ``` -> 测试算法: 根据现实情况修改分类器 - -在利用贝叶斯分类器对文档进行分类时,要计算多个概率的乘积以获得文档属于某个类别的概率,即计算 p(w0 | 1)p(w1 | 1)p(w2 | 1)。如果其中一个概率值为 0,那么最后的乘积也为 0。为降低这种影响,可以将所有词的出现数初始化为 1,并将分母初始化为 2 。 - -另一个遇到的问题是下溢出,这是由于太多很小的数相乘造成的。当计算乘积 p(w0 | ci)p(w1 | ci)p(w2 | ci)...p(wn | ci) 时,由于大部分因子都非常小,所以程序会下溢出或者得到不正确的答案。(用 Python 尝试相乘许多很小的数,最后四舍五入后会得到 0)。一种解决办法是对乘积取自然对数。在代数中有 ln(a * b) = ln(a) + ln(b), 于是通过求对数可以避免下溢出或者浮点数舍入导致的错误。同时,采用自然对数进行处理不会有任何损失。 - -下图给出了函数 f(x) 与 ln(f(x)) 的曲线。可以看出,它们在相同区域内同时增加或者减少,并且在相同点上取到极值。它们的取值虽然不同,但不影响最终结果。通过修改 return 前的两行代码,将上述做法用到分类器中: - -```python -p1Vect = log(p1Num /p1Denom) -p0Vect = log(p0Num / p0Denom) -``` - -![函数图像](/images/4.NaiveBayesian/NB_7.png ) > 使用算法: 对社区留言板言论进行分类 @@ -331,10 +331,12 @@ def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1): :return: 类别1 or 0 """ # 计算公式 log(P(F1|C))+log(P(F2|C))+....+log(P(Fn|C))+log(P(C)) + # 大家可能会发现,上面的计算公式,没有除以贝叶斯准则的公式的分母,也就是 P(w) (P(w) 指的是此文档在所有的文档中出现的概率)就进行概率大小的比较了, + # 因为 P(w) 针对的是包含侮辱和非侮辱的全部文档,所以 P(w) 是相同的。 # 使用 NumPy 数组来计算两个向量相乘的结果,这里的相乘是指对应元素相乘,即先将两个向量中的第一个元素相乘,然后将第2个元素相乘,以此类推。 # 我的理解是:这里的 vec2Classify * p1Vec 的意思就是将每个词与其对应的概率相关联起来 - p1 = sum(vec2Classify * p1Vec) + log(pClass1) - p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1) + p1 = sum(vec2Classify * p1Vec) + log(pClass1) # P(w|c1) * P(c1) ,即贝叶斯准则的分子 + p0 = sum(vec2Classify * p0Vec) + log(1.0 - pClass1) # P(w|c0) * P(c0) ,即贝叶斯准则的分子· if p1 > p0: return 1 else: @@ -403,21 +405,6 @@ Eugene > 准备数据: 将文本文件解析成词条向量 -文档词袋模型 - -我们将每个词的出现与否作为一个特征,这可以被描述为 词集模型(set-of-words model)。如果一个词在文档中出现不止一次,这可能意味着包含该词是否出现在文档中所不能表达的某种信息,这种方法被称为 词袋模型(bag-of-words model)。在词袋中,每个单词可以出现多次,而在词集中,每个词只能出现一次。为适应词袋模型,需要对函数 setOfWords2Vec() 稍加修改,修改后的函数为 bagOfWords2Vec() 。 - -如下给出了基于词袋模型的朴素贝叶斯代码。它与函数 setOfWords2Vec() 几乎完全相同,唯一不同的是每当遇到一个单词时,它会增加词向量中的对应值,而不只是将对应的数值设为 1 。 - -```python -def bagOfWords2VecMN(vocaList, inputSet): - returnVec = [0] * len(vocabList) - for word in inputSet: - if word in inputSet: - returnVec[vocabList.index(word)] += 1 - return returnVec -``` - 使用正则表达式来切分文本 ```python @@ -431,7 +418,7 @@ def bagOfWords2VecMN(vocaList, inputSet): > 分析数据: 检查词条确保解析的正确性 -> 训练算法: 使用我们之前建立的 trainNB() 函数 +> 训练算法: 使用我们之前建立的 trainNB0() 函数 ```python def trainNB0(trainMatrix, trainCategory): @@ -576,6 +563,21 @@ def spamTest(): > 准备数据: 将文本文件解析成词条向量 +文档词袋模型 + +我们将每个词的出现与否作为一个特征,这可以被描述为 词集模型(set-of-words model)。如果一个词在文档中出现不止一次,这可能意味着包含该词是否出现在文档中所不能表达的某种信息,这种方法被称为 词袋模型(bag-of-words model)。在词袋中,每个单词可以出现多次,而在词集中,每个词只能出现一次。为适应词袋模型,需要对函数 setOfWords2Vec() 稍加修改,修改后的函数为 bagOfWords2Vec() 。 + +如下给出了基于词袋模型的朴素贝叶斯代码。它与函数 setOfWords2Vec() 几乎完全相同,唯一不同的是每当遇到一个单词时,它会增加词向量中的对应值,而不只是将对应的数值设为 1 。 + +```python +def bagOfWords2VecMN(vocaList, inputSet): + returnVec = [0] * len(vocabList) + for word in inputSet: + if word in inputSet: + returnVec[vocabList.index(word)] += 1 + return returnVec +``` + ```python #创建一个包含在所有文档中出现的不重复词的列表 def createVocabList(dataSet): diff --git a/src/python/4.NaiveBayes/bayes.py b/src/python/4.NaiveBayes/bayes.py index 5b9aca54..2696bd48 100755 --- a/src/python/4.NaiveBayes/bayes.py +++ b/src/python/4.NaiveBayes/bayes.py @@ -11,6 +11,8 @@ from numpy import * p(xy)=p(x|y)p(y)=p(y|x)p(x) p(x|y)=p(y|x)p(x)/p(y) """ + + # 项目案例1: 屏蔽社区留言板的侮辱性言论 def loadDataSet(): @@ -213,6 +215,7 @@ def textParse(bigString): listOfTokens = re.split(r'\W*', bigString) return [tok.lower() for tok in listOfTokens if len(tok) > 2] + def spamTest(): ''' Desc: @@ -264,12 +267,13 @@ def spamTest(): def testParseTest(): print textParse(open('input/4.NaiveBayes/email/ham/1.txt').read()) + # ----------------------------------------------------------------------------------- # 项目案例3: 使用朴素贝叶斯从个人广告中获取区域倾向 # 将文本文件解析成 词条向量 def setOfWords2VecMN(vocabList,inputSet): - returnVec=[0]*len(vocabList) #创建一个其中所含元素都为0的向量 + returnVec=[0]*len(vocabList) # 创建一个其中所含元素都为0的向量 for word in inputSet: if word in vocabList: returnVec[vocabList.index(word)]+=1 @@ -279,9 +283,10 @@ def setOfWords2VecMN(vocabList,inputSet): #文件解析 def textParse(bigString): import re - listOfTokens=re.split(r'\W*',bigString) + listOfTokens=re.split(r'\W*', bigString) return [tok.lower() for tok in listOfTokens if len(tok)>2] + #RSS源分类器及高频词去除函数 def calcMostFreq(vocabList,fullText): import operator @@ -343,7 +348,8 @@ def getTopWords(ny,sf): for item in sortedNY: print item[0] + if __name__ == "__main__": - testingNB() - # spamTest() + # testingNB() + spamTest() # laTest()