机器学习(五)——朴素贝叶斯
5.1 朴素贝叶斯概述
朴素贝叶斯算法是一种基于贝叶斯定理和条件独立性假设的分类方法。
其中,朴素贝叶斯的朴素代表属性之间独立。
该算法的核心思想分为两个部分:
首先,基于特征条件独立性假设,算法学习输入和输出的联合概率分布。
然后,利用贝叶斯定理,对于给定的输入特征 x,计算各个输出类别 y 的后验概率。
5.1.1 预备知识
5.1.1.1 贝叶斯公式
贝叶斯公式如下
P
(
B
∣
A
)
=
P
(
A
∣
B
)
P
(
B
)
P
(
A
)
P(B|A)=\frac{P(A|B)P(B)}{P(A)}
P(B∣A)=P(A)P(A∣B)P(B)
通俗来讲是以下形式
P
(类别
∣
特征)
=
P
(特征
∣
类别)
P
(
类别
)
P
(
特征
)
P(类别|特征)=\frac{P(特征|类别)P(类别)}{P(特征)}
P(类别∣特征)=P(特征)P(特征∣类别)P(类别)
而不同类别之间进行大小比较,其分母是相同的因此正比关系如下
P
(类别
∣
特征)
∝
P
(特征
∣
类别)
P
(
类别
)
P(类别|特征)\propto {P(特征|类别)P(类别)}
P(类别∣特征)∝P(特征∣类别)P(类别)
由于朴素贝叶斯的朴素代表属性独立性的假设因此式子也可以改写为这样
P (类别 ∣ 特征) ∝ P (特征 1 ∣ 类别) P (特征 2 ∣ 类别) … … P ( 类别 ) P(类别|特征)\propto {P(特征1|类别)P(特征2|类别)……P(类别)} P(类别∣特征)∝P(特征1∣类别)P(特征2∣类别)……P(类别)
5.1.1.2 先验概率&后验概率
先验概率指根据以往经验和分析。在实验或采样前就可以得到的概率。
上文中P(类别)就是先验概率
后验概率指某件事已经发生,想要计算这件事发生的原因是由某个因素引起的概率。
上文中P(类别|特征)就是后验概率
5.1.1.3 例子
假设我们现在有两个盒子,分别为红色和蓝色。在红色盒子中放着2个苹果和6个橙子,在蓝色盒子中放着1个橙子和3个苹果,如下图所示:
图中绿色表示苹果,橙色代表橙子。
假设我们每次实验的时候会随机从某个盒子里挑出一个水果,
随机变量B表示挑出的是哪个盒子,并且P(B=blue) = 0.6(蓝色盒子被选中的概率),P(B=red) = 0.4(红色盒子被选中的概率)。
随机变量F表示挑中的是哪种水果,F的取值为"a "和"o "。
假设我们已经得知某次实验中挑出的水果是orange,求这个orange是从红色盒子里挑出的概率是多大
以下是推导过程:
P ( B = r e d ∣ F = o ) = P ( F = o ∣ B = r e d ) P ( B = r e d ) P ( F = o ) = 3 4 ∗ 4 10 ∗ 20 9 = 2 3 P(B=red|F=o)= \frac {P(F=o|B=red)P(B=red)}{P(F=o)} =\frac{3}{4} * \frac{4}{10} * \frac{20}{9} =\frac{2}{3} P(B=red∣F=o)=P(F=o)P(F=o∣B=red)P(B=red)=43∗104∗920=32
P ( B = b l u e ∣ F = o ) = 1 − 2 3 = 1 3 P(B=blue|F=o)=1-\frac{2}{3} =\frac{1}{3} P(B=blue∣F=o)=1−32=31
在上面的计算过程中,我们将 P(B=red)或者说 P(B)称为先验概率,因为我们在得到F是“a”或者“o”之前,就可以得到 P(B) 。
同理,将 P(B=blue|F=o)和 P(B=red|F=o)称为后验概率,因为我们在完整的一次实验之后也就是得到了F的具体取值之后才能得到这个概率。
5.1.1.4 拉普拉斯修正
由于实验数据存在某些原因导致数据有缺失值,例如没有记录某天是否去打球,导致过拟合。因此在计算概率的时候需要加减降低这种情况的影响。
P
(
c
)
=
∣
D
c
∣
+
1
∣
D
∣
+
N
P(c)= \frac {|D_ {c}|+1}{|D|+N}
P(c)=∣D∣+N∣Dc∣+1
P ( x i ∣ c ) = ∣ D c , x i ∣ + 1 ∣ D ∣ + N i P(x_ {i}|c)= \frac {|D_ {c,x_{i}}|+1}{|D|+N_ {i}} P(xi∣c)=∣D∣+Ni∣Dc,xi∣+1
5.1.1.5 对数似然防溢出
当属性数量多的情况下,导致累乘结果下溢。采用防溢出策略(累乘变累加)
ln ( a ∗ b ) = ln ( a ) + ln ( b ) \ln ( a^ {*} b)= \ln (a)+ \ln (b) ln(a∗b)=ln(a)+ln(b)
5.2 实现
5.2.1 垃圾邮件分类
5.2.1.1 数据集介绍
垃圾邮件分类数据集是一组用于训练和测试垃圾分类模型的数据,主要包括不同类型垃圾的分类信息和相关特征。垃圾分类数据集可以,实现对垃圾的自动分类。
5.2.1.2 代码
createVocabList:根据输入的数据集创建词汇表,返回一个不重复的词条列表。
def createVocabList(dataSet):
vocabSet = set([])
for document in dataSet:
vocabSet = vocabSet | set(document) # 取并集
return list(vocabSet)
setOfWords2Vec:根据词汇表将输入的词条列表转换为向量,向量的每个元素为 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
bagOfWords2VecMN:根据词汇表构建词袋模型,即将每个词条的出现次数转换为一个向量。
def bagOfWords2VecMN(vocabList, inputSet):
returnVec = [0] * len(vocabList)
for word in inputSet:
if word in vocabList:
returnVec[vocabList.index(word)] += 1
return returnVec
trainNB0:训练朴素贝叶斯分类器,输入为训练文档矩阵和训练类别标签向量,返回正常邮件类和垃圾邮件类的条件概率数组以及文档属于垃圾邮件类的概率。
def trainNB0(trainMatrix, trainCategory):
numTrainDocs = len(trainMatrix)
print("文档数目:", numTrainDocs)
numWords = len(trainMatrix[0])
print("词条数", numWords)
print("---:", trainCategory)
# 形状[40,37]
print("---:", trainMatrix)
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:
print("p1Num",p1Num)
p1Num += trainMatrix[i]
print("p1Denom",p1Denom)
p1Denom += sum(trainMatrix[i])
else:
p0Num += trainMatrix[i]
p0Denom += sum(trainMatrix[i])
print("1:",p1Num / p1Denom)
print("0:",p0Num / p1Denom)
p1Vect = np.log(p1Num / p1Denom)
p0Vect = np.log(p0Num / p0Denom)
return p0Vect, p1Vect, pAbusive
```
**classifyNB**:朴素贝叶斯分类器的分类函数,根据输入的词条向量和条件概率数组判断文档属于正常邮件还是垃圾邮件。
```python
def classifyNB(vec2Classify, p0Vec, p1Vec, pClass1):
#p1 = reduce(lambda x, y: x * y, vec2Classify * p1Vec) * pClass1
#p0 = reduce(lambda x, y: x * y, vec2Classify * p0Vec) * (1.0 - pClass1)
p1=sum(vec2Classify*p1Vec)+np.log(pClass1)
p0=sum(vec2Classify*p0Vec)+np.log(1.0-pClass1)
if p1 > p0:
return 1
else:
return 0
textParse:将大字符串解析为字符串列表。
def textParse(bigString):
listOfTokens = re.split(r'\W*', bigString)
return [tok.lower() for tok in listOfTokens if len(tok) > 0]
spamTest:主函数,测试朴素贝叶斯分类器的性能。首先读取垃圾邮件和正常邮件,然后创建词汇表,训练朴素贝叶斯分类器,最后进行交叉验证测试。
def spamTest():
docList = []
classList = []
fullText = []
listOfTokens1 = []
for i in range(1, 26):
wordList = textParse(open('./email/spam/%d.txt' % i, 'r', encoding='iso8859-1').read())
docList.append(wordList)
fullText.append(wordList)
classList.append(1)
wordList = textParse(open('./email/ham/%d.txt' % i, 'r', encoding='iso8859-1').read())
docList.append(wordList)
fullText.append(wordList)
classList.append(0)
vocabList = createVocabList(docList)
trainingSet = list(range(50))
testSet = []
for i in range(10):
randIndex = int(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(np.array(trainMat), np.array(trainClasses))
errorCount = 0
for docIndex in testSet:
wordVector = setOfWords2Vec(vocabList, docList[docIndex])
if classifyNB(np.array(wordVector), p0V, p1V, pSpam) != classList[docIndex]:
errorCount += 1
print("分类错误的测试集:", docList[docIndex])
print('错误率:%.2f%%' % (float(errorCount) / len(testSet) * 100))
5.2.1.3 结果
5.2.2 文本分类
5.2.2.1 数据集介绍
新闻分类数据集是用于训练和测试新闻文本分类模型的数据集,包含大量新闻文章及其所属类别。这些数据集可以实现对新闻内容的自动分类。
5.2.2.2 代码
分离:
# -*- coding: UTF-8 -*-
import os
import jieba
def TextProcessing(folder_path):
folder_list = os.listdir(folder_path)
data_list = []
class_list = []
for folder in folder_list:
new_folder_path = os.path.join(folder_path, folder)
files = os.listdir(new_folder_path)
j = 1
for file in files:
if j > 100:
break
with open(os.path.join(new_folder_path, file), 'r', encoding='utf-8') as f:
raw = f.read()
word_cut = jieba.cut(raw, cut_all=False)
word_list = list(word_cut)
data_list.append(word_list)
class_list.append(folder)
j += 1
print(data_list)
print(class_list)
if __name__ == '__main__':
# 文本预处理
folder_path = './SogouC/Sample'
TextProcessing(folder_path)
统计:
# -*- coding: UTF-8 -*-
import os
import random
import jieba
def TextProcessing(folder_path, test_size=0.2):
folder_list = os.listdir(folder_path)
data_list = []
class_list = []
for folder in folder_list:
new_folder_path = os.path.join(folder_path, folder)
files = os.listdir(new_folder_path)
j = 1
for file in files:
if j > 100:
break
with open(os.path.join(new_folder_path, file), 'r', encoding='utf-8') as f:
raw = f.read()
word_cut = jieba.cut(raw, cut_all=False)
word_list = list(word_cut)
data_list.append(word_list)
class_list.append(folder)
j += 1
data_class_list = list(zip(data_list, class_list))
random.shuffle(data_class_list)
index = int(len(data_class_list) * test_size) + 1
train_list = data_class_list[index:]
test_list = data_class_list[:index]
train_data_list, train_class_list = zip(*train_list)
test_data_list, test_class_list = zip(*test_list)
all_words_dict = {}
for word_list in train_data_list:
for word in word_list:
if word in all_words_dict.keys():
all_words_dict[word] += 1
else:
all_words_dict[word] = 1
all_words_tuple_list = sorted(all_words_dict.items(), key=lambda f: f[1], reverse=True)
all_words_list, all_words_nums = zip(*all_words_tuple_list)
all_words_list = list(all_words_list)
return all_words_list, train_data_list, test_data_list, train_class_list, test_class_list
if __name__ == '__main__':
# 文本预处理
folder_path = './SogouC/Sample'
all_words_list, train_data_list, test_data_list, train_class_list, test_class_list = TextProcessing(folder_path,
test_size=0.2)
print(all_words_list)
清洗:
import os
import jieba
import random
def TextProcessing(folder_path, test_size=0.2):
folder_list = os.listdir(folder_path)
data_list = []
class_list = []
for folder in folder_list:
new_folder_path = os.path.join(folder_path, folder)
files = os.listdir(new_folder_path)
j = 1
for file in files:
if j > 100:
break
with open(os.path.join(new_folder_path, file), 'r', encoding='utf-8') as f:
raw = f.read()
word_cut = jieba.cut(raw, cut_all=False)
word_list = list(word_cut)
data_list.append(word_list)
class_list.append(folder)
j += 1
data_class_list = list(zip(data_list, class_list))
random.shuffle(data_class_list)
index = int(len(data_class_list) * test_size) + 1
train_list = data_class_list[index:]
test_list = data_class_list[:index]
train_data_list, train_class_list = zip(*train_list)
test_data_list, test_class_list = zip(*test_list)
all_words_dict = {}
for word_list in train_data_list:
for word in word_list:
if word in all_words_dict.keys():
all_words_dict[word] += 1
else:
all_words_dict[word] = 1
all_words_tuple_list = sorted(all_words_dict.items(), key=lambda f: f[1], reverse=True)
all_words_list, all_words_nums = zip(*all_words_tuple_list)
all_words_list = list(all_words_list)
return all_words_list, train_data_list, test_data_list, train_class_list, test_class_list
def MakeWordsSet(words_file):
words_set = set()
with open(words_file, 'r', encoding='utf-8') as f:
for line in f.readlines():
word = line.strip()
if len(word) > 0:
words_set.add(word)
return words_set
def words_dict(all_words_list, deleteN, stopwords_set=set()):
feature_words = []
n = 1
for t in range(deleteN, len(all_words_list), 1):
if n > 1000:
break
if not all_words_list[t].isdigit() and all_words_list[t] not in stopwords_set and 1 < len(
all_words_list[t]) < 5:
feature_words.append(all_words_list[t])
n += 1
return feature_words
if __name__ == '__main__':
folder_path = './SogouC/Sample'
all_words_list, train_data_list, test_data_list, train_class_list, test_class_list = TextProcessing(folder_path,
test_size=0.2)
stopwords_file = './stopwords_cn.txt'
stopwords_set = MakeWordsSet(stopwords_file)
feature_words = words_dict(all_words_list, 100, stopwords_set)
print(feature_words)
5.2.2.3 结果
分离:
统计:
清洗:
5.3 总结
5.3.1 优点
生成式模型,通过计算概率来进行分类,可以用来处理多分类问题
对小规模的数据表现很好,适合多分类任务,适合增量式训练,算法较简单
5.3.2 缺点
对输入数据的表达形式很敏感
会带来一些准确率上的损失
需要计算先验概率,分类决策存在错误率
5.3.3 EM算法
EM 算法(Expectation-Maximization,期望最大化算法)是一种迭代优化算法,主要用于解决含有隐变量的概率模型中参数估计问题。它的核心思想是利用隐含量的期望来估计未知参数,进而最大化观测数据的似然性。
EM 算法的基本流程如下:
初始化:给定观测数据集 X 和模型参数θ,初始化参数估计值(如π、μ、Σ等)。
E 步(Expectation):根据当前的参数估计值,计算隐含变量的期望。这一步主要是利用贝叶斯公式,将观测数据和模型参数相结合,求解隐含变量的概率分布。
M 步(Maximization):利用 E 步计算得到的隐含变量期望,更新模型参数估计值。这一步通过最大化似然函数来得到新的参数估计值。
迭代:重复执行 E 步和 M 步,直到收敛。收敛条件可以是参数变化小于某个阈值或达到预设的最大迭代次数。
输出:得到最终的模型参数估计值。
EM 算法的主要优点是能够处理具有隐含变量的概率模型,尤其是在数据量较大时表现出较好的收敛性能。然而,EM 算法也存在缺点,如对初始值敏感、可能导致局部最优解等问题。在实际应用中,为了提高收敛速度和稳定性,通常需要采用一些策略,如多次初始化、使用梯度下降等。
5.3.4 GMM
GMM(Gaussian Mixture Model,高斯混合模型)是一种概率模型,用于描述由多个高斯分布组成的数据分布。GMM 主要用于数据聚类和密度估计,它可以用来分析多峰分布的数据,或者在不知道数据分布形式的情况下对数据进行建模。
GMM 假设观测数据集 X 由 K 个高斯分布组成,每个高斯分布的参数分别为:π_k(概率权重)、μ_k(均值向量)和 Σ_k(协方差矩阵)。GMM 的目标是最小化观测数据与各高斯分布之间的边缘误差,即求解使得总体概率密度函数与观测数据集之间误差最小的参数组合。
GMM 的基本步骤如下:
初始化:给定观测数据集 X,随机初始化高斯分布的数量 K 和各高斯分布的参数(π_k、μ_k、Σ_k)。
计算观测数据与各高斯分布的匹配度:对于数据集中的每个样本 x,计算它与各高斯分布的匹配度,即计算每个样本属于各高斯分布的概率。
更新高斯分布参数:根据匹配度,重新计算各高斯分布的参数(π_k、μ_k、Σ_k)。这一步通过 EM 算法来实现,具体过程参考 EM 算法的描述。
重复步骤 2 和 3,直到高斯分布参数收敛。收敛条件可以是参数变化小于某个阈值或达到预设的最大迭代次数。
输出:得到最终的高斯混合模型,包括各高斯分布的参数(π_k、μ_k、Σ_k)。
GMM 算法在实际应用中具有广泛的应用价值,如图像分割、语音识别、生物信息学等领域。但是,GMM 也存在一些缺点加粗样式,如对初始值敏感、可能导致局部最优解等问题。为了解决这些问题,可以采用多次初始化、使用梯度下降等策略。此外,当数据分布具有多个峰值时,GMM 能够较好地拟合数据;而当数据分布具有一个峰值时,GMM 可能性能较差。在这种情况下,可以使用 GMM 与其他聚类算法(如 K-Means)相结合,以提高聚类效果。
5.3.5 HMM
HMM(Hidden Markov Model,隐马尔可夫模型)是一种统计模型,主要用于时序数据的建模和预测。它由两个部分组成:可见层(Observable layer)和隐藏层(Hidden layer)。可见层表示我们可以观察到的数据,如序列中的符号或数值;隐藏层表示我们无法直接观察到的数据,但它对可见层数据产生影响。
在 HMM 中,隐藏层的状态转移和观测值生成是概率性的,且这些概率分布满足马尔可夫性质(即当前状态的概率分布只依赖于前一个状态,而与过去的状态无关)。HMM 的目标是推断出隐藏层的状态序列,以便更好地解释观测数据。
HMM 主要包括以下三个部分:
状态转移矩阵(State Transition Matrix):描述了隐藏层状态之间的转移概率。矩阵的每一行表示一个状态在下一个状态下转换到其他状态的概率。
观测矩阵(Observation Matrix):描述了每个隐藏状态生成的观测值的概率。矩阵的每一行表示一个状态生成某一观测值的概率。
初始状态概率分布(Initial State Probability Distribution):表示模型初始进入每个状态的概率。
HMM 的训练方法主要有两种:前向 - 后向算法(Forward-Backward Algorithm)和 EM 算法(Expectation-Maximization)。训练过程中,我们需要通过优化状态转移矩阵、观测矩阵和初始状态概率分布,使得模型生成的观测序列与实际观测序列尽可能接近。
HMM 在许多领域有广泛的应用,如语音识别、自然语言处理、生物信息学等。通过推断隐藏层状态序列,我们可以更好地理解和管理时序数据中的模式和变化。
例题: