被称之为“朴素”,是因为只进行最原始、最简单的假设–各个特征之间没有关联性
朴素贝叶斯两种实现方式
- 基于贝努利模型:只考虑词在文本中是否出现,而不考虑出现次数。因此需要假设词的出现是等权重的
- 基于多项式模型:考虑词在文本中出现次数
- 优点:数据较少的情况下依旧有效;可以处理多类别问题;节约了内存消耗和计算时间
- 缺点:数据输入方式较为敏感;较强的特征条件独立假设
- 适用于:标称型数据
- 重要应用于文本分类
贝叶斯决策理论
假设有统计参数,用 p1(x,y)表示数据点(x,y)属于类别1的概率, p2(x,y)表示数据点(x,y)属于类别2的概率。对于一个新的点(x’,y’),如果
p1(x’,y’) > p2(x’,y’) 那么该数据点属于类别1,反之属于类别2
贝叶斯决策理论的核心为:选择高概率对应的类别
条件概率
P
(
A
∣
B
)
=
P
(
A
a
n
d
B
)
P
(
B
)
P(A|B) = \frac{P(A and B)}{P(B)}
P(A∣B)=P(B)P(AandB)
P
(
A
∣
B
)
=
P
(
B
∣
A
)
P
(
A
)
P
(
B
)
P(A|B) = \frac{P(B|A)P(A)}{P(B)}
P(A∣B)=P(B)P(B∣A)P(A)
在实际中,B会是一个向量,公式变为
P
(
c
i
∣
w
⃗
)
=
P
(
w
⃗
∣
c
i
)
P
(
c
i
)
P
(
w
⃗
)
P(c_{i}|\vec{w})= \frac{P(\vec{w}|c_{i})P(c_{i})}{P(\vec{w})}
P(ci∣w)=P(w)P(w∣ci)P(ci)
P
(
c
i
∣
w
⃗
)
P(c_{i}|\vec{w})
P(ci∣w) 表示为,在有
w
⃗
\vec{w}
w特征下,是某一类别的概率;
P
(
c
i
P(c_{i}
P(ci : 每个类别的概率;
P
(
w
⃗
∣
c
i
)
P(\vec{w}|c_{i})
P(w∣ci) : 给定类别下特征(被预测文档出现的词)的概率 ,计算方法:
P
(
w
⃗
∣
c
i
)
=
N
i
N
P(\vec{w}|c_{i}) = \frac{N_{i}}{N}
P(w∣ci)=NNi 其中
N
i
N_{i}
Ni为
v
e
c
w
vec{w}
vecw在
c
i
c_{i}
ci 类别的文档中出现的次数 N 为所属类别
c
i
c_{i}
ci下的所有文档中词出现的总数;
其中因为假设了特征独立,那么
P
(
w
⃗
∣
c
i
)
=
P
(
w
0
∣
c
i
)
P
(
w
1
∣
c
i
)
P
(
w
2
∣
c
i
)
.
.
.
P
(
w
N
∣
c
i
)
P(\vec{w}|c_{i}) = P(w_{0}|c_{i}) P(w_{1}|c_{i})P(w_{2}|c_{i})... P(w_{N}|c_{i})
P(w∣ci)=P(w0∣ci)P(w1∣ci)P(w2∣ci)...P(wN∣ci)
计算例子
词表转换为向量
def createVocabList(dataSet):
'''
获取所有包含词的列表
'''
vocabSet = set([]) # 创建空集,用来存放所有的词
for doument in dataSet:
vocabSet = vocabSet | set(doument)
return list(vocabSet)
def setOfWords2Vec(vocabList, inputSet):
'''
统计inputSet的词在vocabList文档是否出现
'''
returnVec = [0]*len(vocabList) # 初始权为0.没有出现
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):
'''
朴素贝叶斯分类器
trainMatrix: 文档矩阵
trainCategory: 文档类别标签
'''
numTrainDocs = len(trainMatrix) # 样本数
numWords = len(trainMatrix[0]) # 特征
pAbusive = sum(trainCategory)/float(numTrainDocs) # p(1) # 这里是计算class= 1 的占比
# p0Num = np.zeros(numWords) # 放置所有class = 0 的样本中各个特征出现的次数
# p1Num = np.zeros(numWords)
# p0Denom = 0.0 # class = 0 的样本中出现的词的总数量
# p1Denom = 0.0
# 修改后
p0Num = np.ones(numWords) # 放置所有class = 0 的样本中各个特征出现的次数
p1Num = np.ones(numWords)
p0Denom = 2.0 # class = 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 # 是一个向量,每个元素为P(wi|c=1)
p0Vect = p0Num/p0Denom
return p0Vect, p1Vect, pAbusive
修改分类器
- 在计算
P
(
w
⃗
∣
c
i
)
=
P
(
w
0
∣
c
i
)
P
(
w
1
∣
c
i
)
P
(
w
2
∣
c
i
)
.
.
.
P
(
w
N
∣
c
i
)
P(\vec{w}|c_{i}) = P(w_{0}|c_{i}) P(w_{1}|c_{i})P(w_{2}|c_{i})... P(w_{N}|c_{i})
P(w∣ci)=P(w0∣ci)P(w1∣ci)P(w2∣ci)...P(wN∣ci)时可能会出现
P
(
w
2
∣
c
i
)
=
0
P(w_{2}|c_{i})=0
P(w2∣ci)=0 的情况,导致最终的计算结果也为零。为避免此情况:增加拉普拉斯平滑系数
P ( w ⃗ ∣ c i ) = N i + α N + α × m P(\vec{w}|c_{i}) = \frac{N_{i} + \alpha }{N + \alpha × m} P(w∣ci)=N+α×mNi+α
其中 α \alpha α 为指定的系数一般为1,m为训练文档中特征词个数。在代码中可以修改所有词的出现数初始化为1,分母初始化为2 - 另一问题为下溢出,这是由于太多很小的数相乘造成的。在代数中有 ln ( a ∗ b ) = ln ( a ) ∗ ln ( b ) \ln (a*b) = \ln (a) * \ln (b) ln(a∗b)=ln(a)∗ln(b) ,于是通过求对数可以避免下溢出或者浮点数四舍五入导致的错误。
def classifyNB(vec2Classfy, p0Vec, p1Vec, pClass):
'''
求概率,比较大小,决定分类
利用对数相乘,避免下溢出
'''
p1Vec = np.log(p1Vec)
p0Vec = np.log(p0Vec)
p1 = sum(vec2Classfy * p1Vec) + np.log(pClass)
p0 = sum(vec2Classfy * p0Vec) + np.log(pClass)
if p0 > p1:
return 0
else :
return 1
准备数据、测试
def loadDataSet():
'''
准备数据
'''
postingList=[['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],
['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
['stop', 'posting', 'stupid', 'worthless', 'garbage'],
['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]
classVec = [0,1,0,1,0,1] #1代表侮辱性文字,0代表正常言论
return postingList, classVec
def testingNB():
listOPosts, listClasses = loadDataSet() # 加载文档数据
myVocabList = createVocabList(listOPosts) # 获取所有单词的列表
trainMat = []
for postinDoc in listOPosts:
trainMat.append(setOfWords2Vec(myVocabList, postinDoc)) # 文字提取,得文字出现样本[0,1,1,0,0]-0为没有出现,1为出现
p0V, p1V, pAb = trainNB0(np.array(trainMat), np.array(listClasses)) # 分类器 传入参数都为array类型
testEntry = ['love', 'my', 'dalmation']
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as : ', classifyNB(thisDoc, p0V, p1V, pAb)) # 计算概率
testEntry = ['syupid', 'garbage']
thisDoc = np.array(setOfWords2Vec(myVocabList, testEntry))
print(testEntry, 'classified as : ', classifyNB(thisDoc, p0V, p1V, pAb))
# 调用
testingNB()
运行结果
['love', 'my', 'dalmation'] classified as : 0
the word syupid is not in my Vocabulary!
['syupid', 'garbage'] classified as : 1
上述模型为词集模型(set-of-words model),将每个词是否出现作为一个特征,这种方法对于一个词出现很多次的文档,特别是大段文章就不明显了;另一种方式叫做词袋模型(bag-of-words model),每个单词可以出现多次。
因此需要对setOfWords2Vec函数做修改
def bagOfWords2VecMN(vocabList, inputSet):
'''
统计inputSet的词在vocabList文档出现频数
'''
returnVec = [0]*len(vocabList) # 初始权为0.没有出现
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 textParse(bigString):
'''
以空格点等符号切割文本,返回长度大于2的文本集列表
'''
import re
listOfTokens = re.split(r'\w*', bigString) # 利用正则切分文本
return [tok.lower() for tok in listOfTokens if len(tok) > 2]
def spamTest():
'''
贝叶斯垃圾邮件分类器
'''
docList=[]
classList = []
fullList = []
# 按不同文件夹导入并解析文件
for i in range(1,26):
wordList = textParse(open('spam/%d.txt' % i).read())
docList.appded(wordList)
fullList.extend(wordList)
classList.append(1)
wordList = textParse(open('ham/%d.txt' % i).read())
docList.appded(wordList)
fullList.extend(wordList)
classList.append(0)
vocabList = createVocabList(docList)
trainingSet = range(50) # 构建训练集
testSet = [] # 存放测试集
# 随机一个数,将此位置的数从训练集挪到测试集
for i in range(10):
randIndex = int(np.random.uniform(0,len(trainingSet)))
testSet.append(trainingSet[randIndex])
del(trainingSet[randIndex])
trainMat = []
trainClasses = []
for docIndex in trainingSet:
trainMat.append(setOfWords2Vec(vocabList, docList[docIndex]))
trainClasses.append(classList[docIndex])
p0V, p1v, pSpam = trainNB0(array(trainMat), array(trainClasses))
errorCount = 0
for docIndex in testSet:
wordVector = setOfWords2Vec(vocabList, docList[docIndex])
if calssifyNB(array(wordVector), p0V, p1v, pSpam) != calssList[docIndex]:
errorCount += 1
print('the error rate is :', float(errorCount)/len(testSet))
sklearn.naive_bayes.MultinomialNB
class sklearn.naive_bayes.MultinomialNB(alpha=1.0, fit_prior=True, class_prior=None)
"""
:param alpha:float,optional(default = 1.0)加法(拉普拉斯/ Lidstone)平滑参数(0为无平滑)
"""
案例-互联网新闻分类
from sklearn.datasets import fetch_20newsgroups
from sklearn.cross_validation import train_test_split
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.naive_bayes import MultinomialNB
# 读取20类新闻文本的数据细节
news = fetch_20newsgroups(subset='all')
# 20类新闻文本数据分割
X_train,X_test,y_train,y_test = train_test_split(news.data,news.target,test_size=0.25,random_state=42)
# 文本转换为特征向量进行TF特征抽取
vec = CountVectorizer()
# 训练数据输入,并转换为特征向量
X_train = vec.fit_transform(X_train)
# 测试数据转换
X_test = vec.transform(X_test)
# 使用平滑处理初始化的朴素贝叶斯模型
mnb = MultinomialNB(alpha=1.0)
# 利用训练数据对模型参数进行估计
mnb.fit(X_train,y_train)
# 对测试验本进行类别预测。结果存储在变量y_predict中
y_predict = mnb.predict(X_test)
# 查看分类错误率
count = 0
for i in range(len(y_predict)):
if y_predict[i] != y_test[i]:
count += 1
print('分类错误个数:',count, '总个数:',len(y_predict), '错误率:', float(count)/len(y_predict))