朴素贝叶斯

朴素贝叶斯

  • 朴素贝叶斯理论
    • 贝叶斯决策理论
    • 条件概率
    • 全概率公式
    • 贝叶斯公式
    • 朴素贝叶斯
  • 言论屏蔽
  • 新浪新闻分类
  • 朴素贝叶斯算法的优缺点

朴素贝叶斯算法是一种基于贝叶斯定理的有监督的机器学习算法,解决的是分类问题,如文本分类、垃圾邮件过滤、客户是否流失,是否值得投资、信用等级评定等领域,并在实际应用中表现出良好的性能。该算法简单易懂,学习效率高,在某些领域的分类问题中能够与决策树、神经网络等算法相媲美。但由于该算法以自变量之间的独立(假设特征之间相互独立)性和连续变量的正态性假设为前提,就会导致算法精度在某种程度上受影响。

下面是对朴素贝叶斯算法的详细阐述:

  1. 贝叶斯定理: 朴素贝叶斯算法基于贝叶斯定理进行概率推断。贝叶斯定理描述了在已知先验概率的情况下,如何根据新的证据来更新我们对事件发生概率的信念。
  2. 特征独立性假设: 朴素贝叶斯算法假设特征之间相互独立,即给定类别,特征之间的条件概率是相互独立的。虽然这个假设在实际中并不总是成立,但在许多情况下,这种简化可以带来较好的分类效果。
  3. 类别的先验概率: 在朴素贝叶斯算法中,需要计算每个类别的先验概率,即在没有任何信息的情况下,每个类别发生的概率。
  4. 特征的条件概率: 对于给定的类别,需要计算每个特征的条件概率,即在该类别下,每个特征取某个值的概率。这通常需要利用训练数据集来进行估计。
  5. 计算后验概率: 当有新的样本需要分类时,利用贝叶斯定理,可以计算出该样本属于每个类别的后验概率。最终选择具有最高后验概率的类别作为分类结果。
  6. 处理连续特征: 对于连续特征,可以使用概率密度函数来进行条件概率的估计,常见的方法包括高斯朴素贝叶斯和多项式朴素贝叶斯。

总体来说,朴素贝叶斯算法简单易懂,计算效率高,对小规模的数据集表现良好,但在处理特征之间相关性较强的情况下可能表现不佳。在实际应用中,朴素贝叶斯算法通常作为其他更复杂算法的基准进行对比,或者在特征独立性较强的问题上取得良好效果。

朴素贝叶斯理论

贝叶斯决策理论

朴素贝叶斯是贝叶斯决策理论的一部分,所以我们先了解一下贝叶斯决策理论。假设现在有两个类别, p 1 ( x , y ) p1(x, y) p1(x,y) 表示数据点 ( x , y ) (x, y) (x,y) 属于类别一的概率, p 2 ( x , y ) p2(x, y) p2(x,y) 表示数据点 ( x , y ) (x, y) (x,y) 属于类别二的概率,那么对于一个新的数据点 ( x , y ) (x, y) (x,y),我们就可以用下面的规则来判断它所属的类别:

  • 如果 p 1 ( x , y ) > p 2 ( x , y ) p1(x, y) > p2(x, y) p1(x,y)>p2(x,y),那么判定 ( x , y ) (x, y) (x,y) 属于类别一
  • 如果 p 1 ( x , y ) < p 2 ( x , y ) p1(x, y) < p2(x, y) p1(x,y)<p2(x,y),那么判定 ( x , y ) (x, y) (x,y) 属于类别二

也就是说,我们会选择最高概率对应的类别作为最终的分类结果,这就是贝叶斯决策理论的核心思想。

条件概率

条件概率指的是在事件 B B B 发生的情况下,事件 A A A 发生的概率,用 P ( A ∣ B ) P(A|B) P(AB) 来表示。

在这里插入图片描述

从上图可以清楚的得到条件概率 P ( A ∣ B ) P(A|B) P(AB) 的计算公式:
P ( A ∣ B ) = P ( A ∩ B ) P ( B ) P(A|B) = \frac{P(A \cap B)}{P(B)} P(AB)=P(B)P(AB)
因此,
P ( A ∩ B ) = P ( A ∣ B ) P ( B ) P(A \cap B) = P(A|B)P(B) P(AB)=P(AB)P(B)
同理,
P ( A ∩ B ) = P ( B ∣ A ) P ( A ) P(A \cap B) = P(B|A)P(A) P(AB)=P(BA)P(A)
推理可得,
P ( A ∣ B ) P ( B ) = P ( B ∣ A ) P ( A ) ⟹ P ( A ∣ B ) = P ( B ∣ A ) P ( A ) P ( B ) P(A|B)P(B) = P(B|A)P(A) \implies P(A|B) = \frac{P(B|A)P(A)}{P(B)} P(AB)P(B)=P(BA)P(A)P(AB)=P(B)P(BA)P(A)
这就是条件概率的计算公式。

全概率公式

假设样本空间 S S S,是两个事件 A A A A ′ A' A 的总和,如下图所示:

在这里插入图片描述

事件 B B B 也可以划分成两个部分,如下图所示:

在这里插入图片描述

此时,有如下公式:
P ( B ) = P ( B ∩ A ) + P ( B ∩ A ′ ) P(B) = P(B \cap A) + P(B \cap A') P(B)=P(BA)+P(BA)
因为,
P ( B ∩ A ) = P ( B ∣ A ) P ( A ) P ( B ∩ A ′ ) = P ( B ∣ A ′ ) P ( A ′ ) P(B \cap A) = P(B|A)P(A) \\ P(B \cap A') = P(B|A')P(A') P(BA)=P(BA)P(A)P(BA)=P(BA)P(A)
因此有,
P ( B ) = P ( B ∣ A ) P ( A ) + P ( B ∣ A ′ ) P ( A ′ ) P(B) = P(B|A)P(A) + P(B|A')P(A') P(B)=P(BA)P(A)+P(BA)P(A)
这就是全概率的计算公式。

我进一步详细阐述什么是全概率:

  • n n n 个事件两两互斥,且这 n n n 个事件的总和为 Ω \varOmega Ω,则称这 n n n 个事件组为完备事件组
  • 在完备事件组下: A 1 + A 2 + A 3 + ⋅ ⋅ ⋅ + A n = Ω A_1 + A_2 + A_3+···+ A_n = \varOmega A1+A2+A3+⋅⋅⋅+An=Ω
  • 此时,有一事件 X X X,要计算其发生概率,一般情况下无法直接求出,如果能找到一个伴随事件 X X X 发生的完备事件组,则能借助全概率公式求出事件 X X X 的发生概率
  • P ( X ) = ∑ i = 1 n P ( X ∣ A i ) P ( A i ) P(X) = \displaystyle\sum_{i=1}^{n}P(X|A_i)P(A_i) P(X)=i=1nP(XAi)P(Ai)

在这里插入图片描述

贝叶斯公式

全概率公式:已知事件 X X X 与伴随事件 X X X 发生的完备事件组( A 1 、 A 2 、 . . . 、 A n A_1、A_2、...、A_n A1A2...An),根据完备事件组,求出事件 X X X 发生的概率。

贝叶斯公式:已知事件 X X X 与伴随事件 X X X 发生的完备事件组( A 1 、 A 2 、 . . . 、 A n A_1、A_2、...、A_n A1A2...An),根据事件 X X X 发生的概率,求出完备事件组中某一事件 A i A_i Ai 发生的概率。

贝叶斯公式如下所示:
P ( A i ∣ X ) = P ( A i ∩ X ) P ( X ) = P ( X ∣ A i ) P ( A i ) ∑ i = 1 n P ( X ∣ A i ) P ( A i ) P(A_i|X) = \frac{P(A_i \cap X)}{P(X)} = \frac{P(X|A_i)P(A_i)}{\displaystyle\sum_{i=1}^{n}P(X|A_i)P(A_i)} P(AiX)=P(X)P(AiX)=i=1nP(XAi)P(Ai)P(XAi)P(Ai)
其中:

  • P ( A i ) P(A_i) P(Ai) 叫做先验概率,即在事件 X X X 发生之前,我们对事件 A i A_i Ai 发生概率的一个判断
  • P ( A i ∣ X ) P(A_i|X) P(AiX) 叫做后验概率,即在事件 X X X 发生之后,我们对事件 A i A_i Ai 发生概率的一个重新评估;通俗点说,就是在样本空间为 X X X 的情况下, A i A_i Ai 所占的比重大小
  • P ( X ∣ A i ) ∑ i = 1 n P ( X ∣ A i ) P ( A i ) = P ( X ∣ A i ) P ( X ) \frac{P(X|A_i)}{\displaystyle\sum_{i=1}^{n}P(X|A_i)P(A_i)} = \frac{P(X|A_i)}{P(X)} i=1nP(XAi)P(Ai)P(XAi)=P(X)P(XAi) 叫做可能性函数,这是一个调整因子,使得预估概率更接近真实概率

条件概率可以理解成 后验概率 = 先验概率 × 调整因子,这就是贝叶斯推断的含义。我们先预估一个“先验概率”,然后加入实验结果,看这个实验是增强还是削弱了“先验概率”,由此得到更接近事实的“后验概率”。

接下来举一个例子,来加深对贝叶斯推断的理解。假设有两个一模一样的碗,一号碗有 30 颗水果糖和 10 颗巧克力糖,二号碗有 20 颗水果糖和 20 颗巧克力糖,如下图所示。现在随机选择一个碗,从中摸出一颗糖,发现是水果糖,请问这颗水果糖来自一号碗的概率有多大?

在这里插入图片描述

我们假定, H 1 H_1 H1 表示一号碗, H 2 H_2 H2 表示二号碗,由于两个碗是一模一样的,因此 P ( H 1 ) = P ( H 2 ) = 0.5 P(H_1) = P(H_2) = 0.5 P(H1)=P(H2)=0.5,我们把这个概率就叫做“先验概率”,即在没有做实验之前,来自一号碗的概率是 0.5。

假定 E E E 表示水果糖,问题就变成了求条件概率 P ( H 1 ∣ E ) P(H_1|E) P(H1E),我们把这个概率叫做“后验概率”,即在事件 E E E 发生后,对 P ( H 1 ) P(H_1) P(H1) 的修正。

根据条件概率公式,得到:
P ( H 1 ∣ E ) = P ( E ∣ H 1 ) P ( H 1 ) P ( E ) = P ( E ∣ H 1 ) P ( H 1 ) P ( E ∣ H 1 ) P ( H 1 ) + P ( E ∣ H 2 ) P ( H 2 ) = 0.375 0.625 = 0.6 P(H_1|E) = \frac{P(E|H_1)P(H_1)}{P(E)} = \frac{P(E|H_1)P(H_1)}{P(E|H_1)P(H_1)+P(E|H_2)P(H_2)} = \frac{0.375}{0.625} = 0.6 P(H1E)=P(E)P(EH1)P(H1)=P(EH1)P(H1)+P(EH2)P(H2)P(EH1)P(H1)=0.6250.375=0.6
计算结果表示这颗水果糖来自一号碗的概率为 0.6。也就是说,取出水果糖后,事件 H 1 H_1 H1 发生的可能性得到了增强。

这里进一步思考一点,在使用该方法时,如果不需要知道具体的类别概率,而只需知道所属类别 H 1 H_1 H1 H 2 H_2 H2,那我们就没必要计算事件 E E E 的全概率,只需比较 P ( H 1 ∣ E ) P(H_1|E) P(H1E) P ( H 2 ∣ E ) P(H_2|E) P(H2E) 的大小即可。

朴素贝叶斯

朴素贝叶斯与贝叶斯是两个不同的概念。贝叶斯算法是一种基于贝叶斯定理进行概率推断的统计学方法。贝叶斯算法通过利用先验概率和样本数据得到后验概率,从而对未知参数或未来事件进行推断。朴素贝叶斯算法是贝叶斯算法家族中的一员,它是基于贝叶斯定理和特征独立性假设的一种分类算法。与一般的贝叶斯算法相比,朴素贝叶斯算法做出了特征独立性的假设,简化了条件概率的计算,使得算法更加高效,并且适用于大规模的数据集。

朴素贝叶斯公式如下所示:
P ( y ∣ X ) = P ( X ∣ y ) P ( y ) P ( X ) = P ( x 1 , x 2 , . . . , x n ∣ y ) P ( y ) P ( x 1 , x 2 , . . . , x n ) = P ( x 1 ∣ y ) P ( x 2 ∣ y ) ⋅ ⋅ ⋅ P ( x n ∣ y ) P ( y ) P ( x 1 , x 2 , . . . , x n ) P(y|X) = \frac{P(X|y)P(y)}{P(X)}=\frac{P(x_1, x_2, ..., x_n|y)P(y)}{P(x_1, x_2, ..., x_n)}=\frac{P(x_1|y)P(x_2|y)···P(x_n|y)P(y)}{P(x_1, x_2, ..., x_n)} P(yX)=P(X)P(Xy)P(y)=P(x1,x2,...,xn)P(x1,x2,...,xny)P(y)=P(x1,x2,...,xn)P(x1y)P(x2y)⋅⋅⋅P(xny)P(y)
其中, P ( x 1 ∣ y ) 、 P ( x 2 ∣ y ) 、 . . . P(x_1|y)、P(x_2|y)、... P(x1y)P(x2y)... 分别表示在类别为 y y y 的条件下,特征 x 1 , x 2 , . . . , x n x_1, x_2, ..., x_n x1,x2,...,xn 的概率(占比)。

朴素贝叶斯算法通过计算每个类别的后验概率,并选择具有最高后验概率的类别作为分类结果。为了进行分类,需要事先估计先验概率和条件概率,通常通过训练数据集来进行参数估计。

接下来举一个例子,进一步理解朴素贝叶斯推断。某医院上午来了六个门诊病人,情况如下表所示:

症状职业疾病
打喷嚏护士感冒
打喷嚏农夫过敏
头痛建筑工人脑震荡
头痛建筑工人感冒
打喷嚏教师感冒
头痛教师脑震荡

现在来了第七个病人,是一个打喷嚏的建筑工人,请问他患上感冒的概率有多大?

根据贝叶斯定理:
P ( y ∣ X ) = P ( X ∣ y ) P ( y ) P ( X ) P(y|X) = \frac{P(X|y)P(y)}{P(X)} P(yX)=P(X)P(Xy)P(y)
推理可得,
P ( 感冒 ∣ 打喷嚏 , 建筑工人 ) = P ( 打喷嚏 , 建筑工人 ∣ 感冒 ) P ( 感冒 ) P ( 打喷嚏 , 建筑工人 ) P(感冒|打喷嚏, 建筑工人)=\frac{P(打喷嚏, 建筑工人|感冒)P(感冒)}{P(打喷嚏, 建筑工人)} P(感冒打喷嚏,建筑工人)=P(打喷嚏,建筑工人)P(打喷嚏,建筑工人感冒)P(感冒)
根据朴素贝叶斯的特征独立性假设可知,“打喷嚏”和“建筑工人”这两个特征是相互独立的,因此上述公式可写成如下:
P ( 感冒 ∣ 打喷嚏 , 建筑工人 ) = P ( 打喷嚏 ∣ 感冒 ) P ( 建筑工人 ∣ 感冒 ) P ( 感冒 ) P ( 打喷嚏 ) P ( 建筑工人 ) = 0.66 × 0.33 × 0.5 0.5 × 0.33 = 0.66 P(感冒|打喷嚏, 建筑工人)=\frac{P(打喷嚏|感冒)P(建筑工人|感冒)P(感冒)}{P(打喷嚏)P(建筑工人)}=\frac{0.66×0.33×0.5}{0.5×0.33}=0.66 P(感冒打喷嚏,建筑工人)=P(打喷嚏)P(建筑工人)P(打喷嚏感冒)P(建筑工人感冒)P(感冒)=0.5×0.330.66×0.33×0.5=0.66
计算结果表示这个打喷嚏的建筑工人有 66% 的概率患上感冒,同理可计算这个人患上过敏和脑震荡的概率,比较这几个概率,就可以推测他最可能患上的疾病类别。

言论屏蔽

以在线社区留言为例,为了营造一个健康发展的社区,我们需要屏蔽一些带侮辱性的言论,如果某条留言使用了负面或侮辱性的词汇,我们就将其标记为内容不当。我们使用 1 和 2 分别表示内容不当和内容得当。

完整的代码如下:

import numpy as np
from functools import reduce# 读取数据
def read_dataset() -> (list, list):""":return: 返回样本数据集和样本标签"""# 将留言进行单词切分,并转换成词向量samples = [['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']]# 各样本对应标签,1 代表内容不当,2 代表内容得当labels = [2, 1, 2, 1, 2, 1]return samples, labels# 依据数据集创建词汇表
def create_vocabulary(dataset: list) -> list:""":param dataset: 样本数据集:return: 数据集中出现的所有词汇集合,以列表形式返回"""vocab_set = set([])  # 创建一个空集for sample in dataset:vocab_set = vocab_set | set(sample)  # 取并集return list(vocab_set)# 用词汇表的稀疏向量形式来表示每个词向量样本
def vocab_vector_to_vocabulary_vector(vocabulary: list, sample: list) -> list:""":param vocabulary: 词汇表:param sample: 样本数据集中的一个样本:return: 词汇表的稀疏向量"""vocabulary_vector = [0] * len(vocabulary)  # 元素个数与词汇表 vocabulary 一致for vocab in sample:if vocab in vocabulary:  # 如果该词汇出现在词汇表中,则将 vocabulary_vector 对应位置的值置为 1vocabulary_vector[vocabulary.index(vocab)] = 1else:print(f"{vocab} is not in vocabulary!")return vocabulary_vector# 训练朴素贝叶斯分类器
def train_naive_bayes_classifier(train_mat: list, train_labels: list) -> (np.ndarray, np.ndarray, float):""":param train_mat: 训练样本数据,都已转成词汇表的稀疏向量形式:param train_labels: 训练样本数据的对应标签:return: 返回在类别 1 和 2 情况下各个词汇出现的概率以及类别 1 占样本集的概率"""num_samples = len(train_mat)  # 训练样本数;6num_vocabs = len(train_mat[0])  # 每个样本向量含有多少个元素;32p_1 = float(sum([1 for label in train_labels if label == 1]) / num_samples)  # 在训练集中,内容不当的概率p_1_vocab = np.zeros(num_vocabs)  # 当样本标签为 1 时,每个词出现的次数p_2_vocab = np.zeros(num_vocabs)  # 当样本标签为 2 时,每个词出现的次数p_1_vocabs = 0.0  # 当样本标签为 1 时,所有词出现的次数和p_2_vocabs = 0.0  # 当样本标签为 2 时,所有词出现的次数和for i in range(num_samples):if train_labels[i] == 1:p_1_vocab += train_mat[i]p_1_vocabs += sum(train_mat[i])else:p_2_vocab += train_mat[i]p_2_vocabs += sum(train_mat[i])p_1_vector = p_1_vocab / p_1_vocabs  # 在类别 1 的情况下,各个词出现的概率;P(w1|1)、P(w2|1)、P(w3|1)、...p_2_vector = p_2_vocab / p_2_vocabs  # 在类别 2 的情况下,各个词出现的概率;P(w1|2)、P(w2|2)、P(w3|2)、...return p_1_vector, p_2_vector, p_1# 预测分类结果
def predict(predict_data: np.ndarray, p_1_vector: np.ndarray, p_2_vector: np.ndarray, p_1: float) -> int:""":param predict_data: 预测数据,已转成词汇表的稀疏向量形式:param p_1_vector: 在类别 1 的情况下,各个词出现的条件概率:param p_2_vector: 在类别 2 的情况下,各个词出现的条件概率:param p_1: 类别为 1 的先验概率:return: 预测的类别"""p1 = reduce(lambda x, y: x * y, predict_data * p_1_vector) * p_1  # 类别为 1 的概率p2 = reduce(lambda x, y: x * y, predict_data * p_2_vector) * (1 - p_1)  # 类别为 2 的概率print('p1:', p1)print('p2:', p2)if p1 > p2:return 1else:return 2if __name__ == '__main__':# 获取样本数据和对应标签samples, labels = read_dataset()# 获取词汇表vocabulary = create_vocabulary(samples)# 获取训练的样本数值向量train_mat = []for sample in samples:train_mat.append(vocab_vector_to_vocabulary_vector(vocabulary, sample))# 获取在类别 1 和 2 情况下各个词汇出现的概率以及类别 1 占样本集的概率# p_1_vector 中存放的是各个单词在类别 1 情况下出现的条件概率# p_2_vector 中存放的是各个单词在类别 2 情况下出现的条件概率# p_1 就是类别为 1 的先验概率p_1_vector, p_2_vector, p_1 = train_naive_bayes_classifier(train_mat, labels)# 测试predict_data = ['love', 'my', 'dalmation']  # 预测样本predict_data_vector = np.array(vocab_vector_to_vocabulary_vector(vocabulary, predict_data))  # 将预测样本转成词汇表的稀疏向量result = predict(predict_data_vector, p_1_vector, p_2_vector, p_1)if result == 1:print(f'{predict_data} 属于内容不当')else:print(f'{predict_data} 属于内容得当')"""
P(1|X)P(X)=P(X|1)P(1) => P(1|(w1, w2, ..., w32))P(w1, w2, ..., w32)=P((w1, w2, ..., w32)|1)P(1)=P(w1|1)P(w2|1)···P(w32|1)P(1)
P(2|X)P(X)=P(X|2)P(2) => P(2|(w1, w2, ..., w32))P(w1, w2, ..., w32)=P((w1, w2, ..., w32)|2)P(2)=P(w1|2)P(w2|2)···P(w32|2)P(2)
比较 P(1|X) 与 P(2|X) 的大小,将更大值所属类别作为最终的分类结果
"""
---------
p1: 0.0
p2: 0.0
['love', 'my', 'dalmation'] 属于内容得当

上述代码中存在一个问题,就是在计算 P ( 1 ∣ X ) P ( X ) = P ( w 1 ∣ 1 ) P ( w 2 ∣ 1 ) ⋅ ⋅ ⋅ P ( w 32 ∣ 1 ) P ( 1 ) P(1|X)P(X) = P(w1|1)P(w2|1)···P(w32|1)P(1) P(1∣X)P(X)=P(w1∣1)P(w2∣1)⋅⋅⋅P(w32∣1)P(1) 时,只要其中有一个 P ( w i ∣ 1 ) = 0 P(wi|1) = 0 P(wi∣1)=0,其最后的概率值都会等于 0,这显然不是我们想要的结果。

为了解决上述问题,我们可以将各单词的出现次数初始化为 1,将所有单词出现的总次数初始化为 2,这种做法就叫做拉普拉斯平滑(Laplace Smoothing),又称之为加 1 平滑,是比较常用的平滑方法,可以解决 0 概率问题。

除此之外,还有一个下溢出的问题,这是由于很多个很小的数相乘导致的。两个小于 1 的数值相乘,其结果将比两个数值中的任何一个都小,当很多个小于 1 的数值相乘时,结果会非常小,此时若对其进行四舍五入,计算结果很可能就变成 0 了。为了解决这个问题,我们可以对乘积结果取自然对数,通过求对数可以避免下溢出或浮点数舍入导致的错误。同时,采用自然对数进行处理不会产生什么损失。

函数 f ( x ) f(x) f(x) l n f ( x ) lnf(x) lnf(x) 的曲线如下图所示:

在这里插入图片描述

如上图所示,函数 f ( x ) f(x) f(x) l n f ( x ) lnf(x) lnf(x) 在相同区域内同增同减,并在相同点上取到极值,基于这些特性,在某些问题上可以使用 l n f ( x ) lnf(x) lnf(x) 替代 f ( x ) f(x) f(x) 进行相应处理。

修改后的代码如下:

import numpy as np# 读取数据
def read_dataset() -> (list, list):""":return: 返回样本数据集和样本标签"""# 将留言进行单词切分,并转换成词向量samples = [['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']]# 各样本对应标签,1 代表内容不当,2 代表内容得当labels = [2, 1, 2, 1, 2, 1]return samples, labels# 依据数据集创建词汇表
def create_vocabulary(dataset: list) -> list:""":param dataset: 样本数据集:return: 数据集中出现的所有词汇集合,以列表形式返回"""vocab_set = set([])  # 创建一个空集for sample in dataset:vocab_set = vocab_set | set(sample)  # 取并集return list(vocab_set)# 用词汇表的稀疏向量形式来表示每个词向量样本
def vocab_vector_to_vocabulary_vector(vocabulary: list, sample: list) -> list:""":param vocabulary: 词汇表:param sample: 样本数据集中的一个样本:return: 词汇表的稀疏向量"""vocabulary_vector = [0] * len(vocabulary)  # 元素个数与词汇表 vocabulary 一致for vocab in sample:if vocab in vocabulary:  # 如果该词汇出现在词汇表中,则将 vocabulary_vector 对应位置的值置为 1vocabulary_vector[vocabulary.index(vocab)] = 1else:print(f"{vocab} is not in vocabulary!")return vocabulary_vector# 训练朴素贝叶斯分类器
def train_naive_bayes_classifier(train_mat: list, train_labels: list) -> (np.ndarray, np.ndarray, float):""":param train_mat: 训练样本数据,都已转成词汇表的稀疏向量形式:param train_labels: 训练样本数据的对应标签:return: 返回在类别 1 和 2 情况下各个词汇出现的概率以及类别 1 占样本集的概率"""num_samples = len(train_mat)  # 训练样本数;6num_vocabs = len(train_mat[0])  # 每个样本向量含有多少个元素;32p_1 = float(sum([1 for label in train_labels if label == 1]) / num_samples)  # 在训练集中,内容不当的概率p_1_vocab = np.ones(num_vocabs)  # 当样本标签为 1 时,每个词出现的次数p_2_vocab = np.ones(num_vocabs)  # 当样本标签为 2 时,每个词出现的次数p_1_vocabs = 2.0  # 当样本标签为 1 时,所有词出现的次数和p_2_vocabs = 2.0  # 当样本标签为 2 时,所有词出现的次数和for i in range(num_samples):if train_labels[i] == 1:p_1_vocab += train_mat[i]p_1_vocabs += sum(train_mat[i])else:p_2_vocab += train_mat[i]p_2_vocabs += sum(train_mat[i])p_1_vector = np.log(p_1_vocab / p_1_vocabs)  # 在类别 1 的情况下,各个词出现的概率;P(w1|1)、P(w2|1)、P(w3|1)、...p_2_vector = np.log(p_2_vocab / p_2_vocabs)  # 在类别 2 的情况下,各个词出现的概率;P(w1|2)、P(w2|2)、P(w3|2)、...return p_1_vector, p_2_vector, p_1# 预测分类结果
def predict(predict_data: np.ndarray, p_1_vector: np.ndarray, p_2_vector: np.ndarray, p_1: float) -> int:""":param predict_data: 预测数据,已转成词汇表的稀疏向量形式:param p_1_vector: 在类别 1 的情况下,各个词出现的条件概率:param p_2_vector: 在类别 2 的情况下,各个词出现的条件概率:param p_1: 类别为 1 的先验概率:return: 预测的类别"""p1 = sum(predict_data * p_1_vector) + np.log(p_1)  # 类别为 1 的概率p2 = sum(predict_data * p_2_vector) + np.log(1.0 - p_1)  # 类别为 2 的概率print('p1:', p1)print('p2:', p2)if p1 > p2:return 1else:return 2if __name__ == '__main__':# 获取样本数据和对应标签samples, labels = read_dataset()# 获取词汇表vocabulary = create_vocabulary(samples)# 获取训练的样本数值向量train_mat = []for sample in samples:train_mat.append(vocab_vector_to_vocabulary_vector(vocabulary, sample))# 获取在类别 1 和 2 情况下各个词汇出现的概率以及类别 1 占样本集的概率# p_1_vector 中存放的是各个单词在类别 1 情况下出现的条件概率# p_2_vector 中存放的是各个单词在类别 2 情况下出现的条件概率# p_1 就是类别为 1 的先验概率p_1_vector, p_2_vector, p_1 = train_naive_bayes_classifier(train_mat, labels)# 测试predict_data = ['stupid', 'garbage']  # 预测样本predict_data_vector = np.array(vocab_vector_to_vocabulary_vector(vocabulary, predict_data))  # 将预测样本转成词汇表的稀疏向量result = predict(predict_data_vector, p_1_vector, p_2_vector, p_1)if result == 1:print(f'{predict_data} 属于内容不当')else:print(f'{predict_data} 属于内容得当')
---------
p1: -4.702750514326955
p2: -7.20934025660291
['stupid', 'garbage'] 属于内容不当

新浪新闻分类

我们可以使用 sklearn 来构建朴素贝叶斯分类器。在 scikit-learn 中,有三个朴素贝叶斯分类算法,分别为 GaussianNB、MultinomialNB、BernoulliNB。其中,GaussianNB 是先验为高斯分布的朴素贝叶斯,MultinomialNB 是先验为多项式分布的朴素贝叶斯,BernoulliNB 是先验为伯努利分布的朴素贝叶斯。

sklearn.naive_bayes 模块实现了朴素贝叶斯算法,其 MultinomialNB 函数实现如下所示:

sklearn.naive_bayes.MultinomialNB(alpha=1.0, force_alpha=True, fit_prior=True, class_prior=None)- alpha:加法(拉普拉斯/利德斯通)平滑参数,默认为 1.0;如果设置为 0,则表示不平滑- force_alpha:默认为 True;如果为 False,且 alpha 小于 1e-10,则会将 alpha 的值置为 1e-10;如果为 True,alpha 将保持不变;之所以设置这个参数,是因为如果 alpha 太接近 0,可能会导致数值错误- fit_prior:是否学习类别先验概率,默认为 True;如果为 False,则所有的样本类别都有相同的先验概率- class_prior:类别的先验概率,如果指定,则不会根据数据调整先验概率,默认为 None

由 MultinomialNB 创建的实例对象 clf 具有以下方法:

fit(X, y)  # 根据训练集拟合 k 近邻分类器- X:训练数据,形状为 (n_samples, n_features)- y:目标值(训练样本对应的标签),形状为 (n_samples,)- sample_weight:样本权重,如果为 None,则样本权重相同返回拟合的朴素贝叶斯分类器get_params(deep=True)  # 以字典形式返回 MultinomialNB 类的参数- deep:布尔值,默认为 True返回参数partial_fit(X, y, classes=None, sample_weight=None)  # 对一批样本进行拟合,当整个数据集过大,无法一次性放入内存时,这种方法非常管用- X:训练数据,形状为 (n_samples, n_features)- y:目标值(训练样本对应的标签),形状为 (n_samples,)- classes:y 向量中可能出现的所有类别的列表,默认为 None;必须在第一次调用 partial_fit 时提供,后续调用可以省略- sample_weight:样本权重,如果为 None,则样本权重相同返回实例本身predict(X)  # 预测所提供数据的类别标签- X:预测数据,形状为 (n_samples, n_features)以 np.ndarray 形式返回形状为 (n_samples,) 的每个数据样本的类别标签predict_proba(X)  # 返回预测数据 X 在各类别标签中所占的概率- X:预测数据,形状为 (n_samples, n_features)返回该样本在各类别标签中的预测概率,类别的顺序与属性 classes_ 中的顺序一致predict_log_proba(X)  # 返回预测数据 X 在各类别标签中所占的对数概率- X:预测数据,形状为 (n_samples, n_features)返回该样本在各类别标签中的预测对数概率,类别的顺序与属性 classes_ 中的顺序一致predict_joint_log_proba(X)  # 返回预测数据 X 的联合对数概率估计值。对于 X 的每一行 x 和类别 y,联合对数概率由 logP(x, y) = logP(y) + logP(x|y) 给出,其中 logP(y) 是类别先验概率,logP(x|y) 是类别条件概率- X:预测数据,形状为 (n_samples, n_features)返回该样本在各类别标签中的预测对数概率,类别的顺序与属性 classes_ 中的顺序一致score(X, y, sample_weight=None)  # 返回预测结果和标签之间的平均准确率- X:预测数据,形状为 (n_samples, n_features)- y:预测数据的目标值(真实标签)- sample_weight:默认为 None返回预测数据的平均准确率,相当于先执行了 self.predict(X),而后再计算预测值和真实值之间的平均准确率

完整的新浪新闻朴素贝叶斯分类模型代码实现如下:

import os
import random
import jieba
import numpy as np
from sklearn.naive_bayes import MultinomialNB# 数据集处理
def text_process(dir_path: str, train_size=0.8) -> (list, list, list, list, list):""":param dir_path: 数据集目录:param train_size: 从数据集中划分训练集的比例:return: 词汇表,训练数据,测试数据,训练标签,测试标签"""dir_list = os.listdir(dir_path)  # ['C000008', ...]data_list = []labels_list = []# 遍历每个存放了 txt 文件的子目录for dir in dir_list:new_dir_path = os.path.join(dir_path, dir)files = os.listdir(new_dir_path)  # ['10.txt', ...]# 遍历每个存储了新闻文本的 txt 文件for file in files:file_path = os.path.join(new_dir_path, file)with open(file_path, '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)labels_list.append(dir)# 划分训练集与测试集data_labels_list = list(zip(data_list, labels_list))  # 将数据与标签对应压缩random.shuffle(data_labels_list)  # 将 data_labels_list 乱序index = int(len(data_labels_list) * train_size) + 1  # 训练集与测试集划分的索引值train_list = data_labels_list[:index]  # 训练集,包括数据与标签test_list = data_labels_list[index:]  # 测试集,包括数据与标签train_data_list, train_labels_list = zip(*train_list)  # 解压训练集,得到训练数据和标签train_data_list, train_labels_list = list(train_data_list), list(train_labels_list)  # 转成列表test_data_list, test_labels_list = zip(*test_list)  # 解压测试集,得到测试数据和标签test_data_list, test_labels_list = list(test_data_list), list(test_labels_list)  # 转成列表# 统计数据集词频all_words_dict = {}for words in train_data_list:for word in words:if word not in all_words_dict.keys():all_words_dict[word] = 0all_words_dict[word] += 1# 根据字典中的值进行键值对的排序,排列顺序为降序all_words_zip = sorted(all_words_dict.items(), key=lambda x: x[1], reverse=True)  # 排序all_words_tuple, all_words_frequency_tuple = zip(*all_words_zip)  # 解压缩,得到元组形式的词汇表和频次表all_words_list = list(all_words_tuple)  # 转成列表return all_words_list, train_data_list, test_data_list, train_labels_list, test_labels_list# 一些特定的词语如“的”、“在”、“当然”等对新闻分类无实际意义,将这些词整理好并存储在了 stopwords_cn.txt 文件中
# 读取 stopwords_cn.txt 文件,并进行去重处理
def stop_words_set(file_path: str) -> set:""":param file_path: stopwords_cn.txt 的路径:return: 返回一个经过去重处理的词汇集合"""words_set = set()with open(file_path, '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# 词频最高的往往是一些对于分类无意义的符号,有必要删除它们
# 文本特征选取,删除词频最高的 n 个词,并选取合适的词作为特征词
def delete_words(all_words_list: list, stopwords_set: set, n=10) -> list:""":param all_words_list: 训练集的词汇表:param stopwords_set: 无意义的词汇集合:param n: 要删除的前多少个高频词汇数:return: 特征词汇表"""feature_words = []for i in range(n, len(all_words_list)):if all_words_list[i].isdigit() or all_words_list[i] in stopwords_set or len(all_words_list[i]) <= 1 or len(all_words_list[i]) >= 5:continueelse:feature_words.append(all_words_list[i])return feature_words# 根据 feature_words 将训练数据和测试数据向量化
def data_vector(feature_words: list, train_data_list: list, test_data_list: list) -> (list, list):""":param feature_words: 数据集的特征词汇表:param train_data_list: 训练数据,二维列表,每个元素表示一个新闻样本:param test_data_list: 测试数据,二维列表,每个元素表示一个新闻样本:return: 向量化的训练数据和测试数据"""train_feature_list = []  # train_data_list 的向量化形式test_feature_list = []  # test_data_list 的向量化形式# 将训练数据向量化for sample in train_data_list:train_sample_list = []  # 用于存储训练集单个样本的特征词汇,元素个数与 feature_words 一致sample_set = set(sample)  # 将样本数据进行去重for word in feature_words:if word in sample_set:train_sample_list.append(1)else:train_sample_list.append(0)train_feature_list.append(train_sample_list)# 将测试数据向量化for sample in test_data_list:test_sample_list = []  # 用于存储测试集单个样本的特征词汇,元素个数与 feature_words 一致sample_set = set(sample)  # 将样本数据进行去重for word in feature_words:if word in sample_set:test_sample_list.append(1)else:test_sample_list.append(0)test_feature_list.append(test_sample_list)return train_feature_list, test_feature_listif __name__ == '__main__':dir_path = r'D:\MachineLearning\SogouC\Sample'  # 数据集存放目录# 获取词汇表、训练数据、测试数据、训练标签、测试标签all_words_list, train_data_list, test_data_list, train_labels_list, test_labels_list = text_process(dir_path)# 生成 stopwords_setstopwords_file_path = r'D:\MachineLearning\stopwords_cn.txt'stopwords_set = stop_words_set(stopwords_file_path)# 获取数据集的特征词汇表feature_words = delete_words(all_words_list, stopwords_set)# 获取向量化的训练数据和测试数据train_feature_list, test_feature_list = data_vector(feature_words, train_data_list, test_data_list)# print(np.array(train_feature_list).shape)  # (73, 8747)# print(np.array(test_feature_list).shape)  # (17, 8747)# 实例化 MultinomialNB 对象clf = MultinomialNB()# 使用训练数据和训练标签进行拟合clf.fit(train_feature_list, train_labels_list)# 预测predict_result = clf.predict(test_feature_list)# 准确率accuracy = clf.score(test_feature_list, test_labels_list)print('测试结果为:', predict_result)print('准确率为:', accuracy)
---------
测试结果为: ['C000014' 'C000013' 'C000013' 'C000024' 'C000020' 'C000014' 'C000023' 'C000016' 'C000022' 'C000010' 'C000014' 'C000020' 'C000016' 'C000008' 'C000020' 'C000013' 'C000008']
准确率为: 0.8235294117647058

朴素贝叶斯算法的优缺点

优点

  1. 算法简单且易于实现。朴素贝叶斯算法做出了对特征之间条件独立性的假设,这使得算法的计算复杂度较低,适合处理大规模数据集。
  2. 对小规模数据表现良好。即使在数据量较少的情况下,朴素贝叶斯算法也能够有效地进行分类。
  3. 对缺失数据不敏感。朴素贝叶斯算法能够处理缺失数据,并利用已有的数据进行预测。

缺点

  1. 特征条件独立性假设限制了算法的表达能力。朴素贝叶斯算法无法考虑特征之间的相关性,因此在特征之间存在强相关性的情况下,算法的性能可能会受到影响。
  2. 对输入数据的分布假设较强。朴素贝叶斯算法假设输入特征之间服从独立同分布,但实际情况中可能存在违背这个假设的数据。
  3. 需要估计先验概率。朴素贝叶斯算法需要根据训练数据估计先验概率,如果样本量较小或者类别之间的先验概率差异较大,可能会导致分类结果不准确。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/200605.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CFS三层靶机内网渗透

CFS三层靶机内网渗透 一、靶场搭建1.基础参数信息2.靶场搭建2.1网卡配置2.2Target1配置2.2.1 网卡配置2.2.2 Target1 BT配置 2.3Target2配置2.3.1 网卡配置2.3.2 Target2 BT配置 2.4Target3配置 二、内网渗透Target11.1信息收集1.1.1IP收集1.1.2端口收集1.1.3目录收集 1.2 webs…

SQL错题集2

1.插入记录 用户1001在2021年9月1日晚上10点11分12秒开始作答试卷9001&#xff0c;并在50分钟后提交&#xff0c;得了90分&#xff1b; 用户1002在2021年9月4日上午7点1分2秒开始作答试卷9002&#xff0c;并在10分钟后退出了平台。 2.请把exam_record表中2021年9月1日之前开始作…

电子学会C/C++编程等级考试2023年03月(四级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:最佳路径 如下所示的由正整数数字构成的三角形: 7 3 8 8 1 0 2 7 4 4 4 5 2 6 5 从三角形的顶部到底部有很多条不同的路径。对于每条路径,把路径上面的数加起来可以得到一个和,和最大的路径称为最佳路径。你的任务就是求出最…

Linux之重谈文件和c语言文件接口

重谈文件 文件 内容 属性, 所有对文件的操作都是: a.对内容操作 b.对属性操作 关于文件 一&#xff1a; 即使文件的内容为空&#xff0c;该文件也会在磁盘上也会占空间&#xff0c;因为文件不仅仅只有内容还有文件对应的属性&#xff0c;文件的内容会占用空间, 文件的属性也…

Linux基本指令(2.0)

周边知识&#xff1a; 1.Linux中&#xff0c; 一切皆文件 构建大文件 输入如下shell命令 i1; while [ $i -le 10000]; do echo "hello Linux $i"; let i; done 此时大文件已经创建在big.txt 此时我们发现cat查看无法查看开始内容 我们使用more 当占满一屏之后就不…

Unity-Shader - 2DSprite描边效果

实现一个简单的2D精灵图描边效果&#xff0c;效果如下 实现思路&#xff1a; 可以通过判断该像素周围是否有透明度为 0的值&#xff0c;如果有&#xff0c;则说明该像素位于边缘。 所以我们需要打开alpha blend&#xff0c;即&#xff1a; Blend SrcAlpha OneMinusSrcAlpha&am…

leetcode:1422. 分割字符串的最大得分(python3解法)

难度&#xff1a;简单 给你一个由若干 0 和 1 组成的字符串 s &#xff0c;请你计算并返回将该字符串分割成两个 非空 子字符串&#xff08;即 左 子字符串和 右 子字符串&#xff09;所能获得的最大得分。 「分割字符串的得分」为 左 子字符串中 0 的数量加上 右 子字符串中 1…

Android 12.0 Folder文件夹全屏后文件夹图标列表居中时拖拽app到桌面的优化

1.概述 在12.0的系统rom产品开发中,在Launcher3中在目前的产品需求开发中,对于Launcher3中的文件夹Folder的布局UI 进行了定制化的需求要求把Folder修改为全屏,然后在中间显示文件夹图标的列表,这时候如果Folder是全屏的话,如果拖拽文件夹列表中的app图标,只有拖拽 到屏…

UEC++ 探索虚幻5笔记(捡金币案例) day12

吃金币案例 创建金币逻辑 之前的MyActor_One.cpp&#xff0c;直接添加几个资源拿着就用 //静态网格UPROPERTY(VisibleAnywhere, BlueprintReadOnly)class UStaticMeshComponent* StaticMesh;//球形碰撞体UPROPERTY(VisibleAnywhere, BlueprintReadWrite)class USphereCompone…

JSON 语法详解:轻松掌握数据结构(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

【代码随想录】算法训练计划39

dp 1、62. 不同路径 题目&#xff1a; 求路径方案多少个 思路&#xff1a; 这道题就有点dp了哈 func uniquePaths(m int, n int) int {//dp&#xff0c;写过,代表的是多少种// 初始化dp : make([][]int, m)for i : range dp {dp[i] make([]int, n)dp[i][0] 1 // 代表到…

用友NC Cloud FileParserServlet反序列化RCE漏洞复现

0x01 产品简介 用友 NC Cloud 是一种商业级的企业资源规划云平台,为企业提供全面的管理解决方案,包括财务管理、采购管理、销售管理、人力资源管理等功能,实现企业的数字化转型和业务流程优化。 0x02 漏洞概述 用友 NC Cloud FileParserServlet接口存在反序列化代码执行漏…

response应用

文章目录 [TOC](文章目录) response说明一、response文件下载二、待补充。。。 response说明 response是指HttpServletResponse,该响应有很多的应用&#xff0c;比如像浏览器输出消息&#xff0c;下载文件&#xff0c;实现验证码等。 一、response文件下载 1.创建一个javaw…

springboot整合swagger

1&#xff09;简介&#xff1a; 作为后端开放人员&#xff0c;最烦的事就是自己写接口文档和别人没有写接口文档&#xff0c;不管是前端还是后端开发&#xff0c;多多少少都会被接口文档所折磨&#xff0c;前端会抱怨后端没有及时更新接口文档&#xff0c;而后端又会觉得编写接…

备份和恢复Linux服务器上的HTTP配置

备份和恢复Linux服务器上的HTTP配置是一项重要的任务&#xff0c;它可以确保您的服务器在出现故障或配置错误时能够迅速恢复正常运行。下面我们将介绍如何备份和恢复Linux服务器上的HTTP配置。 备份HTTP配置 登录到Linux服务器上&#xff0c;并使用root权限。 备份HTTP配置文…

分部积分法

1.形式&#xff1a;u对v求积分uv-v对u求积分&#xff0c;一前一后&#xff0c;一般把三角函数&#xff0c;反三角函数&#xff0c;In,e的x次方提到d里面 2. 3. 4. 5. 6. 7. 当结果中出现要求的不要慌&#xff0c;不是1直接求&#xff0c;是1重新计算

一体化污水处理设备材质怎么选

在环保意识日益增强的今天&#xff0c;污水处理设备成为城市建设过程中的重要环节。而选择合适的一体化污水处理设备材质&#xff0c;则成为了一项重要的决策。本文将从专业的角度出发&#xff0c;为您解析一体化污水处理设备材质的选取。 首先&#xff0c;一体化污水处理设备材…

postman常用脚本

在参数中动态添加开始时间和结束时间的时间戳 1.先在collection中添加参数&#xff0c;这里的作用域是collection&#xff0c;也可以是其他的任何scope 2.在Pre-request Script 中设定开始时间和结束时间参数&#xff0c;比如昨天和今天的时间戳&#xff0c;下面是js代码 con…

Android Studio Hedgehog | 2023.1.1(刺猬)

Android Gradle 插件和 Android Studio 兼容性 Android Studio 构建系统基于 Gradle&#xff0c;并且 Android Gradle 插件 (AGP) 添加了一些特定于构建 Android 应用程序的功能。下表列出了每个版本的 Android Studio 所需的 AGP 版本。 Android Studio versionRequired AG…

python学习:opencv+用鼠标画矩形和圆形

目录 步骤 定义数据 新建一个窗口黑色画布 显示黑色画布 添加鼠标回调函数 循环 一直显示图片 一直判断有没有按下字母 m 关闭所有窗口 鼠标回调函数 步骤 当鼠标按下记录坐标并记录鼠标标记位为true&#xff0c;移动的时候就会不断的画矩形或者圆&#xff0c;松下的时候就再…